@juspay/neurolink 11.29.2 → 12.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/CHANGELOG.md +54 -2
- package/dist/auth/anthropicOAuth.d.ts +50 -0
- package/dist/auth/anthropicOAuth.js +78 -0
- package/dist/browser/neurolink.min.js +393 -393
- package/dist/cli/commands/proxy.d.ts +2 -0
- package/dist/cli/commands/proxy.js +284 -4
- package/dist/cli/commands/proxyExpose.d.ts +35 -0
- package/dist/cli/commands/proxyExpose.js +252 -0
- package/dist/cli/commands/proxyPeer.d.ts +29 -0
- package/dist/cli/commands/proxyPeer.js +738 -0
- package/dist/cli/commands/proxyShare.d.ts +37 -0
- package/dist/cli/commands/proxyShare.js +1080 -0
- package/dist/cli/parser.js +7 -1
- package/dist/core/baseProvider.js +20 -1
- package/dist/neurolink.js +30 -2
- package/dist/proxy/peerStore.d.ts +52 -0
- package/dist/proxy/peerStore.js +324 -0
- package/dist/proxy/peerTransport.d.ts +38 -0
- package/dist/proxy/peerTransport.js +242 -0
- package/dist/proxy/proxyPaths.d.ts +8 -0
- package/dist/proxy/proxyPaths.js +55 -17
- package/dist/proxy/requestLogger.js +8 -0
- package/dist/proxy/residentGrants.d.ts +57 -0
- package/dist/proxy/residentGrants.js +393 -0
- package/dist/proxy/shareAudit.d.ts +81 -0
- package/dist/proxy/shareAudit.js +280 -0
- package/dist/proxy/shareContext.d.ts +38 -0
- package/dist/proxy/shareContext.js +92 -0
- package/dist/proxy/shareGate.d.ts +64 -0
- package/dist/proxy/shareGate.js +216 -0
- package/dist/proxy/shareGrants.d.ts +115 -0
- package/dist/proxy/shareGrants.js +590 -0
- package/dist/proxy/shareLease.d.ts +101 -0
- package/dist/proxy/shareLease.js +192 -0
- package/dist/proxy/shareLedger.d.ts +105 -0
- package/dist/proxy/shareLedger.js +406 -0
- package/dist/proxy/shareListener.d.ts +60 -0
- package/dist/proxy/shareListener.js +143 -0
- package/dist/proxy/shareNotes.d.ts +97 -0
- package/dist/proxy/shareNotes.js +234 -0
- package/dist/proxy/sharePolicy.d.ts +110 -0
- package/dist/proxy/sharePolicy.js +366 -0
- package/dist/proxy/shareProvisioning.d.ts +110 -0
- package/dist/proxy/shareProvisioning.js +237 -0
- package/dist/proxy/shareReceipts.d.ts +99 -0
- package/dist/proxy/shareReceipts.js +303 -0
- package/dist/proxy/shareSigning.d.ts +40 -0
- package/dist/proxy/shareSigning.js +78 -0
- package/dist/server/routes/claudeProxyRoutes.js +1066 -3
- package/dist/types/cli.d.ts +61 -0
- package/dist/types/proxy.d.ts +781 -0
- package/dist/utils/streamCancellation.d.ts +46 -0
- package/dist/utils/streamCancellation.js +90 -0
- package/dist/utils/ttsStream.js +34 -1
- package/dist/voice/livekit/realtimeEventBridge.js +27 -6
- package/package.json +2 -1
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transferable coin notes — A issues, B holds, C redeems against A.
|
|
3
|
+
*
|
|
4
|
+
* A grant's coins are bound to the pair that agreed them. A note is not: it is a
|
|
5
|
+
* bearer credit against the issuing node, redeemable by whoever presents it and
|
|
6
|
+
* holds a grant with that node. That is what lets capacity move through a mesh
|
|
7
|
+
* rather than only along the edge it was granted on.
|
|
8
|
+
*
|
|
9
|
+
* **Replay protection is the whole design.** A note is worth exactly one
|
|
10
|
+
* redemption, so the issuer keeps a record of every note it minted and marks it
|
|
11
|
+
* spent under the same lock that credits the balance. Two nodes racing the same
|
|
12
|
+
* note produce one credit and one `spent`.
|
|
13
|
+
*
|
|
14
|
+
* **What a holder can and cannot check.** Notes are signed with an HMAC keyed by
|
|
15
|
+
* a secret only the issuer has, so an intermediate holder cannot verify one
|
|
16
|
+
* offline — it can only ask the issuer, which is what `POST /peer/note` is for.
|
|
17
|
+
* That check is safe to expose because holding the note is itself the
|
|
18
|
+
* credential: the request must carry a note whose signature the issuer accepts,
|
|
19
|
+
* so it tells a stranger nothing they did not already have.
|
|
20
|
+
*
|
|
21
|
+
* An asymmetric signature would let a holder verify without asking. It is not
|
|
22
|
+
* available here — the package's browser bundle stubs `node:crypto` down to a
|
|
23
|
+
* subset with no Ed25519 — and the check-with-the-issuer step has to exist
|
|
24
|
+
* regardless, because a valid signature says nothing about whether the note has
|
|
25
|
+
* already been spent.
|
|
26
|
+
*
|
|
27
|
+
* @module proxy/shareNotes
|
|
28
|
+
*/
|
|
29
|
+
import type { ProxyShareNote, ProxyShareNoteRecord, ProxyShareNoteStatus } from "../types/index.js";
|
|
30
|
+
/** Default life of a note. Long enough to hand over, short enough to expire. */
|
|
31
|
+
export declare const DEFAULT_NOTE_TTL_MS = 2592000000;
|
|
32
|
+
/** Wire prefix, so a note is never mistaken for a share token. */
|
|
33
|
+
export declare const NOTE_PREFIX = "nln";
|
|
34
|
+
/**
|
|
35
|
+
* How long a record outlives the note it tracks.
|
|
36
|
+
*
|
|
37
|
+
* Every mint appends a record and nothing ever removed one, so the file grew
|
|
38
|
+
* for the life of the node. A record past its `notAfter` cannot authorize
|
|
39
|
+
* anything — the note is refused as `expired` — so the only thing the extra
|
|
40
|
+
* retention buys is that a holder presenting a just-lapsed note is told
|
|
41
|
+
* `expired` rather than the more alarming `unknown`. A month of that is plenty.
|
|
42
|
+
*/
|
|
43
|
+
export declare const NOTE_RECORD_RETENTION_MS = 2592000000;
|
|
44
|
+
export declare function initShareNotes(filePath: string): void;
|
|
45
|
+
/** Render a note as one line that survives a chat window. */
|
|
46
|
+
export declare function encodeShareNote(note: ProxyShareNote): string;
|
|
47
|
+
/** Read a note back. Returns undefined for anything that is not one of ours. */
|
|
48
|
+
export declare function decodeShareNote(encoded: string): ProxyShareNote | undefined;
|
|
49
|
+
/**
|
|
50
|
+
* Mint a note against this node.
|
|
51
|
+
*
|
|
52
|
+
* The record is written before the note is returned, so a note can never exist
|
|
53
|
+
* that the issuer has no memory of — the case where a crash between the two
|
|
54
|
+
* would leave an unredeemable credit in someone's hands.
|
|
55
|
+
*/
|
|
56
|
+
export declare function issueShareNote(args: {
|
|
57
|
+
issuer: string;
|
|
58
|
+
coins: number;
|
|
59
|
+
ttlMs?: number;
|
|
60
|
+
memo?: string;
|
|
61
|
+
now?: number;
|
|
62
|
+
}): Promise<ProxyShareNote>;
|
|
63
|
+
/**
|
|
64
|
+
* What the issuer makes of a note it is shown.
|
|
65
|
+
*
|
|
66
|
+
* `forged` and `unknown` are kept apart deliberately: the first means the
|
|
67
|
+
* signature does not check out, the second means it does but this node has no
|
|
68
|
+
* record of minting it — which is what a note from a *different* issuer looks
|
|
69
|
+
* like, and is a different thing to tell the holder.
|
|
70
|
+
*/
|
|
71
|
+
export declare function inspectShareNote(note: ProxyShareNote, secret: string | undefined, now?: number): Promise<{
|
|
72
|
+
status: ProxyShareNoteStatus;
|
|
73
|
+
coins: number;
|
|
74
|
+
}>;
|
|
75
|
+
/**
|
|
76
|
+
* Redeem a note into a grant's balance.
|
|
77
|
+
*
|
|
78
|
+
* Marking spent and crediting happen under one lock, so two holders racing the
|
|
79
|
+
* same note produce exactly one credit. The record is marked **before** the
|
|
80
|
+
* credit: a crash between them costs the redeemer the note, which is the safe
|
|
81
|
+
* direction — the alternative is a note that can be redeemed twice.
|
|
82
|
+
*/
|
|
83
|
+
export declare function redeemShareNote(args: {
|
|
84
|
+
note: ProxyShareNote;
|
|
85
|
+
grantId: string;
|
|
86
|
+
secret: string | undefined;
|
|
87
|
+
now?: number;
|
|
88
|
+
}): Promise<{
|
|
89
|
+
ok: true;
|
|
90
|
+
coins: number;
|
|
91
|
+
balance: number | undefined;
|
|
92
|
+
} | {
|
|
93
|
+
ok: false;
|
|
94
|
+
status: ProxyShareNoteStatus;
|
|
95
|
+
}>;
|
|
96
|
+
/** Every note this node has minted, newest first. */
|
|
97
|
+
export declare function listShareNotes(): Promise<ProxyShareNoteRecord[]>;
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transferable coin notes — A issues, B holds, C redeems against A.
|
|
3
|
+
*
|
|
4
|
+
* A grant's coins are bound to the pair that agreed them. A note is not: it is a
|
|
5
|
+
* bearer credit against the issuing node, redeemable by whoever presents it and
|
|
6
|
+
* holds a grant with that node. That is what lets capacity move through a mesh
|
|
7
|
+
* rather than only along the edge it was granted on.
|
|
8
|
+
*
|
|
9
|
+
* **Replay protection is the whole design.** A note is worth exactly one
|
|
10
|
+
* redemption, so the issuer keeps a record of every note it minted and marks it
|
|
11
|
+
* spent under the same lock that credits the balance. Two nodes racing the same
|
|
12
|
+
* note produce one credit and one `spent`.
|
|
13
|
+
*
|
|
14
|
+
* **What a holder can and cannot check.** Notes are signed with an HMAC keyed by
|
|
15
|
+
* a secret only the issuer has, so an intermediate holder cannot verify one
|
|
16
|
+
* offline — it can only ask the issuer, which is what `POST /peer/note` is for.
|
|
17
|
+
* That check is safe to expose because holding the note is itself the
|
|
18
|
+
* credential: the request must carry a note whose signature the issuer accepts,
|
|
19
|
+
* so it tells a stranger nothing they did not already have.
|
|
20
|
+
*
|
|
21
|
+
* An asymmetric signature would let a holder verify without asking. It is not
|
|
22
|
+
* available here — the package's browser bundle stubs `node:crypto` down to a
|
|
23
|
+
* subset with no Ed25519 — and the check-with-the-issuer step has to exist
|
|
24
|
+
* regardless, because a valid signature says nothing about whether the note has
|
|
25
|
+
* already been spent.
|
|
26
|
+
*
|
|
27
|
+
* @module proxy/shareNotes
|
|
28
|
+
*/
|
|
29
|
+
import { randomBytes } from "node:crypto";
|
|
30
|
+
import { readFile } from "node:fs/promises";
|
|
31
|
+
import { homedir } from "node:os";
|
|
32
|
+
import { join } from "node:path";
|
|
33
|
+
import { AsyncMutex } from "../utils/asyncMutex.js";
|
|
34
|
+
import { creditShareGrantCoins, getOrCreateNoteSecret } from "./shareGrants.js";
|
|
35
|
+
import { signSharePayload, verifySharePayload } from "./shareSigning.js";
|
|
36
|
+
const NOTES_FILE = "proxy-share-notes.json";
|
|
37
|
+
/** Default life of a note. Long enough to hand over, short enough to expire. */
|
|
38
|
+
export const DEFAULT_NOTE_TTL_MS = 2_592_000_000;
|
|
39
|
+
/** Wire prefix, so a note is never mistaken for a share token. */
|
|
40
|
+
export const NOTE_PREFIX = "nln";
|
|
41
|
+
/**
|
|
42
|
+
* How long a record outlives the note it tracks.
|
|
43
|
+
*
|
|
44
|
+
* Every mint appends a record and nothing ever removed one, so the file grew
|
|
45
|
+
* for the life of the node. A record past its `notAfter` cannot authorize
|
|
46
|
+
* anything — the note is refused as `expired` — so the only thing the extra
|
|
47
|
+
* retention buys is that a holder presenting a just-lapsed note is told
|
|
48
|
+
* `expired` rather than the more alarming `unknown`. A month of that is plenty.
|
|
49
|
+
*/
|
|
50
|
+
export const NOTE_RECORD_RETENTION_MS = 2_592_000_000;
|
|
51
|
+
let customFilePath = null;
|
|
52
|
+
let notes = {};
|
|
53
|
+
let loaded = false;
|
|
54
|
+
const mutationMutex = new AsyncMutex();
|
|
55
|
+
export function initShareNotes(filePath) {
|
|
56
|
+
customFilePath = filePath;
|
|
57
|
+
notes = {};
|
|
58
|
+
loaded = false;
|
|
59
|
+
}
|
|
60
|
+
function getFilePath() {
|
|
61
|
+
return customFilePath ?? join(homedir(), ".neurolink", NOTES_FILE);
|
|
62
|
+
}
|
|
63
|
+
async function ensureLoaded(options = {}) {
|
|
64
|
+
if (loaded && !options.force) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
const parsed = JSON.parse(await readFile(getFilePath(), "utf8"));
|
|
69
|
+
notes = parsed?.notes ?? {};
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
notes = {};
|
|
73
|
+
}
|
|
74
|
+
loaded = true;
|
|
75
|
+
}
|
|
76
|
+
async function persist() {
|
|
77
|
+
const { writeJsonSnapshotAtomically } = await import("./snapshotPersistence.js");
|
|
78
|
+
const file = { schemaVersion: 1, notes };
|
|
79
|
+
await writeJsonSnapshotAtomically(getFilePath(), file);
|
|
80
|
+
}
|
|
81
|
+
/** Everything the signature covers. */
|
|
82
|
+
function notePayload(note) {
|
|
83
|
+
return note;
|
|
84
|
+
}
|
|
85
|
+
/** Render a note as one line that survives a chat window. */
|
|
86
|
+
export function encodeShareNote(note) {
|
|
87
|
+
return `${NOTE_PREFIX}_${Buffer.from(JSON.stringify(note), "utf8").toString("base64url")}`;
|
|
88
|
+
}
|
|
89
|
+
/** Read a note back. Returns undefined for anything that is not one of ours. */
|
|
90
|
+
export function decodeShareNote(encoded) {
|
|
91
|
+
const trimmed = encoded.trim();
|
|
92
|
+
if (!trimmed.startsWith(`${NOTE_PREFIX}_`)) {
|
|
93
|
+
return undefined;
|
|
94
|
+
}
|
|
95
|
+
try {
|
|
96
|
+
const parsed = JSON.parse(Buffer.from(trimmed.slice(NOTE_PREFIX.length + 1), "base64url").toString("utf8"));
|
|
97
|
+
if (typeof parsed?.noteId !== "string" ||
|
|
98
|
+
typeof parsed.coins !== "number" ||
|
|
99
|
+
typeof parsed.signature !== "string" ||
|
|
100
|
+
typeof parsed.notAfter !== "number") {
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
return parsed;
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Mint a note against this node.
|
|
111
|
+
*
|
|
112
|
+
* The record is written before the note is returned, so a note can never exist
|
|
113
|
+
* that the issuer has no memory of — the case where a crash between the two
|
|
114
|
+
* would leave an unredeemable credit in someone's hands.
|
|
115
|
+
*/
|
|
116
|
+
export async function issueShareNote(args) {
|
|
117
|
+
if (!(args.coins > 0)) {
|
|
118
|
+
throw new Error("A note must carry a positive number of coins.");
|
|
119
|
+
}
|
|
120
|
+
const secret = await getOrCreateNoteSecret();
|
|
121
|
+
const now = args.now ?? Date.now();
|
|
122
|
+
const unsigned = {
|
|
123
|
+
schemaVersion: 1,
|
|
124
|
+
noteId: randomBytes(12).toString("base64url"),
|
|
125
|
+
issuer: args.issuer,
|
|
126
|
+
coins: args.coins,
|
|
127
|
+
issuedAt: now,
|
|
128
|
+
notAfter: now + (args.ttlMs ?? DEFAULT_NOTE_TTL_MS),
|
|
129
|
+
...(args.memo ? { memo: args.memo } : {}),
|
|
130
|
+
};
|
|
131
|
+
const note = {
|
|
132
|
+
...unsigned,
|
|
133
|
+
signature: signSharePayload(notePayload(unsigned), secret),
|
|
134
|
+
};
|
|
135
|
+
await mutationMutex.runExclusive(async () => {
|
|
136
|
+
await ensureLoaded({ force: true });
|
|
137
|
+
// Minting is the only thing that grows this file, so it is also where the
|
|
138
|
+
// pruning belongs — no timer, and the cost is paid by the operation that
|
|
139
|
+
// caused it.
|
|
140
|
+
pruneExpiredNotes(now);
|
|
141
|
+
notes[note.noteId] = {
|
|
142
|
+
noteId: note.noteId,
|
|
143
|
+
coins: note.coins,
|
|
144
|
+
issuedAt: note.issuedAt,
|
|
145
|
+
notAfter: note.notAfter,
|
|
146
|
+
...(note.memo ? { memo: note.memo } : {}),
|
|
147
|
+
};
|
|
148
|
+
await persist();
|
|
149
|
+
});
|
|
150
|
+
return note;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Drop records for notes that can no longer be redeemed or usefully explained.
|
|
154
|
+
*
|
|
155
|
+
* Must be called under the mutation mutex, by something that persists after.
|
|
156
|
+
*/
|
|
157
|
+
function pruneExpiredNotes(now) {
|
|
158
|
+
for (const [noteId, record] of Object.entries(notes)) {
|
|
159
|
+
if (record.notAfter + NOTE_RECORD_RETENTION_MS <= now) {
|
|
160
|
+
delete notes[noteId];
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* What the issuer makes of a note it is shown.
|
|
166
|
+
*
|
|
167
|
+
* `forged` and `unknown` are kept apart deliberately: the first means the
|
|
168
|
+
* signature does not check out, the second means it does but this node has no
|
|
169
|
+
* record of minting it — which is what a note from a *different* issuer looks
|
|
170
|
+
* like, and is a different thing to tell the holder.
|
|
171
|
+
*/
|
|
172
|
+
export async function inspectShareNote(note, secret, now = Date.now()) {
|
|
173
|
+
const { signature, ...unsigned } = note;
|
|
174
|
+
if (!secret || !verifySharePayload(unsigned, signature, secret)) {
|
|
175
|
+
return { status: "forged", coins: 0 };
|
|
176
|
+
}
|
|
177
|
+
await ensureLoaded({ force: true });
|
|
178
|
+
const record = notes[note.noteId];
|
|
179
|
+
if (!record) {
|
|
180
|
+
return { status: "unknown", coins: 0 };
|
|
181
|
+
}
|
|
182
|
+
if (record.redeemedAt !== undefined) {
|
|
183
|
+
return { status: "spent", coins: record.coins };
|
|
184
|
+
}
|
|
185
|
+
if (record.notAfter <= now) {
|
|
186
|
+
return { status: "expired", coins: record.coins };
|
|
187
|
+
}
|
|
188
|
+
return { status: "valid", coins: record.coins };
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Redeem a note into a grant's balance.
|
|
192
|
+
*
|
|
193
|
+
* Marking spent and crediting happen under one lock, so two holders racing the
|
|
194
|
+
* same note produce exactly one credit. The record is marked **before** the
|
|
195
|
+
* credit: a crash between them costs the redeemer the note, which is the safe
|
|
196
|
+
* direction — the alternative is a note that can be redeemed twice.
|
|
197
|
+
*/
|
|
198
|
+
export async function redeemShareNote(args) {
|
|
199
|
+
const now = args.now ?? Date.now();
|
|
200
|
+
const { signature, ...unsigned } = args.note;
|
|
201
|
+
if (!args.secret || !verifySharePayload(unsigned, signature, args.secret)) {
|
|
202
|
+
return { ok: false, status: "forged" };
|
|
203
|
+
}
|
|
204
|
+
const claimed = await mutationMutex.runExclusive(async () => {
|
|
205
|
+
await ensureLoaded({ force: true });
|
|
206
|
+
const record = notes[args.note.noteId];
|
|
207
|
+
if (!record) {
|
|
208
|
+
return { status: "unknown", coins: 0 };
|
|
209
|
+
}
|
|
210
|
+
if (record.redeemedAt !== undefined) {
|
|
211
|
+
return { status: "spent", coins: record.coins };
|
|
212
|
+
}
|
|
213
|
+
if (record.notAfter <= now) {
|
|
214
|
+
return { status: "expired", coins: record.coins };
|
|
215
|
+
}
|
|
216
|
+
notes[record.noteId] = {
|
|
217
|
+
...record,
|
|
218
|
+
redeemedAt: now,
|
|
219
|
+
redeemedByGrant: args.grantId,
|
|
220
|
+
};
|
|
221
|
+
await persist();
|
|
222
|
+
return { status: "valid", coins: record.coins };
|
|
223
|
+
});
|
|
224
|
+
if (claimed.status !== "valid") {
|
|
225
|
+
return { ok: false, status: claimed.status };
|
|
226
|
+
}
|
|
227
|
+
const balance = await creditShareGrantCoins(args.grantId, claimed.coins);
|
|
228
|
+
return { ok: true, coins: claimed.coins, balance };
|
|
229
|
+
}
|
|
230
|
+
/** Every note this node has minted, newest first. */
|
|
231
|
+
export async function listShareNotes() {
|
|
232
|
+
await ensureLoaded({ force: true });
|
|
233
|
+
return Object.values(notes).sort((a, b) => b.issuedAt - a.issuedAt);
|
|
234
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Peer-sharing admission policy.
|
|
3
|
+
*
|
|
4
|
+
* Pure evaluation, no I/O — every input is passed in, so this module is cheap on
|
|
5
|
+
* the hot path and directly exercisable from a test.
|
|
6
|
+
*
|
|
7
|
+
* The gate set is deliberately **orthogonal and AND-ed**: a grant may carry a
|
|
8
|
+
* headroom floor *and* a window-slice ceiling *and* a model allowlist, and each
|
|
9
|
+
* is checked independently. The effective allowance is therefore the minimum
|
|
10
|
+
* across whatever is configured, which is what lets "share only what I am not
|
|
11
|
+
* using, and never more than a fifth of it" be one grant rather than two
|
|
12
|
+
* competing modes.
|
|
13
|
+
*
|
|
14
|
+
* Evaluation splits in two, because the two halves answer different questions:
|
|
15
|
+
*
|
|
16
|
+
* - `evaluateShareAdmission` — request-level. Is this borrower allowed to ask at
|
|
17
|
+
* all, right now, for this model?
|
|
18
|
+
* - `filterAccountsForGrant` — account-level. Of the lender's accounts, which
|
|
19
|
+
* may serve this borrower? Headroom and slice ceilings live here because they
|
|
20
|
+
* are properties of an individual account's windows, not of the request.
|
|
21
|
+
*
|
|
22
|
+
* @module proxy/sharePolicy
|
|
23
|
+
*/
|
|
24
|
+
import type { ProxyShareAccountExclusion, ProxyShareAccountFilterResult, ProxyShareAccountView, ProxyShareAdmission, ProxyShareAdmissionInput, ProxyShareGates, ProxyShareGrant, ProxyShareRefusalReason, ProxyShareRefusedAdmission, ProxySharePoolUsage } from "../types/index.js";
|
|
25
|
+
/**
|
|
26
|
+
* Narrow an admission to its refusing half.
|
|
27
|
+
*
|
|
28
|
+
* A plain `!admission.admitted` check would do this under `strict`, but one of
|
|
29
|
+
* the package's build steps compiles without `strictNullChecks`, where TypeScript
|
|
30
|
+
* declines to narrow a boolean discriminant at all. An explicit predicate holds
|
|
31
|
+
* in both modes.
|
|
32
|
+
*/
|
|
33
|
+
export declare function isShareRefusal(admission: ProxyShareAdmission): admission is ProxyShareRefusedAdmission;
|
|
34
|
+
export declare function shareRefusalMessage(reason: ProxyShareRefusalReason): string;
|
|
35
|
+
/** HTTP status for a refusal. 401 is "who are you", 403 is "not you, not ever
|
|
36
|
+
* under this grant", 429 is "not now" — the borrower retries only the last. */
|
|
37
|
+
export declare function shareRefusalStatus(reason: ProxyShareRefusalReason): number;
|
|
38
|
+
/**
|
|
39
|
+
* Is `now` inside the grant's allowed hours?
|
|
40
|
+
*
|
|
41
|
+
* A window whose start hour is greater than its end hour wraps midnight
|
|
42
|
+
* (`21 → 9` means the night shift), which is the common case for lending
|
|
43
|
+
* capacity you are asleep through.
|
|
44
|
+
*/
|
|
45
|
+
export declare function isWithinSchedule(schedule: {
|
|
46
|
+
fromHour: number;
|
|
47
|
+
toHour: number;
|
|
48
|
+
}, now: number): boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Does the requested model fall inside the allowlist?
|
|
51
|
+
*
|
|
52
|
+
* Entries are matched as case-insensitive substrings so a grant can name a tier
|
|
53
|
+
* (`sonnet`) rather than having to track every dated model id.
|
|
54
|
+
*/
|
|
55
|
+
export declare function isModelAllowed(models: string[] | undefined, model: string | undefined): boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Request-level admission.
|
|
58
|
+
*
|
|
59
|
+
* Order matters: identity and lifecycle first (they are permanent refusals),
|
|
60
|
+
* then scope, then the transient limits that carry a `Retry-After`. A borrower
|
|
61
|
+
* that is told "paused" must not also be told "slow down".
|
|
62
|
+
*/
|
|
63
|
+
export declare function evaluateShareAdmission(input: ProxyShareAdmissionInput): ProxyShareAdmission;
|
|
64
|
+
/**
|
|
65
|
+
* Is the account inside its spillover window — close enough to a reset, with
|
|
66
|
+
* little enough consumed, that the remaining capacity would otherwise expire?
|
|
67
|
+
*
|
|
68
|
+
* Unknown reset times fail closed. A spillover grant is a promise about capacity
|
|
69
|
+
* that is *about to be lost*; without a reset time there is no such promise to
|
|
70
|
+
* keep, and guessing would hand out capacity the lender still intends to use.
|
|
71
|
+
*/
|
|
72
|
+
export declare function isSpilloverActive(gates: ProxyShareGates, account: ProxyShareAccountView, now: number): boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Split the pool into the accounts this grant may draw on and the rest.
|
|
75
|
+
*
|
|
76
|
+
* An entry in `gates.accounts` matches either the full store key
|
|
77
|
+
* (`anthropic:alice`) or the bare label (`alice`), because operators think in
|
|
78
|
+
* labels and the routing path thinks in keys.
|
|
79
|
+
*
|
|
80
|
+
* Exported because the scope decides more than admission: it is also the
|
|
81
|
+
* denominator of the pool-wide slice, and computing that over accounts the grant
|
|
82
|
+
* can never touch would loosen the ceiling in proportion to how many there are.
|
|
83
|
+
*/
|
|
84
|
+
export declare function accountsInGrantScope(gates: ProxyShareGates, accounts: readonly ProxyShareAccountView[]): {
|
|
85
|
+
inScope: ProxyShareAccountView[];
|
|
86
|
+
outOfScope: ProxyShareAccountView[];
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* Decide which of the lender's accounts this grant may draw on.
|
|
90
|
+
*
|
|
91
|
+
* Returning exclusions alongside the survivors is deliberate: when nothing
|
|
92
|
+
* survives, the caller needs to say *why* — "your slice is spent" and "I am
|
|
93
|
+
* holding back my reserve" are different answers, and only one of them will
|
|
94
|
+
* change on its own.
|
|
95
|
+
*/
|
|
96
|
+
export declare function filterAccountsForGrant(grant: ProxyShareGrant, accounts: readonly ProxyShareAccountView[], now: number,
|
|
97
|
+
/**
|
|
98
|
+
* Required, not optional. `gates.maxSlice` is a ceiling on what this grant
|
|
99
|
+
* has already drawn from the pool, and with nothing to compare against the
|
|
100
|
+
* only available answer is "not yet" — so an omitted argument would not relax
|
|
101
|
+
* the ceiling, it would remove it. `readSharePoolWindowUsage` returns zeroed
|
|
102
|
+
* fractions for a grant with no history, which is the honest empty value.
|
|
103
|
+
*/
|
|
104
|
+
poolUsage: ProxySharePoolUsage): ProxyShareAccountFilterResult;
|
|
105
|
+
/**
|
|
106
|
+
* Collapse per-account exclusions into the single reason the borrower is told
|
|
107
|
+
* when nothing survived. A transient cause outranks a structural one so the
|
|
108
|
+
* borrower learns whether waiting is worth anything.
|
|
109
|
+
*/
|
|
110
|
+
export declare function summarizeAccountExclusions(excluded: readonly ProxyShareAccountExclusion[]): ProxyShareRefusalReason;
|