68 lines
2.6 KiB
JavaScript
68 lines
2.6 KiB
JavaScript
// Prepend a caveat (don't drop — customer may be on a higher tier) when a
|
|
// rec prescribes concurrency above a known provider rate limit.
|
|
|
|
const PROVIDER_LIMITS = {
|
|
notion: { rps: 3, label: 'Notion', doc: 'https://developers.notion.com/reference/request-limits' },
|
|
openai: { rps: 30, label: 'OpenAI', doc: 'https://platform.openai.com/docs/guides/rate-limits' },
|
|
stripe: { rps: 100, label: 'Stripe', doc: 'https://docs.stripe.com/rate-limits' },
|
|
anthropic: { rps: 10, label: 'Anthropic', doc: 'https://docs.anthropic.com/en/api/rate-limits' },
|
|
};
|
|
|
|
export const metadata = {
|
|
id: 'rate-limit',
|
|
description: 'Prepend caveat when a rec prescribes concurrency above a known provider rate limit.',
|
|
};
|
|
|
|
const PROVIDER_RE = new RegExp(`\\b(${Object.keys(PROVIDER_LIMITS).join('|')})\\b`, 'gi');
|
|
const CONCURRENCY_RE = /\b(?:concurrency|parallel|in\s+parallel|simultaneous|simultaneously|fan[- ]?out|Promise\.all)\b[^\d]{0,40}(\d{1,4})\b/gi;
|
|
const CONCURRENCY_RE_REVERSE = /\b(\d{1,4})\s*(?:concurrent|parallel|simultaneous|in flight)\b/gi;
|
|
|
|
export function apply(rec, _ctx = {}) {
|
|
const text = collectText(rec);
|
|
const providers = matchProviders(text);
|
|
if (providers.length === 0) return {};
|
|
const concurrency = matchConcurrency(text);
|
|
if (concurrency === null) return {};
|
|
|
|
const tags = [];
|
|
let prepend = '';
|
|
for (const key of providers) {
|
|
const limit = PROVIDER_LIMITS[key];
|
|
if (!limit) continue;
|
|
if (concurrency > limit.rps) {
|
|
const tag = `rate-limit:${limit.label}:${concurrency}/${limit.rps}`;
|
|
tags.push(tag);
|
|
prepend += `⚠ ${limit.label} rate-limits to ~${limit.rps} requests/second on first-tier plans; the prescribed concurrency of ${concurrency} may saturate the limit. Verify your tier before applying.\n\n`;
|
|
}
|
|
}
|
|
if (tags.length === 0) return {};
|
|
if (typeof rec.fix === 'string') rec.fix = prepend + rec.fix;
|
|
else rec.fix = prepend.trim();
|
|
return { tags, needsReview: true };
|
|
}
|
|
|
|
function collectText(rec) {
|
|
return [rec.what, rec.why, rec.fix, rec.currentBehavior, rec.desiredBehavior]
|
|
.filter((s) => typeof s === 'string')
|
|
.join('\n');
|
|
}
|
|
|
|
function matchProviders(text) {
|
|
const out = new Set();
|
|
for (const m of text.matchAll(PROVIDER_RE)) out.add(m[1].toLowerCase());
|
|
return [...out];
|
|
}
|
|
|
|
function matchConcurrency(text) {
|
|
let max = null;
|
|
for (const m of text.matchAll(CONCURRENCY_RE)) {
|
|
const n = Number(m[1]);
|
|
if (Number.isFinite(n) && (max === null || n > max)) max = n;
|
|
}
|
|
for (const m of text.matchAll(CONCURRENCY_RE_REVERSE)) {
|
|
const n = Number(m[1]);
|
|
if (Number.isFinite(n) && (max === null || n > max)) max = n;
|
|
}
|
|
return max;
|
|
}
|