Add hourly rate limits (tokens/hour, requests/hour) and missing providers (chutes, cerebras, ollama)
This commit is contained in:
@@ -187,6 +187,9 @@
|
||||
<option value="google">Google</option>
|
||||
<option value="groq">Groq</option>
|
||||
<option value="nvidia">NVIDIA</option>
|
||||
<option value="chutes">Chutes</option>
|
||||
<option value="cerebras">Cerebras</option>
|
||||
<option value="ollama">Ollama</option>
|
||||
<option value="opencode">OpenCode</option>
|
||||
</select>
|
||||
</label>
|
||||
@@ -207,6 +210,10 @@
|
||||
Tokens per minute
|
||||
<input id="limit-tpm" type="number" min="0" step="1" placeholder="0 = unlimited" />
|
||||
</label>
|
||||
<label>
|
||||
Tokens per hour
|
||||
<input id="limit-tph" type="number" min="0" step="1" placeholder="0 = unlimited" />
|
||||
</label>
|
||||
<label>
|
||||
Tokens per day
|
||||
<input id="limit-tpd" type="number" min="0" step="1" placeholder="0 = unlimited" />
|
||||
@@ -215,6 +222,10 @@
|
||||
Requests per minute
|
||||
<input id="limit-rpm" type="number" min="0" step="1" placeholder="0 = unlimited" />
|
||||
</label>
|
||||
<label>
|
||||
Requests per hour
|
||||
<input id="limit-rph" type="number" min="0" step="1" placeholder="0 = unlimited" />
|
||||
</label>
|
||||
<label>
|
||||
Requests per day
|
||||
<input id="limit-rpd" type="number" min="0" step="1" placeholder="0 = unlimited" />
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
(() => {
|
||||
const DEFAULT_PROVIDERS = ['openrouter', 'mistral', 'google', 'groq', 'nvidia', 'chutes', 'opencode'];
|
||||
const PLANNING_PROVIDERS = ['openrouter', 'mistral', 'google', 'groq', 'nvidia', 'chutes', 'ollama'];
|
||||
const DEFAULT_PROVIDERS = ['openrouter', 'mistral', 'google', 'groq', 'nvidia', 'chutes', 'cerebras', 'ollama', 'opencode'];
|
||||
const PLANNING_PROVIDERS = ['openrouter', 'mistral', 'google', 'groq', 'nvidia', 'chutes', 'cerebras', 'ollama'];
|
||||
const pageType = document?.body?.dataset?.page || 'build';
|
||||
console.log('Admin JS loaded, pageType:', pageType);
|
||||
const state = {
|
||||
@@ -68,8 +68,10 @@
|
||||
limitModel: document.getElementById('limit-model'),
|
||||
limitModelInput: document.getElementById('limit-model-input'),
|
||||
limitTpm: document.getElementById('limit-tpm'),
|
||||
limitTph: document.getElementById('limit-tph'),
|
||||
limitTpd: document.getElementById('limit-tpd'),
|
||||
limitRpm: document.getElementById('limit-rpm'),
|
||||
limitRph: document.getElementById('limit-rph'),
|
||||
limitRpd: document.getElementById('limit-rpd'),
|
||||
limitBackup: document.getElementById('limit-backup'),
|
||||
providerLimitStatus: document.getElementById('provider-limit-status'),
|
||||
@@ -1354,8 +1356,10 @@
|
||||
el.limitModel.value = selectedScope === 'model' ? (modelKey || '') : '';
|
||||
}
|
||||
if (el.limitTpm) el.limitTpm.value = target.tokensPerMinute ?? '';
|
||||
if (el.limitTph) el.limitTph.value = target.tokensPerHour ?? '';
|
||||
if (el.limitTpd) el.limitTpd.value = target.tokensPerDay ?? '';
|
||||
if (el.limitRpm) el.limitRpm.value = target.requestsPerMinute ?? '';
|
||||
if (el.limitRph) el.limitRph.value = target.requestsPerHour ?? '';
|
||||
if (el.limitRpd) el.limitRpd.value = target.requestsPerDay ?? '';
|
||||
if (el.limitBackup && state.opencodeBackupModel !== undefined) el.limitBackup.value = state.opencodeBackupModel || '';
|
||||
}
|
||||
@@ -2304,8 +2308,10 @@
|
||||
scope,
|
||||
model: (pageType === 'plan' && el.limitModelInput) ? el.limitModelInput.value.trim() : el.limitModel.value.trim(),
|
||||
tokensPerMinute: Number(el.limitTpm.value || 0),
|
||||
tokensPerHour: Number(el.limitTph.value || 0),
|
||||
tokensPerDay: Number(el.limitTpd.value || 0),
|
||||
requestsPerMinute: Number(el.limitRpm.value || 0),
|
||||
requestsPerHour: Number(el.limitRph.value || 0),
|
||||
requestsPerDay: Number(el.limitRpd.value || 0),
|
||||
opencodeBackupModel: el.limitBackup.value.trim(),
|
||||
};
|
||||
|
||||
@@ -324,8 +324,9 @@ const TOPUP_PRICES = {
|
||||
const BILLING_CYCLES = ['monthly', 'yearly'];
|
||||
const SUPPORTED_CURRENCIES = ['usd', 'gbp', 'eur'];
|
||||
const MINUTE_MS = 60_000;
|
||||
const DODO_PRODUCTS_CACHE_TTL_MS = Math.max(30_000, Number(process.env.DODO_PRODUCTS_CACHE_TTL_MS || 5 * MINUTE_MS));
|
||||
const HOUR_MS = 3_600_000;
|
||||
const DAY_MS = 86_400_000;
|
||||
const DODO_PRODUCTS_CACHE_TTL_MS = Math.max(30_000, Number(process.env.DODO_PRODUCTS_CACHE_TTL_MS || 5 * MINUTE_MS));
|
||||
const FORTY_EIGHT_HOURS_MS = 48 * DAY_MS;
|
||||
const AVG_CHARS_PER_TOKEN = 4; // rough heuristic
|
||||
const MAX_JSON_BODY_SIZE = Number(process.env.MAX_JSON_BODY_SIZE || 6_000_000); // 6 MB default for JSON payloads (attachments)
|
||||
@@ -516,7 +517,7 @@ const PLAN_PRICES = {
|
||||
};
|
||||
const AUTO_MODEL_TOKEN = 'auto';
|
||||
const DEFAULT_PROVIDER_FALLBACK = 'opencode';
|
||||
const DEFAULT_PROVIDER_SEEDS = ['openrouter', 'mistral', 'google', 'groq', 'nvidia', 'chutes', DEFAULT_PROVIDER_FALLBACK];
|
||||
const DEFAULT_PROVIDER_SEEDS = ['openrouter', 'mistral', 'google', 'groq', 'nvidia', 'chutes', 'cerebras', 'ollama', DEFAULT_PROVIDER_FALLBACK];
|
||||
const PROVIDER_PERSIST_DEBOUNCE_MS = 200;
|
||||
const TOKEN_ESTIMATION_BUFFER = 400;
|
||||
const BOOST_PACK_SIZE = 500_000;
|
||||
@@ -5728,8 +5729,10 @@ function defaultProviderLimit(provider) {
|
||||
provider: normalizeProviderName(provider),
|
||||
scope: 'provider',
|
||||
tokensPerMinute: 0,
|
||||
tokensPerHour: 0,
|
||||
tokensPerDay: 0,
|
||||
requestsPerMinute: 0,
|
||||
requestsPerHour: 0,
|
||||
requestsPerDay: 0,
|
||||
perModel: {},
|
||||
};
|
||||
@@ -5771,16 +5774,20 @@ function ensureProviderLimitDefaults(provider) {
|
||||
const cfg = providerLimits.limits[key];
|
||||
cfg.scope = cfg.scope === 'model' ? 'model' : 'provider';
|
||||
cfg.tokensPerMinute = sanitizeLimitNumber(cfg.tokensPerMinute);
|
||||
cfg.tokensPerHour = sanitizeLimitNumber(cfg.tokensPerHour);
|
||||
cfg.tokensPerDay = sanitizeLimitNumber(cfg.tokensPerDay);
|
||||
cfg.requestsPerMinute = sanitizeLimitNumber(cfg.requestsPerMinute);
|
||||
cfg.requestsPerHour = sanitizeLimitNumber(cfg.requestsPerHour);
|
||||
cfg.requestsPerDay = sanitizeLimitNumber(cfg.requestsPerDay);
|
||||
cfg.perModel = cfg.perModel && typeof cfg.perModel === 'object' ? cfg.perModel : {};
|
||||
Object.keys(cfg.perModel).forEach((model) => {
|
||||
const entry = cfg.perModel[model] || {};
|
||||
cfg.perModel[model] = {
|
||||
tokensPerMinute: sanitizeLimitNumber(entry.tokensPerMinute),
|
||||
tokensPerHour: sanitizeLimitNumber(entry.tokensPerHour),
|
||||
tokensPerDay: sanitizeLimitNumber(entry.tokensPerDay),
|
||||
requestsPerMinute: sanitizeLimitNumber(entry.requestsPerMinute),
|
||||
requestsPerHour: sanitizeLimitNumber(entry.requestsPerHour),
|
||||
requestsPerDay: sanitizeLimitNumber(entry.requestsPerDay),
|
||||
};
|
||||
});
|
||||
@@ -6927,13 +6934,16 @@ function summarizeProviderUsage(provider, model) {
|
||||
const key = normalizeUsageProvider(provider, model);
|
||||
const now = Date.now();
|
||||
const minuteAgo = now - MINUTE_MS;
|
||||
const hourAgo = now - HOUR_MS;
|
||||
const dayAgo = now - DAY_MS;
|
||||
const entries = ensureProviderUsageBucket(key);
|
||||
const filterByModel = !!(model && providerLimits.limits[key] && providerLimits.limits[key].scope === 'model');
|
||||
const result = {
|
||||
tokensLastMinute: 0,
|
||||
tokensLastHour: 0,
|
||||
tokensLastDay: 0,
|
||||
requestsLastMinute: 0,
|
||||
requestsLastHour: 0,
|
||||
requestsLastDay: 0,
|
||||
perModel: {},
|
||||
};
|
||||
@@ -6943,22 +6953,31 @@ function summarizeProviderUsage(provider, model) {
|
||||
const matchesModel = !filterByModel || (entry.model && model && entry.model === model);
|
||||
const targetKey = entry.model || 'unknown';
|
||||
const isMinute = entry.ts >= minuteAgo;
|
||||
const isHour = entry.ts >= hourAgo;
|
||||
const isDay = entry.ts >= dayAgo;
|
||||
if (isMinute && matchesModel) {
|
||||
result.tokensLastMinute += Number(entry.tokens || 0);
|
||||
result.requestsLastMinute += Number(entry.requests || 0);
|
||||
}
|
||||
if (isHour && matchesModel) {
|
||||
result.tokensLastHour += Number(entry.tokens || 0);
|
||||
result.requestsLastHour += Number(entry.requests || 0);
|
||||
}
|
||||
if (isDay && matchesModel) {
|
||||
result.tokensLastDay += Number(entry.tokens || 0);
|
||||
result.requestsLastDay += Number(entry.requests || 0);
|
||||
}
|
||||
if (!result.perModel[targetKey]) {
|
||||
result.perModel[targetKey] = { tokensLastMinute: 0, tokensLastDay: 0, requestsLastMinute: 0, requestsLastDay: 0 };
|
||||
result.perModel[targetKey] = { tokensLastMinute: 0, tokensLastHour: 0, tokensLastDay: 0, requestsLastMinute: 0, requestsLastHour: 0, requestsLastDay: 0 };
|
||||
}
|
||||
if (isMinute) {
|
||||
result.perModel[targetKey].tokensLastMinute += Number(entry.tokens || 0);
|
||||
result.perModel[targetKey].requestsLastMinute += Number(entry.requests || 0);
|
||||
}
|
||||
if (isHour) {
|
||||
result.perModel[targetKey].tokensLastHour += Number(entry.tokens || 0);
|
||||
result.perModel[targetKey].requestsLastHour += Number(entry.requests || 0);
|
||||
}
|
||||
if (isDay) {
|
||||
result.perModel[targetKey].tokensLastDay += Number(entry.tokens || 0);
|
||||
result.perModel[targetKey].requestsLastDay += Number(entry.requests || 0);
|
||||
@@ -6975,8 +6994,10 @@ function isProviderLimited(provider, model) {
|
||||
const modelCfg = (cfg.scope === 'model' && model && cfg.perModel[model]) ? cfg.perModel[model] : cfg;
|
||||
const checks = [
|
||||
['tokensPerMinute', usage.tokensLastMinute, 'minute tokens'],
|
||||
['tokensPerHour', usage.tokensLastHour, 'hourly tokens'],
|
||||
['tokensPerDay', usage.tokensLastDay, 'daily tokens'],
|
||||
['requestsPerMinute', usage.requestsLastMinute, 'minute requests'],
|
||||
['requestsPerHour', usage.requestsLastHour, 'hourly requests'],
|
||||
['requestsPerDay', usage.requestsLastDay, 'daily requests'],
|
||||
];
|
||||
for (const [field, used, label] of checks) {
|
||||
@@ -14530,7 +14551,7 @@ async function handleAdminProviderLimitsPost(req, res) {
|
||||
|
||||
const targetModel = (body.model || '').trim();
|
||||
const target = cfg.scope === 'model' && targetModel ? (cfg.perModel[targetModel] = cfg.perModel[targetModel] || {}) : cfg;
|
||||
['tokensPerMinute', 'tokensPerDay', 'requestsPerMinute', 'requestsPerDay'].forEach((field) => {
|
||||
['tokensPerMinute', 'tokensPerHour', 'tokensPerDay', 'requestsPerMinute', 'requestsPerHour', 'requestsPerDay'].forEach((field) => {
|
||||
if (body[field] !== undefined) target[field] = sanitizeLimitNumber(body[field]);
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user