feat(quota): support dynamic codex additional limits with i18n

This commit is contained in:
moxi
2026-02-13 13:52:41 +08:00
parent 15c5f742f4
commit 7068588c58
5 changed files with 77 additions and 6 deletions

View File

@@ -213,11 +213,13 @@ const buildCodexQuotaWindows = (payload: CodexUsagePayload, t: TFunction): Codex
const rateLimit = payload.rate_limit ?? payload.rateLimit ?? undefined;
const codeReviewLimit = payload.code_review_rate_limit ?? payload.codeReviewRateLimit ?? undefined;
const additionalRateLimits = payload.additional_rate_limits ?? payload.additionalRateLimits ?? [];
const windows: CodexQuotaWindow[] = [];
const addWindow = (
id: string,
labelKey: string,
label: string,
labelKey: string | undefined,
window?: CodexUsageWindow | null,
limitReached?: boolean,
allowed?: boolean
@@ -229,7 +231,7 @@ const buildCodexQuotaWindows = (payload: CodexUsagePayload, t: TFunction): Codex
const usedPercent = usedPercentRaw ?? (isLimitReached && resetLabel !== '-' ? 100 : null);
windows.push({
id,
label: t(labelKey),
label,
labelKey,
usedPercent,
resetLabel,
@@ -247,10 +249,9 @@ const buildCodexQuotaWindows = (payload: CodexUsagePayload, t: TFunction): Codex
const pickClassifiedWindows = (
limitInfo?: CodexRateLimitInfo | null
): { fiveHourWindow: CodexUsageWindow | null; weeklyWindow: CodexUsageWindow | null } => {
const rawWindows = [
limitInfo?.primary_window ?? limitInfo?.primaryWindow ?? null,
limitInfo?.secondary_window ?? limitInfo?.secondaryWindow ?? null,
];
const primaryWindow = limitInfo?.primary_window ?? limitInfo?.primaryWindow ?? null;
const secondaryWindow = limitInfo?.secondary_window ?? limitInfo?.secondaryWindow ?? null;
const rawWindows = [primaryWindow, secondaryWindow];
let fiveHourWindow: CodexUsageWindow | null = null;
let weeklyWindow: CodexUsageWindow | null = null;
@@ -265,12 +266,21 @@ const buildCodexQuotaWindows = (payload: CodexUsagePayload, t: TFunction): Codex
}
}
// Fallback to primary/secondary semantics when window seconds are missing or unknown.
if (!fiveHourWindow) {
fiveHourWindow = primaryWindow && primaryWindow !== weeklyWindow ? primaryWindow : null;
}
if (!weeklyWindow) {
weeklyWindow = secondaryWindow && secondaryWindow !== fiveHourWindow ? secondaryWindow : null;
}
return { fiveHourWindow, weeklyWindow };
};
const rateWindows = pickClassifiedWindows(rateLimit);
addWindow(
WINDOW_META.codeFiveHour.id,
t(WINDOW_META.codeFiveHour.labelKey),
WINDOW_META.codeFiveHour.labelKey,
rateWindows.fiveHourWindow,
rawLimitReached,
@@ -278,6 +288,7 @@ const buildCodexQuotaWindows = (payload: CodexUsagePayload, t: TFunction): Codex
);
addWindow(
WINDOW_META.codeWeekly.id,
t(WINDOW_META.codeWeekly.labelKey),
WINDOW_META.codeWeekly.labelKey,
rateWindows.weeklyWindow,
rawLimitReached,
@@ -289,6 +300,7 @@ const buildCodexQuotaWindows = (payload: CodexUsagePayload, t: TFunction): Codex
const codeReviewAllowed = codeReviewLimit?.allowed;
addWindow(
WINDOW_META.codeReviewFiveHour.id,
t(WINDOW_META.codeReviewFiveHour.labelKey),
WINDOW_META.codeReviewFiveHour.labelKey,
codeReviewWindows.fiveHourWindow,
codeReviewLimitReached,
@@ -296,12 +308,54 @@ const buildCodexQuotaWindows = (payload: CodexUsagePayload, t: TFunction): Codex
);
addWindow(
WINDOW_META.codeReviewWeekly.id,
t(WINDOW_META.codeReviewWeekly.labelKey),
WINDOW_META.codeReviewWeekly.labelKey,
codeReviewWindows.weeklyWindow,
codeReviewLimitReached,
codeReviewAllowed
);
const normalizeWindowId = (raw: string) =>
raw
.trim()
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-+|-+$/g, '');
if (Array.isArray(additionalRateLimits)) {
additionalRateLimits.forEach((limitItem, index) => {
const rateInfo = limitItem?.rate_limit ?? limitItem?.rateLimit ?? null;
if (!rateInfo) return;
const limitName =
normalizeStringValue(limitItem?.limit_name ?? limitItem?.limitName) ??
normalizeStringValue(limitItem?.metered_feature ?? limitItem?.meteredFeature) ??
`additional-${index + 1}`;
const idPrefix = normalizeWindowId(limitName) || `additional-${index + 1}`;
const additionalWindows = pickClassifiedWindows(rateInfo);
const additionalLimitReached = rateInfo.limit_reached ?? rateInfo.limitReached;
const additionalAllowed = rateInfo.allowed;
addWindow(
`${idPrefix}-five-hour-${index}`,
t('codex_quota.additional_primary_window', { name: limitName }),
undefined,
additionalWindows.fiveHourWindow,
additionalLimitReached,
additionalAllowed
);
addWindow(
`${idPrefix}-weekly-${index}`,
t('codex_quota.additional_secondary_window', { name: limitName }),
undefined,
additionalWindows.weeklyWindow,
additionalLimitReached,
additionalAllowed
);
});
}
return windows;
};

