mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 19:04:02 +00:00
fix(server): throttle the cost-bearing proxies by default
The OpenAI endpoints (/api/realtime/token, /api/openai/hud-summary) now allow 30 requests per minute per client IP and the Google Places endpoints 120, the caps the Pinokio build already ships. GEV_RATELIMIT_OPENAI_PER_MIN and GEV_RATELIMIT_GOOGLE_PER_MIN still override them and exactly 0 disables them; an unreadable value falls back to the default rather than to unlimited. Salvaged from #683 onto current main. Co-authored-by: Sameh Khamis <[email protected]> Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
1 parent
7aa9bbd3be
commit
bb74071dc2
13 files changed
+409
-61
No files matched your search
@@ -2,7 +2,7 @@ import {
|
||||
HUD_SUMMARY_INSTRUCTIONS,
|
||||
keylessHudSummaryResponse,
|
||||
} from '../../../src/hudSummaryResponse.js';
|
||||
import { enforceOptInRateLimit, openAiRateLimiter } from './rate-limit.js';
|
||||
import { enforceRateLimit, openAiRateLimiter } from './rate-limit.js';
|
||||
import { readRequestBody } from '../common/request.js';
|
||||
import { OPENAI_HUD_SUMMARY_MODEL_DEFAULT } from './constants.js';
|
||||
|
||||
@@ -46,10 +46,10 @@ async function handleHudSummary(req, res) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Opt-in per-IP throttle (GEV_RATELIMIT_OPENAI_PER_MIN). Keyless HUD
|
||||
// fallback has no provider cost and resolves above without consuming a
|
||||
// paid-endpoint quota slot.
|
||||
if (!enforceOptInRateLimit(openAiRateLimiter(), req, res)) return;
|
||||
// Per-IP throttle (GEV_RATELIMIT_OPENAI_PER_MIN). On by default; 0 disables.
|
||||
// Keyless HUD fallback has no provider cost and resolves above without
|
||||
// consuming a paid-endpoint quota slot.
|
||||
if (!enforceRateLimit(openAiRateLimiter(), req, res)) return;
|
||||
|
||||
try {
|
||||
const body = await readRequestBody(req, 64 * 1024);
|
||||
|
||||
@@ -1,33 +1,46 @@
|
||||
import { makeOptInRateLimiter, clientKey } from '../common/rate-limit.js';
|
||||
import { makeCostRateLimiter, clientKey } from '../common/rate-limit.js';
|
||||
|
||||
/**
|
||||
* Requests/min/IP applied when GEV_RATELIMIT_OPENAI_PER_MIN is unset. Both
|
||||
* places that already name a value agree on it: `.env.example` recommends 30
|
||||
* and the Pinokio build ships 30 (pinokio/_ENVIRONMENT), so the packaged app
|
||||
* is unaffected and only an unconfigured server changes. The HUD asks for a
|
||||
* summary every 15s (HUD_SUMMARY_INTERVAL_MS), so an open tab spends 4/min and
|
||||
* a voice session adds one token mint; the cap clears ordinary use several
|
||||
* times over and bites only a caller working the endpoint far harder than the
|
||||
* app does.
|
||||
*/
|
||||
const OPENAI_DEFAULT_PER_MIN = 30;
|
||||
|
||||
// Built LAZILY on first request, NOT at module load: `.env` values are applied to process.env later
|
||||
// (the plugin config hook calls loadEnv → process.env, AFTER this module is imported), so reading
|
||||
// process.env here at import time would always see them unset and silently stay unlimited even when
|
||||
// process.env here at import time would always see them unset and fall back to the default even when
|
||||
// configured via .env. Building on first request (like the OPENAI_API_KEY reads) sees the loaded env;
|
||||
// the result is cached so the limiter's per-IP window state persists. `null` = unlimited (default).
|
||||
// the result is cached so the limiter's per-IP window state persists. `null` = the explicit 0 opt-out.
|
||||
let _openAiRateLimiter;
|
||||
|
||||
/** OpenAI cost endpoints (realtime/token + hud-summary). Null = unlimited (default). */
|
||||
/** OpenAI cost endpoints (realtime/token + hud-summary). Null only when set to 0. */
|
||||
function openAiRateLimiter() {
|
||||
if (_openAiRateLimiter === undefined)
|
||||
_openAiRateLimiter = makeOptInRateLimiter(
|
||||
_openAiRateLimiter = makeCostRateLimiter(
|
||||
process.env.GEV_RATELIMIT_OPENAI_PER_MIN,
|
||||
OPENAI_DEFAULT_PER_MIN,
|
||||
);
|
||||
return _openAiRateLimiter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply an opt-in limiter to a request, writing a 429 when over the cap.
|
||||
* When `limiter` is null (unlimited, the default) this is a no-op returning
|
||||
* `true`, so the handler proceeds exactly as before.
|
||||
* Apply the limiter to a request, writing a 429 when over the cap.
|
||||
* When `limiter` is null — the explicit `0` opt-out — this is a no-op
|
||||
* returning `true`, so the handler proceeds unthrottled.
|
||||
*
|
||||
* @param {((key:string)=>boolean)|null} limiter
|
||||
* @param {import('http').IncomingMessage} req
|
||||
* @param {import('http').ServerResponse} res
|
||||
* @returns {boolean} True if the request may proceed; false if a 429 was sent.
|
||||
*/
|
||||
function enforceOptInRateLimit(limiter, req, res) {
|
||||
if (!limiter) return true; // unlimited (default) — no behavior change
|
||||
function enforceRateLimit(limiter, req, res) {
|
||||
if (!limiter) return true; // explicit 0 opt-out — unthrottled
|
||||
if (limiter(clientKey(req))) return true;
|
||||
res.statusCode = 429;
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
@@ -36,4 +49,4 @@ function enforceOptInRateLimit(limiter, req, res) {
|
||||
return false;
|
||||
}
|
||||
|
||||
export { enforceOptInRateLimit, openAiRateLimiter };
|
||||
export { enforceRateLimit, openAiRateLimiter, OPENAI_DEFAULT_PER_MIN };
|
||||
@@ -1,4 +1,4 @@
|
||||
import { enforceOptInRateLimit, openAiRateLimiter } from './rate-limit.js';
|
||||
import { enforceRateLimit, openAiRateLimiter } from './rate-limit.js';
|
||||
import {
|
||||
resolveVoiceModel,
|
||||
isKnownVoiceTier,
|
||||
@@ -31,8 +31,8 @@ function createRealtimeTokenHandler({
|
||||
return;
|
||||
}
|
||||
|
||||
// Opt-in per-IP throttle (GEV_RATELIMIT_OPENAI_PER_MIN). No-op when unset.
|
||||
if (!enforceOptInRateLimit(openAiRateLimiter(), req, res)) return;
|
||||
// Per-IP throttle (GEV_RATELIMIT_OPENAI_PER_MIN). On by default; 0 disables.
|
||||
if (!enforceRateLimit(openAiRateLimiter(), req, res)) return;
|
||||
|
||||
const apiKey = resolveApiKey();
|
||||
if (!apiKey) {
|
||||
|
||||
Reference in new issue
Block a user