@juspay/neurolink
Version:
Universal AI Development Platform with working MCP integration, multi-provider support, voice (TTS/STT/realtime), and professional CLI. 58+ external MCP servers discoverable, multimodal file processing, RAG pipelines. Build, test, and deploy AI applicatio
184 lines • 6.63 kB
JavaScript
/**
* Per-Account Quota Tracking
*
* Captures Anthropic rate-limit / utilisation headers from proxy responses
* and persists them to ~/.neurolink/account-quotas.json so the CLI can
* display remaining session & weekly capacity per account.
*
* Hot-path design: parseQuotaHeaders is pure CPU (no I/O). saveAccountQuota
* updates an in-memory cache and debounces disk writes so the request/response
* path is never blocked by file I/O.
*/
import { dirname, join } from "path";
import { homedir } from "os";
import { promises as fs } from "fs";
// ---------------------------------------------------------------------------
// Header parsing (pure CPU — no I/O, safe for hot path)
// ---------------------------------------------------------------------------
function getHeader(headers, name) {
if (typeof headers.get === "function") {
return headers.get(name) ?? undefined;
}
const rec = headers;
if (rec[name] !== undefined) {
return rec[name];
}
const lower = name.toLowerCase();
for (const key of Object.keys(rec)) {
if (key.toLowerCase() === lower) {
return rec[key];
}
}
return undefined;
}
/**
* Parse Anthropic rate-limit / quota headers into an `AccountQuota`.
* Returns `null` when key headers are absent.
* Pure computation — no I/O, no blocking.
*/
export function parseQuotaHeaders(headers) {
// Anthropic prefixes all quota headers with "anthropic-ratelimit-"
const P = "anthropic-ratelimit-";
const sessionUtilRaw = getHeader(headers, `${P}unified-5h-utilization`);
const weeklyUtilRaw = getHeader(headers, `${P}unified-7d-utilization`);
if (sessionUtilRaw === undefined || weeklyUtilRaw === undefined) {
return null;
}
const sessionUsed = parseFloat(sessionUtilRaw);
const weeklyUsed = parseFloat(weeklyUtilRaw);
if (Number.isNaN(sessionUsed) || Number.isNaN(weeklyUsed)) {
return null;
}
const sessionResetRaw = getHeader(headers, `${P}unified-5h-reset`);
const weeklyResetRaw = getHeader(headers, `${P}unified-7d-reset`);
const fallbackRaw = getHeader(headers, `${P}unified-fallback-percentage`);
return {
sessionUsed,
sessionStatus: getHeader(headers, `${P}unified-5h-status`) ?? "unknown",
sessionResetAt: sessionResetRaw ? parseInt(sessionResetRaw, 10) || 0 : 0,
weeklyUsed,
weeklyStatus: getHeader(headers, `${P}unified-7d-status`) ?? "unknown",
weeklyResetAt: weeklyResetRaw ? parseInt(weeklyResetRaw, 10) || 0 : 0,
fallbackPercentage: fallbackRaw ? parseFloat(fallbackRaw) || 0 : 0,
overageStatus: getHeader(headers, `${P}unified-overage-status`) ?? "unknown",
lastUpdated: Date.now(),
};
}
// ---------------------------------------------------------------------------
// In-memory cache + debounced async persistence
// ---------------------------------------------------------------------------
const QUOTA_FILE = "account-quotas.json";
const FLUSH_INTERVAL_MS = 5_000; // write to disk at most every 5 seconds
let memoryCache = {};
let cacheLoaded = false;
let dirty = false;
let flushTimer = null;
/** Custom quota file path set via initAccountQuota(). */
let customQuotaFilePath = null;
/**
* Initialise the quota module with a custom file path.
* When set, all reads/writes go to this path instead of the default
* ~/.neurolink/account-quotas.json. Call before the first load/save.
*/
export function initAccountQuota(quotaFilePath) {
customQuotaFilePath = quotaFilePath;
// Cancel any pending flush from a previous configuration so it does not
// write stale data to the new path.
if (flushTimer) {
clearTimeout(flushTimer);
flushTimer = null;
}
// Reset cache so the new path is picked up on next load
memoryCache = {};
cacheLoaded = false;
dirty = false;
}
function getQuotaFilePath() {
return customQuotaFilePath ?? join(homedir(), ".neurolink", QUOTA_FILE);
}
async function ensureDir() {
const filePath = getQuotaFilePath();
const dir = dirname(filePath);
await fs.mkdir(dir, { recursive: true, mode: 0o700 }).catch(() => {
// Non-fatal: directory may already exist
});
}
/** Flush the in-memory cache to disk (async, non-blocking). */
async function flushToDisk() {
if (!dirty) {
return;
}
try {
// Snapshot before async I/O so we only clear dirty if nothing changed
const snapshot = JSON.stringify(memoryCache, null, 2);
await ensureDir();
const filePath = getQuotaFilePath();
const tmpPath = `${filePath}.tmp`;
await fs.writeFile(tmpPath, snapshot, {
mode: 0o600,
});
await fs.rename(tmpPath, filePath);
// Only clear dirty if the cache hasn't changed during the write
if (JSON.stringify(memoryCache, null, 2) === snapshot) {
dirty = false;
}
}
catch {
// Non-fatal — quota is best-effort telemetry
}
}
function scheduleFlush() {
if (flushTimer) {
return;
} // already scheduled
flushTimer = setTimeout(() => {
flushTimer = null;
flushToDisk().catch(() => {
// Non-fatal: quota persistence is best-effort
});
}, FLUSH_INTERVAL_MS);
// Don't prevent process exit
if (flushTimer && typeof flushTimer === "object" && "unref" in flushTimer) {
flushTimer.unref();
}
}
// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------
/**
* Load all persisted account quotas.
* First call reads from disk; subsequent calls return the in-memory cache.
*/
export async function loadAccountQuotas() {
if (cacheLoaded) {
return { ...memoryCache };
}
try {
const raw = await fs.readFile(getQuotaFilePath(), "utf-8");
memoryCache = JSON.parse(raw);
}
catch {
memoryCache = {};
}
cacheLoaded = true;
return { ...memoryCache };
}
/**
* Load quota for a single account.
*/
export async function loadAccountQuota(accountKey) {
const all = await loadAccountQuotas();
return all[accountKey] ?? null;
}
/**
* Update quota for a single account.
* Updates in-memory cache immediately (non-blocking),
* then debounces the disk write to every 5 seconds.
*/
export async function saveAccountQuota(accountKey, quota) {
memoryCache[accountKey] = quota;
cacheLoaded = true;
dirty = true;
scheduleFlush();
}
//# sourceMappingURL=accountQuota.js.map