@cortexkit/antigravity-auth-core 2.2.1 → 3.0.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/README.md +24 -2
- package/dist/abort-forwarding.d.ts +3 -0
- package/dist/abort-forwarding.d.ts.map +1 -0
- package/dist/abort-forwarding.js +19 -0
- package/dist/abort-forwarding.js.map +1 -0
- package/dist/account-identity.d.ts +52 -0
- package/dist/account-identity.d.ts.map +1 -0
- package/dist/account-identity.js +66 -0
- package/dist/account-identity.js.map +1 -0
- package/dist/account-manager.d.ts +228 -43
- package/dist/account-manager.d.ts.map +1 -1
- package/dist/account-manager.js +942 -903
- package/dist/account-manager.js.map +1 -1
- package/dist/account-migration.d.ts +309 -0
- package/dist/account-migration.d.ts.map +1 -0
- package/dist/account-migration.js +2282 -0
- package/dist/account-migration.js.map +1 -0
- package/dist/account-repository-codecs.d.ts +82 -0
- package/dist/account-repository-codecs.d.ts.map +1 -0
- package/dist/account-repository-codecs.js +609 -0
- package/dist/account-repository-codecs.js.map +1 -0
- package/dist/account-repository-types.d.ts +831 -0
- package/dist/account-repository-types.d.ts.map +1 -0
- package/dist/account-repository-types.js +137 -0
- package/dist/account-repository-types.js.map +1 -0
- package/dist/account-repository.d.ts +442 -0
- package/dist/account-repository.d.ts.map +1 -0
- package/dist/account-repository.js +2253 -0
- package/dist/account-repository.js.map +1 -0
- package/dist/account-selector.d.ts +470 -0
- package/dist/account-selector.d.ts.map +1 -0
- package/dist/account-selector.js +1383 -0
- package/dist/account-selector.js.map +1 -0
- package/dist/account-storage.d.ts.map +1 -1
- package/dist/account-storage.js +5 -0
- package/dist/account-storage.js.map +1 -1
- package/dist/agy-transport.d.ts.map +1 -1
- package/dist/agy-transport.js +21 -35
- package/dist/agy-transport.js.map +1 -1
- package/dist/antigravity-command-menu.d.ts +171 -0
- package/dist/antigravity-command-menu.d.ts.map +1 -0
- package/dist/antigravity-command-menu.js +523 -0
- package/dist/antigravity-command-menu.js.map +1 -0
- package/dist/atomic-write.d.ts +18 -1
- package/dist/atomic-write.d.ts.map +1 -1
- package/dist/atomic-write.js +15 -3
- package/dist/atomic-write.js.map +1 -1
- package/dist/common-auth-embedded/NOTICE.txt +21 -0
- package/dist/common-auth-embedded/auth-menu/accounts.d.ts +97 -0
- package/dist/common-auth-embedded/auth-menu/accounts.js +387 -0
- package/dist/common-auth-embedded/auth-menu/ansi.d.ts +26 -0
- package/dist/common-auth-embedded/auth-menu/ansi.js +98 -0
- package/dist/common-auth-embedded/auth-menu/confirm.d.ts +7 -0
- package/dist/common-auth-embedded/auth-menu/confirm.js +21 -0
- package/dist/common-auth-embedded/auth-menu/doctor.d.ts +52 -0
- package/dist/common-auth-embedded/auth-menu/doctor.js +81 -0
- package/dist/common-auth-embedded/auth-menu/index.d.ts +17 -0
- package/dist/common-auth-embedded/auth-menu/index.js +12 -0
- package/dist/common-auth-embedded/auth-menu/login.d.ts +55 -0
- package/dist/common-auth-embedded/auth-menu/login.js +83 -0
- package/dist/common-auth-embedded/auth-menu/menu.d.ts +59 -0
- package/dist/common-auth-embedded/auth-menu/menu.js +73 -0
- package/dist/common-auth-embedded/auth-menu/opencode-v1.d.ts +49 -0
- package/dist/common-auth-embedded/auth-menu/opencode-v1.js +47 -0
- package/dist/common-auth-embedded/auth-menu/select.d.ts +21 -0
- package/dist/common-auth-embedded/auth-menu/select.js +159 -0
- package/dist/common-auth-embedded/auth-menu/terminal.d.ts +43 -0
- package/dist/common-auth-embedded/auth-menu/terminal.js +19 -0
- package/dist/common-auth-embedded/claustrum/consumer.d.ts +102 -0
- package/dist/common-auth-embedded/claustrum/consumer.js +298 -0
- package/dist/common-auth-embedded/claustrum/custody.d.ts +175 -0
- package/dist/common-auth-embedded/claustrum/custody.js +354 -0
- package/dist/common-auth-embedded/claustrum/enrollment.d.ts +158 -0
- package/dist/common-auth-embedded/claustrum/enrollment.js +759 -0
- package/dist/common-auth-embedded/claustrum/errors.d.ts +16 -0
- package/dist/common-auth-embedded/claustrum/errors.js +8 -0
- package/dist/common-auth-embedded/claustrum/host-slot.d.ts +39 -0
- package/dist/common-auth-embedded/claustrum/host-slot.js +72 -0
- package/dist/common-auth-embedded/claustrum/index.d.ts +18 -0
- package/dist/common-auth-embedded/claustrum/index.js +22 -0
- package/dist/common-auth-embedded/claustrum/interlock.d.ts +27 -0
- package/dist/common-auth-embedded/claustrum/interlock.js +29 -0
- package/dist/common-auth-embedded/claustrum/roster.d.ts +208 -0
- package/dist/common-auth-embedded/claustrum/roster.js +536 -0
- package/dist/common-auth-embedded/commands/builtins.d.ts +71 -0
- package/dist/common-auth-embedded/commands/builtins.js +534 -0
- package/dist/common-auth-embedded/commands/index.d.ts +10 -0
- package/dist/common-auth-embedded/commands/index.js +5 -0
- package/dist/common-auth-embedded/commands/menu.d.ts +61 -0
- package/dist/common-auth-embedded/commands/menu.js +300 -0
- package/dist/common-auth-embedded/commands/model.d.ts +238 -0
- package/dist/common-auth-embedded/commands/model.js +16 -0
- package/dist/common-auth-embedded/commands/pi.d.ts +23 -0
- package/dist/common-auth-embedded/commands/pi.js +206 -0
- package/dist/common-auth-embedded/commands/seam.d.ts +72 -0
- package/dist/common-auth-embedded/commands/seam.js +301 -0
- package/dist/common-auth-embedded/fs/atomic-write.d.ts +20 -0
- package/dist/common-auth-embedded/fs/atomic-write.js +80 -0
- package/dist/common-auth-embedded/fs/index.d.ts +7 -0
- package/dist/common-auth-embedded/fs/index.js +6 -0
- package/dist/common-auth-embedded/fs/lock-constants.d.ts +14 -0
- package/dist/common-auth-embedded/fs/lock-constants.js +14 -0
- package/dist/common-auth-embedded/fs/refresh-file-lock.d.ts +33 -0
- package/dist/common-auth-embedded/fs/refresh-file-lock.js +501 -0
- package/dist/common-auth-embedded/fs/with-lock.d.ts +33 -0
- package/dist/common-auth-embedded/fs/with-lock.js +48 -0
- package/dist/common-auth-embedded/logger/capture-sink.d.ts +14 -0
- package/dist/common-auth-embedded/logger/capture-sink.js +13 -0
- package/dist/common-auth-embedded/logger/engine.d.ts +100 -0
- package/dist/common-auth-embedded/logger/engine.js +246 -0
- package/dist/common-auth-embedded/logger/index.d.ts +6 -0
- package/dist/common-auth-embedded/logger/index.js +3 -0
- package/dist/common-auth-embedded/logger/redact.d.ts +11 -0
- package/dist/common-auth-embedded/logger/redact.js +83 -0
- package/dist/common-auth-embedded/pi-slot/index.d.ts +3 -0
- package/dist/common-auth-embedded/pi-slot/index.js +2 -0
- package/dist/common-auth-embedded/pi-slot/lock.js +212 -0
- package/dist/common-auth-embedded/pi-slot/slot.d.ts +49 -0
- package/dist/common-auth-embedded/pi-slot/slot.js +383 -0
- package/dist/common-auth-embedded/quota/codec.d.ts +11 -0
- package/dist/common-auth-embedded/quota/codec.js +12 -0
- package/dist/common-auth-embedded/quota/format.d.ts +46 -0
- package/dist/common-auth-embedded/quota/format.js +138 -0
- package/dist/common-auth-embedded/quota/index.d.ts +10 -0
- package/dist/common-auth-embedded/quota/index.js +5 -0
- package/dist/common-auth-embedded/quota/map.d.ts +63 -0
- package/dist/common-auth-embedded/quota/map.js +105 -0
- package/dist/common-auth-embedded/quota/merge.d.ts +48 -0
- package/dist/common-auth-embedded/quota/merge.js +162 -0
- package/dist/common-auth-embedded/quota/projection.d.ts +64 -0
- package/dist/common-auth-embedded/quota/projection.js +134 -0
- package/dist/common-auth-embedded/routing/admission.d.ts +93 -0
- package/dist/common-auth-embedded/routing/admission.js +142 -0
- package/dist/common-auth-embedded/routing/index.d.ts +8 -0
- package/dist/common-auth-embedded/routing/index.js +4 -0
- package/dist/common-auth-embedded/routing/ordered.d.ts +47 -0
- package/dist/common-auth-embedded/routing/ordered.js +58 -0
- package/dist/common-auth-embedded/routing/pins.d.ts +22 -0
- package/dist/common-auth-embedded/routing/pins.js +34 -0
- package/dist/common-auth-embedded/routing/sticky.d.ts +158 -0
- package/dist/common-auth-embedded/routing/sticky.js +339 -0
- package/dist/common-auth-embedded/source-output.json +1090 -0
- package/dist/common-auth-embedded/store/attribution.d.ts +28 -0
- package/dist/common-auth-embedded/store/attribution.js +60 -0
- package/dist/common-auth-embedded/store/errors.d.ts +55 -0
- package/dist/common-auth-embedded/store/errors.js +38 -0
- package/dist/common-auth-embedded/store/hooks.d.ts +12 -0
- package/dist/common-auth-embedded/store/hooks.js +34 -0
- package/dist/common-auth-embedded/store/identity.d.ts +39 -0
- package/dist/common-auth-embedded/store/identity.js +79 -0
- package/dist/common-auth-embedded/store/index.d.ts +21 -0
- package/dist/common-auth-embedded/store/index.js +7 -0
- package/dist/common-auth-embedded/store/mutate.d.ts +189 -0
- package/dist/common-auth-embedded/store/mutate.js +500 -0
- package/dist/common-auth-embedded/store/pool.d.ts +196 -0
- package/dist/common-auth-embedded/store/pool.js +120 -0
- package/dist/common-auth-embedded/store/provider-state.d.ts +131 -0
- package/dist/common-auth-embedded/store/provider-state.js +226 -0
- package/dist/common-auth-embedded/store/publication.d.ts +47 -0
- package/dist/common-auth-embedded/store/publication.js +304 -0
- package/dist/common-auth-embedded/store/pull.d.ts +36 -0
- package/dist/common-auth-embedded/store/pull.js +143 -0
- package/dist/common-auth-embedded/store/refresh-lock.d.ts +63 -0
- package/dist/common-auth-embedded/store/refresh-lock.js +136 -0
- package/dist/common-auth-embedded/store/refresh.d.ts +72 -0
- package/dist/common-auth-embedded/store/refresh.js +225 -0
- package/dist/common-auth-embedded/store/reserved.js +13 -0
- package/dist/common-auth-embedded/store/rows.d.ts +313 -0
- package/dist/common-auth-embedded/store/rows.js +1258 -0
- package/dist/common-auth-embedded/store/runtime.d.ts +36 -0
- package/dist/common-auth-embedded/store/runtime.js +68 -0
- package/dist/common-auth-embedded/store/schema.d.ts +443 -0
- package/dist/common-auth-embedded/store/schema.js +754 -0
- package/dist/common-auth-embedded/store/settings.d.ts +63 -0
- package/dist/common-auth-embedded/store/settings.js +120 -0
- package/dist/common-auth-embedded/store/torn.d.ts +78 -0
- package/dist/common-auth-embedded/store/torn.js +294 -0
- package/dist/common-auth-embedded/util/any-signal.js +42 -0
- package/dist/common-auth-runtime.d.ts +73 -0
- package/dist/common-auth-runtime.d.ts.map +1 -0
- package/dist/common-auth-runtime.js +65 -0
- package/dist/common-auth-runtime.js.map +1 -0
- package/dist/credential-redaction.d.ts +13 -0
- package/dist/credential-redaction.d.ts.map +1 -0
- package/dist/credential-redaction.js +230 -0
- package/dist/credential-redaction.js.map +1 -0
- package/dist/fetch-timeout.d.ts +4 -2
- package/dist/fetch-timeout.d.ts.map +1 -1
- package/dist/fetch-timeout.js +121 -14
- package/dist/fetch-timeout.js.map +1 -1
- package/dist/index.d.ts +16 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -1
- package/dist/quota-manager.d.ts.map +1 -1
- package/dist/quota-manager.js +35 -10
- package/dist/quota-manager.js.map +1 -1
- package/dist/quota-presentation.d.ts +10 -0
- package/dist/quota-presentation.d.ts.map +1 -0
- package/dist/quota-presentation.js +60 -0
- package/dist/quota-presentation.js.map +1 -0
- package/dist/quota-types.d.ts +2 -0
- package/dist/quota-types.d.ts.map +1 -1
- package/dist/rotation.d.ts +8 -0
- package/dist/rotation.d.ts.map +1 -1
- package/dist/rotation.js +24 -0
- package/dist/rotation.js.map +1 -1
- package/dist/transform/claude.d.ts +7 -3
- package/dist/transform/claude.d.ts.map +1 -1
- package/dist/transform/claude.js +6 -2
- package/dist/transform/claude.js.map +1 -1
- package/dist/transform/types.d.ts +1 -1
- package/dist/transform/types.d.ts.map +1 -1
- package/dist/vault-account-source.d.ts +183 -0
- package/dist/vault-account-source.d.ts.map +1 -0
- package/dist/vault-account-source.js +296 -0
- package/dist/vault-account-source.js.map +1 -0
- package/dist/vault-enrollment-authority.d.ts +16 -0
- package/dist/vault-enrollment-authority.d.ts.map +1 -0
- package/dist/vault-enrollment-authority.js +19 -0
- package/dist/vault-enrollment-authority.js.map +1 -0
- package/dist/vault-family.d.ts +11 -0
- package/dist/vault-family.d.ts.map +1 -0
- package/dist/vault-family.js +24 -0
- package/dist/vault-family.js.map +1 -0
- package/dist/vault-provider-state.d.ts +162 -0
- package/dist/vault-provider-state.d.ts.map +1 -0
- package/dist/vault-provider-state.js +422 -0
- package/dist/vault-provider-state.js.map +1 -0
- package/package.json +39 -2
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
// Admission: which candidate rows may be dispatched for one request.
|
|
2
|
+
//
|
|
3
|
+
// Pure: the caller passes the candidate rows with their quota maps, the
|
|
4
|
+
// request's scope, the window labels that scope requires, and the rate-limit
|
|
5
|
+
// marks and refresh backoff it holds. Nothing is read or written here.
|
|
6
|
+
//
|
|
7
|
+
// A marked or backed-off row is excluded before the gates. Stage 1 then
|
|
8
|
+
// judges each remaining row alone, in gate order:
|
|
9
|
+
// 1. an API-key row is admitted without consulting quota (a provider that
|
|
10
|
+
// offers paid rows only after its OAuth rows are spent leaves them out
|
|
11
|
+
// of `rows` until then);
|
|
12
|
+
// 2. an OAuth row whose projection resolves no entry for the scope needs a
|
|
13
|
+
// first reading: refused, pull requested;
|
|
14
|
+
// 3. a required label with no entry is unknown: refused, pull requested;
|
|
15
|
+
// 4. a reading at or beyond 100% whose reset is missing, unparsable or not
|
|
16
|
+
// after `now` cannot confirm the exhaustion, so the window is unknown:
|
|
17
|
+
// refused, pull requested;
|
|
18
|
+
// 5. a reading at or beyond 100% with a future reset is exhausted: refused.
|
|
19
|
+
// Gates 4 and 5 look at every projected limit, not only the required labels.
|
|
20
|
+
// Stage 2 judges the credit budget across the stage-1 survivors: a row whose
|
|
21
|
+
// budget is known spent (reached, with a parsable future reset) is refused
|
|
22
|
+
// unless every survivor's budget is spent, in which case all of them stay
|
|
23
|
+
// admitted as the last path. Every missing, malformed or passed budget reset
|
|
24
|
+
// fails open. Stage-1 refusals have no last-path exception.
|
|
25
|
+
import { ALL_SCOPE, DEFAULT_REQUIRED_LABELS, } from '../quota/map.js';
|
|
26
|
+
import { budgetExhaustedResetAt, futureResetAt, projectQuota, readsExhausted, } from '../quota/projection.js';
|
|
27
|
+
/** The exclusion that applies to `id` at `now`, if any. */
|
|
28
|
+
export function exclusionFor(id, inputs, now) {
|
|
29
|
+
const markedUntil = inputs.rateLimitMarks?.get(id);
|
|
30
|
+
if (markedUntil !== undefined && now < markedUntil) {
|
|
31
|
+
return { id, reason: 'rate-limited', until: markedUntil };
|
|
32
|
+
}
|
|
33
|
+
const backoffUntil = inputs.refreshBackoff?.get(id);
|
|
34
|
+
if (backoffUntil !== undefined && now < backoffUntil) {
|
|
35
|
+
return { id, reason: 'refresh-backoff', until: backoffUntil };
|
|
36
|
+
}
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
function judgeStageOne(id, projection, requiredLabels, now) {
|
|
40
|
+
if (projection.limits.length === 0) {
|
|
41
|
+
return {
|
|
42
|
+
id,
|
|
43
|
+
stage: 1,
|
|
44
|
+
gate: 2,
|
|
45
|
+
reason: 'needs-first-reading',
|
|
46
|
+
pullRequested: true,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
for (const label of requiredLabels) {
|
|
50
|
+
if (!projection.limits.some((limit) => limit.label === label)) {
|
|
51
|
+
return {
|
|
52
|
+
id,
|
|
53
|
+
stage: 1,
|
|
54
|
+
gate: 3,
|
|
55
|
+
reason: 'unknown-window',
|
|
56
|
+
window: { scope: projection.scope, label },
|
|
57
|
+
pullRequested: true,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const exhausted = projection.limits.filter(readsExhausted);
|
|
62
|
+
for (const limit of exhausted) {
|
|
63
|
+
if (futureResetAt(limit, now) === undefined) {
|
|
64
|
+
return {
|
|
65
|
+
id,
|
|
66
|
+
stage: 1,
|
|
67
|
+
gate: 4,
|
|
68
|
+
reason: 'unknown-reset',
|
|
69
|
+
window: { scope: limit.scope, label: limit.label },
|
|
70
|
+
pullRequested: true,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
for (const limit of exhausted) {
|
|
75
|
+
const resetAtMs = futureResetAt(limit, now);
|
|
76
|
+
if (resetAtMs !== undefined && typeof limit.resetsAt === 'string') {
|
|
77
|
+
return {
|
|
78
|
+
id,
|
|
79
|
+
stage: 1,
|
|
80
|
+
gate: 5,
|
|
81
|
+
reason: 'exhausted',
|
|
82
|
+
window: { scope: limit.scope, label: limit.label },
|
|
83
|
+
resetsAt: limit.resetsAt,
|
|
84
|
+
resetAtMs,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
/** Runs both admission stages over `input.rows`. */
|
|
91
|
+
export function admit(input) {
|
|
92
|
+
const scope = input.scope ?? ALL_SCOPE;
|
|
93
|
+
const requiredLabels = input.requiredLabels ?? DEFAULT_REQUIRED_LABELS;
|
|
94
|
+
const excluded = [];
|
|
95
|
+
const refused = [];
|
|
96
|
+
const survivors = [];
|
|
97
|
+
for (const row of input.rows) {
|
|
98
|
+
const exclusion = exclusionFor(row.id, input, input.now);
|
|
99
|
+
if (exclusion) {
|
|
100
|
+
excluded.push(exclusion);
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (row.kind === 'api-key') {
|
|
104
|
+
survivors.push({ id: row.id, kind: row.kind });
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
const projection = projectQuota(row.quota, scope);
|
|
108
|
+
const refusal = judgeStageOne(row.id, projection, requiredLabels, input.now);
|
|
109
|
+
if (refusal) {
|
|
110
|
+
refused.push(refusal);
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
survivors.push({ id: row.id, kind: row.kind, projection });
|
|
114
|
+
}
|
|
115
|
+
const spent = new Map(survivors.flatMap((row) => {
|
|
116
|
+
const reset = budgetExhaustedResetAt(row.projection, input.now);
|
|
117
|
+
return reset ? [[row.id, reset]] : [];
|
|
118
|
+
}));
|
|
119
|
+
const lastPath = spent.size > 0 && spent.size === survivors.length;
|
|
120
|
+
const admitted = [];
|
|
121
|
+
for (const row of survivors) {
|
|
122
|
+
const reset = spent.get(row.id);
|
|
123
|
+
if (reset === undefined) {
|
|
124
|
+
admitted.push(row);
|
|
125
|
+
}
|
|
126
|
+
else if (lastPath) {
|
|
127
|
+
admitted.push({ ...row, lastPath: true });
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
refused.push({ id: row.id, stage: 2, reason: 'budget-spent', ...reset });
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
const order = new Map(input.rows.map((row, index) => [row.id, index]));
|
|
134
|
+
const byInput = (left, right) => (order.get(left.id) ?? 0) - (order.get(right.id) ?? 0);
|
|
135
|
+
refused.sort(byInput);
|
|
136
|
+
const pulls = refused
|
|
137
|
+
.filter((refusal) => 'pullRequested' in refusal)
|
|
138
|
+
.map((refusal) => refusal.id);
|
|
139
|
+
for (const id of pulls)
|
|
140
|
+
input.requestPull?.(id);
|
|
141
|
+
return { admitted, refused, excluded, pulls };
|
|
142
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export type { AdmissionExclusion, AdmissionInput, AdmissionRefusal, AdmissionResult, AdmittedRow, ExclusionInputs, RoutingRow, RowKind, WindowRef, } from './admission.js';
|
|
2
|
+
export { admit, exclusionFor } from './admission.js';
|
|
3
|
+
export type { OrderedAttempt, OrderedPlacement, OrderedRoute, OrderedRouteInput, ResolvedRoutingMode, RoutingMode, } from './ordered.js';
|
|
4
|
+
export { DEFAULT_FORMER_MAIN_ID, nextOrderedAttempt, orderForPlacement, resolveRoutingMode, routeOrdered, } from './ordered.js';
|
|
5
|
+
export type { StickyPin } from './pins.js';
|
|
6
|
+
export { isPinValid, pendingBytesForPins } from './pins.js';
|
|
7
|
+
export type { PinAction, StickyBreakDecision, StickyRoute, StickyRouteInput, StickySelection, StickySelectionCandidate, StickySelectionInput, StickyStatusClass, StickyStatusClassifier, } from './sticky.js';
|
|
8
|
+
export { decideStickyBreak, defaultStickyStatusClass, MIN_RESET_HOURS, MIN_WEIGHT, QUOTA_STALENESS_MS, routeSticky, selectStickyCandidate, snapshotCheckedAt, sustainableWindowWeight, } from './sticky.js';
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { admit, exclusionFor } from './admission.js';
|
|
2
|
+
export { DEFAULT_FORMER_MAIN_ID, nextOrderedAttempt, orderForPlacement, resolveRoutingMode, routeOrdered, } from './ordered.js';
|
|
3
|
+
export { isPinValid, pendingBytesForPins } from './pins.js';
|
|
4
|
+
export { decideStickyBreak, defaultStickyStatusClass, MIN_RESET_HOURS, MIN_WEIGHT, QUOTA_STALENESS_MS, routeSticky, selectStickyCandidate, snapshotCheckedAt, sustainableWindowWeight, } from './sticky.js';
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { type AdmissionInput, type AdmissionResult } from './admission.js';
|
|
2
|
+
export type RoutingMode = 'ordered' | 'sticky-balanced';
|
|
3
|
+
/** Where `ordered` routing places the former main row. */
|
|
4
|
+
export type OrderedPlacement = 'roster' | 'main-first' | 'fallback-first';
|
|
5
|
+
export interface ResolvedRoutingMode {
|
|
6
|
+
mode: RoutingMode;
|
|
7
|
+
placement: OrderedPlacement;
|
|
8
|
+
}
|
|
9
|
+
/** The former main row's id when the caller supplies none. */
|
|
10
|
+
export declare const DEFAULT_FORMER_MAIN_ID = "main";
|
|
11
|
+
/**
|
|
12
|
+
* Resolves a persisted `routing.mode` value. `main-first` and
|
|
13
|
+
* `fallback-first` are aliases of `ordered` that move the former main row;
|
|
14
|
+
* an absent or unrecognised value is `ordered` in roster order. The
|
|
15
|
+
* persisted value is only read here, never rewritten.
|
|
16
|
+
*/
|
|
17
|
+
export declare function resolveRoutingMode(value: unknown): ResolvedRoutingMode;
|
|
18
|
+
/**
|
|
19
|
+
* Orders `ids` (in roster order) for a placement: `main-first` moves the row
|
|
20
|
+
* named `formerMainId` to the front, `fallback-first` to the back, and
|
|
21
|
+
* `roster` (or a missing former main row) keeps roster order.
|
|
22
|
+
*/
|
|
23
|
+
export declare function orderForPlacement(ids: readonly string[], placement: OrderedPlacement, formerMainId?: string): string[];
|
|
24
|
+
export interface OrderedRouteInput extends AdmissionInput {
|
|
25
|
+
placement?: OrderedPlacement;
|
|
26
|
+
formerMainId?: string;
|
|
27
|
+
/** Killswitch verdict per row; `false` drops the row, a missing row passes. */
|
|
28
|
+
killswitch?: ReadonlyMap<string, boolean>;
|
|
29
|
+
}
|
|
30
|
+
export interface OrderedRoute {
|
|
31
|
+
/** Admitted, non-killed row ids in the order they are to be tried. */
|
|
32
|
+
order: string[];
|
|
33
|
+
admission: AdmissionResult;
|
|
34
|
+
}
|
|
35
|
+
export declare function routeOrdered(input: OrderedRouteInput): OrderedRoute;
|
|
36
|
+
export interface OrderedAttempt {
|
|
37
|
+
id: string;
|
|
38
|
+
/** The response status; undefined when no response arrived. */
|
|
39
|
+
status?: number;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* The next row to try, given the attempts so far: the first row when none
|
|
43
|
+
* has been tried, the next untried row when the last attempt's status is one
|
|
44
|
+
* of `retryStatuses`, and undefined otherwise (the last response stands) or
|
|
45
|
+
* when every row has been tried.
|
|
46
|
+
*/
|
|
47
|
+
export declare function nextOrderedAttempt(order: readonly string[], attempts: readonly OrderedAttempt[], retryStatuses: readonly number[]): string | undefined;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// `ordered` routing: admitted rows tried in a fixed order, moving to the next
|
|
2
|
+
// row only when a response carries one of the caller's retry statuses.
|
|
3
|
+
import { admit, } from './admission.js';
|
|
4
|
+
/** The former main row's id when the caller supplies none. */
|
|
5
|
+
export const DEFAULT_FORMER_MAIN_ID = 'main';
|
|
6
|
+
/**
|
|
7
|
+
* Resolves a persisted `routing.mode` value. `main-first` and
|
|
8
|
+
* `fallback-first` are aliases of `ordered` that move the former main row;
|
|
9
|
+
* an absent or unrecognised value is `ordered` in roster order. The
|
|
10
|
+
* persisted value is only read here, never rewritten.
|
|
11
|
+
*/
|
|
12
|
+
export function resolveRoutingMode(value) {
|
|
13
|
+
switch (value) {
|
|
14
|
+
case 'sticky-balanced':
|
|
15
|
+
return { mode: 'sticky-balanced', placement: 'roster' };
|
|
16
|
+
case 'main-first':
|
|
17
|
+
return { mode: 'ordered', placement: 'main-first' };
|
|
18
|
+
case 'fallback-first':
|
|
19
|
+
return { mode: 'ordered', placement: 'fallback-first' };
|
|
20
|
+
default:
|
|
21
|
+
return { mode: 'ordered', placement: 'roster' };
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Orders `ids` (in roster order) for a placement: `main-first` moves the row
|
|
26
|
+
* named `formerMainId` to the front, `fallback-first` to the back, and
|
|
27
|
+
* `roster` (or a missing former main row) keeps roster order.
|
|
28
|
+
*/
|
|
29
|
+
export function orderForPlacement(ids, placement, formerMainId = DEFAULT_FORMER_MAIN_ID) {
|
|
30
|
+
if (placement === 'roster' || !ids.includes(formerMainId))
|
|
31
|
+
return [...ids];
|
|
32
|
+
const rest = ids.filter((id) => id !== formerMainId);
|
|
33
|
+
return placement === 'main-first'
|
|
34
|
+
? [formerMainId, ...rest]
|
|
35
|
+
: [...rest, formerMainId];
|
|
36
|
+
}
|
|
37
|
+
export function routeOrdered(input) {
|
|
38
|
+
const admission = admit(input);
|
|
39
|
+
const admitted = new Set(admission.admitted.map((row) => row.id));
|
|
40
|
+
const order = orderForPlacement(input.rows.map((row) => row.id), input.placement ?? 'roster', input.formerMainId).filter((id) => admitted.has(id) && input.killswitch?.get(id) !== false);
|
|
41
|
+
return { order, admission };
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* The next row to try, given the attempts so far: the first row when none
|
|
45
|
+
* has been tried, the next untried row when the last attempt's status is one
|
|
46
|
+
* of `retryStatuses`, and undefined otherwise (the last response stands) or
|
|
47
|
+
* when every row has been tried.
|
|
48
|
+
*/
|
|
49
|
+
export function nextOrderedAttempt(order, attempts, retryStatuses) {
|
|
50
|
+
const last = attempts.at(-1);
|
|
51
|
+
if (last !== undefined) {
|
|
52
|
+
if (last.status === undefined || !retryStatuses.includes(last.status)) {
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const tried = new Set(attempts.map((attempt) => attempt.id));
|
|
57
|
+
return order.find((id) => !tried.has(id));
|
|
58
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface StickyPin {
|
|
2
|
+
accountId: string;
|
|
3
|
+
/** The row's recorded wire identity when the pin was made, if known. */
|
|
4
|
+
wireIdentity?: string;
|
|
5
|
+
/** The request size recorded with the pin; weighs pending bytes. */
|
|
6
|
+
inputBytes?: number;
|
|
7
|
+
/** The projection time the placement was judged on. */
|
|
8
|
+
quotaCheckedAt?: number;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* A pin survives while its row id is in `validIds` and the row still holds
|
|
12
|
+
* the account it was placed on. Only two known, differing identities prove
|
|
13
|
+
* the account changed: an unknown identity on either side keeps the pin,
|
|
14
|
+
* because re-placing a session on missing evidence throws its cache away.
|
|
15
|
+
*/
|
|
16
|
+
export declare function isPinValid(pin: StickyPin, validIds: ReadonlySet<string>, currentIdentity: string | undefined): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Sums the request bytes of every other session's pin per row, as openai-auth
|
|
19
|
+
* does. A pin counts only while it was judged on the row's current
|
|
20
|
+
* projection time, so a fresh reading resets the row's pending load.
|
|
21
|
+
*/
|
|
22
|
+
export declare function pendingBytesForPins(pins: Iterable<readonly [sessionKey: string, pin: StickyPin]>, quotaCheckedAtById: ReadonlyMap<string, number | undefined>, excludedSessionKey?: string): Map<string, number>;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Session pins for `sticky-balanced` routing.
|
|
2
|
+
//
|
|
3
|
+
// A pin records which row a session was placed on so its prompt cache stays
|
|
4
|
+
// warm. The library persists no pin: the caller holds pins (in memory, or in
|
|
5
|
+
// a cross-process file it owns) and passes the relevant ones in per call.
|
|
6
|
+
/**
|
|
7
|
+
* A pin survives while its row id is in `validIds` and the row still holds
|
|
8
|
+
* the account it was placed on. Only two known, differing identities prove
|
|
9
|
+
* the account changed: an unknown identity on either side keeps the pin,
|
|
10
|
+
* because re-placing a session on missing evidence throws its cache away.
|
|
11
|
+
*/
|
|
12
|
+
export function isPinValid(pin, validIds, currentIdentity) {
|
|
13
|
+
if (!validIds.has(pin.accountId))
|
|
14
|
+
return false;
|
|
15
|
+
return !(typeof pin.wireIdentity === 'string' &&
|
|
16
|
+
typeof currentIdentity === 'string' &&
|
|
17
|
+
pin.wireIdentity !== currentIdentity);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Sums the request bytes of every other session's pin per row, as openai-auth
|
|
21
|
+
* does. A pin counts only while it was judged on the row's current
|
|
22
|
+
* projection time, so a fresh reading resets the row's pending load.
|
|
23
|
+
*/
|
|
24
|
+
export function pendingBytesForPins(pins, quotaCheckedAtById, excludedSessionKey) {
|
|
25
|
+
const pending = new Map();
|
|
26
|
+
for (const [sessionKey, pin] of pins) {
|
|
27
|
+
if (sessionKey === excludedSessionKey)
|
|
28
|
+
continue;
|
|
29
|
+
if (pin.quotaCheckedAt !== quotaCheckedAtById.get(pin.accountId))
|
|
30
|
+
continue;
|
|
31
|
+
pending.set(pin.accountId, (pending.get(pin.accountId) ?? 0) + (pin.inputBytes ?? 0));
|
|
32
|
+
}
|
|
33
|
+
return pending;
|
|
34
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { type ProjectedQuota } from '../quota/projection.js';
|
|
2
|
+
import { type AdmissionInput, type AdmissionRefusal, type AdmissionResult, type RoutingRow, type WindowRef } from './admission.js';
|
|
3
|
+
import { type StickyPin } from './pins.js';
|
|
4
|
+
export declare const QUOTA_STALENESS_MS: number;
|
|
5
|
+
export declare const MIN_RESET_HOURS: number;
|
|
6
|
+
export declare const MIN_WEIGHT = 0.000001;
|
|
7
|
+
/** The projection's time, else the caller's cache-entry time. */
|
|
8
|
+
export declare function snapshotCheckedAt(quota: ProjectedQuota | null | undefined, entryCheckedAt?: number): number | undefined;
|
|
9
|
+
export type StickyBreakDecision = {
|
|
10
|
+
action: 'retain';
|
|
11
|
+
reason: 'unknown' | 'stale' | 'healthy' | 'transient';
|
|
12
|
+
} | {
|
|
13
|
+
action: 'migrate';
|
|
14
|
+
reason: 'exhausted' | 'permanent' | 'killswitch';
|
|
15
|
+
/** The exhausted limit, named by its (scope, label), not its position. */
|
|
16
|
+
window?: WindowRef;
|
|
17
|
+
resetsAt?: string;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* How the adapter reads a failed response's HTTP status for the break
|
|
21
|
+
* decision. `permanent` moves the session off its row before any quota is
|
|
22
|
+
* consulted; `transient` and `healthy` keep it unless the quota says the row
|
|
23
|
+
* is spent. A missing status means the request failed without a response.
|
|
24
|
+
*/
|
|
25
|
+
export type StickyStatusClass = 'permanent' | 'transient' | 'healthy';
|
|
26
|
+
export type StickyStatusClassifier = (status: number | undefined) => StickyStatusClass;
|
|
27
|
+
/**
|
|
28
|
+
* The status rule used when the adapter supplies none, carried from
|
|
29
|
+
* openai-auth: 401 and 403 are permanent; no response, 0, a non-finite
|
|
30
|
+
* status, 429 and 5xx are transient; anything else leaves the row healthy.
|
|
31
|
+
* A provider whose 403 can mean an organisation or model policy rather than
|
|
32
|
+
* a dead account supplies its own classifier and can fall back to this one
|
|
33
|
+
* for the statuses it does not single out.
|
|
34
|
+
*/
|
|
35
|
+
export declare function defaultStickyStatusClass(status: number | undefined): StickyStatusClass;
|
|
36
|
+
/** Classifies whether a pinned session should leave its row after a failure. */
|
|
37
|
+
export declare function decideStickyBreak(input: {
|
|
38
|
+
quota: ProjectedQuota | null | undefined;
|
|
39
|
+
quotaCheckedAt?: number;
|
|
40
|
+
status?: number;
|
|
41
|
+
now: number;
|
|
42
|
+
killswitchPasses?: boolean;
|
|
43
|
+
/** Defaults to `defaultStickyStatusClass`. */
|
|
44
|
+
classifyStatus?: StickyStatusClassifier;
|
|
45
|
+
}): StickyBreakDecision;
|
|
46
|
+
export declare function sustainableWindowWeight(window: {
|
|
47
|
+
remainingPercent: number;
|
|
48
|
+
resetsAt?: string;
|
|
49
|
+
}, reservePercent: number, now: number): number;
|
|
50
|
+
export interface StickySelectionCandidate {
|
|
51
|
+
accountId: string;
|
|
52
|
+
quota: ProjectedQuota | null | undefined;
|
|
53
|
+
quotaCheckedAt?: number;
|
|
54
|
+
/** Reserve percent per window label; a missing label reserves nothing. */
|
|
55
|
+
reservePercent: Readonly<Record<string, number>>;
|
|
56
|
+
configuredOrder: number;
|
|
57
|
+
resetCreditsApplicable?: number;
|
|
58
|
+
/** `false` excludes the candidate from weighted and fallback placement. */
|
|
59
|
+
killswitchPasses?: boolean;
|
|
60
|
+
}
|
|
61
|
+
export interface StickySelectionInput {
|
|
62
|
+
candidates: readonly StickySelectionCandidate[];
|
|
63
|
+
pendingBytes: ReadonlyMap<string, number>;
|
|
64
|
+
requestBytes: number;
|
|
65
|
+
now: number;
|
|
66
|
+
onEmptyWeightedSet?: () => void;
|
|
67
|
+
}
|
|
68
|
+
export interface StickySelection {
|
|
69
|
+
accountId: string;
|
|
70
|
+
quotaCheckedAt?: number;
|
|
71
|
+
source: 'weighted' | 'mode-fallback';
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Places a session: the lowest projected pressure among candidates with a
|
|
75
|
+
* fresh positive weight, else (`mode-fallback`) the first candidate in
|
|
76
|
+
* configured order, preferring one with an applicable reset credit.
|
|
77
|
+
*/
|
|
78
|
+
export declare function selectStickyCandidate(input: StickySelectionInput): StickySelection | undefined;
|
|
79
|
+
/** Reserve percent per window label; a missing label reserves nothing. */
|
|
80
|
+
export type ReservePercent = Readonly<Record<string, number>>;
|
|
81
|
+
/**
|
|
82
|
+
* Reserve percentages per row: a map keyed by row id, or a function of the
|
|
83
|
+
* row. A row the map lacks, or for which the function returns undefined,
|
|
84
|
+
* takes the shared `reservePercent`.
|
|
85
|
+
*/
|
|
86
|
+
export type RowReservePercent = ReadonlyMap<string, ReservePercent> | ((row: RoutingRow) => ReservePercent | undefined);
|
|
87
|
+
/**
|
|
88
|
+
* What a valid pin does when its row is not dispatched.
|
|
89
|
+
*
|
|
90
|
+
* `keep`: the pin is retained whatever kept its row from this request.
|
|
91
|
+
*
|
|
92
|
+
* `move-on-confirmed-exhaustion`: the pin moves to the row this request is
|
|
93
|
+
* dispatched to when its own row was refused as confirmed exhausted (a spent
|
|
94
|
+
* window with a future reset, or a spent credit budget) or killed by the
|
|
95
|
+
* killswitch. A refusal for unknown quota (no reading yet, a missing window,
|
|
96
|
+
* an exhausted reading without a usable reset) and an exclusion (rate-limit
|
|
97
|
+
* mark, refresh backoff) keep the pin while this request is served elsewhere.
|
|
98
|
+
* With no admissible row the pin is retained either way.
|
|
99
|
+
*/
|
|
100
|
+
export type RefusedPinPolicy = 'keep' | 'move-on-confirmed-exhaustion';
|
|
101
|
+
export interface StickyRouteInput extends AdmissionInput {
|
|
102
|
+
requestBytes: number;
|
|
103
|
+
/** Bytes already committed per row, for example from other sessions' pins. */
|
|
104
|
+
pendingBytes?: ReadonlyMap<string, number>;
|
|
105
|
+
/** Killswitch verdict per row; a missing row passes. */
|
|
106
|
+
killswitch?: ReadonlyMap<string, boolean>;
|
|
107
|
+
/** Reserve percent per window label, for every row without its own. */
|
|
108
|
+
reservePercent?: ReservePercent;
|
|
109
|
+
/** Per-row reserves, which replace `reservePercent` for the rows they cover. */
|
|
110
|
+
rowReservePercent?: RowReservePercent;
|
|
111
|
+
/** Defaults to `keep`. */
|
|
112
|
+
refusedPinPolicy?: RefusedPinPolicy;
|
|
113
|
+
resetCreditsApplicable?: ReadonlyMap<string, number>;
|
|
114
|
+
/** The session's current pin, if it has one. */
|
|
115
|
+
pin?: StickyPin;
|
|
116
|
+
/** Each row's recorded wire identity; missing means unknown. */
|
|
117
|
+
identities?: ReadonlyMap<string, string | undefined>;
|
|
118
|
+
onEmptyWeightedSet?: () => void;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* What the caller does with the session's pin: keep it, replace it with
|
|
122
|
+
* `pin`, or drop it. Under the default `keep` policy a valid pin is always
|
|
123
|
+
* kept, even when this request was routed elsewhere because its row was
|
|
124
|
+
* refused, excluded or killed; `refusedPinPolicy` can move it instead.
|
|
125
|
+
*/
|
|
126
|
+
export type PinAction = {
|
|
127
|
+
action: 'retain';
|
|
128
|
+
} | {
|
|
129
|
+
action: 'assign';
|
|
130
|
+
pin: StickyPin;
|
|
131
|
+
} | {
|
|
132
|
+
action: 'clear';
|
|
133
|
+
} | {
|
|
134
|
+
action: 'none';
|
|
135
|
+
};
|
|
136
|
+
export type StickyRoute = {
|
|
137
|
+
outcome: 'dispatch';
|
|
138
|
+
accountId: string;
|
|
139
|
+
source: 'pin' | 'weighted' | 'mode-fallback';
|
|
140
|
+
quotaCheckedAt?: number;
|
|
141
|
+
pin: PinAction;
|
|
142
|
+
/** Rows that selection chose and admission then refused, in selection order. */
|
|
143
|
+
refusedSelections: AdmissionRefusal[];
|
|
144
|
+
admission: AdmissionResult;
|
|
145
|
+
} | {
|
|
146
|
+
outcome: 'no-admissible-account';
|
|
147
|
+
pin: PinAction;
|
|
148
|
+
refusedSelections: AdmissionRefusal[];
|
|
149
|
+
admission: AdmissionResult;
|
|
150
|
+
};
|
|
151
|
+
/**
|
|
152
|
+
* Routes one request in `sticky-balanced` mode. A valid pin whose row is
|
|
153
|
+
* admitted, not excluded and not killed is dispatched as is. Otherwise
|
|
154
|
+
* selection runs over the non-excluded rows; each selected row admission
|
|
155
|
+
* refused is removed and selection re-runs, so the loop ends either on an
|
|
156
|
+
* admitted row or with no admissible account.
|
|
157
|
+
*/
|
|
158
|
+
export declare function routeSticky(input: StickyRouteInput): StickyRoute;
|