View File

@@ -490,6 +490,8 @@
"secondary_window": "Weekly limit",
"code_review_primary_window": "Code review 5-hour limit",
"code_review_secondary_window": "Code review weekly limit",
"additional_primary_window": "{{name}} 5-hour limit",
"additional_secondary_window": "{{name}} weekly limit",
"plan_label": "Plan",
"plan_plus": "Plus",
"plan_team": "Team",

View File

@@ -493,6 +493,8 @@
"secondary_window": "Недельный лимит",
"code_review_primary_window": "Лимит code review на 5 часов",
"code_review_secondary_window": "Недельный лимит code review",
"additional_primary_window": "{{name}}: лимит на 5 часов",
"additional_secondary_window": "{{name}}: недельный лимит",
"plan_label": "Тариф",
"plan_plus": "Plus",
"plan_team": "Team",

View File

@@ -490,6 +490,8 @@
"secondary_window": "周限额",
"code_review_primary_window": "代码审查 5 小时限额",
"code_review_secondary_window": "代码审查周限额",
"additional_primary_window": "{{name}} 5 小时限额",
"additional_secondary_window": "{{name}} 周限额",
"plan_label": "套餐",
"plan_plus": "Plus",
"plan_team": "Team",

View File

@@ -88,6 +88,15 @@ export interface CodexRateLimitInfo {
secondaryWindow?: CodexUsageWindow | null;
}
export interface CodexAdditionalRateLimit {
limit_name?: string;
limitName?: string;
metered_feature?: string;
meteredFeature?: string;
rate_limit?: CodexRateLimitInfo | null;
rateLimit?: CodexRateLimitInfo | null;
}
export interface CodexUsagePayload {
plan_type?: string;
planType?: string;
@@ -95,6 +104,8 @@ export interface CodexUsagePayload {
rateLimit?: CodexRateLimitInfo | null;
code_review_rate_limit?: CodexRateLimitInfo | null;
codeReviewRateLimit?: CodexRateLimitInfo | null;
additional_rate_limits?: CodexAdditionalRateLimit[] | null;
additionalRateLimits?: CodexAdditionalRateLimit[] | null;
}
// Claude API payload types