fix(quota): support z.ai credit limits

This commit is contained in:
Bohdan Triapitsyn
2026-08-20 19:09:13 +03:00
parent 108c9f529b
commit 3613127e2d
19 changed files with 162 additions and 48 deletions
@@ -327,6 +327,33 @@ describe('Z.ai quota provider (VS Code parity)', () => {
assert.equal(windows['MCP Tools']!.windowSeconds, 30 * 24 * 60 * 60);
assert.equal(windows['MCP Tools']!.resetAt, 1787128459979);
});
test('maps CREDIT_LIMIT entries to windows with credit value labels and plan level', async () => {
stubFetchReturning(() => Promise.resolve(mockResponse({
code: 200,
data: {
limits: [
{ type: 'CREDIT_LIMIT', unit: 3, number: 5, usage: 12000, currentValue: 65, remaining: 11934, percentage: 1, nextResetTime: 1787257978907 },
{ type: 'CREDIT_LIMIT', unit: 6, number: 1, usage: 60000, currentValue: 65, remaining: 59934, percentage: 1, nextResetTime: 1787844668997 },
],
level: 'pro',
},
})));
const result = await fetchQuotaForProvider('zai-coding-plan');
const windows = result.usage!.windows;
assert.equal(result.ok, true);
assert.equal(result.planLabel, 'pro');
assert.equal(windows['5h']!.usedPercent, 1);
assert.equal(windows['5h']!.windowSeconds, 5 * 60 * 60);
assert.equal(windows['5h']!.resetAt, 1787257978907);
assert.equal(windows['5h']!.valueLabel, '65 / 12k credits');
assert.equal(windows.weekly!.usedPercent, 1);
assert.equal(windows.weekly!.windowSeconds, 7 * 24 * 60 * 60);
assert.equal(windows.weekly!.resetAt, 1787844668997);
assert.equal(windows.weekly!.valueLabel, '65 / 60k credits');
});
});
describe('NeuralWatt quota provider (VS Code parity)', () => {
+41 -14
View File
@@ -72,13 +72,31 @@ type ZaiLimit = {
type?: string;
number?: number;
unit?: number;
usage?: number;
currentValue?: number;
remaining?: number;
nextResetTime?: number;
percentage?: number;
};
// CREDIT_LIMIT entries carry `usage` (total credits) and `currentValue` (consumed);
// TOKENS_LIMIT entries only carry a percentage.
const formatZaiCreditAmount = (value: number): string => {
if (value < 1000) return value.toLocaleString('en-US');
return `${Math.round(value / 100) / 10}k`;
};
const formatZaiCreditValueLabel = (limit: ZaiLimit): string | null => {
const used = toNumber(limit.currentValue);
const total = toNumber(limit.usage);
if (used === null || total === null) return null;
return `${formatZaiCreditAmount(used)} / ${formatZaiCreditAmount(total)} credits`;
};
type ZaiPayload = {
data?: {
limits?: ZaiLimit[];
level?: string;
};
};
@@ -411,15 +429,20 @@ const buildResult = (data: {
configured: boolean;
usage?: ProviderUsage | null;
error?: string;
}): ProviderResult => ({
providerId: data.providerId,
providerName: data.providerName,
ok: data.ok,
configured: data.configured,
usage: data.usage ?? null,
...(data.error ? { error: data.error } : {}),
fetchedAt: Date.now(),
});
planLabel?: string | null;
}): ProviderResult => {
const result: ProviderResult = {
providerId: data.providerId,
providerName: data.providerName,
ok: data.ok,
configured: data.configured,
usage: data.usage ?? null,
...(data.error ? { error: data.error } : {}),
fetchedAt: Date.now(),
};
if (data.planLabel) result.planLabel = data.planLabel;
return result;
};
const resolveXaiAuth = (): XaiAuthEntry | null => {
const entry = getProviderAuth('xai');
@@ -1291,7 +1314,7 @@ const buildClaudeRateLimitResult = (): ProviderResult => (
providerName: 'Claude',
ok: false,
configured: true,
error: 'Rate limited by Anthropic. Retrying shortly.',
error: 'Rate limited. Retrying soon.',
})
);
@@ -2059,16 +2082,19 @@ const fetchZaiQuota = async (): Promise<ProviderResult> => {
const payload = await response.json() as ZaiPayload;
const limits = Array.isArray(payload?.data?.limits) ? payload.data.limits : [];
const windows: Record<string, UsageWindow> = {};
for (const tokensLimit of limits.filter((limit) => limit?.type === 'TOKENS_LIMIT')) {
const windowSeconds = resolveWindowSeconds(tokensLimit as Record<string, unknown>);
// The API renamed TOKENS_LIMIT to CREDIT_LIMIT; field semantics stayed the same,
// so both limit types map to the same windows.
for (const limit of limits.filter((entry) => entry?.type === 'TOKENS_LIMIT' || entry?.type === 'CREDIT_LIMIT')) {
const windowSeconds = resolveWindowSeconds(limit as Record<string, unknown>);
const windowLabel = resolveWindowLabel(windowSeconds);
const resetAt = tokensLimit.nextResetTime ? normalizeTimestamp(tokensLimit.nextResetTime) : null;
const usedPercent = typeof tokensLimit.percentage === 'number' ? tokensLimit.percentage : null;
const resetAt = limit.nextResetTime ? normalizeTimestamp(limit.nextResetTime) : null;
const usedPercent = typeof limit.percentage === 'number' ? limit.percentage : null;
windows[windowLabel] = toUsageWindow({
usedPercent,
windowSeconds,
resetAt,
valueLabel: formatZaiCreditValueLabel(limit),
});
}
@@ -2087,6 +2113,7 @@ const fetchZaiQuota = async (): Promise<ProviderResult> => {
ok: true,
configured: true,
usage: { windows },
planLabel: payload?.data?.level || null,
});
} catch (error) {
return buildResult({