@juspay/neurolink 11.29.2 → 11.30.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 +3 -3
- 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/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/package.json +2 -1
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Leases — how a lender keeps control of a credential that lives on someone
|
|
3
|
+
* else's machine.
|
|
4
|
+
*
|
|
5
|
+
* In **live** sharing the lender's gate is in the request path, so control is
|
|
6
|
+
* immediate and total. **Complete** sharing trades that away: the borrower holds
|
|
7
|
+
* its own credential on the lender's account and calls the upstream directly, so
|
|
8
|
+
* the lender is not consulted per request. What is left is this — a signed,
|
|
9
|
+
* time-boxed statement of consent that the borrower enforces on itself and must
|
|
10
|
+
* keep renewing.
|
|
11
|
+
*
|
|
12
|
+
* The design turns on one number: **how long may the borrower run without
|
|
13
|
+
* hearing from me.** `offlineGraceMs` is that number. Set it to zero and
|
|
14
|
+
* complete mode collapses into live mode's availability; set it to a day and the
|
|
15
|
+
* borrower keeps working through a weekend when the lender's laptop is shut, at
|
|
16
|
+
* the cost of a day's revocation latency. Both are legitimate; neither is free.
|
|
17
|
+
*
|
|
18
|
+
* `notAfter` is the backstop that does not depend on the borrower's cooperation
|
|
19
|
+
* at all — it is baked into the signed payload, so a borrower that simply never
|
|
20
|
+
* calls home still stops.
|
|
21
|
+
*
|
|
22
|
+
* **Signing.** HMAC-SHA256 keyed by a per-grant secret shared with the borrower
|
|
23
|
+
* at provisioning time. Asymmetric signatures would be tidier, but the key
|
|
24
|
+
* distribution problem they solve does not exist here — exactly two parties are
|
|
25
|
+
* involved and they already share a secret — and the package's browser bundle
|
|
26
|
+
* stubs `node:crypto` down to a subset with no Ed25519 in it.
|
|
27
|
+
*
|
|
28
|
+
* @module proxy/shareLease
|
|
29
|
+
*/
|
|
30
|
+
import { generateShareSecret, secretsMatch, signSharePayload, } from "./shareSigning.js";
|
|
31
|
+
/** A week: long enough to survive a holiday, short enough to be a real bound. */
|
|
32
|
+
export const DEFAULT_LEASE_TTL_MS = 604_800_000;
|
|
33
|
+
/** Fifteen minutes — frequent enough that a pause lands the same session. */
|
|
34
|
+
export const DEFAULT_HEARTBEAT_MS = 900_000;
|
|
35
|
+
/** Twenty-four hours of running unheard-from. The headline trade-off. */
|
|
36
|
+
export const DEFAULT_OFFLINE_GRACE_MS = 86_400_000;
|
|
37
|
+
/**
|
|
38
|
+
* Narrow a verdict to its refusing half.
|
|
39
|
+
*
|
|
40
|
+
* One of the package's build steps compiles without `strictNullChecks`, where
|
|
41
|
+
* TypeScript will not narrow a boolean discriminant. An explicit predicate holds
|
|
42
|
+
* in both modes — see the same pattern in `sharePolicy.isShareRefusal`.
|
|
43
|
+
*/
|
|
44
|
+
export function isLeaseRefusal(verdict) {
|
|
45
|
+
return !verdict.usable;
|
|
46
|
+
}
|
|
47
|
+
export function generateLeaseSecret() {
|
|
48
|
+
return generateShareSecret();
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* The signed portion of a lease — everything but the signature itself.
|
|
52
|
+
*
|
|
53
|
+
* Field order is irrelevant: `signSharePayload` canonicalises before hashing.
|
|
54
|
+
* The explicit list is still worth keeping, because it says which fields a
|
|
55
|
+
* borrower is entitled to rely on rather than signing whatever happens to be on
|
|
56
|
+
* the object.
|
|
57
|
+
*/
|
|
58
|
+
function leasePayload(lease) {
|
|
59
|
+
return {
|
|
60
|
+
schemaVersion: lease.schemaVersion,
|
|
61
|
+
grantId: lease.grantId,
|
|
62
|
+
peerLabel: lease.peerLabel,
|
|
63
|
+
issuedAt: lease.issuedAt,
|
|
64
|
+
notAfter: lease.notAfter,
|
|
65
|
+
heartbeatEveryMs: lease.heartbeatEveryMs,
|
|
66
|
+
offlineGraceMs: lease.offlineGraceMs,
|
|
67
|
+
gates: lease.gates,
|
|
68
|
+
entitlementSnapshot: lease.entitlementSnapshot,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function sign(payload, secret) {
|
|
72
|
+
return signSharePayload(payload, secret);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Issue a lease for a grant.
|
|
76
|
+
*
|
|
77
|
+
* The gates are snapshotted rather than referenced: the borrower enforces what
|
|
78
|
+
* the lender agreed to at issue time, and a tightened policy reaches them at the
|
|
79
|
+
* next heartbeat rather than silently mid-lease.
|
|
80
|
+
*/
|
|
81
|
+
export function issueLease(grant, now = Date.now()) {
|
|
82
|
+
const policy = grant.leasePolicy;
|
|
83
|
+
const secret = grant.leaseSecret;
|
|
84
|
+
if (!secret) {
|
|
85
|
+
throw new Error(`Grant ${grant.id} has no lease secret — provision it before issuing a lease.`);
|
|
86
|
+
}
|
|
87
|
+
const ttl = policy?.ttlMs ?? DEFAULT_LEASE_TTL_MS;
|
|
88
|
+
// A lease can never outlive the grant's own expiry.
|
|
89
|
+
const notAfter = Math.min(now + ttl, grant.gates.notAfter ?? Number.POSITIVE_INFINITY);
|
|
90
|
+
const unsigned = {
|
|
91
|
+
schemaVersion: 1,
|
|
92
|
+
grantId: grant.id,
|
|
93
|
+
peerLabel: grant.peerLabel,
|
|
94
|
+
issuedAt: now,
|
|
95
|
+
notAfter,
|
|
96
|
+
heartbeatEveryMs: policy?.heartbeatEveryMs ?? DEFAULT_HEARTBEAT_MS,
|
|
97
|
+
offlineGraceMs: policy?.offlineGraceMs ?? DEFAULT_OFFLINE_GRACE_MS,
|
|
98
|
+
gates: grant.gates,
|
|
99
|
+
entitlementSnapshot: grant.entitlement.ledger === "coins"
|
|
100
|
+
? (grant.entitlement.coins ?? 0)
|
|
101
|
+
: "unlimited",
|
|
102
|
+
};
|
|
103
|
+
return { ...unsigned, signature: sign(leasePayload(unsigned), secret) };
|
|
104
|
+
}
|
|
105
|
+
/** Does this lease actually come from the lender it claims to? */
|
|
106
|
+
export function isLeaseAuthentic(lease, secret) {
|
|
107
|
+
const { signature, ...unsigned } = lease;
|
|
108
|
+
return secretsMatch(sign(leasePayload(unsigned), secret), signature);
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* May the borrower serve from this lease right now?
|
|
112
|
+
*
|
|
113
|
+
* Three independent stops, in the order that matters:
|
|
114
|
+
*
|
|
115
|
+
* 1. **Unsigned** — someone edited the file. Nothing else is worth checking.
|
|
116
|
+
* 2. **Expired** — `notAfter` passed. Immune to a borrower that never checks in.
|
|
117
|
+
* 3. **Grace elapsed** — the lender has been unreachable for longer than it
|
|
118
|
+
* agreed to be trusted for. This is the one that makes "the lender turned
|
|
119
|
+
* their laptop off" survivable and "the lender revoked me" eventually
|
|
120
|
+
* binding.
|
|
121
|
+
*/
|
|
122
|
+
export function evaluateLease(args) {
|
|
123
|
+
const now = args.now ?? Date.now();
|
|
124
|
+
if (!isLeaseAuthentic(args.lease, args.secret)) {
|
|
125
|
+
return {
|
|
126
|
+
usable: false,
|
|
127
|
+
reason: "unsigned",
|
|
128
|
+
detail: "lease signature does not match the lender's secret",
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
if (args.lease.notAfter <= now) {
|
|
132
|
+
return {
|
|
133
|
+
usable: false,
|
|
134
|
+
reason: "expired",
|
|
135
|
+
detail: "the lease's hard expiry has passed",
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
// The lease's own issue time is the first heartbeat: a freshly provisioned
|
|
139
|
+
// borrower has not failed to check in, it simply has not had to yet.
|
|
140
|
+
const lastContact = args.lastHeartbeatAt ?? args.lease.issuedAt;
|
|
141
|
+
const graceDeadline = lastContact + args.lease.offlineGraceMs;
|
|
142
|
+
if (graceDeadline <= now) {
|
|
143
|
+
return {
|
|
144
|
+
usable: false,
|
|
145
|
+
reason: "grace_elapsed",
|
|
146
|
+
detail: "the lender has been unreachable for longer than this lease allows",
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
return {
|
|
150
|
+
usable: true,
|
|
151
|
+
nextHeartbeatDueAt: lastContact + args.lease.heartbeatEveryMs,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
/** Should the borrower check in now? */
|
|
155
|
+
export function isHeartbeatDue(lease, lastHeartbeatAt, now = Date.now()) {
|
|
156
|
+
const lastContact = lastHeartbeatAt ?? lease.issuedAt;
|
|
157
|
+
return now - lastContact >= lease.heartbeatEveryMs;
|
|
158
|
+
}
|
|
159
|
+
/** The gates a resident grant must enforce on itself, from its lease. */
|
|
160
|
+
export function leasedGates(lease) {
|
|
161
|
+
return lease.gates;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Assemble what the borrower collects once the lender has authorized.
|
|
165
|
+
*
|
|
166
|
+
* Carries no token, by construction. The borrower already holds the verifier
|
|
167
|
+
* that turns the enclosed code into tokens, and it exchanges the two on its own
|
|
168
|
+
* machine — so nothing here is worth intercepting, and the lender never holds a
|
|
169
|
+
* credential for the account it just authorized.
|
|
170
|
+
*
|
|
171
|
+
* `accountLabel` is the lender's *suggestion*, not the key the credential ends
|
|
172
|
+
* up under. The borrower names the account itself — `<peer>-shared` unless
|
|
173
|
+
* `--label` says otherwise — and refuses to install over an existing one,
|
|
174
|
+
* because Anthropic quota snapshots are keyed by the bare label and two
|
|
175
|
+
* accounts sharing one would merge each other's windows. Derived from both
|
|
176
|
+
* parties' names so that a lender running `share list` and a borrower running
|
|
177
|
+
* `auth list` are looking at recognisably the same thing.
|
|
178
|
+
*/
|
|
179
|
+
export function buildProvisionClaim(args) {
|
|
180
|
+
const now = args.now ?? Date.now();
|
|
181
|
+
if (!args.grant.leaseSecret) {
|
|
182
|
+
throw new Error(`Grant ${args.grant.id} has no lease secret — attach lease material first.`);
|
|
183
|
+
}
|
|
184
|
+
return {
|
|
185
|
+
code: args.code,
|
|
186
|
+
state: args.state,
|
|
187
|
+
accountLabel: `${args.grant.peerLabel}-via-${args.lenderName}`,
|
|
188
|
+
leaseSecret: args.grant.leaseSecret,
|
|
189
|
+
lease: issueLease(args.grant, now),
|
|
190
|
+
lenderUrl: (args.lenderUrl ?? "").replace(/\/+$/, ""),
|
|
191
|
+
};
|
|
192
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NeuroCoin ledger and per-window consumption tracking for borrowed traffic.
|
|
3
|
+
*
|
|
4
|
+
* Two jobs, deliberately in one module because they settle from the same event:
|
|
5
|
+
*
|
|
6
|
+
* 1. **Coins.** A grant with a coin entitlement spends against a balance. The
|
|
7
|
+
* balance itself lives on the grant (one source of truth); this module holds
|
|
8
|
+
* the entries and drives the deduction.
|
|
9
|
+
* 2. **Window buckets.** How much of an account's 5h/7d window a grant has taken,
|
|
10
|
+
* keyed by that window's reset timestamp so the counter starts fresh when the
|
|
11
|
+
* window does. This is what makes a slice ceiling mean "a fifth of *this*
|
|
12
|
+
* window" rather than "a fifth, once, forever".
|
|
13
|
+
*
|
|
14
|
+
* **Why hold-then-settle.** Real usage is only known when the response finishes
|
|
15
|
+
* — for a stream, at `message_delta`. A balance checked at admission and
|
|
16
|
+
* deducted at completion lets N concurrent streams each pass the same check and
|
|
17
|
+
* overspend by N-1 requests. So admission opens a *hold* for an estimate, the
|
|
18
|
+
* available balance is `balance - Σ open holds`, and settlement replaces the
|
|
19
|
+
* hold with the real figure. A crash loses in-flight holds, which is the safe
|
|
20
|
+
* direction: the balance is only ever reduced by traffic that actually happened.
|
|
21
|
+
*
|
|
22
|
+
* @module proxy/shareLedger
|
|
23
|
+
*/
|
|
24
|
+
import type { ProxyShareGrant, ProxyShareGrantUsageSummary, ProxyShareHold, ProxySharePoolUsage, ProxyShareSettlement, ProxyShareUsage, ProxyShareWindowObservation } from "../types/index.js";
|
|
25
|
+
/** Normalized tokens per coin. */
|
|
26
|
+
export declare const TOKENS_PER_COIN = 1000;
|
|
27
|
+
export declare function initShareLedger(ledgerFilePath: string): void;
|
|
28
|
+
export declare function modelCoinWeight(model: string | undefined): number;
|
|
29
|
+
/** Convert real usage into coins. Pure — the pricing table is the whole story. */
|
|
30
|
+
export declare function usageToCoins(usage: ProxyShareUsage, model: string | undefined): number;
|
|
31
|
+
/**
|
|
32
|
+
* What to hold at admission, before anything is known about the response.
|
|
33
|
+
*
|
|
34
|
+
* Intentionally rough: the hold exists to stop concurrent streams from each
|
|
35
|
+
* spending the last coin, not to predict the bill. Settlement replaces it with
|
|
36
|
+
* the real figure moments later.
|
|
37
|
+
*/
|
|
38
|
+
export declare function estimateHoldCoins(model: string | undefined, maxTokens: number | undefined): number;
|
|
39
|
+
/** Coins currently held against a grant by in-flight requests. */
|
|
40
|
+
export declare function heldCoins(grantId: string): number;
|
|
41
|
+
/**
|
|
42
|
+
* Balance a new request may draw on: the stored balance minus what in-flight
|
|
43
|
+
* requests have already claimed.
|
|
44
|
+
*/
|
|
45
|
+
export declare function availableCoins(grant: ProxyShareGrant): number;
|
|
46
|
+
export declare function openShareHold(grantId: string, coins: number, now?: number): ProxyShareHold;
|
|
47
|
+
/** Drop a hold without spending it — the request never reached the upstream. */
|
|
48
|
+
export declare function releaseShareHold(holdId: string | undefined): void;
|
|
49
|
+
/**
|
|
50
|
+
* Record how much of an account's windows a borrowed request consumed.
|
|
51
|
+
*
|
|
52
|
+
* Never throws: a bookkeeping failure must not turn a response the borrower has
|
|
53
|
+
* already received into an error.
|
|
54
|
+
*/
|
|
55
|
+
export declare function recordShareWindowDelta(observation: ProxyShareWindowObservation): Promise<void>;
|
|
56
|
+
/**
|
|
57
|
+
* Charge a finished borrowed request against its grant and close the hold.
|
|
58
|
+
*
|
|
59
|
+
* Never throws, for the same reason as above.
|
|
60
|
+
*/
|
|
61
|
+
export declare function settleShareUsage(settlement: ProxyShareSettlement): Promise<number>;
|
|
62
|
+
/**
|
|
63
|
+
* What a grant has taken from one account's current windows.
|
|
64
|
+
*
|
|
65
|
+
* Buckets whose reset timestamp no longer matches the account's are reported as
|
|
66
|
+
* zero: they describe a window that has since rolled over.
|
|
67
|
+
*/
|
|
68
|
+
export declare function readShareWindowUsage(grantId: string, accountKey: string, currentSessionResetAt: number | null, currentWeeklyResetAt: number | null): Promise<{
|
|
69
|
+
sessionFraction: number;
|
|
70
|
+
weeklyFraction: number;
|
|
71
|
+
}>;
|
|
72
|
+
/**
|
|
73
|
+
* What a grant has taken from the pool as a whole, normalised to one window.
|
|
74
|
+
*
|
|
75
|
+
* Summing the per-account fractions and dividing by the account count is what
|
|
76
|
+
* makes a ceiling mean the same thing on a one-account pool and a ten-account
|
|
77
|
+
* one. Without the division, `--max-slice 20` would silently grant 20% of every
|
|
78
|
+
* credential — 200% of a single window's worth across ten accounts.
|
|
79
|
+
*
|
|
80
|
+
* Buckets whose reset timestamp no longer matches contribute zero: they
|
|
81
|
+
* describe a window that has since rolled over.
|
|
82
|
+
*/
|
|
83
|
+
export declare function readSharePoolWindowUsage(grantId: string, accounts: ReadonlyArray<{
|
|
84
|
+
accountKey: string;
|
|
85
|
+
sessionResetAt: number | null;
|
|
86
|
+
weeklyResetAt: number | null;
|
|
87
|
+
}>): Promise<ProxySharePoolUsage>;
|
|
88
|
+
/** Per-grant rollup across accounts, for `share status`. */
|
|
89
|
+
export declare function summarizeGrantUsage(grantId: string): Promise<ProxyShareGrantUsageSummary>;
|
|
90
|
+
/**
|
|
91
|
+
* Apply a standing allowance for every period that has elapsed.
|
|
92
|
+
*
|
|
93
|
+
* Called opportunistically at admission — there is no timer, so a node that is
|
|
94
|
+
* off across one or more refill boundaries pays them on its next borrowed
|
|
95
|
+
* request. Paying only the newest period would quietly turn "100 a week" into
|
|
96
|
+
* "100 whenever you next happen to ask", which is not what the operator wrote.
|
|
97
|
+
*
|
|
98
|
+
* `lastAt` advances by whole periods rather than to `now`, so the schedule stays
|
|
99
|
+
* anchored to when the grant was issued instead of drifting later with every
|
|
100
|
+
* catch-up. It advances past the periods the cap refused to pay as well — see
|
|
101
|
+
* {@link MAX_REFILL_CATCHUP_PERIODS}.
|
|
102
|
+
*/
|
|
103
|
+
export declare function applyRefillIfDue(grant: ProxyShareGrant, now?: number): Promise<ProxyShareGrant>;
|
|
104
|
+
/** Drop all ledger state. Test isolation only. */
|
|
105
|
+
export declare function resetShareLedgerForTests(): void;
|