Skip to content

Commit c5073e3

Browse files
committed
fix(usage): guard malformed Z.ai usage payloads
1 parent 64c8281 commit c5073e3

2 files changed

Lines changed: 15 additions & 3 deletions

File tree

src/infra/provider-usage.fetch.zai.test.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,17 @@ describe("fetchZaiUsage", () => {
2020
expect(result.windows).toHaveLength(0);
2121
});
2222

23+
it.each([
24+
["null", null],
25+
["array", []],
26+
])("returns a stable API error for a successful %s payload", async (_name, payload) => {
27+
const mockFetch = createProviderUsageFetch(async () => makeResponse(200, payload));
28+
const result = await fetchZaiUsage("key", 5000, mockFetch);
29+
30+
expect(result.error).toBe("API error");
31+
expect(result.windows).toHaveLength(0);
32+
});
33+
2334
it("returns API message errors for unsuccessful payloads", async () => {
2435
const mockFetch = createProviderUsageFetch(async () =>
2536
makeResponse(200, {

src/infra/provider-usage.fetch.zai.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
// Fetches and normalizes Z.ai provider usage records.
2+
import { isRecord } from "@openclaw/normalization-core/record-coerce";
23
import {
34
buildUsageHttpErrorSnapshot,
45
discardUsageResponseBody,
@@ -56,9 +57,9 @@ export async function fetchZaiUsage(
5657
if (!parsed.ok) {
5758
return parsed.snapshot;
5859
}
59-
const data = parsed.data as ZaiUsageResponse;
60-
if (!data.success || data.code !== 200) {
61-
const errorMessage = typeof data.msg === "string" ? data.msg.trim() : "";
60+
const data = isRecord(parsed.data) ? (parsed.data as ZaiUsageResponse) : undefined;
61+
if (!data || !data.success || data.code !== 200) {
62+
const errorMessage = typeof data?.msg === "string" ? data.msg.trim() : "";
6263
return {
6364
provider: "zai",
6465
displayName: PROVIDER_LABELS.zai,

0 commit comments

Comments
 (0)