All checks were successful
Deploy to Production / deploy (push) Successful in 52s
The earlier caps (Team 150/day, Enterprise 1000/day) used Sonnet/Opus pricing that put max-usage above the tier's monthly revenue — a Bot with a Team subscription could out-cost €199 in Anthropic spend. Drop to 50/day Team and 200/day Enterprise; both now keep ~55-65% margin even when maxed. Pricing page Team feature line updated to match (150 -> 50). Build caps loosened slightly less since the 24h cache TTL makes most builds cache-hits. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
63 lines
1.9 KiB
TypeScript
63 lines
1.9 KiB
TypeScript
import type { Plan } from '@bmm/llm';
|
||
import { getRedis } from './redis.js';
|
||
|
||
const DAY_SEC = 24 * 60 * 60;
|
||
|
||
function todayKey(): string {
|
||
return new Date().toISOString().slice(0, 10);
|
||
}
|
||
|
||
export interface RateLimitResult {
|
||
ok: boolean;
|
||
remaining: number;
|
||
resetIn: number;
|
||
}
|
||
|
||
/**
|
||
* Daily counter via Redis INCR. Atomic — no race window between read & write.
|
||
* First INCR (count === 1) sets the TTL so the key auto-rolls at midnight UTC.
|
||
*/
|
||
export async function checkDailyLimit(
|
||
scope: string,
|
||
userId: string,
|
||
max: number,
|
||
): Promise<RateLimitResult> {
|
||
const key = `ratelimit:${scope}:${userId}:${todayKey()}`;
|
||
const redis = getRedis();
|
||
const count = await redis.incr(key);
|
||
if (count === 1) await redis.expire(key, DAY_SEC);
|
||
const ttl = await redis.ttl(key);
|
||
return {
|
||
ok: count <= max,
|
||
remaining: Math.max(0, max - count),
|
||
resetIn: ttl > 0 ? ttl : DAY_SEC,
|
||
};
|
||
}
|
||
|
||
// Per-tier daily limits on the two LLM-priced actions.
|
||
// Preview = ~€0.002-0.115/call (model-dependent) · Build = ~€0.005-0.22/call.
|
||
//
|
||
// Caps are set so that even a max-usage power-user stays profitable at the
|
||
// tier's price point. Critical for Team/Enterprise where Sonnet/Opus tokens
|
||
// add up fast — a runaway Bot with a Team subscription could otherwise
|
||
// out-cost the €199 monthly revenue. Math (max-case):
|
||
// Pro: 40 prev × €0.020 × 30 = €24/mo → margin €25 (~50%)
|
||
// Team: 50 prev × €0.058 × 30 = €87/mo → margin €112 (~56%)
|
||
// Enterprise: 200 prev × €0.060 × 30 = €360/mo → margin €639 (~64%)
|
||
// Build caps are looser because the 24h cache TTL means most builds are
|
||
// cache-HITS (no LLM call) — the cap is mostly about runner-port / hosting
|
||
// budget, not token cost.
|
||
export const PREVIEW_DAILY_LIMIT: Record<Plan, number> = {
|
||
hobby: 5,
|
||
pro: 40,
|
||
team: 50,
|
||
enterprise: 200,
|
||
};
|
||
|
||
export const BUILD_DAILY_LIMIT: Record<Plan, number> = {
|
||
hobby: 3,
|
||
pro: 20,
|
||
team: 30,
|
||
enterprise: 100,
|
||
};
|