11// Fetches and normalizes Z.ai provider usage records.
2+ import { asFiniteNumber } from "@openclaw/normalization-core/number-coercion" ;
3+ import { isRecord } from "@openclaw/normalization-core/record-coerce" ;
4+ import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce" ;
25import {
36 buildUsageHttpErrorSnapshot ,
47 discardUsageResponseBody ,
@@ -9,23 +12,55 @@ import {
912import { clampPercent , PROVIDER_LABELS } from "./provider-usage.shared.js" ;
1013import type { ProviderUsageSnapshot , UsageWindow } from "./provider-usage.types.js" ;
1114
12- type ZaiUsageResponse = {
13- success ?: boolean ;
14- code ?: number ;
15- msg ?: string ;
16- data ?: {
17- planName ?: string ;
18- plan ?: string ;
19- limits ?: Array < {
20- type ?: string ;
21- percentage ?: number ;
22- unit ?: number ;
23- number ?: number ;
24- nextResetTime ?: string ;
25- } > ;
26- } ;
15+ type NormalizedZaiLimit = {
16+ type ?: string ;
17+ percentage ?: number ;
18+ unit ?: number ;
19+ number ?: number ;
20+ nextResetTime ?: string ;
2721} ;
2822
23+ type NormalizedZaiUsage =
24+ | { ok : false ; message ?: string }
25+ | {
26+ ok : true ;
27+ plan ?: string ;
28+ limits : NormalizedZaiLimit [ ] ;
29+ } ;
30+
31+ function normalizeZaiUsage ( value : unknown ) : NormalizedZaiUsage | undefined {
32+ if ( ! isRecord ( value ) ) {
33+ return undefined ;
34+ }
35+ const message = normalizeOptionalString ( value . msg ) ;
36+ if ( value . success !== true || asFiniteNumber ( value . code ) !== 200 ) {
37+ return { ok : false , message } ;
38+ }
39+
40+ const data = isRecord ( value . data ) ? value . data : { } ;
41+ const rawLimits = Array . isArray ( data . limits ) ? data . limits : [ ] ;
42+
43+ const limits : NormalizedZaiLimit [ ] = [ ] ;
44+ for ( const rawLimit of rawLimits ) {
45+ if ( ! isRecord ( rawLimit ) ) {
46+ continue ;
47+ }
48+ limits . push ( {
49+ type : normalizeOptionalString ( rawLimit . type ) ,
50+ percentage : asFiniteNumber ( rawLimit . percentage ) ,
51+ unit : asFiniteNumber ( rawLimit . unit ) ,
52+ number : asFiniteNumber ( rawLimit . number ) ,
53+ nextResetTime : normalizeOptionalString ( rawLimit . nextResetTime ) ,
54+ } ) ;
55+ }
56+
57+ return {
58+ ok : true ,
59+ plan : normalizeOptionalString ( data . planName ) ?? normalizeOptionalString ( data . plan ) ,
60+ limits,
61+ } ;
62+ }
63+
2964export async function fetchZaiUsage (
3065 apiKey : string ,
3166 timeoutMs : number ,
@@ -56,29 +91,26 @@ export async function fetchZaiUsage(
5691 if ( ! parsed . ok ) {
5792 return parsed . snapshot ;
5893 }
59- const data = parsed . data as ZaiUsageResponse ;
60- if ( ! data . success || data . code !== 200 ) {
61- const errorMessage = typeof data . msg === "string" ? data . msg . trim ( ) : "" ;
94+ const usage = normalizeZaiUsage ( parsed . data ) ;
95+ if ( ! usage || ! usage . ok ) {
6296 return {
6397 provider : "zai" ,
6498 displayName : PROVIDER_LABELS . zai ,
6599 windows : [ ] ,
66- error : errorMessage || "API error" ,
100+ error : usage ?. message || "API error" ,
67101 } ;
68102 }
69103
70104 const windows : UsageWindow [ ] = [ ] ;
71- const limits = data . data ?. limits || [ ] ;
72-
73- for ( const limit of limits ) {
74- const percent = clampPercent ( limit . percentage || 0 ) ;
105+ for ( const limit of usage . limits ) {
106+ const percent = clampPercent ( limit . percentage ?? 0 ) ;
75107 const nextReset = parseUsageResetAt ( limit . nextResetTime ) ;
76108 let windowLabel = "Limit" ;
77- if ( limit . unit === 1 ) {
109+ if ( limit . unit === 1 && limit . number !== undefined ) {
78110 windowLabel = `${ limit . number } d` ;
79- } else if ( limit . unit === 3 ) {
111+ } else if ( limit . unit === 3 && limit . number !== undefined ) {
80112 windowLabel = `${ limit . number } h` ;
81- } else if ( limit . unit === 5 ) {
113+ } else if ( limit . unit === 5 && limit . number !== undefined ) {
82114 windowLabel = `${ limit . number } m` ;
83115 }
84116
@@ -97,11 +129,10 @@ export async function fetchZaiUsage(
97129 }
98130 }
99131
100- const planName = data . data ?. planName || data . data ?. plan || undefined ;
101132 return {
102133 provider : "zai" ,
103134 displayName : PROVIDER_LABELS . zai ,
104135 windows,
105- plan : planName ,
136+ plan : usage . plan ,
106137 } ;
107138}
0 commit comments