Files
OmniRoute/src/lib/db/migrations/073_per_model_token_limits.sql
Muhammad Mugni Hadi 1461a78d07 feat(usage): per-API-key token limits scoped to model/provider/global (#2888)
Integrated into release/v3.8.6
2026-05-29 05:29:10 -03:00

62 lines
2.9 KiB
SQL

-- Migration 073: per-API-key token limits (scoped to model and/or provider)
--
-- Adds enforcement-grade token budgets attachable to an API key and scoped to a
-- specific model, a specific provider, or globally (all traffic for the key).
-- When a key exceeds its configured token budget within the active reset window,
-- requests are rejected with HTTP 429. This complements (does not replace) the
-- USD cost budgets in `domain_budgets` / src/domain/costRules.ts. Token usage is
-- already captured per request in usage_history; these tables provide the
-- authoritative rolling-window counters used for pre-flight enforcement.
--
-- Three tables:
-- api_key_token_limits - the limit definitions (one per scope per key)
-- api_key_token_counters - rolling-window usage counters (implicit rollover)
-- api_key_token_limit_reset_logs - audit trail of window resets
--
-- Plus a composite index on usage_history to make the cold-start seed-on-miss
-- SUM (api_key_id, provider, model, timestamp) an index scan rather than a table
-- scan. The build does NOT run with PRAGMA foreign_keys=ON, so the FK clause is
-- declarative only (matches existing repo convention; application code is the
-- single writer and prunes counters via ON DELETE CASCADE semantics best-effort).
CREATE TABLE IF NOT EXISTS api_key_token_limits (
id TEXT PRIMARY KEY,
api_key_id TEXT NOT NULL,
scope_type TEXT NOT NULL CHECK (scope_type IN ('model', 'provider', 'global')),
scope_value TEXT NOT NULL DEFAULT '',
token_limit INTEGER NOT NULL CHECK (token_limit > 0),
reset_interval TEXT NOT NULL DEFAULT 'monthly' CHECK (reset_interval IN ('daily', 'weekly', 'monthly')),
reset_time TEXT,
enabled INTEGER NOT NULL DEFAULT 1,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
UNIQUE (api_key_id, scope_type, scope_value)
);
CREATE INDEX IF NOT EXISTS idx_aktl_api_key_id
ON api_key_token_limits (api_key_id);
CREATE TABLE IF NOT EXISTS api_key_token_counters (
limit_id TEXT NOT NULL,
window_start TEXT NOT NULL,
tokens_used INTEGER NOT NULL DEFAULT 0,
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
PRIMARY KEY (limit_id, window_start),
FOREIGN KEY (limit_id) REFERENCES api_key_token_limits (id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS api_key_token_limit_reset_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
limit_id TEXT NOT NULL,
reset_at TEXT NOT NULL DEFAULT (datetime('now')),
prev_tokens INTEGER NOT NULL DEFAULT 0,
window_start TEXT NOT NULL,
FOREIGN KEY (limit_id) REFERENCES api_key_token_limits (id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_aktlrl_limit_id
ON api_key_token_limit_reset_logs (limit_id);
CREATE INDEX IF NOT EXISTS idx_uh_key_provider_model_ts
ON usage_history (api_key_id, provider, model, timestamp);