agl 40.4.0 → 40.5.0
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/changelog.md +27 -0
- package/dist_serve/bundle.js +2228 -2177
- package/dist_serve/bundle.js.map +2 -2
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/aglapi/aglapisurface.document.js +2 -2
- package/dist_ts/classes.accountsactions.d.ts +11 -1
- package/dist_ts/classes.accountsactions.js +59 -2
- package/dist_ts/classes.controller.js +5 -5
- package/dist_ts/classes.flexaccountaccess.js +3 -2
- package/dist_ts/classes.harnessreleases.d.ts +14 -4
- package/dist_ts/classes.harnessreleases.js +28 -4
- package/dist_ts/classes.inferencegateway.d.ts +28 -3
- package/dist_ts/classes.inferencegateway.js +177 -61
- package/dist_ts/classes.inferenceplacement.d.ts +5 -3
- package/dist_ts/classes.inferenceplacement.js +2 -2
- package/dist_ts/classes.inferencerouter.d.ts +349 -91
- package/dist_ts/classes.inferencerouter.js +804 -297
- package/dist_ts/classes.inferenceservice.d.ts +14 -2
- package/dist_ts/classes.inferenceservice.js +53 -14
- package/dist_ts/classes.inferencespend.d.ts +339 -0
- package/dist_ts/classes.inferencespend.js +815 -0
- package/dist_ts/classes.inferenceusagecache.d.ts +8 -0
- package/dist_ts/classes.inferenceusagecache.js +9 -5
- package/dist_ts/classes.inferenceusagetap.d.ts +50 -9
- package/dist_ts/classes.inferenceusagetap.js +185 -31
- package/dist_ts/classes.proxycontrol.d.ts +3 -2
- package/dist_ts/classes.proxycontrol.js +1 -2
- package/dist_ts/classes.proxydaemon.js +3 -2
- package/dist_ts/classes.proxystore.d.ts +6 -0
- package/dist_ts/classes.proxystore.js +11 -2
- package/dist_ts/fleet/classes.claudeworkers.d.ts +5 -4
- package/dist_ts/fleet/classes.claudeworkers.js +10 -9
- package/dist_ts/functions.claudecoderelease.d.ts +12 -6
- package/dist_ts/functions.claudecoderelease.js +13 -7
- package/dist_ts/functions.inferencecli.js +12 -8
- package/dist_ts/interfaces.auth.d.ts +1 -1
- package/dist_ts/interfaces.auth.js +3 -1
- package/dist_ts/plugins.d.ts +3 -1
- package/dist_ts/plugins.js +7 -2
- package/dist_ts_authswitch/authority-apikeys.d.ts +32 -0
- package/dist_ts_authswitch/authority-apikeys.js +65 -0
- package/dist_ts_authswitch/authority-contract.d.ts +142 -28
- package/dist_ts_authswitch/authority-contract.js +48 -1
- package/dist_ts_authswitch/authority-overview.js +8 -1
- package/dist_ts_authswitch/authority-placement.d.ts +7 -2
- package/dist_ts_authswitch/authority-placement.js +10 -3
- package/dist_ts_authswitch/authority-runtime-contract.d.ts +2 -2
- package/dist_ts_authswitch/authority-spend.d.ts +103 -0
- package/dist_ts_authswitch/authority-spend.js +110 -0
- package/dist_ts_authswitch/authority-subscription.d.ts +12 -1
- package/dist_ts_authswitch/authority-subscription.js +24 -1
- package/dist_ts_authswitch/authority-usagelimit.d.ts +7 -4
- package/dist_ts_authswitch/authority-usagelimit.js +89 -39
- package/dist_ts_authswitch/authority-usagewindows.d.ts +4 -1
- package/dist_ts_authswitch/authority-usagewindows.js +41 -16
- package/dist_ts_authswitch/classes.authoritybroker.d.ts +30 -19
- package/dist_ts_authswitch/classes.authoritybroker.js +152 -61
- package/dist_ts_authswitch/classes.authoritycli.d.ts +9 -4
- package/dist_ts_authswitch/classes.authoritycli.js +94 -29
- package/dist_ts_authswitch/classes.authorityclient.d.ts +24 -7
- package/dist_ts_authswitch/classes.authorityclient.js +31 -11
- package/dist_ts_authswitch/classes.authoritydaemon.js +39 -12
- package/dist_ts_authswitch/classes.authoritydatabase.d.ts +28 -5
- package/dist_ts_authswitch/classes.authoritydatabase.js +171 -9
- package/dist_ts_authswitch/classes.authorityimport.js +2 -2
- package/dist_ts_authswitch/classes.authoritymodels.d.ts +71 -3
- package/dist_ts_authswitch/classes.authoritymodels.js +249 -20
- package/dist_ts_authswitch/classes.authorityregistry.js +4 -2
- package/dist_ts_authswitch/classes.authorityusage.d.ts +69 -7
- package/dist_ts_authswitch/classes.authorityusage.js +118 -4
- package/dist_ts_authswitch/classes.claudenative.d.ts +16 -7
- package/dist_ts_authswitch/classes.claudenative.js +29 -16
- package/dist_ts_authswitch/classes.claudesignin.js +5 -2
- package/dist_ts_authswitch/testsupport.d.ts +24 -5
- package/dist_ts_authswitch/testsupport.js +56 -12
- package/dist_ts_authswitch/ts_migration/0011_account_api_key_fields.d.ts +12 -0
- package/dist_ts_authswitch/ts_migration/0011_account_api_key_fields.js +21 -0
- package/dist_ts_authswitch/ts_migration/index.js +3 -1
- package/dist_ts_interfaces/functions.authorityscope.d.ts +18 -7
- package/dist_ts_interfaces/functions.authorityscope.js +35 -8
- package/dist_ts_interfaces/interfaces.d.ts +1 -1
- package/dist_ts_interfaces/interfaces.js +1 -1
- package/dist_ts_interfaces/plugins.d.ts +2 -1
- package/dist_ts_interfaces/plugins.js +7 -4
- package/dist_ts_interfaces/requests.accounts.d.ts +56 -0
- package/dist_ts_interfaces/requests.accounts.js +11 -1
- package/dist_ts_interfaces/requests.d.ts +2 -2
- package/dist_ts_interfaces/requests.js +1 -1
- package/docs/authswitch.md +75 -12
- package/package.json +5 -5
- package/readme.md +198 -21
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/aglapi/aglapisurface.document.ts +2 -2
- package/ts/classes.accountsactions.ts +75 -1
- package/ts/classes.controller.ts +4 -4
- package/ts/classes.flexaccountaccess.ts +2 -1
- package/ts/classes.harnessreleases.ts +25 -4
- package/ts/classes.inferencegateway.ts +184 -64
- package/ts/classes.inferenceplacement.ts +6 -4
- package/ts/classes.inferencerouter.ts +1026 -314
- package/ts/classes.inferenceservice.ts +56 -13
- package/ts/classes.inferencespend.ts +870 -0
- package/ts/classes.inferenceusagecache.ts +16 -4
- package/ts/classes.inferenceusagetap.ts +159 -34
- package/ts/classes.proxycontrol.ts +3 -3
- package/ts/classes.proxydaemon.ts +2 -1
- package/ts/classes.proxystore.ts +11 -1
- package/ts/fleet/classes.claudeworkers.ts +9 -8
- package/ts/functions.claudecoderelease.ts +13 -6
- package/ts/functions.inferencecli.ts +11 -7
- package/ts/interfaces.auth.ts +2 -0
- package/ts/plugins.ts +7 -0
- package/ts_authswitch/authority-apikeys.ts +85 -0
- package/ts_authswitch/authority-contract.ts +167 -29
- package/ts_authswitch/authority-overview.ts +7 -0
- package/ts_authswitch/authority-placement.ts +12 -4
- package/ts_authswitch/authority-runtime-contract.ts +2 -2
- package/ts_authswitch/authority-spend.ts +168 -0
- package/ts_authswitch/authority-subscription.ts +28 -1
- package/ts_authswitch/authority-usagelimit.ts +95 -40
- package/ts_authswitch/authority-usagewindows.ts +42 -17
- package/ts_authswitch/classes.authoritybroker.ts +161 -68
- package/ts_authswitch/classes.authoritycli.ts +108 -32
- package/ts_authswitch/classes.authorityclient.ts +41 -10
- package/ts_authswitch/classes.authoritydaemon.ts +43 -13
- package/ts_authswitch/classes.authoritydatabase.ts +178 -8
- package/ts_authswitch/classes.authorityimport.ts +1 -1
- package/ts_authswitch/classes.authoritymodels.ts +165 -19
- package/ts_authswitch/classes.authorityregistry.ts +3 -0
- package/ts_authswitch/classes.authorityusage.ts +171 -10
- package/ts_authswitch/classes.claudenative.ts +28 -15
- package/ts_authswitch/classes.claudesignin.ts +4 -1
- package/ts_authswitch/native-contracts.md +81 -20
- package/ts_authswitch/readme.md +1 -1
- package/ts_authswitch/testsupport.ts +58 -11
- package/ts_authswitch/ts_migration/0011_account_api_key_fields.ts +20 -0
- package/ts_authswitch/ts_migration/index.ts +2 -0
- package/ts_interfaces/functions.authorityscope.ts +43 -8
- package/ts_interfaces/interfaces.ts +1 -1
- package/ts_interfaces/plugins.ts +6 -3
- package/ts_interfaces/requests.accounts.ts +48 -0
- package/ts_interfaces/requests.ts +4 -0
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/accounts.plugins.ts +3 -1
- package/ts_web/accounts.templates.ts +20 -11
- package/ts_web/classes.accountsreadmodel.ts +322 -70
- package/ts_web/elements.accounts.ts +219 -20
- package/ts_web/elements.codexconnections.ts +3 -1
- package/ts_web/plugins.ts +4 -2
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
* The authswitch command line and AGL's Accounts screen both show these, so their words are written here, once. Besides
|
|
5
5
|
* types, the module imports nothing, and therefore bundles for a browser with nothing behind it.
|
|
6
6
|
*/
|
|
7
|
-
import type { TAuthSwitchRecheckOutcome,
|
|
7
|
+
import type { IAuthSwitchPaymentProblem, TAuthSwitchRecheckOutcome,
|
|
8
|
+
TAuthSwitchUsageReading } from './classes.authorityusage.js';
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* What the `subscription_status` values Claude's profile states mean, in plain words, by value. It is empty: no value's
|
|
@@ -54,6 +55,14 @@ export const authSwitchSubscriptionFacts = (readingArg: TAuthSwitchUsageReading
|
|
|
54
55
|
.map((factArg) => ({ label: factArg.label, value: factArg.value }))
|
|
55
56
|
: [];
|
|
56
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Whether a payment problem still holds the account at `nowArg` (epoch milliseconds): every one does until it is
|
|
60
|
+
* cleared, except a `spend_cap`, which holds nothing once its `until` passed. Every reader of a held problem decides by
|
|
61
|
+
* this, the authority included.
|
|
62
|
+
*/
|
|
63
|
+
export const authSwitchPaymentProblemHeld = (problemArg: IAuthSwitchPaymentProblem, nowArg: number): boolean =>
|
|
64
|
+
problemArg.cause !== 'spend_cap' || (problemArg.until !== undefined && Date.parse(problemArg.until) > nowArg);
|
|
65
|
+
|
|
57
66
|
/**
|
|
58
67
|
* What an account's payment problem means, as one sentence (`IAuthSwitchPaymentProblem`); the time it was seen is the
|
|
59
68
|
* caller's to show.
|
|
@@ -62,6 +71,22 @@ export const authSwitchPaymentProblemText = 'Anthropic refused a request on this
|
|
|
62
71
|
+ ' (HTTP 402), so AGL routes nothing to it until a usage read shows its subscription changed or a re-check reads it'
|
|
63
72
|
+ ' healthy.';
|
|
64
73
|
|
|
74
|
+
/**
|
|
75
|
+
* What an API key's payment problem means, as one sentence (`IAuthSwitchPaymentProblem`), by its cause and the key's
|
|
76
|
+
* provider; when it was seen, and when a spend cap resets, are the caller's to show.
|
|
77
|
+
*/
|
|
78
|
+
export const authSwitchApiKeyPaymentProblemText = (problemArg: IAuthSwitchPaymentProblem, providerIdArg: string): string => {
|
|
79
|
+
const provider = providerIdArg === 'openai' ? 'OpenAI' : 'Anthropic';
|
|
80
|
+
switch (problemArg.cause) {
|
|
81
|
+
case undefined: return `${provider} refused a request on this key with a billing error, so AGL routes nothing to it`
|
|
82
|
+
+ ' until it is re-checked.';
|
|
83
|
+
case 'spend_cap': return `${provider} stopped this key at its monthly spend cap, so AGL routes nothing to it until the`
|
|
84
|
+
+ ' cap resets, or until it is re-checked.';
|
|
85
|
+
case 'quota': return `${provider} reports this key's credit or spend limit used up, so AGL routes nothing to it until`
|
|
86
|
+
+ ' it is re-checked.';
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
65
90
|
/** How a re-check went, as one sentence. */
|
|
66
91
|
export const authSwitchRecheckOutcomeText = (outcomeArg: TAuthSwitchRecheckOutcome): string => {
|
|
67
92
|
switch (outcomeArg) {
|
|
@@ -71,5 +96,7 @@ export const authSwitchRecheckOutcomeText = (outcomeArg: TAuthSwitchRecheckOutco
|
|
|
71
96
|
case 'profile_missing': return 'Claude did not answer the account\'s profile.';
|
|
72
97
|
case 'mismatch': return 'Claude answered with a different account or organization.';
|
|
73
98
|
case 'read_failed': return 'The account could not be read now; the next read is shown with its usage.';
|
|
99
|
+
case 'cleared': return 'The key\'s hold is lifted: AGL places requests on it again, and the provider\'s next answer '
|
|
100
|
+
+ 'shows whether it takes them.';
|
|
74
101
|
}
|
|
75
102
|
};
|
|
@@ -2,17 +2,18 @@
|
|
|
2
2
|
* Whether an account's usage reading still lets it take work: the one rule the inference proxy's placement, Flex's
|
|
3
3
|
* dispatch and resume, the authswitch command line and AGL's Accounts screen all decide by.
|
|
4
4
|
*
|
|
5
|
-
* Besides types, the module imports only the window reader of `authority-usagewindows.ts
|
|
6
|
-
* nothing to a page, so it bundles for a browser with nothing behind it.
|
|
5
|
+
* Besides types, the module imports only the window reader of `authority-usagewindows.ts` and the spend rule of
|
|
6
|
+
* `authority-spend.ts`, whose own imports carry nothing to a page, so it bundles for a browser with nothing behind it.
|
|
7
7
|
*/
|
|
8
|
-
import type { TAuthSwitchUsageReading } from './classes.authorityusage.js';
|
|
8
|
+
import type { TAuthSwitchProviderUsageReading, TAuthSwitchUsageReading } from './classes.authorityusage.js';
|
|
9
9
|
import { authSwitchUsageWindowName, authSwitchUsageWindowPercent, authSwitchUsageWindows,
|
|
10
10
|
type IAuthSwitchUsageWindow } from './authority-usagewindows.js';
|
|
11
|
+
import { authSwitchSpendExhausted, authSwitchSpendMonthEnded } from './authority-spend.js';
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* `available`: the account takes work. `reserved`: a counted window reached the account's `maxUsagePercent`, so
|
|
14
|
-
* the rest of it is kept for use outside AGL. `exhausted`: a counted window is used up,
|
|
15
|
-
* requests until a time it names.
|
|
15
|
+
* the rest of it is kept for use outside AGL. `exhausted`: a counted window is used up, the provider refuses
|
|
16
|
+
* requests until a time it names, or an API key reached its monthly spend limit.
|
|
16
17
|
*/
|
|
17
18
|
export type TAuthSwitchLimitState = 'available' | 'reserved' | 'exhausted';
|
|
18
19
|
|
|
@@ -51,6 +52,82 @@ export const authSwitchReserveLiftActive = (untilArg: string | null | undefined,
|
|
|
51
52
|
export const authSwitchEffectiveMaxUsage = (maxArg: number | null, untilArg: string | null | undefined,
|
|
52
53
|
nowArg: number): number | null => maxArg !== null && authSwitchReserveLiftActive(untilArg, nowArg) ? 100 : maxArg;
|
|
53
54
|
|
|
55
|
+
/** The windows' latest reset among those it names, as ISO 8601; null when none names one. */
|
|
56
|
+
const latestReset = (resetsArg: readonly (string | null)[]): string | null => {
|
|
57
|
+
const resetTimes = resetsArg.flatMap((resetArg) => {
|
|
58
|
+
const at = resetArg === null ? NaN : Date.parse(resetArg);
|
|
59
|
+
return Number.isFinite(at) ? [at] : [];
|
|
60
|
+
});
|
|
61
|
+
return resetTimes.length ? new Date(Math.max(...resetTimes)).toISOString() : null;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/** A subscription sign-in's limit: see `authSwitchUsageLimit`. */
|
|
65
|
+
const subscriptionLimit = (readingArg: TAuthSwitchProviderUsageReading, maxUsagePercentArg: number | null,
|
|
66
|
+
nowArg: number, reserveLiftUntilArg: string | null): IAuthSwitchUsageLimit => {
|
|
67
|
+
const threshold = authSwitchEffectiveMaxUsage(maxUsagePercentArg, reserveLiftUntilArg, nowArg) ?? 100;
|
|
68
|
+
const windows = authSwitchCountedWindows(readingArg, maxUsagePercentArg);
|
|
69
|
+
const crossing = windows.filter((windowArg) => authSwitchUsageWindowPercent(windowArg) >= threshold);
|
|
70
|
+
const resets = crossing.map((windowArg) => windowArg.resetAt);
|
|
71
|
+
let refused: boolean;
|
|
72
|
+
switch (readingArg.kind) {
|
|
73
|
+
case 'openai': {
|
|
74
|
+
const details = readingArg.rateLimits.rateLimit;
|
|
75
|
+
const mainWindows = [details?.primaryWindow, details?.secondaryWindow]
|
|
76
|
+
.filter((windowArg) => windowArg !== undefined);
|
|
77
|
+
// The refusal is the used-up windows' where the reading states any, else the whole limit's; once each of those
|
|
78
|
+
// started a new period past the reset the reading stated, so did the refusal.
|
|
79
|
+
const used = mainWindows.filter((windowArg) => windowArg.usedPercent >= 100);
|
|
80
|
+
const causes = used.length ? used : mainWindows;
|
|
81
|
+
const rolled = causes.length > 0 && causes.every((windowArg) => windowArg.estimated === true);
|
|
82
|
+
refused = details !== undefined && !rolled && (!details.allowed || details.limitReached);
|
|
83
|
+
// A refusal that names no window is lifted by the latest reset of the windows the limit counts.
|
|
84
|
+
if (refused && crossing.length === 0) resets.push(...windows.map((windowArg) => windowArg.resetAt));
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
case 'anthropic': {
|
|
88
|
+
const retryAt = readingArg.status.rateLimit?.retryAt ?? null;
|
|
89
|
+
refused = retryAt !== null && Date.parse(retryAt) > nowArg;
|
|
90
|
+
if (refused) resets.push(retryAt);
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
default: {
|
|
94
|
+
const unhandledReading: never = readingArg;
|
|
95
|
+
return unhandledReading;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
const state: TAuthSwitchLimitState = refused || crossing.some((windowArg) => authSwitchUsageWindowPercent(windowArg) >= 100)
|
|
99
|
+
? 'exhausted' : crossing.length ? 'reserved' : 'available';
|
|
100
|
+
return {
|
|
101
|
+
state,
|
|
102
|
+
threshold,
|
|
103
|
+
headroom: windows.length
|
|
104
|
+
? Math.min(...windows.map((windowArg) => Math.max(0, threshold - authSwitchUsageWindowPercent(windowArg)))) : null,
|
|
105
|
+
crossing,
|
|
106
|
+
resetAt: state !== 'available' ? latestReset(resets) : null,
|
|
107
|
+
};
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* An API key's limit, from its monthly spend (`authSwitchSpendExhausted`): with a spend limit the key is `exhausted`
|
|
112
|
+
* once its month spent the limit or holds an answer that could not be priced, until the month ends; without one it is
|
|
113
|
+
* always `available`. The threshold is always 100, and neither a usage threshold nor a reserve lift applies to a key.
|
|
114
|
+
*/
|
|
115
|
+
const spendLimit = (readingArg: Extract<TAuthSwitchUsageReading, { kind: 'spend' }>, nowArg: number): IAuthSwitchUsageLimit => {
|
|
116
|
+
const windows = authSwitchUsageWindows(readingArg);
|
|
117
|
+
const exhausted = authSwitchSpendExhausted(readingArg.spend, nowArg);
|
|
118
|
+
// A month that ended says nothing about the next one, which starts with nothing spent.
|
|
119
|
+
const ended = authSwitchSpendMonthEnded(readingArg.spend, nowArg);
|
|
120
|
+
return {
|
|
121
|
+
state: exhausted ? 'exhausted' : 'available',
|
|
122
|
+
threshold: 100,
|
|
123
|
+
headroom: windows.length
|
|
124
|
+
? Math.min(...windows.map((windowArg) => ended ? 100 : Math.max(0, 100 - authSwitchUsageWindowPercent(windowArg))))
|
|
125
|
+
: null,
|
|
126
|
+
crossing: exhausted ? windows : [],
|
|
127
|
+
resetAt: exhausted ? readingArg.spend.resetAt : null,
|
|
128
|
+
};
|
|
129
|
+
};
|
|
130
|
+
|
|
54
131
|
/**
|
|
55
132
|
* The limit an account's usage reading states, judged against its `maxUsagePercent` at `nowArg` (epoch
|
|
56
133
|
* milliseconds). An active reserve lift raises only the threshold to 100 until its absolute expiry.
|
|
@@ -69,47 +146,25 @@ export const authSwitchEffectiveMaxUsage = (maxArg: number | null, untilArg: str
|
|
|
69
146
|
* of a Claude reading is the provider refusing inference, as an answer's headers stated it; a refused usage read never
|
|
70
147
|
* sets it.
|
|
71
148
|
*
|
|
149
|
+
* An API key's spend reading is judged by its spend limit alone (`spendLimit`); `maxUsagePercentArg` and
|
|
150
|
+
* `reserveLiftUntilArg` do not apply to it.
|
|
151
|
+
*
|
|
72
152
|
* Whether a reading is recent enough to decide on, and a cooldown after a provider 429, are the caller's: they belong
|
|
73
153
|
* to how it reads usage, not to what a reading says.
|
|
74
154
|
*/
|
|
75
155
|
export const authSwitchUsageLimit = (readingArg: TAuthSwitchUsageReading, maxUsagePercentArg: number | null,
|
|
76
156
|
nowArg: number, reserveLiftUntilArg: string | null = null): IAuthSwitchUsageLimit => {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
// started a new period past the reset the reading stated, so did the refusal.
|
|
88
|
-
const used = mainWindows.filter((windowArg) => windowArg.usedPercent >= 100);
|
|
89
|
-
const causes = used.length ? used : mainWindows;
|
|
90
|
-
const rolled = causes.length > 0 && causes.every((windowArg) => windowArg.estimated === true);
|
|
91
|
-
refused = details !== undefined && !rolled && (!details.allowed || details.limitReached);
|
|
92
|
-
// A refusal that names no window is lifted by the latest reset of the windows the limit counts.
|
|
93
|
-
if (refused && crossing.length === 0) resets.push(...windows.map((windowArg) => windowArg.resetAt));
|
|
94
|
-
} else {
|
|
95
|
-
const retryAt = readingArg.status.rateLimit?.retryAt ?? null;
|
|
96
|
-
refused = retryAt !== null && Date.parse(retryAt) > nowArg;
|
|
97
|
-
if (refused) resets.push(retryAt);
|
|
157
|
+
switch (readingArg.kind) {
|
|
158
|
+
case 'openai':
|
|
159
|
+
case 'anthropic':
|
|
160
|
+
return subscriptionLimit(readingArg, maxUsagePercentArg, nowArg, reserveLiftUntilArg);
|
|
161
|
+
case 'spend':
|
|
162
|
+
return spendLimit(readingArg, nowArg);
|
|
163
|
+
default: {
|
|
164
|
+
const unhandledReading: never = readingArg;
|
|
165
|
+
return unhandledReading;
|
|
166
|
+
}
|
|
98
167
|
}
|
|
99
|
-
const state: TAuthSwitchLimitState = refused || crossing.some((windowArg) => authSwitchUsageWindowPercent(windowArg) >= 100)
|
|
100
|
-
? 'exhausted' : crossing.length ? 'reserved' : 'available';
|
|
101
|
-
const resetTimes = resets.flatMap((resetArg) => {
|
|
102
|
-
const at = resetArg === null ? NaN : Date.parse(resetArg);
|
|
103
|
-
return Number.isFinite(at) ? [at] : [];
|
|
104
|
-
});
|
|
105
|
-
return {
|
|
106
|
-
state,
|
|
107
|
-
threshold,
|
|
108
|
-
headroom: windows.length
|
|
109
|
-
? Math.min(...windows.map((windowArg) => Math.max(0, threshold - authSwitchUsageWindowPercent(windowArg)))) : null,
|
|
110
|
-
crossing,
|
|
111
|
-
resetAt: state !== 'available' && resetTimes.length ? new Date(Math.max(...resetTimes)).toISOString() : null,
|
|
112
|
-
};
|
|
113
168
|
};
|
|
114
169
|
|
|
115
170
|
const windowNames = (windowsArg: readonly IAuthSwitchUsageWindow[]): string => {
|
|
@@ -54,29 +54,52 @@ const openAiWindows = (limitNameArg: string | null,
|
|
|
54
54
|
}))
|
|
55
55
|
);
|
|
56
56
|
|
|
57
|
+
/** The name of an API key's one window, its spend in the calendar month against its limit. */
|
|
58
|
+
export const authSwitchSpendWindowLabel = 'Monthly spend';
|
|
59
|
+
|
|
57
60
|
/**
|
|
58
61
|
* The windows of a reading: for ChatGPT the main limit's two windows, then each named extra limit's; for Claude the
|
|
59
|
-
* windows as the service reported them
|
|
62
|
+
* windows as the service reported them; for an API key with a spend limit one account window, the month's spend in
|
|
63
|
+
* percent of the limit, which resets when the month ends -- a key without a limit has none. A missing reading has none.
|
|
60
64
|
*/
|
|
61
65
|
export const authSwitchUsageWindows = (readingArg: TAuthSwitchUsageReading | null): IAuthSwitchUsageWindow[] => {
|
|
62
66
|
if (readingArg === null) return [];
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
67
|
+
switch (readingArg.kind) {
|
|
68
|
+
case 'openai':
|
|
69
|
+
return [
|
|
70
|
+
...openAiWindows(null, readingArg.rateLimits.rateLimit),
|
|
71
|
+
...readingArg.rateLimits.additionalRateLimits.flatMap((limitArg) => (
|
|
72
|
+
openAiWindows(limitArg.limitName, limitArg.rateLimit)
|
|
73
|
+
)),
|
|
74
|
+
];
|
|
75
|
+
case 'anthropic':
|
|
76
|
+
return (readingArg.status.windows ?? []).map((windowArg) => ({
|
|
77
|
+
label: windowArg.label,
|
|
78
|
+
limitName: null,
|
|
79
|
+
scope: windowArg.scope,
|
|
80
|
+
durationSeconds: windowArg.durationSeconds,
|
|
81
|
+
usedPercent: windowArg.usedPercent,
|
|
82
|
+
estimatedUsedPercent: windowArg.estimated ? windowArg.estimatedUsedPercent ?? null : null,
|
|
83
|
+
resetAt: windowArg.resetAt,
|
|
84
|
+
}));
|
|
85
|
+
case 'spend': {
|
|
86
|
+
const spend = readingArg.spend;
|
|
87
|
+
if (spend.limitMicroUsd === null) return [];
|
|
88
|
+
return [{
|
|
89
|
+
label: authSwitchSpendWindowLabel,
|
|
90
|
+
limitName: null,
|
|
91
|
+
scope: 'account',
|
|
92
|
+
durationSeconds: (Date.parse(spend.resetAt) - Date.parse(spend.periodStart)) / 1000,
|
|
93
|
+
usedPercent: spend.spentMicroUsd * 100 / spend.limitMicroUsd,
|
|
94
|
+
estimatedUsedPercent: null,
|
|
95
|
+
resetAt: spend.resetAt,
|
|
96
|
+
}];
|
|
97
|
+
}
|
|
98
|
+
default: {
|
|
99
|
+
const unhandledReading: never = readingArg;
|
|
100
|
+
return unhandledReading;
|
|
101
|
+
}
|
|
70
102
|
}
|
|
71
|
-
return (readingArg.status.windows ?? []).map((windowArg) => ({
|
|
72
|
-
label: windowArg.label,
|
|
73
|
-
limitName: null,
|
|
74
|
-
scope: windowArg.scope,
|
|
75
|
-
durationSeconds: windowArg.durationSeconds,
|
|
76
|
-
usedPercent: windowArg.usedPercent,
|
|
77
|
-
estimatedUsedPercent: windowArg.estimated ? windowArg.estimatedUsedPercent ?? null : null,
|
|
78
|
-
resetAt: windowArg.resetAt,
|
|
79
|
-
}));
|
|
80
103
|
};
|
|
81
104
|
|
|
82
105
|
/** The percent a window is used to as far as AGL knows: the estimate between reads, else the reported value. */
|
|
@@ -101,5 +124,7 @@ export const authSwitchUsageProblemText = (problemArg: TAuthSwitchUsageProblem):
|
|
|
101
124
|
case 'expired': return 'The last reading is too old to show.';
|
|
102
125
|
case 'held_in_container': return 'This login is in use in a container, so its usage is not read here.';
|
|
103
126
|
case 'pending': return 'The account\'s usage has not been read yet.';
|
|
127
|
+
case 'spend_unavailable': return 'The key\'s spend could not be read, so AGL places nothing on it while it has a '
|
|
128
|
+
+ 'spend limit.';
|
|
104
129
|
}
|
|
105
130
|
};
|