UNPKG

lynkr

Version:

Self-hosted LLM gateway and tier-routing proxy for Claude Code, Cursor, and Codex. Routes across Ollama, AWS Bedrock, OpenRouter, Databricks, Azure OpenAI, llama.cpp, and LM Studio with prompt caching, MCP tools, and 60-80% cost savings.

88 lines (76 loc) 2.62 kB
const { getBudgetManager } = require('../../budget'); const logger = require('../../logger'); /** * Budget and rate limiting middleware */ function budgetMiddleware(req, res, next) { const budgetManager = getBudgetManager(); // Extract user ID (from session, auth header, or default) const userId = req.session?.id || req.headers['x-user-id'] || 'default'; // Check rate limits const rateLimitCheck = budgetManager.checkRateLimit(userId); if (!rateLimitCheck.allowed) { logger.warn({ userId, reason: rateLimitCheck.reason, limit: rateLimitCheck.limit, current: rateLimitCheck.current, }, 'Rate limit exceeded'); return res.status(429).json({ error: 'rate_limit_exceeded', message: `Rate limit exceeded: ${rateLimitCheck.limit} requests per ${rateLimitCheck.reason === 'rate_limit_minute' ? 'minute' : 'hour'}`, limit: rateLimitCheck.limit, current: rateLimitCheck.current, resetInMs: rateLimitCheck.resetInMs, retryAfter: Math.ceil(rateLimitCheck.resetInMs / 1000), // seconds }); } // Check budget const budgetCheck = budgetManager.checkBudget(userId); if (!budgetCheck.allowed) { logger.warn({ userId, reason: budgetCheck.reason, limit: budgetCheck.limit, current: budgetCheck.current, }, 'Budget limit exceeded'); return res.status(402).json({ // 402 Payment Required error: 'budget_exceeded', message: `Budget limit exceeded: ${budgetCheck.reason}`, reason: budgetCheck.reason, limit: budgetCheck.limit, current: budgetCheck.current, }); } // Log warnings if approaching limits if (budgetCheck.warnings && budgetCheck.warnings.length > 0) { logger.warn({ userId, warnings: budgetCheck.warnings, }, 'Budget warning: approaching limits'); } req.budgetInfo = { userId, budgetCheck, startTime: Date.now(), }; // Record usage after response completes res.on('finish', () => { try { const usage = res.locals.usage; if (!usage) return; budgetManager.recordUsage(userId, req.session?.id || null, { tokensInput: usage.prompt_tokens || usage.input_tokens || 0, tokensOutput: usage.completion_tokens || usage.output_tokens || 0, costUsd: usage.cost_usd || 0, model: usage.model || null, endpoint: req.path, latencyMs: Date.now() - req.budgetInfo.startTime, }); } catch (err) { logger.warn({ err: err.message }, 'Failed to record usage after response'); } }); next(); } module.exports = { budgetMiddleware };