@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
217 lines (216 loc) • 8.54 kB
JavaScript
/**
* Inbound gate for borrowed traffic.
*
* Transport-agnostic on purpose: it takes a header bag and returns a decision,
* so the same gate covers the Anthropic, OpenAI and Codex route groups from one
* place in the server bootstrap instead of being re-implemented per engine.
*
* **The refusal contract is the load-bearing part.** A borrower must be able to
* tell "you are out of credit" from "the upstream throttled me". If both arrive
* as a bare 429, the borrower's cooldown planner treats an exhausted grant as a
* transient rate limit and retries a peer that will never serve it again this
* week. Every refusal therefore carries `x-neurolink-grant-status` and
* `x-neurolink-grant-reason`, and the borrower routes on those, not the status
* code.
*
* @module proxy/shareGate
*/
import { logger } from "../utils/logger.js";
import { acquireShareSlot, readShareCounters } from "./shareContext.js";
import { looksLikeShareToken, resolveShareToken, touchShareGrantUsage, } from "./shareGrants.js";
import { applyRefillIfDue, availableCoins, estimateHoldCoins, openShareHold, releaseShareHold, } from "./shareLedger.js";
import { evaluateShareAdmission, isShareRefusal, shareRefusalMessage, } from "./sharePolicy.js";
/** Dedicated header, so a share token never collides with a client credential. */
export const SHARE_TOKEN_HEADER = "x-neurolink-share-token";
export const GRANT_STATUS_HEADER = "x-neurolink-grant-status";
export const GRANT_REASON_HEADER = "x-neurolink-grant-reason";
export const GRANT_COINS_HEADER = "x-neurolink-grant-remaining-coins";
export const GRANT_PEER_HEADER = "x-neurolink-grant-peer";
/**
* Pull a share token out of the request.
*
* `Authorization: Bearer` is accepted only when the value carries our token
* prefix — a bare client forwarding its own Anthropic credential must never be
* mistaken for a borrower, and a borrower must never have its token confused
* with an upstream credential.
*/
export function extractShareToken(headers) {
const dedicated = headers[SHARE_TOKEN_HEADER];
if (dedicated?.trim()) {
return dedicated.trim();
}
const authorization = headers.authorization ?? headers.Authorization;
if (!authorization) {
return undefined;
}
const trimmed = authorization.trim();
const bearer = trimmed.toLowerCase().startsWith("bearer ")
? trimmed.slice("bearer ".length).trim()
: trimmed;
return looksLikeShareToken(bearer) ? bearer : undefined;
}
/** Anthropic-shaped error type for a status, so clients parse it as usual. */
function errorTypeForStatus(status) {
if (status === 401) {
return "authentication_error";
}
if (status === 403) {
return "permission_error";
}
return "rate_limit_error";
}
/**
* Grant lifecycle state as the borrower sees it. Distinct from the refusal
* reason: the state answers "can this grant ever serve me again", the reason
* answers "why not this request".
*/
function grantStatusHeaderValue(reason, grant) {
switch (reason) {
case "paused":
return "paused";
case "revoked":
return "revoked";
case "expired":
return "expired";
case "exhausted":
return "exhausted";
case "out_of_window":
return "out-of-window";
case "missing_token":
case "unknown_token":
case "malformed_token":
return "unauthorized";
default:
return grant?.state ?? "active";
}
}
export function buildShareRefusal(reason, options) {
const { status, grant, retryAfterSeconds } = options;
const headers = {
[GRANT_STATUS_HEADER]: grantStatusHeaderValue(reason, grant),
[GRANT_REASON_HEADER]: reason,
};
if (grant) {
headers[GRANT_PEER_HEADER] = grant.peerLabel;
if (grant.entitlement.ledger === "coins") {
headers[GRANT_COINS_HEADER] = String(Math.max(0, Math.floor(grant.entitlement.coins ?? 0)));
}
}
if (retryAfterSeconds !== undefined) {
headers["retry-after"] = String(Math.max(1, Math.round(retryAfterSeconds)));
}
return {
status,
headers,
body: {
type: "error",
error: {
type: errorTypeForStatus(status),
message: options.message ?? shareRefusalMessage(reason),
},
},
};
}
function refusalOutcome(reason, options) {
return { kind: "refused", response: buildShareRefusal(reason, options) };
}
function toContext(grant, extra) {
return {
grantId: grant.id,
peerLabel: grant.peerLabel,
level: grant.level,
gates: grant.gates,
ledger: grant.entitlement.ledger,
...(extra.holdId ? { holdId: extra.holdId } : {}),
...(extra.model ? { model: extra.model } : {}),
};
}
/**
* Should an untokened request be refused?
*
* Off by default so a node's own client keeps working on loopback exactly as
* before. It must be turned on before the listener is exposed — an exposed
* listener without it hands the lender's subscription to anyone who can reach
* the tunnel.
*/
export function isGrantRequiredByEnv() {
const raw = (process.env.NEUROLINK_PROXY_REQUIRE_GRANT ?? "")
.trim()
.toLowerCase();
return raw === "1" || raw === "true" || raw === "on" || raw === "yes";
}
/**
* Decide whether an inbound request may proceed.
*
* An admitted outcome owns a concurrency slot; the caller **must** call
* `release()` when the request finishes, including on error and on client
* disconnect, or the grant's concurrency ceiling leaks.
*/
export async function admitInboundShareRequest(input) {
const now = input.now ?? Date.now();
const requireGrant = input.requireGrant ?? isGrantRequiredByEnv();
const token = extractShareToken(input.headers);
if (!token) {
if (!requireGrant) {
return { kind: "local" };
}
return refusalOutcome("missing_token", {
status: 401,
message: "This proxy requires a share token. Ask the lender for one with " +
"`neurolink proxy share create`.",
});
}
const resolved = await resolveShareToken(token);
if (!resolved) {
return refusalOutcome("unknown_token", { status: 401 });
}
// A standing allowance tops up here rather than on a timer: a node that was
// asleep across the boundary still refills on the next borrowed request.
const grant = resolved.state === "active"
? await applyRefillIfDue(resolved, now)
: resolved;
const coinBalance = grant.entitlement.ledger === "coins"
? ((await input.coinBalanceLookup?.(grant)) ?? availableCoins(grant))
: undefined;
const admission = evaluateShareAdmission({
grant,
now,
model: input.model,
counters: readShareCounters(grant.id, now),
...(coinBalance !== undefined ? { coinBalance } : {}),
});
if (isShareRefusal(admission)) {
logger.debug(`[proxy] share refused peer=${grant.peerLabel} reason=${admission.reason}`);
return refusalOutcome(admission.reason, {
status: admission.status,
grant,
...(admission.retryAfterSeconds !== undefined
? { retryAfterSeconds: admission.retryAfterSeconds }
: {}),
message: admission.message,
});
}
// Pre-authorize before the request goes upstream. Without this, concurrent
// streams each pass the balance check and settle afterwards, overspending by
// one request per stream in flight.
const hold = grant.entitlement.ledger === "coins"
? openShareHold(grant.id, estimateHoldCoins(input.model, input.maxTokens), now)
: undefined;
const releaseSlot = acquireShareSlot(grant.id, now);
// Cosmetic, in-memory, flushed at most once a minute — see the store.
touchShareGrantUsage(grant.id);
return {
kind: "admitted",
context: toContext(grant, {
...(hold ? { holdId: hold.id } : {}),
...(input.model ? { model: input.model } : {}),
}),
release: () => {
releaseSlot();
// Settlement normally consumes the hold; releasing here covers the paths
// that never settle at all — a handler that threw, a client that vanished
// before the upstream answered.
releaseShareHold(hold?.id);
},
};
}