- {credit.title || tr("resetCreditDefaultTitle", "Full reset")}
+ {getResetCreditWindowTitle(provider, credit, tr)}
{recommended && (
diff --git a/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/index.tsx b/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/index.tsx
index eda5308eb4..d1858ae933 100644
--- a/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/index.tsx
+++ b/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/index.tsx
@@ -17,7 +17,10 @@ import {
import Card from "@/shared/components/Card";
import { CardSkeleton } from "@/shared/components/Loading";
import { pickDisplayValue } from "@/shared/utils/maskEmail";
-import { supportsProviderQuota, isProviderQuotaVisible } from "@/shared/utils/providerQuotaVisibility";
+import {
+ supportsProviderQuota,
+ isProviderQuotaVisible,
+} from "@/shared/utils/providerQuotaVisibility";
import useEmailPrivacyStore from "@/store/emailPrivacyStore";
import { useNotificationStore } from "@/store/notificationStore";
@@ -1110,6 +1113,7 @@ export default function ProviderLimits({
credits={resetCreditRedemption.resetCreditPicker.credits}
availableCount={resetCreditRedemption.resetCreditPicker.availableCount}
loading={resetCreditRedemption.redeemingResetCreditId !== null}
+ provider={resetCreditRedemption.resetCreditPicker.provider}
onClose={resetCreditRedemption.closeResetCreditPicker}
onRedeem={resetCreditRedemption.redeemCodexResetCredit}
/>
diff --git a/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/providerColumns.ts b/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/providerColumns.ts
index b4e4e0c7fe..757efffa56 100644
--- a/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/providerColumns.ts
+++ b/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/providerColumns.ts
@@ -21,10 +21,10 @@ const PROVIDER_COLUMNS: Record = {
"banked_reset_credits",
],
claude: ["session", "weekly"],
- glm: ["session", "weekly", "mcp_monthly"],
- "glm-cn": ["session", "weekly", "mcp_monthly"],
- glmt: ["session", "weekly", "mcp_monthly"],
- zai: ["session", "weekly", "mcp_monthly"],
+ glm: ["session", "weekly", "mcp_monthly", "banked_reset_credits"],
+ "glm-cn": ["session", "weekly", "mcp_monthly", "banked_reset_credits"],
+ glmt: ["session", "weekly", "mcp_monthly", "banked_reset_credits"],
+ zai: ["session", "weekly", "mcp_monthly", "banked_reset_credits"],
github: ["chat", "completions", "premium_interactions"],
minimax: ["session"],
"minimax-cn": ["session"],
diff --git a/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/quotaParsing.ts b/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/quotaParsing.ts
index 21fe5168cb..0808afea17 100644
--- a/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/quotaParsing.ts
+++ b/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/quotaParsing.ts
@@ -10,7 +10,7 @@ const CODEX_QUOTA_ORDER: Record = {
gpt_5_3_codex_spark_weekly: 3,
banked_reset_credits: 4,
};
-const GLM_FAMILY_PROVIDERS = ["glm", "glm-cn", "glmt", "opencode-go"];
+const GLM_FAMILY_PROVIDERS = ["glm", "glm-cn", "glmt", "zai", "opencode-go"];
const KIMI_CODING_PROVIDERS: readonly string[] = getProviderConnectionFamilyIds("kimi-coding");
/**
@@ -163,7 +163,7 @@ function parseGithub(data: any) {
}
function parseGlmFamily(data: any) {
- return quotaEntries(data).map(([name, quota]) =>
+ const quotas = quotaEntries(data).map(([name, quota]) =>
normalizeQuotaEntry(name, quota, {
displayName: quota?.displayName,
details: Array.isArray(quota?.details) ? quota.details : undefined,
@@ -171,6 +171,14 @@ function parseGlmFamily(data: any) {
Number(quota?.total || 0) === 100 && quota?.remainingPercentage !== undefined,
})
);
+
+ // GLM Coding Plan Reset Cards, surfaced by getGlmUsage alongside the windows.
+ const bankedResetCredits = Number(data?.bankedResetCredits);
+ if (Number.isFinite(bankedResetCredits) && bankedResetCredits > 0) {
+ quotas.push(buildBankedResetCreditsQuota(bankedResetCredits));
+ }
+
+ return quotas;
}
function buildCreditsQuota(
@@ -467,7 +475,7 @@ function looksLikeMoonshotBalance(data: any): boolean {
function parseProviderQuotas(providerId: string, data: any) {
if (looksLikeMoonshotBalance(data)) return parseMoonshotBalance(data);
if (providerId === "github") return parseGithub(data);
- if (["glm", "glm-cn", "glmt", "opencode-go"].includes(providerId)) return parseGlmFamily(data);
+ if (GLM_FAMILY_PROVIDERS.includes(providerId)) return parseGlmFamily(data);
if (providerId === "antigravity" || providerId === "agy") return parseAntigravity(data);
if (providerId === "codex") return parseCodex(data);
if (providerId === "claude") return parseClaude(data);
diff --git a/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/useCodexResetCreditRedemption.ts b/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/useCodexResetCreditRedemption.ts
index 6f77137205..1b89bb6b3e 100644
--- a/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/useCodexResetCreditRedemption.ts
+++ b/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/useCodexResetCreditRedemption.ts
@@ -3,7 +3,7 @@
import { useCallback, useRef, useState } from "react";
import { useNotificationStore } from "@/store/notificationStore";
-import { parseQuotaData } from "./utils";
+import { canProviderRedeemResetCredit, getResetCreditEndpoint, parseQuotaData } from "./utils";
import type { UsageTranslationValues } from "./i18nFallback";
type TranslateUsage = (key: string, fallback: string, values?: UsageTranslationValues) => string;
@@ -43,18 +43,28 @@ interface ResetCreditRequestState {
// Module-level so the ref-store mutation stays outside any hook body — the
// immutability rule bars in-callback writes to `state.idempotencyKeysRef.current`.
-function resetIdempotencyKeys(keys: React.MutableRefObject>): void {
- keys.current = {};
+function resetIdempotencyKey(
+ keys: React.MutableRefObject>,
+ connectionId: string,
+ selectionToken: string
+): void {
+ delete keys.current[`${connectionId}:${selectionToken}`];
}
function ensureIdempotencyKey(
keys: React.MutableRefObject>,
+ connectionId: string,
selectionToken: string
): string {
- const existing = keys.current[selectionToken];
+ const key = `${connectionId}:${selectionToken}`;
+ const existing = keys.current[key];
if (existing) return existing;
+
+ for (const storedKey of Object.keys(keys.current)) {
+ if (storedKey.startsWith(`${connectionId}:`)) delete keys.current[storedKey];
+ }
const created = createIdempotencyKey();
- keys.current[selectionToken] = created;
+ keys.current[key] = created;
return created;
}
@@ -68,20 +78,38 @@ function getRequestErrorMessage(error: unknown, fallback: string): string {
return error instanceof Error && error.message ? error.message : fallback;
}
+export function applyCommittedResetCreditFallback(entry: any): any {
+ if (!entry) return entry;
+ const quotas = Array.isArray(entry.quotas)
+ ? entry.quotas.flatMap((quota: any) => {
+ if (!quota?.isResetCredits) return [quota];
+ const count = Math.max(0, Number(quota.creditCount ?? quota.remaining ?? 0) - 1);
+ return count > 0 ? [{ ...quota, creditCount: count, remaining: count }] : [];
+ })
+ : entry.quotas;
+ const raw =
+ entry.raw && typeof entry.raw === "object"
+ ? {
+ ...entry.raw,
+ bankedResetCredits: Math.max(0, Number(entry.raw.bankedResetCredits ?? 0) - 1),
+ }
+ : entry.raw;
+ return { ...entry, quotas, raw };
+}
+
function useOpenCodexResetCredits(
loadingResetCreditsId: string | null,
redeemingResetCreditId: string | null,
setErrors: SetErrors,
setLoadingResetCreditsId: React.Dispatch>,
setResetCreditPicker: React.Dispatch>,
- idempotencyKeysRef: React.MutableRefObject>,
tr: TranslateUsage
) {
const notify = useNotificationStore();
return useCallback(
async (connectionId: string, provider: string) => {
if (
- (provider !== "codex" && provider !== "grok-cli") ||
+ !canProviderRedeemResetCredit(provider) ||
loadingResetCreditsId ||
redeemingResetCreditId
)
@@ -89,13 +117,14 @@ function useOpenCodexResetCredits(
setLoadingResetCreditsId(connectionId);
setErrors((prev) => ({ ...prev, [connectionId]: null }));
try {
+ const endpoint = getResetCreditEndpoint(provider);
+ if (!endpoint) return;
const response = await fetch(
- `/api/usage/codex-reset-credit?connectionId=${encodeURIComponent(connectionId)}`,
+ `${endpoint}?connectionId=${encodeURIComponent(connectionId)}`,
{ cache: "no-store" }
);
const data = await response.json().catch(() => ({}));
if (!response.ok) throw new Error(data.error || response.statusText);
- idempotencyKeysRef.current = {};
setResetCreditPicker({
connectionId,
provider,
@@ -116,7 +145,6 @@ function useOpenCodexResetCredits(
}
},
[
- idempotencyKeysRef,
loadingResetCreditsId,
notify,
redeemingResetCreditId,
@@ -134,11 +162,17 @@ function useRedeemCodexResetCredit(state: ResetCreditRequestState) {
async (selectionToken: string) => {
const picker = state.resetCreditPicker;
if (!picker || state.redeemingResetCreditId || !selectionToken) return;
- const idempotencyKey = ensureIdempotencyKey(state.idempotencyKeysRef, selectionToken);
+ const idempotencyKey = ensureIdempotencyKey(
+ state.idempotencyKeysRef,
+ picker.connectionId,
+ selectionToken
+ );
state.setRedeemingResetCreditId(picker.connectionId);
state.setErrors((prev) => ({ ...prev, [picker.connectionId]: null }));
try {
- const response = await fetch("/api/usage/codex-reset-credit", {
+ const endpoint = getResetCreditEndpoint(picker.provider);
+ if (!endpoint) return;
+ const response = await fetch(endpoint, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
@@ -149,23 +183,36 @@ function useRedeemCodexResetCredit(state: ResetCreditRequestState) {
});
const data = await response.json().catch(() => ({}));
if (!response.ok) throw new Error(data.error || response.statusText);
- const usage = data.usage || {};
- state.setQuotaData((prev) => ({
- ...prev,
- [picker.connectionId]: {
- quotas: parseQuotaData(picker.provider, usage),
- plan: usage.plan || null,
- message: usage.message || null,
- raw: usage,
- stale: usage._stale ? { since: usage._staleSince, reason: usage._staleReason } : null,
- },
- }));
+ const refreshPending = data.refreshPending === true && !data.usage;
+ if (refreshPending) {
+ // The upstream redemption already committed but the post-commit quota
+ // refresh failed: keep the existing windows and only decrement the
+ // consumed reset-credit count so success is not reported as failure.
+ state.setQuotaData((prev) => {
+ const entry = prev[picker.connectionId];
+ return entry
+ ? { ...prev, [picker.connectionId]: applyCommittedResetCreditFallback(entry) }
+ : prev;
+ });
+ } else {
+ const usage = data.usage || {};
+ state.setQuotaData((prev) => ({
+ ...prev,
+ [picker.connectionId]: {
+ quotas: parseQuotaData(picker.provider, usage),
+ plan: usage.plan || null,
+ message: usage.message || null,
+ raw: usage,
+ stale: usage._stale ? { since: usage._staleSince, reason: usage._staleReason } : null,
+ },
+ }));
+ }
state.setLastRefreshedAt((prev) => ({
...prev,
[picker.connectionId]: new Date().toISOString(),
}));
state.setResetCreditPicker(null);
- resetIdempotencyKeys(state.idempotencyKeysRef);
+ resetIdempotencyKey(state.idempotencyKeysRef, picker.connectionId, selectionToken);
notify.success(state.tr("resetCreditRedeemed", "Reset redeemed"));
} catch (error) {
const message = getRequestErrorMessage(
@@ -199,7 +246,6 @@ export function useCodexResetCreditRedemption(
setErrors,
setLoadingResetCreditsId,
setResetCreditPicker,
- idempotencyKeysRef,
tr
);
const redeemCodexResetCredit = useRedeemCodexResetCredit({
diff --git a/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/utils.tsx b/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/utils.tsx
index b34a50de56..1a770e9a90 100644
--- a/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/utils.tsx
+++ b/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/utils.tsx
@@ -337,9 +337,37 @@ export function computeCanEditCutoff(quotas: any[]): boolean {
return quotas.some((q: any) => q && typeof q.name === "string" && !q.isCredits);
}
+/**
+ * Providers that can redeem a banked reset credit: Codex reset credits and the GLM
+ * Coding Plan Reset Cards, which share the list/redeem contract behind different routes.
+ */
+export const RESET_CREDIT_PROVIDERS = [
+ "codex",
+ // grok-cli redeems through the same codex-reset-credit endpoint (tip of
+ // release/v3.8.51); kept here so the generalized gate below covers it.
+ "grok-cli",
+ "glm",
+ "glm-cn",
+ "glmt",
+ "zai",
+] as const;
+
+/** The redemption API backing a provider's reset credits, if supported. */
+export function getResetCreditEndpoint(provider: string): string | null {
+ if (provider === "codex" || provider === "grok-cli") return "/api/usage/codex-reset-credit";
+ if (["glm", "glm-cn", "glmt", "zai"].includes(provider)) {
+ return "/api/usage/glm-reset-card";
+ }
+ return null;
+}
+
+export function canProviderRedeemResetCredit(provider: string): boolean {
+ return (RESET_CREDIT_PROVIDERS as readonly string[]).includes(provider);
+}
+
export function computeCanRedeemResetCredit(provider: string, quotas: any[]): boolean {
return (
- (provider === "codex" || provider === "grok-cli") &&
+ canProviderRedeemResetCredit(provider) &&
quotas.some((q: any) => q?.isResetCredits && Number(q.creditCount ?? q.remaining ?? 0) > 0)
);
}
diff --git a/src/app/api/usage/glm-reset-card/route.ts b/src/app/api/usage/glm-reset-card/route.ts
new file mode 100644
index 0000000000..bb162e555c
--- /dev/null
+++ b/src/app/api/usage/glm-reset-card/route.ts
@@ -0,0 +1,78 @@
+import { NextResponse } from "next/server";
+import { z } from "zod";
+import { requireManagementAuth } from "@/lib/api/requireManagementAuth";
+import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error.ts";
+import {
+ GlmResetCardError,
+ consumeGlmResetCard,
+ listGlmResetCards,
+} from "@/lib/usage/glmResetCards";
+
+const ConnectionIdSchema = z.string().trim().min(1).max(256);
+
+const GlmResetCardBodySchema = z.object({
+ connectionId: ConnectionIdSchema,
+ idempotencyKey: z.string().trim().min(1).max(256),
+ creditId: z.string().trim().min(1).max(512).optional(),
+});
+
+function buildErrorResponse(error: unknown) {
+ const status = error instanceof GlmResetCardError ? error.status : 500;
+ const code = error instanceof GlmResetCardError ? error.code : "glm_reset_card_failed";
+ const message =
+ error instanceof GlmResetCardError
+ ? sanitizeErrorMessage(error.message) || "GLM reset-card request failed."
+ : "GLM reset-card request failed.";
+ console.error("[API] GLM reset-card request failed", {
+ status,
+ code,
+ message,
+ });
+ return NextResponse.json({ ok: false, code, error: message }, { status });
+}
+
+export async function GET(request: Request) {
+ const authError = await requireManagementAuth(request);
+ if (authError) return authError;
+
+ try {
+ const parsed = ConnectionIdSchema.safeParse(
+ new URL(request.url).searchParams.get("connectionId")
+ );
+ if (!parsed.success) {
+ return NextResponse.json(
+ { ok: false, code: "invalid_connection_id", error: "Invalid connectionId." },
+ { status: 400 }
+ );
+ }
+ const result = await listGlmResetCards(parsed.data);
+ return NextResponse.json({ ok: true, ...result });
+ } catch (error) {
+ return buildErrorResponse(error);
+ }
+}
+
+export async function POST(request: Request) {
+ const authError = await requireManagementAuth(request);
+ if (authError) return authError;
+
+ try {
+ const raw = await request.json().catch(() => ({}));
+ const parsed = GlmResetCardBodySchema.safeParse(raw);
+ if (!parsed.success) {
+ return NextResponse.json(
+ { ok: false, code: "invalid_request_body", error: "Invalid request body." },
+ { status: 400 }
+ );
+ }
+
+ const result = await consumeGlmResetCard(
+ parsed.data.connectionId,
+ parsed.data.idempotencyKey,
+ parsed.data.creditId
+ );
+ return NextResponse.json({ ok: true, ...result });
+ } catch (error) {
+ return buildErrorResponse(error);
+ }
+}
diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json
index 217e58a993..f14bcb121d 100644
--- a/src/i18n/messages/en.json
+++ b/src/i18n/messages/en.json
@@ -9503,7 +9503,7 @@
"redeemResetCredit": "Redeem reset",
"manageResetCredits": "View credits",
"viewResetCredits": "View reset credits",
- "resetCreditsModalTitle": "Codex reset credits",
+ "resetCreditsModalTitle": "Reset credits",
"resetCreditsModalExplainer": "Credits are ordered by expiration. Automatic redemption always uses the credit that expires first.",
"resetCreditsLoadFailed": "Failed to load reset credits",
"resetCreditsDetailsUnavailable": "Credit details are currently unavailable. Refresh and try again.",
@@ -9514,10 +9514,17 @@
"resetCreditNoExpiry": "No expiration date",
"redeemThisResetCredit": "Redeem",
"confirmRedeemResetCreditTitle": "Redeem this reset credit?",
- "confirmRedeemResetCredit": "Redeeming immediately resets the eligible Codex usage windows and permanently consumes this credit.",
+ "confirmRedeemResetCredit": "Redeeming immediately resets the eligible usage windows and permanently consumes this credit.",
"confirmRedeemResetCreditButton": "Redeem credit",
"resetCreditRedeemed": "Reset redeemed",
"resetCreditRedeemFailed": "Failed to redeem reset credit",
+ "glmResetCreditsModalTitle": "GLM Coding Plan reset cards",
+ "glmResetCreditsModalExplainer": "Reset cards are ordered by expiration. Choose the 5-hour or weekly window you want to reset.",
+ "glmResetCreditFiveHourTitle": "5-hour window reset",
+ "glmResetCreditWeekTitle": "Weekly window reset",
+ "glmConfirmRedeemResetCredit": "Redeeming immediately resets the selected usage window and permanently consumes this card.",
+ "glmConfirmRedeemFiveHourResetCredit": "Redeeming immediately resets the 5-hour usage window and permanently consumes this card.",
+ "glmConfirmRedeemWeekResetCredit": "Redeeming immediately resets the weekly usage window and permanently consumes this card.",
"suiteBuilderSaveFailed": "Failed to save suite",
"clone": "Clone",
"exportSuite": "Export",
diff --git a/src/i18n/messages/pt-BR.json b/src/i18n/messages/pt-BR.json
index 15d993f78b..185a24637e 100644
--- a/src/i18n/messages/pt-BR.json
+++ b/src/i18n/messages/pt-BR.json
@@ -9516,6 +9516,13 @@
"confirmRedeemResetCreditTitle": "Resgatar este crédito de redefinição?",
"confirmRedeemResetCredit": "O resgate redefine imediatamente as janelas de uso elegíveis do Codex e consome este crédito de forma permanente.",
"confirmRedeemResetCreditButton": "Resgatar crédito",
+ "glmResetCreditsModalTitle": "Cartões de redefinição do GLM Coding Plan",
+ "glmResetCreditsModalExplainer": "Os cartões de redefinição estão ordenados por expiração. Escolha a janela de 5 horas ou semanal que deseja redefinir.",
+ "glmResetCreditFiveHourTitle": "Redefinição da janela de 5 horas",
+ "glmResetCreditWeekTitle": "Redefinição da janela semanal",
+ "glmConfirmRedeemResetCredit": "O resgate redefine imediatamente a janela de uso selecionada e consome este cartão de forma permanente.",
+ "glmConfirmRedeemFiveHourResetCredit": "O resgate redefine imediatamente a janela de uso de 5 horas e consome este cartão de forma permanente.",
+ "glmConfirmRedeemWeekResetCredit": "O resgate redefine imediatamente a janela de uso semanal e consome este cartão de forma permanente.",
"resetCreditRedeemed": "Redefinição resgatada",
"resetCreditRedeemFailed": "Falha ao resgatar crédito de redefinição",
"suiteBuilderSaveFailed": "Falha ao salvar a suíte customizada",
diff --git a/src/i18n/messages/vi.json b/src/i18n/messages/vi.json
index 6dda431cda..2548fa5102 100644
--- a/src/i18n/messages/vi.json
+++ b/src/i18n/messages/vi.json
@@ -9516,6 +9516,13 @@
"confirmRedeemResetCreditTitle": "Đổi tín dụng đặt lại này?",
"confirmRedeemResetCredit": "Đổi một tín dụng đặt lại Codex cho tài khoản này? Thao tác này sẽ tiêu tốn một tín dụng đặt lại.",
"confirmRedeemResetCreditButton": "Đổi tín dụng",
+ "glmResetCreditsModalTitle": "Thẻ đặt lại của GLM Coding Plan",
+ "glmResetCreditsModalExplainer": "Thẻ đặt lại được sắp xếp theo thời gian hết hạn. Chọn cửa sổ 5 giờ hoặc hàng tuần bạn muốn đặt lại.",
+ "glmResetCreditFiveHourTitle": "Đặt lại cửa sổ 5 giờ",
+ "glmResetCreditWeekTitle": "Đặt lại cửa sổ hàng tuần",
+ "glmConfirmRedeemResetCredit": "Việc đổi sẽ đặt lại ngay cửa sổ sử dụng đã chọn và tiêu tốn vĩnh viễn thẻ này.",
+ "glmConfirmRedeemFiveHourResetCredit": "Việc đổi sẽ đặt lại ngay cửa sổ sử dụng 5 giờ và tiêu tốn vĩnh viễn thẻ này.",
+ "glmConfirmRedeemWeekResetCredit": "Việc đổi sẽ đặt lại ngay cửa sổ sử dụng hàng tuần và tiêu tốn vĩnh viễn thẻ này.",
"resetCreditRedeemed": "Đã đổi lượt đặt lại",
"resetCreditRedeemFailed": "Không thể đổi tín dụng đặt lại",
"suiteBuilderSaveFailed": "Không thể lưu bộ đánh giá",
diff --git a/src/lib/usage/glmResetCards.ts b/src/lib/usage/glmResetCards.ts
new file mode 100644
index 0000000000..aafb5f979a
--- /dev/null
+++ b/src/lib/usage/glmResetCards.ts
@@ -0,0 +1,442 @@
+import { randomBytes } from "node:crypto";
+
+import {
+ acquireExclusiveConnectionLease,
+ releaseExclusiveConnectionLease,
+} from "@/lib/db/exclusiveConnectionLeases";
+import { getProviderConnectionById } from "@/lib/db/providers";
+import { resolveProxyForConnection } from "@/lib/db/settings";
+import { isConnectionUnavailableToAuxiliaryActivity } from "@/lib/exclusiveLeaseIsolation";
+import { fetchAndPersistProviderLimits } from "@/lib/usage/providerLimits";
+import {
+ fetchGlmResetCardList,
+ getGlmResetCardEnvelopeMessage,
+ getGlmResetCardEnvelopeStatus,
+ isGlmResetCardEnvelopeOk,
+ isGlmResetCardListEnvelopeOk,
+ parseGlmResetCards,
+ redeemGlmResetCard,
+ type GlmResetCard,
+} from "@omniroute/open-sse/services/usage/glmResetCards.ts";
+import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error.ts";
+import {
+ runWithDirectFetchContext,
+ runWithProxyContext,
+} from "@omniroute/open-sse/utils/proxyFetch.ts";
+
+type JsonRecord = Record;
+
+export const GLM_RESET_CARD_PROVIDERS = ["glm", "glm-cn", "glmt", "zai"] as const;
+
+const ATTEMPT_TTL_MS = 10 * 60_000;
+const MAX_ATTEMPTS = 500;
+
+// Short-lived synthetic operation lease: `exclusive_connection_leases` has no FK on
+// api_key_id, so a namespaced synthetic id is schema-legal. The partial unique index
+// on (connection_id) WHERE state = 'ACTIVE' gives an atomic fence around the wire
+// operations that no check-then-act availability probe can provide.
+const OPERATION_LEASE_TTL_MS = 60_000;
+const OPERATION_LEASE_API_KEY_ID = "omniroute:glm-reset-card-operation";
+const OPERATION_LEASE_OWNER_PREFIX = "vlo_";
+
+type ProxyLike = Parameters[0];
+
+/** A connection with no proxy must go explicitly direct — passing null to
+ * runWithProxyContext would inherit an ambient context instead. */
+function runWithConnectionFetch(proxy: ProxyLike, fn: () => T): T {
+ return proxy ? runWithProxyContext(proxy, fn) : runWithDirectFetchContext(fn);
+}
+
+/** Map transport-layer proxy failures to safe typed errors. Public messages are
+ * static: the underlying error text can embed proxy host:port topology. */
+function toSafeTransportError(error: unknown, fallback: GlmResetCardError): GlmResetCardError {
+ const err = error as { code?: unknown; errorCode?: unknown };
+ const code = typeof err?.code === "string" ? err.code : null;
+ const errorCode = typeof err?.errorCode === "string" ? err.errorCode : null;
+ if (code === "PROXY_UNREACHABLE" || errorCode === "proxy_unreachable") {
+ return new GlmResetCardError(503, "proxy_unreachable", "The connection proxy is unreachable.");
+ }
+ if (code === "PROXY_FAMILY_UNAVAILABLE" || errorCode === "proxy_family_unavailable") {
+ return new GlmResetCardError(
+ 502,
+ "proxy_family_unavailable",
+ "No proxy is available for this request."
+ );
+ }
+ if (code === "PROXY_REQUEST_FAILED" || errorCode === "proxy_request_failed") {
+ return new GlmResetCardError(502, "proxy_request_failed", "The proxy request failed.");
+ }
+ if (code === "RELAY_TIMEOUT" || errorCode === "relay_timeout") {
+ return new GlmResetCardError(504, "relay_timeout", "The proxied request timed out.");
+ }
+ return fallback;
+}
+
+/** Atomically claim the connection for one list/use operation. */
+async function withOperationLease(
+ connection: GlmConnectionLike,
+ operation: () => Promise
+): Promise {
+ const leaseOwnerId = OPERATION_LEASE_OWNER_PREFIX + randomBytes(32).toString("base64url");
+ const acquired = acquireExclusiveConnectionLease({
+ leaseOwnerId,
+ apiKeyId: OPERATION_LEASE_API_KEY_ID,
+ provider: connection.provider,
+ connectionId: connection.id,
+ ttlMs: OPERATION_LEASE_TTL_MS,
+ });
+ if (acquired.kind === "CONNECTION_BUSY" || acquired.kind === "OWNER_ALREADY_ACTIVE") {
+ throw new GlmResetCardError(
+ 409,
+ "exclusive_lease_active",
+ "Reset-card operations are deferred while an exclusive lease is active."
+ );
+ }
+ try {
+ return await operation();
+ } finally {
+ releaseExclusiveConnectionLease({
+ leaseOwnerId,
+ generation: acquired.lease.generation,
+ apiKeyId: OPERATION_LEASE_API_KEY_ID,
+ });
+ }
+}
+
+type GlmConnectionLike = JsonRecord & {
+ id: string;
+ provider: string;
+ apiKey?: string;
+ providerSpecificData?: JsonRecord;
+};
+
+export type PublicGlmResetCard = Omit & {
+ selectionToken: string;
+};
+
+export interface GlmResetCardListResult {
+ credits: PublicGlmResetCard[];
+ availableCount: number;
+ lastFiveHourResetAt: string | null;
+ lastWeekResetAt: string | null;
+}
+
+export interface GlmResetCardConsumeResult {
+ outcome: "reset";
+ usage?: JsonRecord;
+ refreshPending?: true;
+}
+
+interface RedemptionAttempt {
+ requestedSelection: string | null;
+ card?: GlmResetCard;
+ inFlight?: Promise;
+ committed?: GlmResetCardConsumeResult;
+ expiresAt: number;
+}
+
+const redemptionAttempts = new Map();
+
+export class GlmResetCardError extends Error {
+ status: number;
+ code: string;
+
+ constructor(status: number, code: string, message: string) {
+ super(message);
+ this.name = "GlmResetCardError";
+ this.status = status;
+ this.code = code;
+ }
+}
+
+export function isGlmResetCardProvider(provider: unknown): boolean {
+ return (
+ typeof provider === "string" &&
+ (GLM_RESET_CARD_PROVIDERS as readonly string[]).includes(provider)
+ );
+}
+
+function toRecord(value: unknown): JsonRecord {
+ return value && typeof value === "object" && !Array.isArray(value) ? (value as JsonRecord) : {};
+}
+
+function toPublicCard(card: GlmResetCard): PublicGlmResetCard {
+ const { id, ...metadata } = card;
+ return { ...metadata, selectionToken: id };
+}
+
+function getProviderSpecificData(connection: GlmConnectionLike): JsonRecord {
+ return {
+ ...toRecord(connection.providerSpecificData),
+ ...(connection.provider === "glm-cn" ? { apiRegion: "china" } : {}),
+ };
+}
+
+async function loadGlmConnection(connectionId: string): Promise {
+ if (await isConnectionUnavailableToAuxiliaryActivity(connectionId)) {
+ throw new GlmResetCardError(
+ 409,
+ "exclusive_lease_active",
+ "Reset-card operations are deferred while an exclusive lease is active."
+ );
+ }
+
+ const connection = (await getProviderConnectionById(
+ connectionId
+ )) as unknown as GlmConnectionLike | null;
+
+ if (!connection) {
+ throw new GlmResetCardError(404, "connection_not_found", "Connection not found.");
+ }
+ if (!isGlmResetCardProvider(connection.provider)) {
+ throw new GlmResetCardError(
+ 400,
+ "glm_provider_required",
+ "Reset cards can only be redeemed for GLM coding-plan accounts."
+ );
+ }
+ if (!connection.apiKey) {
+ throw new GlmResetCardError(
+ 401,
+ "glm_api_key_missing",
+ "GLM coding-plan API key is missing on this connection."
+ );
+ }
+
+ return connection;
+}
+
+function assertEnvelopeOk(
+ payload: unknown,
+ httpStatus: number,
+ fallbackMessage: string,
+ requireListData = false
+): void {
+ const envelopeOk = requireListData
+ ? isGlmResetCardListEnvelopeOk(payload)
+ : isGlmResetCardEnvelopeOk(payload);
+ if (envelopeOk && httpStatus < 400) return;
+
+ const status = getGlmResetCardEnvelopeStatus(payload, httpStatus);
+ if (status === 401 || status === 403) {
+ throw new GlmResetCardError(
+ 401,
+ "glm_reset_card_unauthorized",
+ "The GLM API key was rejected by the reset-card API."
+ );
+ }
+
+ const upstreamMessage = getGlmResetCardEnvelopeMessage(payload);
+ throw new GlmResetCardError(
+ status >= 400 ? status : 502,
+ "glm_reset_card_upstream_error",
+ sanitizeErrorMessage(upstreamMessage) || fallbackMessage
+ );
+}
+
+function pruneAttempts(now = Date.now()): void {
+ for (const [key, attempt] of redemptionAttempts) {
+ if (!attempt.inFlight && attempt.expiresAt <= now) redemptionAttempts.delete(key);
+ }
+
+ if (redemptionAttempts.size <= MAX_ATTEMPTS) return;
+ for (const [key, attempt] of redemptionAttempts) {
+ if (!attempt.inFlight) redemptionAttempts.delete(key);
+ if (redemptionAttempts.size <= MAX_ATTEMPTS) break;
+ }
+}
+
+function attemptKey(connectionId: string, idempotencyKey: string): string {
+ return `${connectionId}:${idempotencyKey}`;
+}
+
+function normalizeSelection(selectionToken?: string): string | null {
+ return typeof selectionToken === "string" && selectionToken.trim() ? selectionToken.trim() : null;
+}
+
+function assertCompatibleAttempt(
+ attempt: RedemptionAttempt,
+ requestedSelection: string | null
+): void {
+ if (attempt.requestedSelection !== requestedSelection) {
+ throw new GlmResetCardError(
+ 409,
+ "idempotency_key_conflict",
+ "This idempotency key is already bound to a different reset-card selection."
+ );
+ }
+}
+
+async function refreshAfterCommit(connectionId: string): Promise {
+ try {
+ const refreshed = await fetchAndPersistProviderLimits(connectionId, "manual", {
+ allowRotatingRefresh: true,
+ });
+ return { outcome: "reset", usage: refreshed.usage };
+ } catch {
+ // Redemption is already irreversible. The caller can preserve its current
+ // quotas and refresh later instead of turning committed success into a 500.
+ return { outcome: "reset", refreshPending: true };
+ }
+}
+
+export async function listGlmResetCards(connectionId: string): Promise {
+ if (!connectionId || typeof connectionId !== "string") {
+ throw new GlmResetCardError(400, "connection_id_required", "connectionId is required.");
+ }
+
+ try {
+ const connection = await loadGlmConnection(connectionId);
+ const proxyInfo = await resolveProxyForConnection(connection.id);
+ const { response, payload } = await withOperationLease(connection, () =>
+ runWithConnectionFetch(proxyInfo?.proxy ?? null, () =>
+ fetchGlmResetCardList(connection.apiKey as string, getProviderSpecificData(connection))
+ )
+ );
+ assertEnvelopeOk(payload, response.status, "The GLM reset-card API returned an error.", true);
+
+ const parsed = parseGlmResetCards(payload);
+ return {
+ credits: parsed.cards.map(toPublicCard),
+ availableCount: parsed.availableCount,
+ lastFiveHourResetAt: parsed.lastFiveHourResetAt,
+ lastWeekResetAt: parsed.lastWeekResetAt,
+ };
+ } catch (error) {
+ if (error instanceof GlmResetCardError) throw error;
+ throw toSafeTransportError(
+ error,
+ new GlmResetCardError(500, "glm_reset_card_list_failed", "Failed to load GLM reset cards.")
+ );
+ }
+}
+
+async function executeRedemption(
+ attempt: RedemptionAttempt,
+ connection: GlmConnectionLike,
+ requestId: string,
+ proxyConfig: ProxyLike
+): Promise {
+ const providerSpecificData = getProviderSpecificData(connection);
+ const apiKey = connection.apiKey as string;
+
+ if (!attempt.card) {
+ const listed = await runWithConnectionFetch(proxyConfig, () =>
+ fetchGlmResetCardList(apiKey, providerSpecificData)
+ );
+ assertEnvelopeOk(
+ listed.payload,
+ listed.response.status,
+ "The GLM reset-card API returned an error.",
+ true
+ );
+
+ const { cards } = parseGlmResetCards(listed.payload);
+ const card = attempt.requestedSelection
+ ? cards.find((entry) => entry.id === attempt.requestedSelection)
+ : cards[0];
+ if (!card) {
+ throw new GlmResetCardError(
+ 409,
+ attempt.requestedSelection ? "selected_card_unavailable" : "no_reset_card",
+ attempt.requestedSelection
+ ? "The selected GLM reset card is no longer available."
+ : "No GLM reset cards are available."
+ );
+ }
+ attempt.card = card;
+ }
+
+ // A transport rejection is ambiguous: z.ai may have committed the card before
+ // the response was lost. Keep `attempt.card`, and the next call will retry the
+ // exact same body/requestId without relisting.
+ const redeemed = await runWithConnectionFetch(proxyConfig, () =>
+ redeemGlmResetCard(apiKey, providerSpecificData, attempt.card as GlmResetCard, requestId)
+ );
+ assertEnvelopeOk(
+ redeemed.payload,
+ redeemed.response.status,
+ "The GLM reset-card API rejected the redemption."
+ );
+}
+
+export async function consumeGlmResetCard(
+ connectionId: string,
+ idempotencyKey: string,
+ selectionToken?: string
+): Promise {
+ if (!connectionId || typeof connectionId !== "string") {
+ throw new GlmResetCardError(400, "connection_id_required", "connectionId is required.");
+ }
+ if (!idempotencyKey || typeof idempotencyKey !== "string" || !idempotencyKey.trim()) {
+ throw new GlmResetCardError(400, "idempotency_key_required", "idempotencyKey is required.");
+ }
+
+ const requestId = idempotencyKey.trim();
+ const requestedSelection = normalizeSelection(selectionToken);
+ const key = attemptKey(connectionId, requestId);
+
+ pruneAttempts();
+ const activeAttempt = redemptionAttempts.get(key);
+ if (activeAttempt?.inFlight) {
+ assertCompatibleAttempt(activeAttempt, requestedSelection);
+ activeAttempt.expiresAt = Date.now() + ATTEMPT_TTL_MS;
+ return activeAttempt.inFlight;
+ }
+
+ try {
+ const connection = await loadGlmConnection(connectionId);
+ const proxyInfo = await resolveProxyForConnection(connection.id);
+
+ const existing = redemptionAttempts.get(key);
+ if (existing) {
+ assertCompatibleAttempt(existing, requestedSelection);
+ existing.expiresAt = Date.now() + ATTEMPT_TTL_MS;
+ if (existing.committed) return existing.committed;
+ if (existing.inFlight) return existing.inFlight;
+ }
+
+ const attempt: RedemptionAttempt = existing ?? {
+ requestedSelection,
+ expiresAt: Date.now() + ATTEMPT_TTL_MS,
+ };
+ redemptionAttempts.set(key, attempt);
+
+ const operation = (async () => {
+ await withOperationLease(connection, () =>
+ executeRedemption(attempt, connection, requestId, proxyInfo?.proxy ?? null)
+ );
+ return runWithConnectionFetch(proxyInfo?.proxy ?? null, () =>
+ refreshAfterCommit(connection.id)
+ );
+ })();
+ attempt.inFlight = operation;
+
+ try {
+ const result = await operation;
+ attempt.committed = result;
+ attempt.expiresAt = Date.now() + ATTEMPT_TTL_MS;
+ return result;
+ } catch (error) {
+ // Keep ambiguous transport failures after a card has been selected. A lease
+ // conflict is also a retryable local deferral, so it must not discard the
+ // selected card/requestId binding from a previous ambiguous attempt.
+ const retryableLeaseConflict =
+ error instanceof GlmResetCardError && error.code === "exclusive_lease_active";
+ if ((error instanceof GlmResetCardError && !retryableLeaseConflict) || !attempt.card) {
+ redemptionAttempts.delete(key);
+ } else {
+ attempt.expiresAt = Date.now() + ATTEMPT_TTL_MS;
+ }
+ throw error;
+ } finally {
+ attempt.inFlight = undefined;
+ }
+ } catch (error) {
+ if (error instanceof GlmResetCardError) throw error;
+ throw toSafeTransportError(
+ error,
+ new GlmResetCardError(500, "glm_reset_card_failed", "Failed to redeem GLM reset card.")
+ );
+ }
+}
diff --git a/src/lib/usage/providerLimitsCache.ts b/src/lib/usage/providerLimitsCache.ts
index cdf9b6414a..c63328e278 100644
--- a/src/lib/usage/providerLimitsCache.ts
+++ b/src/lib/usage/providerLimitsCache.ts
@@ -3,6 +3,7 @@ import { sanitizeProviderBillingStatus } from "@/shared/utils/providerBilling";
import { GROK_BUILD_ADDITIONAL_CREDITS_URL } from "@/shared/utils/grokBilling";
const GROK_CLI_PROVIDER = "grok-cli";
+const GLM_RESET_CARD_PROVIDERS = new Set(["glm", "glm-cn", "glmt", "zai"]);
type JsonRecord = Record;
@@ -43,6 +44,18 @@ export function mergeProviderLimitsCacheEntry(
return previous;
}
+ // A GLM quota refresh omits `bankedResetCredits` when the auxiliary reset-card
+ // list request failed (fetchGlmResetCardCount → null). That "unknown" must not
+ // erase a previously known count; an explicit number (including an
+ // authoritative 0) always wins.
+ if (
+ GLM_RESET_CARD_PROVIDERS.has(provider) &&
+ next.bankedResetCredits === undefined &&
+ previous.bankedResetCredits !== undefined
+ ) {
+ return { ...next, bankedResetCredits: previous.bankedResetCredits };
+ }
+
if (provider !== GROK_CLI_PROVIDER) return next;
const nextBilling = next.billing;
diff --git a/tests/unit/glm-reset-card-cache.test.ts b/tests/unit/glm-reset-card-cache.test.ts
new file mode 100644
index 0000000000..7eb0a7d2c6
--- /dev/null
+++ b/tests/unit/glm-reset-card-cache.test.ts
@@ -0,0 +1,52 @@
+import test from "node:test";
+import assert from "node:assert/strict";
+
+const { mergeProviderLimitsCacheEntry, toProviderLimitsCacheEntry } =
+ await import("../../src/lib/usage/providerLimitsCache.ts");
+
+const fetchedAt = "2026-09-05T00:00:00.000Z";
+const previous = {
+ quotas: { session: { remainingPercentage: 20 } },
+ plan: "pro",
+ message: null,
+ fetchedAt,
+ source: "sync",
+ bankedResetCredits: 3,
+};
+
+test("GLM cache preserves a known reset-card count only when the auxiliary count is unknown", () => {
+ for (const provider of ["glm", "glm-cn", "glmt", "zai"]) {
+ const unknownCount = toProviderLimitsCacheEntry(
+ { quotas: { session: { remainingPercentage: 80 } }, plan: "pro" },
+ "manual",
+ fetchedAt
+ );
+ const merged = mergeProviderLimitsCacheEntry(provider, unknownCount, previous);
+ assert.equal(merged.bankedResetCredits, 3, `${provider} should preserve a known count`);
+ assert.deepEqual(merged.quotas, { session: { remainingPercentage: 80 } });
+
+ const authoritativeZero = toProviderLimitsCacheEntry(
+ {
+ quotas: { session: { remainingPercentage: 90 } },
+ plan: "pro",
+ bankedResetCredits: 0,
+ },
+ "manual",
+ fetchedAt
+ );
+ const cleared = mergeProviderLimitsCacheEntry(provider, authoritativeZero, previous);
+ assert.equal(cleared.bankedResetCredits, 0, `${provider} should accept authoritative zero`);
+ }
+});
+
+test("non-GLM providers do not inherit GLM reset-card cache semantics", () => {
+ const next = toProviderLimitsCacheEntry(
+ { quotas: { session: { remainingPercentage: 80 } }, plan: "pro" },
+ "manual",
+ fetchedAt
+ );
+ assert.equal(
+ mergeProviderLimitsCacheEntry("codex", next, previous).bankedResetCredits,
+ undefined
+ );
+});
diff --git a/tests/unit/glm-reset-card-route.test.ts b/tests/unit/glm-reset-card-route.test.ts
new file mode 100644
index 0000000000..14d2c6d3c8
--- /dev/null
+++ b/tests/unit/glm-reset-card-route.test.ts
@@ -0,0 +1,242 @@
+import test from "node:test";
+import assert from "node:assert/strict";
+import fs from "node:fs";
+import os from "node:os";
+import path from "node:path";
+
+const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-glm-reset-card-route-"));
+process.env.DATA_DIR = TEST_DATA_DIR;
+process.env.API_KEY_SECRET = "test-glm-reset-card-route-secret";
+process.env.INITIAL_PASSWORD = "route-test-password";
+
+const core = await import("../../src/lib/db/core.ts");
+const providersDb = await import("../../src/lib/db/providers.ts");
+const auth = await import("../../src/lib/api/requireManagementAuth.ts");
+const authHeaders = await import("../../src/server/authz/headers.ts");
+const route = await import("../../src/app/api/usage/glm-reset-card/route.ts");
+
+const originalFetch = globalThis.fetch;
+const originalConsoleError = console.error;
+
+function json(body: unknown, status = 200) {
+ return new Response(JSON.stringify(body), {
+ status,
+ headers: { "content-type": "application/json" },
+ });
+}
+
+function request(pathname: string, init: RequestInit = {}) {
+ return new Request(`http://localhost:20128${pathname}`, init);
+}
+
+function managementRequest(pathname: string, init: RequestInit = {}) {
+ return request(pathname, {
+ ...init,
+ headers: {
+ ...(init.headers ?? {}),
+ [authHeaders.AUTHZ_HEADER_AUTH_KIND]: "management_key",
+ [authHeaders.AUTHZ_HEADER_AUTH_LABEL]: "local-cli-token",
+ },
+ });
+}
+
+async function resetStorage() {
+ core.resetDbInstance();
+ fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
+ fs.mkdirSync(TEST_DATA_DIR, { recursive: true });
+}
+
+test.beforeEach(async () => {
+ globalThis.fetch = originalFetch;
+ await resetStorage();
+});
+
+test.after(async () => {
+ globalThis.fetch = originalFetch;
+ console.error = originalConsoleError;
+ core.resetDbInstance();
+ fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
+});
+
+test("GET and POST authorize before parsing any payload", () => {
+ const source = fs.readFileSync("src/app/api/usage/glm-reset-card/route.ts", "utf8");
+ const getIndex = source.indexOf("export async function GET");
+ const postIndex = source.indexOf("export async function POST");
+ assert.ok(getIndex >= 0 && postIndex > getIndex);
+
+ const getBody = source.slice(getIndex, postIndex);
+ const postBody = source.slice(postIndex);
+ for (const [label, body, parseMarker] of [
+ ["GET", getBody, "new URL(request.url)"],
+ ["POST", postBody, "request.json()"],
+ ] as const) {
+ assert.match(body, /const authError = await requireManagementAuth\(request\);/);
+ assert.ok(
+ body.indexOf("requireManagementAuth(request)") < body.indexOf(parseMarker),
+ `${label} must authorize before parsing`
+ );
+ }
+});
+
+test("unauthenticated requests never reach the upstream connection", async () => {
+ let called = false;
+ globalThis.fetch = async () => {
+ called = true;
+ return new Response("unexpected", { status: 500 });
+ };
+
+ const getResponse = await route.GET(request("/api/usage/glm-reset-card"));
+ assert.equal(getResponse.status, 401);
+
+ const postResponse = await route.POST(
+ request("/api/usage/glm-reset-card", {
+ method: "POST",
+ body: JSON.stringify({ connectionId: "c1", idempotencyKey: "k1" }),
+ })
+ );
+ assert.equal(postResponse.status, 401);
+ assert.equal(called, false);
+});
+
+test("GET rejects an invalid connectionId with 400", async () => {
+ const response = await route.GET(managementRequest("/api/usage/glm-reset-card"));
+ assert.equal(response.status, 400);
+ const body = (await response.json()) as { ok: boolean; code: string };
+ assert.equal(body.ok, false);
+ assert.equal(body.code, "invalid_connection_id");
+});
+
+test("POST rejects a malformed body with 400 and no upstream call", async () => {
+ let called = false;
+ globalThis.fetch = async () => {
+ called = true;
+ return new Response("unexpected", { status: 500 });
+ };
+
+ for (const payload of [
+ {},
+ { connectionId: "" },
+ { idempotencyKey: "k1" },
+ { connectionId: "c1" },
+ { connectionId: "c1", idempotencyKey: 5 },
+ ]) {
+ const response = await route.POST(
+ managementRequest("/api/usage/glm-reset-card", {
+ method: "POST",
+ body: JSON.stringify(payload),
+ })
+ );
+ assert.equal(response.status, 400);
+ const body = (await response.json()) as { ok: boolean; code: string };
+ assert.equal(body.code, "invalid_request_body");
+ }
+ assert.equal(called, false);
+});
+
+test("GET maps a z.ai auth failure to a typed 401 without leaking upstream detail", async () => {
+ const connection = (await providersDb.createProviderConnection({
+ provider: "glm",
+ authType: "apikey",
+ name: `GLM Route 401 ${Date.now()}`,
+ apiKey: "glm-route-key",
+ })) as { id: string };
+
+ globalThis.fetch = async () =>
+ json({ code: 1001, msg: "Authentication parameter not received in Header" });
+
+ const errors: unknown[] = [];
+ console.error = (...args: unknown[]) => errors.push(args);
+
+ const response = await route.GET(
+ managementRequest(`/api/usage/glm-reset-card?connectionId=${connection.id}`)
+ );
+
+ console.error = originalConsoleError;
+ assert.equal(response.status, 401);
+ const body = (await response.json()) as { ok: boolean; code: string; error: string };
+ assert.equal(body.ok, false);
+ assert.ok(!body.error.includes("at /"), "no stack trace may leak into responses");
+ assert.ok(!body.error.toLowerCase().includes("bearer"), "no credential detail may leak");
+});
+
+test("GET maps a malformed upstream envelope to a sanitized 502", async () => {
+ const connection = (await providersDb.createProviderConnection({
+ provider: "glm",
+ authType: "apikey",
+ name: `GLM Route 502 ${Date.now()}`,
+ apiKey: "glm-route-key",
+ })) as { id: string };
+
+ globalThis.fetch = async () => new Response("gateway", { status: 200 });
+
+ console.error = () => {};
+ const response = await route.GET(
+ managementRequest(`/api/usage/glm-reset-card?connectionId=${connection.id}`)
+ );
+ console.error = originalConsoleError;
+
+ assert.equal(response.status, 502);
+ const body = (await response.json()) as { ok: boolean; error: string };
+ assert.ok(!body.error.includes(""), "raw upstream payload may not leak");
+});
+
+test("POST returns the committed redemption shape", async () => {
+ const connection = (await providersDb.createProviderConnection({
+ provider: "glm",
+ authType: "apikey",
+ name: `GLM Route OK ${Date.now()}`,
+ apiKey: "glm-route-key",
+ })) as { id: string };
+
+ globalThis.fetch = async (url) => {
+ const href = String(url);
+ if (href.includes("/customer-package-reset/list")) {
+ return json({
+ code: 200,
+ success: true,
+ data: {
+ fiveHourResets: [],
+ weekResets: [{ recordId: 124140, expireTime: "2099-01-01 00:00:00" }],
+ },
+ });
+ }
+ if (href.includes("/customer-package-reset/use")) {
+ return json({ code: 200, msg: "Operation successful", data: 124140, success: true });
+ }
+ if (href.includes("/monitor/usage/quota/limit")) {
+ return json({ code: 200, success: true, data: { limits: [] } });
+ }
+ return new Response("unexpected", { status: 500 });
+ };
+
+ const response = await route.POST(
+ managementRequest("/api/usage/glm-reset-card", {
+ method: "POST",
+ body: JSON.stringify({ connectionId: connection.id, idempotencyKey: "route-key-1" }),
+ })
+ );
+
+ assert.equal(response.status, 200);
+ const body = (await response.json()) as { ok: boolean; outcome: string };
+ assert.equal(body.ok, true);
+ assert.equal(body.outcome, "reset");
+});
+
+test("error logging stays bounded and sanitized", async () => {
+ const source = fs.readFileSync("src/app/api/usage/glm-reset-card/route.ts", "utf8");
+ assert.doesNotMatch(
+ source,
+ /console\.error\([^\n]*error\)/,
+ "raw error objects (with stacks) must not be logged"
+ );
+});
+
+test("auth helper refuses a wrong management password", async () => {
+ const response = await route.GET(
+ request("/api/usage/glm-reset-card?connectionId=c1", {
+ headers: { "x-omniroute-admin": "wrong-password" },
+ })
+ );
+ assert.equal(response.status, 401);
+ assert.ok(auth.requireManagementAuth);
+});
diff --git a/tests/unit/glm-reset-cards.test.ts b/tests/unit/glm-reset-cards.test.ts
new file mode 100644
index 0000000000..be20b2c878
--- /dev/null
+++ b/tests/unit/glm-reset-cards.test.ts
@@ -0,0 +1,712 @@
+import test from "node:test";
+import assert from "node:assert/strict";
+import fs from "node:fs";
+import net from "node:net";
+import os from "node:os";
+import path from "node:path";
+
+const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-glm-reset-cards-"));
+process.env.DATA_DIR = TEST_DATA_DIR;
+process.env.API_KEY_SECRET = "test-glm-reset-cards-secret";
+
+const core = await import("../../src/lib/db/core.ts");
+const providersDb = await import("../../src/lib/db/providers.ts");
+const leasesDb = await import("../../src/lib/db/exclusiveConnectionLeases.ts");
+const settingsDb = await import("../../src/lib/db/settings.ts");
+const glmResetCards = await import("../../src/lib/usage/glmResetCards.ts");
+const wire = await import("../../open-sse/services/usage/glmResetCards.ts");
+const glmProvider = await import("../../open-sse/config/glmProvider.ts");
+const proxyFetch = await import("../../open-sse/utils/proxyFetch.ts");
+const uiUtils =
+ await import("../../src/app/(dashboard)/dashboard/usage/components/ProviderLimits/utils.tsx");
+
+const originalFetch = globalThis.fetch;
+
+/** The exact envelope z.ai returns for an account with no cards banked. */
+const EMPTY_LIST_ENVELOPE = {
+ code: 200,
+ msg: "Operation successful",
+ data: {
+ customerId: 75751781508272646,
+ targetType: "PERSONAL",
+ organizationId: null,
+ projectId: null,
+ lastFiveHourResetTime: null,
+ lastWeekResetTime: "2026-09-04 18:39:23",
+ fiveHourResets: [],
+ weekResets: [],
+ },
+ success: true,
+};
+
+function listEnvelopeWith(overrides: Record) {
+ return {
+ ...EMPTY_LIST_ENVELOPE,
+ data: { ...EMPTY_LIST_ENVELOPE.data, ...overrides },
+ };
+}
+
+function json(body: unknown, status = 200) {
+ return new Response(JSON.stringify(body), {
+ status,
+ headers: { "content-type": "application/json" },
+ });
+}
+
+async function resetStorage() {
+ core.resetDbInstance();
+ fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
+ fs.mkdirSync(TEST_DATA_DIR, { recursive: true });
+}
+
+async function createGlmConnection(overrides: Record = {}) {
+ return providersDb.createProviderConnection({
+ provider: "glm",
+ authType: "apikey",
+ name: `GLM Reset ${Date.now()} ${Math.random()}`,
+ apiKey: "glm-test-key",
+ ...overrides,
+ });
+}
+
+test.beforeEach(async () => {
+ globalThis.fetch = originalFetch;
+ await resetStorage();
+});
+
+test.after(async () => {
+ globalThis.fetch = originalFetch;
+ core.resetDbInstance();
+ fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
+});
+
+test("parseGlmResetCards reports no cards for an account with both buckets empty", () => {
+ const parsed = wire.parseGlmResetCards(EMPTY_LIST_ENVELOPE);
+ assert.equal(parsed.availableCount, 0);
+ assert.deepEqual(parsed.cards, []);
+ assert.equal(parsed.lastWeekResetAt, "2026-09-04 18:39:23");
+ assert.equal(parsed.lastFiveHourResetAt, null);
+});
+
+test("parseGlmResetCards derives the reset window from the bucket it came from", () => {
+ const parsed = wire.parseGlmResetCards(
+ listEnvelopeWith({
+ fiveHourResets: [{ recordId: 111 }],
+ weekResets: [{ recordId: 124128 }],
+ })
+ );
+
+ assert.equal(parsed.availableCount, 2);
+ assert.deepEqual(
+ parsed.cards.map((card) => [card.id, card.resetType]),
+ [
+ ["111", "FIVE_HOUR"],
+ ["124128", "WEEK"],
+ ]
+ );
+});
+
+test("parseGlmResetCards prefers an explicit resetType and skips entries without an id", () => {
+ const parsed = wire.parseGlmResetCards(
+ listEnvelopeWith({
+ fiveHourResets: [{ recordId: 222, resetType: "WEEK" }, { packageName: "no id here" }],
+ })
+ );
+
+ assert.equal(parsed.availableCount, 1);
+ assert.equal(parsed.cards[0].resetType, "WEEK");
+});
+
+test("z.ai envelopes fail closed despite an HTTP 200 status line", () => {
+ // Observed live: no auth header → code 1001, bad Bearer token → code 401.
+ const missingAuth = { code: 1001, msg: "Authentication parameter not received in Header" };
+ const badToken = { code: 401, msg: "token expired or incorrect", success: false };
+
+ for (const malformed of [
+ null,
+ "upstream error",
+ {},
+ { code: 200 },
+ { code: 200, success: "true" },
+ { code: "200", success: true },
+ { code: 201, success: true },
+ ]) {
+ assert.equal(wire.isGlmResetCardEnvelopeOk(malformed), false);
+ }
+ assert.equal(wire.isGlmResetCardEnvelopeOk(missingAuth), false);
+ assert.equal(wire.isGlmResetCardEnvelopeOk(badToken), false);
+ assert.equal(wire.isGlmResetCardEnvelopeOk(EMPTY_LIST_ENVELOPE), true);
+ assert.equal(wire.isGlmResetCardEnvelopeOk({ code: 0, success: true }), true);
+ assert.equal(wire.isGlmResetCardListEnvelopeOk(EMPTY_LIST_ENVELOPE), true);
+ assert.equal(wire.isGlmResetCardListEnvelopeOk({ code: 200, success: true }), false);
+ assert.equal(wire.isGlmResetCardListEnvelopeOk({ code: 200, success: true, data: null }), false);
+ assert.equal(wire.isGlmResetCardListEnvelopeOk({ code: 200, success: true, data: {} }), false);
+ assert.equal(
+ wire.isGlmResetCardListEnvelopeOk(listEnvelopeWith({ fiveHourResets: undefined })),
+ false
+ );
+
+ assert.equal(wire.getGlmResetCardEnvelopeStatus(missingAuth, 200), 401);
+ assert.equal(wire.getGlmResetCardEnvelopeStatus(badToken, 200), 401);
+ assert.equal(wire.getGlmResetCardEnvelopeMessage(badToken), "token expired or incorrect");
+});
+
+test("parseGlmResetCards filters unavailable cards and orders usable cards by expiry", () => {
+ const parsed = wire.parseGlmResetCards(
+ listEnvelopeWith({
+ fiveHourResets: [
+ { recordId: 1, status: "consumed", expireTime: "2099-01-01 00:00:00" },
+ { recordId: 2, available: false, expireTime: "2099-01-01 00:00:00" },
+ { recordId: 3, consumed: true, expireTime: "2099-01-01 00:00:00" },
+ { recordId: 4, expireTime: "2000-01-01 00:00:00" },
+ { recordId: 5, expireTime: "2099-03-01 00:00:00" },
+ { recordId: 6, expireTime: "not-a-date" },
+ ],
+ weekResets: [
+ { recordId: 7, status: "redeeming", expireTime: "2099-01-01 00:00:00" },
+ { recordId: 8, redeemed: true, expireTime: "2099-01-01 00:00:00" },
+ { recordId: 9, expireTime: "2099-02-01T00:00:00Z" },
+ ],
+ })
+ );
+
+ assert.equal(parsed.availableCount, 3);
+ assert.deepEqual(
+ parsed.cards.map((card) => card.id),
+ ["9", "5", "6"]
+ );
+});
+
+test("buildGlmResetCardFetch targets the right host, path and headers per region", () => {
+ const list = glmProvider.buildGlmResetCardFetch("key-1", undefined, "list");
+ assert.equal(
+ list.url,
+ "https://api.z.ai/api/biz/customer-package-reset/list?targetType=PERSONAL"
+ );
+ assert.equal(list.headers.Authorization, "Bearer key-1");
+ assert.equal(list.headers["Content-Type"], undefined);
+
+ const use = glmProvider.buildGlmResetCardFetch("key-1", { apiRegion: "china" }, "use");
+ assert.equal(use.url, "https://open.bigmodel.cn/api/biz/customer-package-reset/use");
+ assert.equal(use.headers["Content-Type"], "application/json");
+
+ const team = glmProvider.buildGlmResetCardFetch(
+ "key-1",
+ { glmOrganizationId: "org-1", glmProjectId: "proj-1" },
+ "list"
+ );
+ assert.equal(team.headers["bigmodel-organization"], "org-1");
+ assert.equal(team.headers["bigmodel-project"], "proj-1");
+});
+
+test("consumeGlmResetCard redeems the listed card with z.ai's wire body, then refreshes usage", async () => {
+ const connection = (await createGlmConnection()) as { id: string };
+ const calls: Array<{ url: string; init: RequestInit }> = [];
+
+ globalThis.fetch = async (url, init = {}) => {
+ const href = String(url);
+ calls.push({ url: href, init });
+
+ if (href.includes("/customer-package-reset/list")) {
+ assert.equal((init.headers as Record).Authorization, "Bearer glm-test-key");
+ return json(listEnvelopeWith({ weekResets: [{ recordId: 124128 }] }));
+ }
+
+ if (href.includes("/customer-package-reset/use")) {
+ assert.deepEqual(JSON.parse(String(init.body)), {
+ targetType: "PERSONAL",
+ resetType: "WEEK",
+ recordId: 124128,
+ requestId: "redeem-1",
+ });
+ return json({ code: 200, msg: "Operation successful", data: 124128, success: true });
+ }
+
+ if (href.includes("/monitor/usage/quota/limit")) {
+ return json({
+ code: 200,
+ success: true,
+ data: { limits: [{ type: "CREDIT_LIMIT", unit: 3, number: 5, percentage: 0 }] },
+ });
+ }
+
+ return new Response("unexpected", { status: 500 });
+ };
+
+ const result = await glmResetCards.consumeGlmResetCard(connection.id, "redeem-1");
+
+ assert.equal(result.outcome, "reset");
+ assert.ok(
+ calls.some((call) => call.url.includes("/customer-package-reset/use")),
+ "expected the redemption call to be issued"
+ );
+});
+
+test("GLM reset-card list and redemption run inside the assigned proxy context", async () => {
+ const connection = (await createGlmConnection()) as { id: string };
+ const server = net.createServer((socket) => socket.destroy());
+ await new Promise((resolve, reject) => {
+ server.once("error", reject);
+ server.listen(0, "127.0.0.1", resolve);
+ });
+ const address = server.address();
+ assert.ok(address && typeof address === "object");
+
+ await settingsDb.setProxyForLevel("key", connection.id, {
+ type: "http",
+ host: "127.0.0.1",
+ port: address.port,
+ });
+
+ const sources: string[] = [];
+ globalThis.fetch = async (url) => {
+ sources.push(proxyFetch.resolveProxyForRequest(String(url)).source);
+ const href = String(url);
+ if (href.includes("/customer-package-reset/list")) {
+ return json(listEnvelopeWith({ weekResets: [{ recordId: 124141 }] }));
+ }
+ if (href.includes("/customer-package-reset/use")) {
+ return json({ code: 200, success: true, data: 124141 });
+ }
+ if (href.includes("/monitor/usage/quota/limit")) {
+ return json({ code: 200, success: true, data: { limits: [] } });
+ }
+ return new Response("unexpected", { status: 500 });
+ };
+
+ try {
+ const listed = await glmResetCards.listGlmResetCards(connection.id);
+ assert.equal(listed.availableCount, 1);
+ await glmResetCards.consumeGlmResetCard(connection.id, "redeem-proxied", "124141");
+ assert.ok(sources.length >= 3);
+ assert.ok(sources.every((source) => source === "context"));
+ } finally {
+ await new Promise((resolve) => server.close(() => resolve()));
+ }
+});
+
+test("an unreachable assigned proxy blocks GLM reset-card requests before direct fallback", async () => {
+ const connection = (await createGlmConnection()) as { id: string };
+ const server = net.createServer();
+ await new Promise((resolve, reject) => {
+ server.once("error", reject);
+ server.listen(0, "127.0.0.1", resolve);
+ });
+ const address = server.address();
+ assert.ok(address && typeof address === "object");
+ const deadPort = address.port;
+ await new Promise((resolve) => server.close(() => resolve()));
+
+ await settingsDb.setProxyForLevel("key", connection.id, {
+ type: "http",
+ host: "127.0.0.1",
+ port: deadPort,
+ });
+
+ const sources: string[] = [];
+ globalThis.fetch = async (url) => {
+ sources.push(proxyFetch.resolveProxyForRequest(String(url)).source);
+ return new Promise(() => {});
+ };
+
+ await assert.rejects(
+ () => glmResetCards.listGlmResetCards(connection.id),
+ (error: unknown) => {
+ assert.ok(error instanceof glmResetCards.GlmResetCardError);
+ assert.equal(error.status, 503);
+ assert.equal(error.code, "proxy_unreachable");
+ assert.equal(error.message, "The connection proxy is unreachable.");
+ assert.doesNotMatch(error.message, /127\.0\.0\.1|:\d{2,5}/);
+ return true;
+ }
+ );
+ assert.deepEqual(sources, ["context"], "an unreachable proxy must never retry directly");
+});
+
+test("GLM reset-card requests remain direct when no proxy is configured", async () => {
+ const connection = (await createGlmConnection()) as { id: string };
+ let source: string | null = null;
+ globalThis.fetch = async (url) => {
+ source = proxyFetch.resolveProxyForRequest(String(url)).source;
+ return json(EMPTY_LIST_ENVELOPE);
+ };
+
+ await glmResetCards.listGlmResetCards(connection.id);
+ assert.equal(source, "direct");
+});
+
+test("an unproxied GLM connection does not inherit an ambient proxy context", async () => {
+ const connection = (await createGlmConnection()) as { id: string };
+ let source: string | null = null;
+ globalThis.fetch = async (url) => {
+ source = proxyFetch.resolveProxyForRequest(String(url)).source;
+ return json(EMPTY_LIST_ENVELOPE);
+ };
+
+ await proxyFetch.runWithProxyContext({ type: "vercel", host: "ambient-proxy.invalid" }, () =>
+ glmResetCards.listGlmResetCards(connection.id)
+ );
+
+ assert.equal(source, "direct");
+});
+
+test("consumeGlmResetCard atomically fences the connection while the wire operation is active", async () => {
+ const connection = (await createGlmConnection()) as { id: string };
+ let releaseList!: () => void;
+ let listStarted!: () => void;
+ const started = new Promise((resolve) => {
+ listStarted = resolve;
+ });
+ const blockedList = new Promise((resolve) => {
+ releaseList = resolve;
+ });
+
+ globalThis.fetch = async (url) => {
+ const href = String(url);
+ if (href.includes("/customer-package-reset/list")) {
+ listStarted();
+ await blockedList;
+ return json(listEnvelopeWith({ weekResets: [{ recordId: 124142 }] }));
+ }
+ if (href.includes("/customer-package-reset/use")) {
+ return json({ code: 200, success: true, data: 124142 });
+ }
+ if (href.includes("/monitor/usage/quota/limit")) {
+ return json({ code: 200, success: true, data: { limits: [] } });
+ }
+ return new Response("unexpected", { status: 500 });
+ };
+
+ const redemption = glmResetCards.consumeGlmResetCard(connection.id, "redeem-fenced");
+ await started;
+
+ const competingLease = leasesDb.acquireExclusiveConnectionLease({
+ leaseOwnerId: `vlo_${"a".repeat(43)}`,
+ apiKeyId: "test-competing-key",
+ provider: "glm",
+ connectionId: connection.id,
+ });
+ assert.equal(competingLease.kind, "CONNECTION_BUSY");
+
+ releaseList();
+ assert.equal((await redemption).outcome, "reset");
+});
+
+test("consumeGlmResetCard releases its operation lease after success and failure", async () => {
+ const connection = (await createGlmConnection()) as { id: string };
+ let failList = false;
+
+ globalThis.fetch = async (url) => {
+ const href = String(url);
+ if (href.includes("/customer-package-reset/list")) {
+ if (failList) throw new Error("list transport failed");
+ return json(listEnvelopeWith({ weekResets: [{ recordId: 124143 }] }));
+ }
+ if (href.includes("/customer-package-reset/use")) {
+ return json({ code: 200, success: true, data: 124143 });
+ }
+ if (href.includes("/monitor/usage/quota/limit")) {
+ return json({ code: 200, success: true, data: { limits: [] } });
+ }
+ return new Response("unexpected", { status: 500 });
+ };
+
+ await glmResetCards.consumeGlmResetCard(connection.id, "redeem-release-success");
+ failList = true;
+ await assert.rejects(() => glmResetCards.listGlmResetCards(connection.id));
+
+ const competingLease = leasesDb.acquireExclusiveConnectionLease({
+ leaseOwnerId: `vlo_${"b".repeat(43)}`,
+ apiKeyId: "test-after-operation",
+ provider: "glm",
+ connectionId: connection.id,
+ });
+ assert.equal(competingLease.kind, "ACQUIRED");
+ if (competingLease.kind === "ACQUIRED") {
+ leasesDb.releaseExclusiveConnectionLease({
+ leaseOwnerId: `vlo_${"b".repeat(43)}`,
+ generation: competingLease.lease.generation,
+ apiKeyId: "test-after-operation",
+ });
+ }
+});
+
+test("consumeGlmResetCard reports committed success when usage refresh fails", async () => {
+ const connection = (await createGlmConnection()) as { id: string };
+ let uses = 0;
+
+ globalThis.fetch = async (url) => {
+ const href = String(url);
+ if (href.includes("/customer-package-reset/list")) {
+ return json(listEnvelopeWith({ weekResets: [{ recordId: 124129 }] }));
+ }
+ if (href.includes("/customer-package-reset/use")) {
+ uses += 1;
+ return json({ code: 200, msg: "Operation successful", data: 124129, success: true });
+ }
+ if (href.includes("/monitor/usage/quota/limit")) {
+ throw new Error("quota refresh unavailable");
+ }
+ return new Response("unexpected", { status: 500 });
+ };
+
+ const result = await glmResetCards.consumeGlmResetCard(connection.id, "redeem-refresh-fails");
+ assert.equal(result.outcome, "reset");
+ assert.equal(result.refreshPending, true);
+ assert.equal(uses, 1);
+});
+
+test("consumeGlmResetCard coalesces concurrent requests with one idempotency key", async () => {
+ const connection = (await createGlmConnection()) as { id: string };
+ let lists = 0;
+ let uses = 0;
+
+ globalThis.fetch = async (url) => {
+ const href = String(url);
+ if (href.includes("/customer-package-reset/list")) {
+ lists += 1;
+ return json(listEnvelopeWith({ weekResets: [{ recordId: 124130 }] }));
+ }
+ if (href.includes("/customer-package-reset/use")) {
+ uses += 1;
+ await new Promise((resolve) => setTimeout(resolve, 20));
+ return json({ code: 200, msg: "Operation successful", data: 124130, success: true });
+ }
+ if (href.includes("/monitor/usage/quota/limit")) {
+ return json({ code: 200, success: true, data: { limits: [] } });
+ }
+ return new Response("unexpected", { status: 500 });
+ };
+
+ const [first, second] = await Promise.all([
+ glmResetCards.consumeGlmResetCard(connection.id, "redeem-concurrent"),
+ glmResetCards.consumeGlmResetCard(connection.id, "redeem-concurrent"),
+ ]);
+ assert.deepEqual(second, first);
+ assert.equal(lists, 1);
+ assert.equal(uses, 1);
+});
+
+test("consumeGlmResetCard coalesces a duplicate that arrives after lease acquisition", async () => {
+ const connection = (await createGlmConnection()) as { id: string };
+ let releaseList!: () => void;
+ let listStarted!: () => void;
+ let lists = 0;
+ let uses = 0;
+ const started = new Promise((resolve) => {
+ listStarted = resolve;
+ });
+ const blockedList = new Promise((resolve) => {
+ releaseList = resolve;
+ });
+
+ globalThis.fetch = async (url) => {
+ const href = String(url);
+ if (href.includes("/customer-package-reset/list")) {
+ lists += 1;
+ listStarted();
+ await blockedList;
+ return json(listEnvelopeWith({ weekResets: [{ recordId: 124144 }] }));
+ }
+ if (href.includes("/customer-package-reset/use")) {
+ uses += 1;
+ return json({ code: 200, success: true, data: 124144 });
+ }
+ if (href.includes("/monitor/usage/quota/limit")) {
+ return json({ code: 200, success: true, data: { limits: [] } });
+ }
+ return new Response("unexpected", { status: 500 });
+ };
+
+ const first = glmResetCards.consumeGlmResetCard(connection.id, "redeem-late-duplicate");
+ await started;
+ const second = glmResetCards.consumeGlmResetCard(connection.id, "redeem-late-duplicate");
+ releaseList();
+
+ assert.deepEqual(await second, await first);
+ assert.equal(lists, 1);
+ assert.equal(uses, 1);
+});
+
+test("an unproxied redemption ignores an ambient proxy context", async () => {
+ const connection = (await createGlmConnection()) as { id: string };
+ const sources: string[] = [];
+
+ globalThis.fetch = async (url) => {
+ sources.push(proxyFetch.resolveProxyForRequest(String(url)).source);
+ const href = String(url);
+ if (href.includes("/customer-package-reset/list")) {
+ return json(listEnvelopeWith({ weekResets: [{ recordId: 124145 }] }));
+ }
+ if (href.includes("/customer-package-reset/use")) {
+ return json({ code: 200, success: true, data: 124145 });
+ }
+ if (href.includes("/monitor/usage/quota/limit")) {
+ return json({ code: 200, success: true, data: { limits: [] } });
+ }
+ return new Response("unexpected", { status: 500 });
+ };
+
+ await proxyFetch.runWithProxyContext({ type: "vercel", host: "ambient-proxy.invalid" }, () =>
+ glmResetCards.consumeGlmResetCard(connection.id, "redeem-direct-context")
+ );
+
+ assert.ok(sources.length >= 3);
+ assert.ok(sources.every((source) => source === "direct"));
+});
+
+test("consumeGlmResetCard retries an ambiguous use with the same card and request id", async () => {
+ const connection = (await createGlmConnection()) as { id: string };
+ const bodies: unknown[] = [];
+ let lists = 0;
+
+ globalThis.fetch = async (url, init = {}) => {
+ const href = String(url);
+ if (href.includes("/customer-package-reset/list")) {
+ lists += 1;
+ return json(listEnvelopeWith({ weekResets: [{ recordId: 124131 }] }));
+ }
+ if (href.includes("/customer-package-reset/use")) {
+ bodies.push(JSON.parse(String(init.body)));
+ if (bodies.length === 1) throw new Error("response lost after send");
+ return json({ code: 200, msg: "Operation successful", data: 124131, success: true });
+ }
+ if (href.includes("/monitor/usage/quota/limit")) {
+ return json({ code: 200, success: true, data: { limits: [] } });
+ }
+ return new Response("unexpected", { status: 500 });
+ };
+
+ await assert.rejects(() =>
+ glmResetCards.consumeGlmResetCard(connection.id, "redeem-ambiguous", "124131")
+ );
+ const result = await glmResetCards.consumeGlmResetCard(
+ connection.id,
+ "redeem-ambiguous",
+ "124131"
+ );
+
+ assert.equal(result.outcome, "reset");
+ assert.equal(lists, 1, "retry must not relist a card that may already be consumed");
+ assert.equal(bodies.length, 2);
+ assert.deepEqual(bodies[1], bodies[0]);
+});
+
+test("consumeGlmResetCard rejects reuse of an idempotency key for another card", async () => {
+ const connection = (await createGlmConnection()) as { id: string };
+
+ globalThis.fetch = async (url, init = {}) => {
+ const href = String(url);
+ if (href.includes("/customer-package-reset/list")) {
+ return json(
+ listEnvelopeWith({
+ weekResets: [{ recordId: 124132 }, { recordId: 124133 }],
+ })
+ );
+ }
+ if (href.includes("/customer-package-reset/use")) {
+ return json({
+ code: 200,
+ msg: "Operation successful",
+ data: JSON.parse(String(init.body)).recordId,
+ success: true,
+ });
+ }
+ if (href.includes("/monitor/usage/quota/limit")) {
+ return json({ code: 200, success: true, data: { limits: [] } });
+ }
+ return new Response("unexpected", { status: 500 });
+ };
+
+ await glmResetCards.consumeGlmResetCard(connection.id, "redeem-conflict", "124132");
+ await assert.rejects(
+ () => glmResetCards.consumeGlmResetCard(connection.id, "redeem-conflict", "124133"),
+ (error: InstanceType) => {
+ assert.equal(error.status, 409);
+ assert.equal(error.code, "idempotency_key_conflict");
+ return true;
+ }
+ );
+});
+
+test("consumeGlmResetCard reports a 409 when nothing is banked", async () => {
+ const connection = (await createGlmConnection()) as { id: string };
+
+ globalThis.fetch = async (url) => {
+ if (String(url).includes("/customer-package-reset/list")) return json(EMPTY_LIST_ENVELOPE);
+ return new Response("unexpected", { status: 500 });
+ };
+
+ await assert.rejects(
+ () => glmResetCards.consumeGlmResetCard(connection.id, "redeem-2"),
+ (error: InstanceType) => {
+ assert.equal(error.status, 409);
+ assert.equal(error.code, "no_reset_card");
+ return true;
+ }
+ );
+});
+
+test("non-GLM connections are rejected before any upstream call", async () => {
+ const connection = (await createGlmConnection({
+ provider: "openai",
+ apiKey: "sk-openai",
+ })) as { id: string };
+
+ let called = false;
+ globalThis.fetch = async () => {
+ called = true;
+ return new Response("unexpected", { status: 500 });
+ };
+
+ await assert.rejects(
+ () => glmResetCards.listGlmResetCards(connection.id),
+ (error: InstanceType) => {
+ assert.equal(error.status, 400);
+ assert.equal(error.code, "glm_provider_required");
+ return true;
+ }
+ );
+ assert.equal(called, false, "no upstream request should be made for a non-GLM provider");
+});
+
+test("fetchGlmResetCardCount distinguishes an authoritative zero from unknown", async () => {
+ globalThis.fetch = async () => json(EMPTY_LIST_ENVELOPE);
+ assert.equal(await wire.fetchGlmResetCardCount("glm-test-key"), 0);
+ assert.equal(await wire.fetchGlmResetCardCount(""), 0);
+
+ globalThis.fetch = async () => {
+ throw new Error("network down");
+ };
+ assert.equal(await wire.fetchGlmResetCardCount("glm-test-key"), null);
+
+ // A truncated-but-JSON body must stay "unknown", never an authoritative zero.
+ globalThis.fetch = async () => json({ code: 200, success: true });
+ assert.equal(await wire.fetchGlmResetCardCount("glm-test-key"), null);
+ globalThis.fetch = async () => json({ code: 200, success: true, data: {} });
+ assert.equal(await wire.fetchGlmResetCardCount("glm-test-key"), null);
+
+ globalThis.fetch = async () => json({ code: 401, msg: "token expired or incorrect" });
+ assert.equal(await wire.fetchGlmResetCardCount("glm-test-key"), null);
+});
+
+test("the redeem button unlocks for the GLM family, not only for Codex", () => {
+ const quotas = [{ isResetCredits: true, creditCount: 1 }];
+
+ for (const provider of ["codex", "glm", "glm-cn", "glmt", "zai"]) {
+ assert.equal(
+ uiUtils.computeCanRedeemResetCredit(provider, quotas),
+ true,
+ `${provider} should be able to redeem`
+ );
+ }
+
+ assert.equal(uiUtils.computeCanRedeemResetCredit("openai", quotas), false);
+ assert.equal(uiUtils.computeCanRedeemResetCredit("glm", [{ isResetCredits: true }]), false);
+ assert.equal(uiUtils.getResetCreditEndpoint("codex"), "/api/usage/codex-reset-credit");
+ for (const provider of ["glm", "glm-cn", "glmt", "zai"]) {
+ assert.equal(uiUtils.getResetCreditEndpoint(provider), "/api/usage/glm-reset-card");
+ }
+ assert.equal(uiUtils.getResetCreditEndpoint("openai"), null);
+ assert.equal(uiUtils.getResetCreditEndpoint("opencode-go"), null);
+});
diff --git a/tests/unit/glm-team-quota.test.ts b/tests/unit/glm-team-quota.test.ts
index a7e6cac870..0a60368471 100644
--- a/tests/unit/glm-team-quota.test.ts
+++ b/tests/unit/glm-team-quota.test.ts
@@ -318,6 +318,112 @@ describe("getGlmUsage team quota parsing", () => {
});
});
+describe("getGlmUsage reset-card integration", () => {
+ const RESETTABLE_QUOTA_RESPONSE = {
+ code: 200,
+ success: true,
+ data: {
+ limits: [
+ { type: "TOKENS_LIMIT", unit: 3, number: 5, percentage: 10 },
+ { type: "TOKENS_LIMIT", unit: 6, number: 1, percentage: 20 },
+ ],
+ level: "pro",
+ },
+ };
+
+ it("adds the available reset-card count for resettable coding-plan windows", async () => {
+ const originalFetch = globalThis.fetch;
+ const requestedUrls: string[] = [];
+ globalThis.fetch = async (url) => {
+ const requestedUrl = String(url);
+ requestedUrls.push(requestedUrl);
+ if (requestedUrl.includes("customer-package-reset/list")) {
+ return new Response(
+ JSON.stringify({
+ code: 200,
+ success: true,
+ data: {
+ fiveHourResets: [{ recordId: 101 }],
+ weekResets: [{ recordId: 202 }],
+ },
+ }),
+ { status: 200 }
+ );
+ }
+ return new Response(JSON.stringify(RESETTABLE_QUOTA_RESPONSE), { status: 200 });
+ };
+
+ try {
+ const usage = await getGlmUsage("glm-key", { apiRegion: "international" });
+
+ assert.equal(usage.bankedResetCredits, 2);
+ assert.equal(requestedUrls.length, 2);
+ assert.match(requestedUrls[0], /api\/monitor\/usage\/quota\/limit/);
+ assert.match(requestedUrls[1], /customer-package-reset\/list/);
+ } finally {
+ globalThis.fetch = originalFetch;
+ }
+ });
+
+ it("keeps normal quota data and omits the card count when the auxiliary list fails", async () => {
+ const originalFetch = globalThis.fetch;
+ globalThis.fetch = async (url) => {
+ if (String(url).includes("customer-package-reset/list")) {
+ throw new Error("auxiliary endpoint unavailable");
+ }
+ return new Response(JSON.stringify(RESETTABLE_QUOTA_RESPONSE), { status: 200 });
+ };
+
+ try {
+ const usage = await getGlmUsage("glm-key", { apiRegion: "international" });
+
+ assert.equal(usage.quotas.session.remainingPercentage, 90);
+ assert.equal(usage.quotas.weekly.remainingPercentage, 80);
+ assert.equal("bankedResetCredits" in usage, false);
+ } finally {
+ globalThis.fetch = originalFetch;
+ }
+ });
+
+ it("skips the auxiliary request and reports zero when no resettable window exists", async () => {
+ const originalFetch = globalThis.fetch;
+ const requestedUrls: string[] = [];
+ globalThis.fetch = async (url) => {
+ requestedUrls.push(String(url));
+ return new Response(
+ JSON.stringify({
+ code: 200,
+ success: true,
+ data: {
+ limits: [
+ {
+ type: "TIME_LIMIT",
+ unit: 5,
+ usage: 100,
+ currentValue: 25,
+ remaining: 75,
+ percentage: 25,
+ },
+ ],
+ level: "pro",
+ },
+ }),
+ { status: 200 }
+ );
+ };
+
+ try {
+ const usage = await getGlmUsage("glm-key", { apiRegion: "international" });
+
+ assert.equal(usage.bankedResetCredits, 0);
+ assert.equal(requestedUrls.length, 1);
+ assert.match(requestedUrls[0], /api\/monitor\/usage\/quota\/limit/);
+ } finally {
+ globalThis.fetch = originalFetch;
+ }
+ });
+});
+
describe("getGlmUsage CREDIT_LIMIT (coding-plan subscription keys)", () => {
// Real-world response from https://api.z.ai/api/monitor/usage/quota/limit
// for a GLM Coding Max subscription key (2026-08): limits use CREDIT_LIMIT
diff --git a/tests/unit/hard-session-lease-bypass-inventory.test.ts b/tests/unit/hard-session-lease-bypass-inventory.test.ts
index 72475e7d1d..23ce007b58 100644
--- a/tests/unit/hard-session-lease-bypass-inventory.test.ts
+++ b/tests/unit/hard-session-lease-bypass-inventory.test.ts
@@ -190,6 +190,7 @@ const EXPECTED: Record> = {
"src/lib/usage/callLogs.ts": 1,
"src/lib/usage/codexResetCredits.ts": 1,
"src/lib/usage/comboScoringInspector.ts": 1,
+ "src/lib/usage/glmResetCards.ts": 1,
// v3.8.51 #12805 (c042a5188): grok-cli sibling of codexResetCredits.ts, same
// shape — isConnectionUnavailableToAuxiliaryActivity() gates the lookup, so an
// ACTIVE exclusive lease defers redemption (409 exclusive_lease_active).
@@ -243,6 +244,7 @@ const CLASSIFICATION: Record> = {
"src/lib/providers/volcenginePlanBinding.ts",
"src/lib/services/quotaAutoPing.ts",
"src/lib/usage/codexResetCredits.ts",
+ "src/lib/usage/glmResetCards.ts",
"src/lib/usage/grokResetCredits.ts",
"src/lib/usage/providerLimits.ts",
"src/lib/vncSession/service.ts",
@@ -352,6 +354,7 @@ test("managed request surfaces are fenced centrally or rejected before independe
"src/lib/api/modelTestRunner.ts",
"src/lib/services/quotaAutoPing.ts",
"src/lib/usage/codexResetCredits.ts",
+ "src/lib/usage/glmResetCards.ts",
"src/lib/usage/grokResetCredits.ts",
"src/lib/vncSession/service.ts",
"src/lib/warmupScheduler.ts",
diff --git a/tests/unit/provider-limits-ui.test.ts b/tests/unit/provider-limits-ui.test.ts
index 8b93c0ea92..517fd9b46f 100644
--- a/tests/unit/provider-limits-ui.test.ts
+++ b/tests/unit/provider-limits-ui.test.ts
@@ -7,6 +7,10 @@ const providerLimitUtils =
await import("../../src/app/(dashboard)/dashboard/usage/components/ProviderLimits/utils.tsx");
const providerConstants = await import("../../src/shared/constants/providers.ts");
const settingsSchemas = await import("../../src/shared/validation/settingsSchemas.ts");
+const resetCreditModal =
+ await import("../../src/app/(dashboard)/dashboard/usage/components/ProviderLimits/CodexResetCreditsModal.tsx");
+const resetCreditRedemption =
+ await import("../../src/app/(dashboard)/dashboard/usage/components/ProviderLimits/useCodexResetCreditRedemption.ts");
type ParsedQuota = {
name?: string;
@@ -212,6 +216,69 @@ test("Codex banked reset credits parse as an integer reset-credit counter", () =
assert.equal(resetCredits.creditCount, 2);
});
+test("reset-credit modal uses provider-specific titles and confirmations", () => {
+ const tr = (_key: string, fallback: string) => fallback;
+ const upstreamTitle = { selectionToken: "card-1", title: "Bonus card" };
+
+ assert.equal(
+ resetCreditModal.getResetCreditWindowTitle("codex", upstreamTitle, tr),
+ "Bonus card"
+ );
+ assert.equal(
+ resetCreditModal.getResetCreditWindowTitle("glm", upstreamTitle, tr),
+ "5-hour window reset · Bonus card"
+ );
+ assert.equal(
+ resetCreditModal.getResetCreditWindowTitle("zai", { ...upstreamTitle, resetType: "WEEK" }, tr),
+ "Weekly window reset · Bonus card"
+ );
+ assert.match(
+ resetCreditModal.getResetCreditConfirmation("codex", undefined, tr),
+ /Codex usage windows/
+ );
+ assert.match(resetCreditModal.getResetCreditConfirmation("glm", "FIVE_HOUR", tr), /5-hour/);
+ assert.match(resetCreditModal.getResetCreditConfirmation("glm-cn", "WEEK", tr), /weekly/);
+});
+
+test("committed refresh fallback preserves usage windows and decrements only reset cards", () => {
+ const session = { name: "session", used: 50, total: 100 };
+ const entry = {
+ quotas: [
+ session,
+ {
+ name: "banked_reset_credits",
+ isResetCredits: true,
+ remaining: 2,
+ creditCount: 2,
+ },
+ ],
+ raw: { bankedResetCredits: 2, quotas: { session } },
+ plan: "pro",
+ };
+
+ const decremented = resetCreditRedemption.applyCommittedResetCreditFallback(entry);
+ assert.deepEqual(decremented.quotas, [
+ session,
+ {
+ name: "banked_reset_credits",
+ isResetCredits: true,
+ remaining: 1,
+ creditCount: 1,
+ },
+ ]);
+ assert.equal(decremented.raw.bankedResetCredits, 1);
+ assert.deepEqual(decremented.raw.quotas, entry.raw.quotas);
+ assert.equal(decremented.plan, "pro");
+
+ const exhausted = resetCreditRedemption.applyCommittedResetCreditFallback({
+ ...entry,
+ quotas: [{ isResetCredits: true, remaining: 1, creditCount: 1 }],
+ raw: { ...entry.raw, bankedResetCredits: 1 },
+ });
+ assert.deepEqual(exhausted.quotas, []);
+ assert.equal(exhausted.raw.bankedResetCredits, 0);
+});
+
test("quota labels normalize session and weekly windows while preserving readable titles", () => {
assert.equal(providerLimitUtils.formatQuotaLabel("session"), "Session");
assert.equal(providerLimitUtils.formatQuotaLabel("session (5h)"), "Session");
@@ -268,19 +335,25 @@ test("MiniMax quota payloads use generic provider parsing and stale resets still
assert.equal(providerLimitUtils.formatQuotaLabel(parsed[1].name), "Weekly");
});
-test("GLM quota rows are ordered by session, weekly, then monthly", () => {
- const parsed = providerLimitUtils.parseQuotaData("glm", {
- quotas: {
- mcp_monthly: { used: 10, total: 100, remainingPercentage: 90 },
- weekly: { used: 20, total: 100, remainingPercentage: 80 },
- session: { used: 30, total: 100, remainingPercentage: 70 },
- },
- });
+test("GLM quota rows are ordered by session, weekly, monthly, then reset cards", () => {
+ for (const provider of ["glm", "glm-cn", "glmt", "zai"]) {
+ const parsed = providerLimitUtils.parseQuotaData(provider, {
+ bankedResetCredits: 2,
+ quotas: {
+ mcp_monthly: { used: 10, total: 100, remainingPercentage: 90 },
+ weekly: { used: 20, total: 100, remainingPercentage: 80 },
+ session: { used: 30, total: 100, remainingPercentage: 70 },
+ },
+ });
- assert.deepEqual(
- parsed.map((quota) => quota.name),
- ["session", "weekly", "mcp_monthly"]
- );
+ assert.deepEqual(
+ parsed.map((quota) => quota.name),
+ ["session", "weekly", "mcp_monthly", "banked_reset_credits"],
+ `${provider} should use GLM family parsing`
+ );
+ assert.equal(parsed[3].creditCount, 2);
+ assert.equal(parsed[3].isResetCredits, true);
+ }
});
test("OpenRouter credits render as a USD credit count, not a percentage row", () => {
@@ -411,6 +484,13 @@ test("usage namespace includes Provider Limits UI translation keys", () => {
"confirmRedeemResetCreditButton",
"resetCreditRedeemed",
"resetCreditRedeemFailed",
+ "glmResetCreditsModalTitle",
+ "glmResetCreditsModalExplainer",
+ "glmResetCreditFiveHourTitle",
+ "glmResetCreditWeekTitle",
+ "glmConfirmRedeemResetCredit",
+ "glmConfirmRedeemFiveHourResetCredit",
+ "glmConfirmRedeemWeekResetCredit",
]) {
assert.equal(typeof usage[key], "string", `usage.${key} should be defined in en.json`);
assert.ok(!usage[key].startsWith("__MISSING__:"), `usage.${key} should not be a placeholder`);