@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
257 lines (256 loc) • 10.8 kB
JavaScript
import { logger } from "../utils/logger.js";
import { tokenStore } from "../auth/tokenStore.js";
import { ANTHROPIC_TOKEN_URL, ANTHROPIC_TOKEN_URL_FALLBACK, CLAUDE_CLI_USER_AGENT, CLAUDE_CODE_CLIENT_ID, } from "../auth/anthropicOAuth.js";
import { writeJsonSnapshotAtomically } from "./snapshotPersistence.js";
import { withTimeout } from "../utils/async/withTimeout.js";
import { redactUrlForError } from "../utils/logSanitize.js";
const BUFFER_MS = 5 * 60 * 1000;
const SUCCESS_CACHE_MS = 60_000;
// Bound the best-effort persisted-credential reconciliation read so a hung or
// slow token-store decrypt can never stall (or abort) a live token refresh.
const PEEK_TOKENS_TIMEOUT_MS = 2_000;
const refreshesInFlight = new Map();
export function needsRefresh(account) {
return !!(account.expiresAt &&
account.expiresAt <= Date.now() + BUFFER_MS &&
account.refreshToken);
}
function isRefreshCredentialRejection(status) {
return status === 400 || status === 401 || status === 403 || status === 404;
}
export function isPermanentRefreshFailure(result) {
return isRefreshCredentialRejection(result.status);
}
async function performTokenRefresh(account) {
if (!account.refreshToken) {
return { success: false, error: "No refresh token available" };
}
// OAuth 2.0 token endpoints require an `application/x-www-form-urlencoded`
// request body (RFC 6749 §6); a JSON body is nonstandard for this grant and
// is rejected by RFC-compliant endpoints. Mirror the interactive auth flow
// in `anthropicOAuth.ts::_refreshAccessToken`.
const requestBody = new URLSearchParams({
grant_type: "refresh_token",
refresh_token: account.refreshToken,
client_id: CLAUDE_CODE_CLIENT_ID,
}).toString();
const headers = {
"Content-Type": "application/x-www-form-urlencoded",
Accept: "application/json",
"User-Agent": CLAUDE_CLI_USER_AGENT,
};
const urls = [ANTHROPIC_TOKEN_URL, ANTHROPIC_TOKEN_URL_FALLBACK];
let terminalFailure;
for (const url of urls) {
try {
const resp = await fetch(url, {
method: "POST",
headers,
body: requestBody,
signal: AbortSignal.timeout(10_000),
});
if (!resp.ok) {
const errorBody = await resp.text();
logger.warn(`[token-refresh] failed for ${account.label} at ${redactUrlForError(url)}`, {
status: resp.status,
error: errorBody.slice(0, 500),
});
const failure = {
success: false,
error: errorBody,
status: resp.status,
};
if (isRefreshCredentialRejection(resp.status)) {
terminalFailure = failure;
}
// If primary URL returned a non-ok status, try fallback.
if (url === ANTHROPIC_TOKEN_URL) {
continue;
}
return terminalFailure ?? failure;
}
const data = (await resp.json());
account.token = data.access_token;
account.expiresAt =
data.expires_in !== undefined
? Date.now() + data.expires_in * 1000
: Date.now() + 55 * 60 * 1000;
if (data.refresh_token) {
account.refreshToken = data.refresh_token;
}
logger.debug(`[token-refresh] refreshed for ${account.label}`);
return { success: true };
}
catch (e) {
logger.warn(`[token-refresh] exception for ${account.label} at ${redactUrlForError(url)}`, {
error: String(e),
});
// If primary URL threw, try fallback
if (url === ANTHROPIC_TOKEN_URL) {
continue;
}
return terminalFailure ?? { success: false, error: String(e) };
}
}
// Should not reach here, but guard against empty urls array
return (terminalFailure ?? {
success: false,
error: "No refresh URLs available",
});
}
/**
* Serialize refreshes by refresh-token value. OAuth refresh tokens may rotate,
* so concurrent refreshes with the same old token can make all but the first
* request fail with invalid_grant. A short success cache covers callers that
* loaded either the old token or the newly rotated token around persistence.
*/
export async function refreshToken(account) {
if (!account.refreshToken) {
return { success: false, error: "No refresh token available" };
}
const lockKey = account.refreshToken;
let sharedPromise = refreshesInFlight.get(lockKey);
if (!sharedPromise) {
const refreshAccount = { ...account };
const createdPromise = performTokenRefresh(refreshAccount).then((result) => {
const shared = {
result,
token: refreshAccount.token,
refreshToken: refreshAccount.refreshToken,
expiresAt: refreshAccount.expiresAt,
};
if (result.success) {
const cachedKeys = new Set([lockKey]);
const rotatedKey = refreshAccount.refreshToken;
if (rotatedKey &&
rotatedKey !== lockKey &&
!refreshesInFlight.has(rotatedKey)) {
refreshesInFlight.set(rotatedKey, createdPromise);
cachedKeys.add(rotatedKey);
}
const timer = setTimeout(() => {
for (const key of cachedKeys) {
if (refreshesInFlight.get(key) === createdPromise) {
refreshesInFlight.delete(key);
}
}
}, SUCCESS_CACHE_MS);
timer.unref?.();
}
else if (refreshesInFlight.get(lockKey) === createdPromise) {
refreshesInFlight.delete(lockKey);
}
return shared;
}, (error) => {
if (refreshesInFlight.get(lockKey) === createdPromise) {
refreshesInFlight.delete(lockKey);
}
throw error;
});
refreshesInFlight.set(lockKey, createdPromise);
sharedPromise = createdPromise;
}
const shared = await sharedPromise;
if (shared.result.success) {
account.token = shared.token;
account.refreshToken = shared.refreshToken;
account.expiresAt = shared.expiresAt;
}
return shared.result;
}
async function reloadPersistedTokenStoreAccount(account, target) {
if (!target || typeof target === "string" || !("providerKey" in target)) {
return false;
}
// Best-effort, bounded reconciliation: this only adopts a newer persisted
// credential generation if one exists. A decryption/IO failure — or a hung
// read — must NOT abort the caller, which can still refresh with the
// already-loaded token. Swallow the failure and treat it as "no newer
// persisted generation found" so refresh proceeds instead of the account
// being spuriously disabled.
let stored;
try {
stored = await withTimeout(tokenStore.peekTokens(target.providerKey), PEEK_TOKENS_TIMEOUT_MS, "[token-refresh] peekTokens reconciliation timed out");
}
catch (err) {
logger.debug("[token-refresh] skipping persisted-state reconciliation (peek failed)", { error: err instanceof Error ? err.message : String(err) });
return false;
}
if (!stored ||
stored.expiresAt <= (account.expiresAt ?? 0) ||
(stored.accessToken === account.token &&
stored.refreshToken === account.refreshToken &&
stored.expiresAt === account.expiresAt)) {
return false;
}
account.token = stored.accessToken;
account.refreshToken = stored.refreshToken;
account.expiresAt = stored.expiresAt;
return true;
}
/**
* Refreshes against the latest persisted credential generation. Queued
* requests can otherwise reject a rotated refresh token and disable a newer
* login that was saved while they were waiting.
*/
export async function refreshTokenFromLatest(account, target) {
const reloadedBeforeRefresh = await reloadPersistedTokenStoreAccount(account, target);
if (reloadedBeforeRefresh && !needsRefresh(account)) {
return { success: true };
}
const result = await refreshToken(account);
if (result.success) {
return result;
}
return (await reloadPersistedTokenStoreAccount(account, target))
? { success: true }
: result;
}
export function clearRefreshStateForTests() {
refreshesInFlight.clear();
}
export async function persistTokens(target, account) {
if (typeof target !== "string" && "providerKey" in target) {
await persistTokenStoreAccount(target.providerKey, account);
return;
}
const credPath = typeof target === "string" ? target : target.credPath;
await persistLegacyCredentials(credPath, account);
}
async function persistLegacyCredentials(credPath, account) {
try {
const fs = await import("fs/promises");
const existing = JSON.parse(await fs.readFile(credPath, "utf8"));
existing.oauth = {
...existing.oauth,
accessToken: account.token,
expiresAt: account.expiresAt,
refreshToken: account.refreshToken,
};
existing.updatedAt = Date.now();
await writeJsonSnapshotAtomically(credPath, existing, 0o600);
}
catch (err) {
logger.warn("[token-refresh] Failed to persist legacy credentials", {
error: err instanceof Error ? err.message : String(err),
});
}
}
async function persistTokenStoreAccount(providerKey, account) {
try {
const existing = await withTimeout(tokenStore.peekTokens(providerKey), PEEK_TOKENS_TIMEOUT_MS, "[token-refresh] peekTokens for persistence timed out");
const merged = {
accessToken: account.token,
refreshToken: account.refreshToken ?? existing?.refreshToken,
expiresAt: account.expiresAt ?? existing?.expiresAt ?? Date.now() + 55 * 60 * 1000,
tokenType: existing?.tokenType ?? "Bearer",
...(existing?.scope ? { scope: existing.scope } : {}),
};
await withTimeout(tokenStore.saveTokens(providerKey, merged), PEEK_TOKENS_TIMEOUT_MS, "[token-refresh] saveTokens timed out");
}
catch (err) {
logger.warn("[token-refresh] Failed to persist TokenStore credentials", {
error: err instanceof Error ? err.message : String(err),
});
}
}