UNPKG

@juspay/neurolink

Version:

The pipe layer of an AI nervous system: one interface connecting provider neurons to your application, across three inference types — generate, stream and decide. `decide` returns typed, calibrated judgements from a non-generative model (~400ms, ~$0.00002

738 lines • 31.6 kB
/** * `neurolink proxy peer` — the borrower's controls. * * A peer is a lender's exposed proxy plus the share token they issued. Peers are * only consulted after every local account is spent, so adding one can never * make this node spend someone else's capacity while it still has its own. * * @module cli/commands/proxyPeer */ import { createHash, randomBytes } from "node:crypto"; import { addPeer, getPeer, listPeers, removePeer, setPeerEnabled, updatePeer, } from "../../proxy/peerStore.js"; import { resolveProxyPaths, resolveProxyPeersPath, } from "../../proxy/proxyPaths.js"; const ACTIONS = [ "add", "request", "sync", "receipts", "net", "redeem", "list", "status", "test", "remove", "pause", "resume", "set", ]; /** * Parse a share link into its parts. * * The token rides in the fragment so it is never sent to whatever host resolves * the URL — fragments are not transmitted. * Shape: `neurolink://share/<host>[/<path>]#<token>` * * Everything between `share/` and the fragment is the lender's address, * **including any path**. A lender fronted at `example.com/proxy` is an ordinary * reverse-proxy layout, and dropping the path silently produced a peer URL * nothing answers on. */ export function parseShareLink(link) { const hashIndex = link.indexOf("#"); if (hashIndex < 0) { return undefined; } const fragment = link.slice(hashIndex + 1).trim(); // `<token>.<receiptSecret>`. Neither half's alphabet contains a ".", and a // link minted before receipts existed simply has no second half. const separator = fragment.indexOf("."); const token = separator < 0 ? fragment : fragment.slice(0, separator); const receiptSecret = separator < 0 ? undefined : fragment.slice(separator + 1); const withoutFragment = link.slice(0, hashIndex); const queryIndex = withoutFragment.indexOf("?"); const query = queryIndex >= 0 ? withoutFragment.slice(queryIndex + 1) : ""; const path = queryIndex >= 0 ? withoutFragment.slice(0, queryIndex) : withoutFragment; const match = /^neurolink:\/\/share\/(.+)$/.exec(path); if (!match || !token) { return undefined; } const host = match[1]; // https unless the link says otherwise. Guessing https for a loopback or LAN // origin would produce a peer URL nothing answers on. const scheme = /(^|&)scheme=http(&|$)/.test(query) ? "http" : "https"; const url = /^https?:\/\//.test(host) ? host : `${scheme}://${host}`; return { url: url.replace(/\/+$/, ""), token, ...(receiptSecret ? { receiptSecret } : {}), }; } function describePeer(peer, now) { const cooling = peer.cooldownUntil && peer.cooldownUntil > now ? `cooling ${Math.ceil((peer.cooldownUntil - now) / 1000)}s (${peer.cooldownReason ?? "unknown"})` : "ready"; const lines = [ `${peer.name}`, ` url ${peer.url}`, ` priority ${peer.priority}`, ` state ${peer.enabled ? cooling : "disabled"}`, ]; if (peer.lastUsedAt) { lines.push(` last served ${new Date(peer.lastUsedAt).toISOString()}`); } if (peer.lastObservation) { const observation = peer.lastObservation; const parts = []; if (observation.grantStatus) { parts.push(observation.grantStatus); } if (observation.remainingCoins !== undefined) { parts.push(`${Math.floor(observation.remainingCoins)} coins left`); } if (parts.length > 0) { lines.push(` lender says ${parts.join(", ")}`); } } if (peer.note) { lines.push(` note ${peer.note}`); } return lines.join("\n"); } async function requirePeer(name) { if (!name) { throw new Error("--name is required for this action."); } const peer = await getPeer(name); if (!peer) { throw new Error(`No peer named "${name}".`); } return peer; } /** * Ask a peer what our grant may still do. * * `/peer/handshake` and `/peer/limits` touch no account and spend nothing, so * this is genuinely free to run. Older lenders predate those routes; for them * the fall-back reads the gate's refusal headers off a request shaped so it * cannot be served. */ async function testPeer(peer) { const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), 15_000); try { const handshake = await fetch(`${peer.url}/peer/handshake`, { headers: { "x-neurolink-share-token": peer.token }, signal: controller.signal, }); if (handshake.ok) { const negotiated = (await handshake.json().catch(() => null)); if (negotiated?.error?.type === "authentication_error") { return "rejected — the lender does not recognize this token"; } if (negotiated?.ok === true) { const state = negotiated.grant?.state ?? "active"; if (state !== "active") { return `reachable — grant is ${state}`; } const limits = await fetch(`${peer.url}/peer/limits`, { headers: { "x-neurolink-share-token": peer.token }, signal: controller.signal, }); const view = (await limits.json().catch(() => null)); if (view?.servable === false) { return `reachable — withheld (${view.withheldReason ?? "no capacity"})`; } return view?.remainingCoins !== undefined ? `reachable — grant accepted, ${view.remainingCoins} coins left` : "reachable — grant accepted"; } } // Pre-handshake lender. A deliberately invalid model exercises the gate // without naming a real one, and the refusal headers carry the answer. const response = await fetch(`${peer.url}/v1/messages`, { method: "POST", headers: { "content-type": "application/json", "x-neurolink-share-token": peer.token, }, body: JSON.stringify({ model: "neurolink-share-probe", max_tokens: 1, messages: [{ role: "user", content: "probe" }], }), signal: controller.signal, }); await response.text().catch(() => ""); const reason = response.headers.get("x-neurolink-grant-reason"); const status = response.headers.get("x-neurolink-grant-status"); if (reason === "missing_token" || reason === "unknown_token") { return "rejected — the lender does not recognize this token"; } if (status && status !== "active") { return `reachable — grant is ${status}`; } return "reachable — grant accepted"; } catch (error) { return `unreachable — ${error instanceof Error ? error.message : String(error)}`; } finally { clearTimeout(timeout); } } /** How long a peer control-plane call may sit before it is abandoned. */ const PEER_REQUEST_TIMEOUT_MS = 15_000; /** * `fetch` with a deadline, for the peer control plane. * * Every call below reaches a machine on someone else's network, and Node's * fetch has no default timeout — a lender that accepts the connection and then * says nothing hangs the command forever, with no output and no exit. * * The body is drained inside the deadline rather than after it: each caller * reads the payload immediately, and a timer cleared at the response headers * would leave a stalled body just as unbounded as no timer at all. The returned * Response is a buffered copy, so `.ok`, `.status` and `.json()` all still work. */ async function peerFetch(url, init = {}) { const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), PEER_REQUEST_TIMEOUT_MS); try { const response = await fetch(url, { ...init, signal: controller.signal }); const body = await response.text(); // 204/205/304 are null-body statuses; the Response constructor rejects a // body on them even when it is empty. const nullBody = response.status === 204 || response.status === 205 || response.status === 304; return new Response(nullBody ? null : body, { status: response.status, statusText: response.statusText, headers: response.headers, }); } finally { clearTimeout(timeout); } } async function lodgeProvisionRequest(peer) { const { generateProvisionState } = await import("../../proxy/shareProvisioning.js"); const codeVerifier = randomBytes(32).toString("base64url"); const codeChallenge = createHash("sha256") .update(codeVerifier) .digest("base64url"); const state = generateProvisionState(); const response = await peerFetch(`${peer.url}/peer/provision`, { method: "POST", headers: { "content-type": "application/json", "x-neurolink-share-token": peer.token, }, body: JSON.stringify({ codeChallenge, state }), }); const payload = (await response.json().catch(() => null)); if (!response.ok || !payload?.ok) { throw new Error(`${peer.name} declined the request: ${payload?.error?.message ?? `HTTP ${response.status}`}`); } await updatePeer(peer.name, { pendingProvision: { codeVerifier, state, requestedAt: Date.now() }, }); console.info(`Asked ${peer.name} to provision a credential for you.`); console.info(""); console.info(" They run:"); console.info(" neurolink proxy share provision --peer <your-label>"); console.info(""); console.info(" Then collect it with:"); console.info(` neurolink proxy peer request --name ${peer.name} --claim`); console.info(""); console.info(" Your PKCE verifier stays on this machine — the lender never sees it,"); console.info(" and the code they relay is worthless to anyone who intercepts it."); } /** * Collect an authorized code and turn it into a resident credential. * * The token exchange happens here, on this machine, with this machine's * verifier. It is the only step in the whole flow that ever holds a token for * the lender's account. */ async function claimProvisionedCredential(peer, argv) { const pending = peer.pendingProvision; if (!pending) { throw new Error(`No outstanding provisioning request for ${peer.name}. ` + `Run \`neurolink proxy peer request --name ${peer.name}\` first.`); } const claimResponse = await peerFetch(`${peer.url}/peer/provision`, { headers: { "x-neurolink-share-token": peer.token }, }); const claimPayload = (await claimResponse.json().catch(() => null)); if (!claimResponse.ok || !claimPayload?.ok) { throw new Error(`${peer.name} declined the claim: ${claimPayload?.error?.message ?? `HTTP ${claimResponse.status}`}`); } if (claimPayload.status === "pending") { console.info(`${peer.name} has not authorized yet. Try again once they have.`); return; } if (claimPayload.status !== "ready" || !claimPayload.claim?.code) { // "none" means the request expired or was never lodged; either way the // verifier we are holding can no longer be redeemed. await updatePeer(peer.name, { pendingProvision: null }); throw new Error(`${peer.name} has no authorization waiting — the request expired. Ask again.`); } const claim = claimPayload.claim; const lease = claim.lease; const code = claim.code; if (!code || !claim.accountLabel || !claim.leaseSecret || !lease?.grantId || !claim.state) { throw new Error("That claim is missing required fields."); } if (claim.state !== pending.state) { // The state we generated must come back untouched. A mismatch means the // code belongs to some other authorization, and exchanging it would bind us // to an account nobody agreed to. throw new Error("The lender returned a code for a different request. Nothing was installed."); } const { tokenStore } = await import("../../auth/tokenStore.js"); const accountLabel = argv.label ?? `${peer.name}-shared`; const providerKey = `anthropic:${accountLabel}`; // Label collisions are not cosmetic here: Anthropic quota snapshots are keyed // by the bare label, so two accounts sharing one would merge each other's // windows and route on numbers that describe neither. const existing = await tokenStore.listByPrefix("anthropic:"); if (existing.some((key) => key.toLowerCase() === providerKey.toLowerCase())) { throw new Error(`An account labelled "${accountLabel}" already exists here. ` + "Re-run with --label <name> to pick another."); } const { exchangeSubscriptionCode } = await import("../../auth/anthropicOAuth.js"); const tokens = await exchangeSubscriptionCode({ code, state: claim.state, codeVerifier: pending.codeVerifier, }); // Open the grant store before the credential lands, so a failure to reach it // costs nothing. Past this point the two writes have to be kept together. const { initResidentGrants, saveResidentGrant } = await import("../../proxy/residentGrants.js"); const { resolveProxyResidentGrantsPath } = await import("../../proxy/proxyPaths.js"); initResidentGrants(resolveProxyResidentGrantsPath(resolveProxyPaths(argv.dev ?? false))); await tokenStore.saveTokens(providerKey, { accessToken: tokens.accessToken, ...(tokens.refreshToken ? { refreshToken: tokens.refreshToken } : {}), // Without a stated expiry, assume it is already due so the first use // refreshes rather than sending a token the provider may have retired. expiresAt: tokens.expiresAt ?? Date.now(), tokenType: "Bearer", }); try { await saveResidentGrant({ schemaVersion: 1, accountLabel, grantId: lease.grantId, lenderName: peer.name, lenderUrl: (claim.lenderUrl ?? peer.url).replace(/\/+$/, ""), leaseSecret: claim.leaseSecret, lease, }); } catch (error) { // The credential routes from the moment it lands in the token store, but // it is the resident grant that carries the lease — the expiry, the // heartbeat address, the accounting. A credential stored without one runs // forever, renews nothing and shows no sign of having come from a peer, // which is exactly the arrangement both sides agreed it would not be. // // So take it back out. The label collision check above is what makes that // safe: this key cannot be an account that already existed here. let rolledBack = true; await tokenStore.clearTokens(providerKey).catch(() => { rolledBack = false; }); const detail = error instanceof Error ? error.message : String(error); throw new Error(`Could not record the lease for ${accountLabel}: ${detail}. ` + (rolledBack ? "The credential was removed rather than left unleased — nothing was installed." : `The credential could NOT be removed; delete "${providerKey}" by hand before retrying.`), { cause: error }); } await updatePeer(peer.name, { pendingProvision: null }); console.info(`Provisioned ${accountLabel} from ${peer.name}.`); console.info(" It now routes alongside your own accounts, for as long as the lease holds."); console.info(" The lender never held this credential — you exchanged the code yourself."); if (!claim.lenderUrl) { console.info(""); console.info(" The claim carries no heartbeat address, so this credential cannot"); console.info(" renew — it will stop when the lease's offline grace runs out."); } } /** * Collect and check a lender's receipts. * * The point is not to see the charges — it is to check them. Every receipt is * verified against the shared secret, recomputed from its own usage block, and * the run is checked for holes, because a charge the lender simply never showed * us is the one failure a list of charges cannot reveal. */ async function reviewPeerReceipts(peer, json) { const response = await peerFetch(`${peer.url}/peer/receipts?since=0`, { headers: { "x-neurolink-share-token": peer.token }, }); const payload = (await response.json().catch(() => null)); if (!response.ok || payload?.ok !== true) { throw new Error(`${peer.name} would not hand over receipts: ${payload?.error?.message ?? `HTTP ${response.status}`}`); } const collected = payload.receipts ?? []; const { auditShareReceipts } = await import("../../proxy/shareReceipts.js"); const statement = auditShareReceipts(collected[0]?.grantId ?? peer.name, collected, peer.receiptSecret); if (json) { console.info(JSON.stringify({ statement, receipts: collected }, null, 2)); return; } if (collected.length === 0) { console.info(`${peer.name} has charged you nothing yet.`); return; } console.info(`${peer.name}: ${statement.receipts} receipt(s), ${statement.coins.toFixed(1)} coins charged`); if (!peer.receiptSecret) { console.info(" No receipt secret on file — nothing here has been verified."); } else if (statement.unverified > 0) { console.info(` ⚠ ${statement.unverified} receipt(s) did not verify against this lender's secret`); } if (statement.miscounted > 0) { console.info(` ⚠ ${statement.miscounted} receipt(s) charge more or less than their own usage implies`); } if (statement.gaps.length > 0) { console.info(` ⚠ ${statement.gaps.length} charge(s) were never shown to you (missing sequence numbers)`); } if (statement.unverified === 0 && statement.miscounted === 0 && statement.gaps.length === 0 && peer.receiptSecret) { console.info(" Every charge verified and matches its own usage."); } await updatePeer(peer.name, { lastReceiptSequence: statement.latestSequence, }); } /** * Settle a round of reciprocal netting with a peer we also lend to. * * Both sides state cumulative totals rather than a delta, so running this twice * forgives nothing the second time instead of paying out again. */ async function netWithPeer(peer, argv) { if (!peer.receiptSecret) { throw new Error(`No receipt secret on file for ${peer.name}, so a netting claim cannot be signed. ` + "Re-add the peer from a fresh share link."); } // The other half of the pair: the grant *this* node issued to the same person. const reciprocal = argv.reciprocal ?? peer.reciprocalPeer ?? peer.name; const { initShareGrants, findShareGrantByPeer } = await import("../../proxy/shareGrants.js"); const { resolveProxyGrantsPath, resolveProxyReceiptsPath } = await import("../../proxy/proxyPaths.js"); const paths = resolveProxyPaths(argv.dev ?? false); initShareGrants(resolveProxyGrantsPath(paths)); const ourGrant = await findShareGrantByPeer(reciprocal); if (!ourGrant) { throw new Error(`Netting needs a grant you issued to ${peer.name}. None is labelled "${reciprocal}" — ` + "name it with --reciprocal <label>, or issue one with `neurolink proxy share create`."); } const { initShareReceipts, totalReceiptedCoins, nettedCoinsFor } = await import("../../proxy/shareReceipts.js"); initShareReceipts(resolveProxyReceiptsPath(paths)); const consumedByYou = await totalReceiptedCoins(ourGrant.id); const alreadyNetted = await nettedCoinsFor(ourGrant.id); const { signSharePayload } = await import("../../proxy/shareSigning.js"); const grantId = await resolvePeerGrantId(peer); const signature = signSharePayload({ consumedByYou, alreadyNetted, grantId }, peer.receiptSecret); const response = await peerFetch(`${peer.url}/peer/net`, { method: "POST", headers: { "content-type": "application/json", "x-neurolink-share-token": peer.token, }, body: JSON.stringify({ consumedByYou, alreadyNetted, signature }), }); const payload = (await response.json().catch(() => null)); if (!response.ok || payload?.ok !== true) { throw new Error(`${peer.name} declined to net: ${payload?.error?.message ?? `HTTP ${response.status}`}`); } const round = payload.netted ?? 0; if (round > 0) { // Forgive the same amount on our own side, so the two ledgers stay level. const { applyReciprocalNetting } = await import("../../proxy/shareReceipts.js"); await applyReciprocalNetting({ grantId: ourGrant.id, consumedFromPeer: alreadyNetted + round, peerAlreadyNetted: alreadyNetted, }); } await updatePeer(peer.name, { reciprocalPeer: reciprocal }); console.info(round > 0 ? `Netted ${round.toFixed(1)} coins with ${peer.name}. ${payload.detail ?? ""}`.trim() : `Nothing to net with ${peer.name}: ${payload.detail ?? "positions already level"}`); } /** * The grant id behind our token for this peer. * * A share token carries its grant id, so this needs no round trip — and the * netting signature has to be bound to the grant it credits, or a claim signed * for one peer could be replayed against another. */ async function resolvePeerGrantId(peer) { const { parseShareToken } = await import("../../proxy/shareGrants.js"); const parsed = parseShareToken(peer.token); if (!parsed) { throw new Error(`The token stored for ${peer.name} is not a share token.`); } return parsed.grantId; } /** Present a coin note to its issuer, to check it or to spend it. */ async function presentCoinNote(peer, argv) { const note = argv.noteValue; if (!note) { throw new Error("peer redeem needs --coin-note <the note the issuer gave you>."); } const redeem = !argv.check; const response = await peerFetch(`${peer.url}/peer/note`, { method: "POST", headers: { "content-type": "application/json", "x-neurolink-share-token": peer.token, }, body: JSON.stringify({ note, redeem }), }); const payload = (await response.json().catch(() => null)); if (payload?.ok !== true) { throw new Error(payload?.error?.message ?? `${peer.name} would not honour that note (HTTP ${response.status})`); } if (!redeem) { console.info(`${peer.name} says that note is ${payload.status}${payload.coins ? ` (${payload.coins} coins)` : ""}.`); return; } console.info(`Redeemed ${payload.coins ?? 0} coins with ${peer.name}.` + (payload.balance !== null && payload.balance !== undefined ? ` Your balance there is now ${Math.floor(payload.balance)}.` : "")); } async function runPeerCommand(argv) { const { initPeerStore } = await import("../../proxy/peerStore.js"); initPeerStore(resolveProxyPeersPath(resolveProxyPaths(argv.dev ?? false))); const now = Date.now(); switch (argv.action) { case "add": { let url = argv.url; let token = argv.token; let receiptSecret = argv.receiptSecret; if (argv.link) { const parsed = parseShareLink(argv.link); if (!parsed) { throw new Error("Could not read that share link. Expected neurolink://share/<host>#<token>."); } url = parsed.url; token = parsed.token; receiptSecret = receiptSecret ?? parsed.receiptSecret; } if (!argv.name || !url || !token) { throw new Error("peer add needs --name plus either --link, or --url and --token."); } const peer = await addPeer({ name: argv.name, url, token, ...(receiptSecret ? { receiptSecret } : {}), ...(argv.priority !== undefined ? { priority: argv.priority } : {}), ...(argv.note ? { note: argv.note } : {}), }); console.info(describePeer(peer, now)); console.info(""); console.info(" This peer is consulted only after every local account is spent."); if (!receiptSecret) { console.info(" No receipt secret came with this link, so charges cannot be checked."); console.info(" Ask the lender for one, or re-add from a link they mint fresh."); } return; } case "request": { const peer = await requirePeer(argv.name); if (argv.claim) { await claimProvisionedCredential(peer, argv); } else { await lodgeProvisionRequest(peer); } return; } case "receipts": { const peer = await requirePeer(argv.name); await reviewPeerReceipts(peer, argv.json ?? false); return; } case "net": { const peer = await requirePeer(argv.name); await netWithPeer(peer, argv); return; } case "redeem": { const peer = await requirePeer(argv.name); await presentCoinNote(peer, argv); return; } case "sync": { const { initResidentGrants, heartbeatResidentGrant, listResidentGrants } = await import("../../proxy/residentGrants.js"); const { resolveProxyResidentGrantsPath } = await import("../../proxy/proxyPaths.js"); initResidentGrants(resolveProxyResidentGrantsPath(resolveProxyPaths(argv.dev ?? false))); const residents = await listResidentGrants(); if (residents.length === 0) { console.info("No adopted credentials to sync."); return; } for (const resident of residents) { if (!resident.lenderUrl) { console.info(`${resident.lenderName}: no heartbeat address`); continue; } const result = await heartbeatResidentGrant(resident); console.info(`${resident.lenderName}: ${result.stopped ? "stopped by lender" : result.detail}`); } return; } case "list": case "status": { const peers = argv.name ? [await requirePeer(argv.name)] : await listPeers(); if (argv.json) { console.info(JSON.stringify(peers, null, 2)); return; } if (peers.length === 0) { console.info("No peers configured."); return; } for (const peer of peers) { console.info(describePeer(peer, now)); console.info(""); } return; } case "test": { const peers = argv.name ? [await requirePeer(argv.name)] : await listPeers(); for (const peer of peers) { const verdict = await testPeer(peer); console.info(`${peer.name}: ${verdict}`); } return; } case "remove": { const peer = await requirePeer(argv.name); await removePeer(peer.name); console.info(`${peer.name} removed.`); return; } case "pause": case "resume": { const peer = await requirePeer(argv.name); const enabled = argv.action === "resume"; await setPeerEnabled(peer.name, enabled); console.info(`${peer.name} is now ${enabled ? "enabled" : "paused"}.`); return; } case "set": { const peer = await requirePeer(argv.name); const updated = await updatePeer(peer.name, { ...(argv.priority !== undefined ? { priority: argv.priority } : {}), ...(argv.note !== undefined ? { note: argv.note } : {}), ...(argv.url ? { url: argv.url } : {}), ...(argv.token ? { token: argv.token } : {}), }); console.info(updated ? describePeer(updated, now) : "No change."); return; } default: throw new Error(`Unknown peer action: ${String(argv.action)}`); } } export const proxyPeerCommand = { command: "peer <action>", describe: "Borrow pool capacity from a peer when this node's own is spent", builder: (yargs) => yargs .positional("action", { type: "string", choices: [...ACTIONS], describe: "Peer action", }) .option("name", { type: "string", description: "Local name for the peer", }) .option("link", { type: "string", description: "Share link from the lender (neurolink://share/...)", }) .option("url", { type: "string", description: "Lender's exposed proxy URL", }) .option("token", { type: "string", description: "Share token issued by the lender", }) .option("priority", { type: "number", description: "Lower is tried first (default 100)", }) .option("note", { type: "string", description: "Free-text note kept with the peer", }) .option("claim", { type: "boolean", default: false, description: "With `peer request`: collect a code the lender has authorized", }) .option("label", { type: "string", description: "Local account label for the provisioned credential (default <peer>-shared)", }) .option("receipt-secret", { type: "string", alias: "receiptSecret", description: "Secret this lender signs receipts with, when adding a peer by hand", }) .option("reciprocal", { type: "string", description: "With `peer net`: label of the grant you issued to the same person", }) .option("coin-note", { type: "string", alias: "noteValue", description: "With `peer redeem`: the coin note to present", }) .option("check", { type: "boolean", default: false, description: "With `peer redeem`: ask the issuer about the note without spending it", }) .option("json", { type: "boolean", default: false, description: "Emit JSON instead of formatted text", }) .option("dev", { type: "boolean", default: false, description: "Use the isolated dev-mode state directory", }), handler: async (argv) => { try { await runPeerCommand(argv); } catch (error) { console.error(error instanceof Error ? error.message : String(error)); process.exitCode = 1; } }, }; //# sourceMappingURL=proxyPeer.js.map