@narumitw/pi-usage 0.60.7 → 0.60.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,183 +4,176 @@ import type { UsageBucket, UsageMetric, UsageReport, XaiBillingPayload } from ".
4
4
  const MAX_SAFE_CENTS = Number.MAX_SAFE_INTEGER;
5
5
 
6
6
  export function normalizeXaiBillingPayload(
7
- payload: XaiBillingPayload,
8
- subscriptionTier: unknown,
9
- capturedAt: number,
7
+ payload: XaiBillingPayload,
8
+ subscriptionTier: unknown,
9
+ capturedAt: number,
10
10
  ): UsageReport {
11
- const configValue = payload.config;
12
- if (configValue !== null && configValue !== undefined && !isRecord(configValue)) {
13
- throw new Error("xAI billing response config was not an object or null.");
14
- }
15
- const config = isRecord(configValue) ? configValue : undefined;
16
- const buckets: UsageBucket[] = [];
17
- const metrics: UsageMetric[] = [];
18
- const notes: string[] = [];
11
+ const configValue = payload.config;
12
+ if (configValue !== null && configValue !== undefined && !isRecord(configValue)) {
13
+ throw new Error("xAI billing response config was not an object or null.");
14
+ }
15
+ const config = isRecord(configValue) ? configValue : undefined;
16
+ const buckets: UsageBucket[] = [];
17
+ const metrics: UsageMetric[] = [];
18
+ const notes: string[] = [];
19
19
 
20
- if (config) {
21
- const period = normalizePeriod(
22
- config.currentPeriod,
23
- config.billingPeriodStart,
24
- config.billingPeriodEnd,
25
- );
26
- const preferredPercent = optionalPercent(config.creditUsagePercent, "creditUsagePercent");
27
- if (preferredPercent !== undefined) {
28
- buckets.push({
29
- id: "included-allowance",
30
- label: "Included allowance",
31
- used: preferredPercent,
32
- remaining: 100 - preferredPercent,
33
- unit: "percent",
34
- ...period,
35
- });
36
- } else {
37
- const limit = optionalUsd(config.monthlyLimit, "monthlyLimit");
38
- const used = optionalUsd(config.used, "used");
39
- if (limit !== undefined || used !== undefined) {
40
- buckets.push({
41
- id: "included-allowance",
42
- label: "Included allowance",
43
- ...(limit !== undefined ? { limit } : {}),
44
- ...(used !== undefined ? { used } : {}),
45
- ...(limit !== undefined && used !== undefined ? { remaining: limit - used } : {}),
46
- unit: "usd",
47
- ...period,
48
- });
49
- } else if (period.period || period.resetsAt !== undefined) {
50
- buckets.push({
51
- id: "included-allowance",
52
- label: "Included allowance",
53
- unit: "percent",
54
- ...period,
55
- });
56
- }
57
- }
20
+ if (config) {
21
+ const period = normalizePeriod(config.currentPeriod, config.billingPeriodStart, config.billingPeriodEnd);
22
+ const preferredPercent = optionalPercent(config.creditUsagePercent, "creditUsagePercent");
23
+ if (preferredPercent !== undefined) {
24
+ buckets.push({
25
+ id: "included-allowance",
26
+ label: "Included allowance",
27
+ used: preferredPercent,
28
+ remaining: 100 - preferredPercent,
29
+ unit: "percent",
30
+ ...period,
31
+ });
32
+ } else {
33
+ const limit = optionalUsd(config.monthlyLimit, "monthlyLimit");
34
+ const used = optionalUsd(config.used, "used");
35
+ if (limit !== undefined || used !== undefined) {
36
+ buckets.push({
37
+ id: "included-allowance",
38
+ label: "Included allowance",
39
+ ...(limit !== undefined ? { limit } : {}),
40
+ ...(used !== undefined ? { used } : {}),
41
+ ...(limit !== undefined && used !== undefined ? { remaining: limit - used } : {}),
42
+ unit: "usd",
43
+ ...period,
44
+ });
45
+ } else if (period.period || period.resetsAt !== undefined) {
46
+ buckets.push({
47
+ id: "included-allowance",
48
+ label: "Included allowance",
49
+ unit: "percent",
50
+ ...period,
51
+ });
52
+ }
53
+ }
58
54
 
59
- const onDemandCap = optionalUsd(config.onDemandCap, "onDemandCap");
60
- const onDemandUsed = optionalUsd(config.onDemandUsed, "onDemandUsed");
61
- if (onDemandCap !== undefined || onDemandUsed !== undefined) {
62
- buckets.push({
63
- id: "on-demand",
64
- label: "On-demand usage",
65
- ...(onDemandCap !== undefined ? { limit: onDemandCap } : {}),
66
- ...(onDemandUsed !== undefined ? { used: onDemandUsed } : {}),
67
- ...(onDemandCap !== undefined && onDemandUsed !== undefined
68
- ? { remaining: onDemandCap - onDemandUsed }
69
- : {}),
70
- unit: "usd",
71
- });
72
- }
55
+ const onDemandCap = optionalUsd(config.onDemandCap, "onDemandCap");
56
+ const onDemandUsed = optionalUsd(config.onDemandUsed, "onDemandUsed");
57
+ if (onDemandCap !== undefined || onDemandUsed !== undefined) {
58
+ buckets.push({
59
+ id: "on-demand",
60
+ label: "On-demand usage",
61
+ ...(onDemandCap !== undefined ? { limit: onDemandCap } : {}),
62
+ ...(onDemandUsed !== undefined ? { used: onDemandUsed } : {}),
63
+ ...(onDemandCap !== undefined && onDemandUsed !== undefined ? { remaining: onDemandCap - onDemandUsed } : {}),
64
+ unit: "usd",
65
+ });
66
+ }
73
67
 
74
- const prepaidBalance = optionalUsd(config.prepaidBalance, "prepaidBalance");
75
- if (prepaidBalance !== undefined) {
76
- metrics.push({
77
- id: "prepaid-balance",
78
- label: "Prepaid balance",
79
- value: prepaidBalance,
80
- unit: "usd",
81
- });
82
- }
83
- }
68
+ const prepaidBalance = optionalUsd(config.prepaidBalance, "prepaidBalance");
69
+ if (prepaidBalance !== undefined) {
70
+ metrics.push({
71
+ id: "prepaid-balance",
72
+ label: "Prepaid balance",
73
+ value: prepaidBalance,
74
+ unit: "usd",
75
+ });
76
+ }
77
+ }
84
78
 
85
- const tier = optionalTier(subscriptionTier);
86
- if (tier) metrics.push({ id: "subscription-tier", label: "Plan tier", value: tier });
87
- if (!config) notes.push("No xAI consumer billing configuration is available for this account.");
88
- else if (buckets.length === 0 && metrics.length === 0) {
89
- notes.push("The xAI consumer billing response contained no displayable usage fields.");
90
- }
79
+ const tier = optionalTier(subscriptionTier);
80
+ if (tier) metrics.push({ id: "subscription-tier", label: "Plan tier", value: tier });
81
+ if (!config) notes.push("No xAI consumer billing configuration is available for this account.");
82
+ else if (buckets.length === 0 && metrics.length === 0) {
83
+ notes.push("The xAI consumer billing response contained no displayable usage fields.");
84
+ }
91
85
 
92
- return {
93
- providerId: "xai",
94
- providerName: "xAI",
95
- capturedAt,
96
- source: "cli-chat-proxy.grok.com consumer billing",
97
- semantics: {
98
- kind: "consumer-subscription",
99
- label: "xAI consumer subscription usage",
100
- },
101
- buckets,
102
- metrics,
103
- ...(notes.length > 0 ? { notes } : {}),
104
- };
86
+ return {
87
+ providerId: "xai",
88
+ providerName: "xAI",
89
+ capturedAt,
90
+ source: "cli-chat-proxy.grok.com consumer billing",
91
+ semantics: {
92
+ kind: "consumer-subscription",
93
+ label: "xAI consumer subscription usage",
94
+ },
95
+ buckets,
96
+ metrics,
97
+ ...(notes.length > 0 ? { notes } : {}),
98
+ };
105
99
  }
106
100
 
107
101
  function normalizePeriod(
108
- currentPeriod: unknown,
109
- legacyStart: unknown,
110
- legacyEnd: unknown,
102
+ currentPeriod: unknown,
103
+ legacyStart: unknown,
104
+ legacyEnd: unknown,
111
105
  ): Pick<UsageBucket, "period" | "resetsAt"> {
112
- if (currentPeriod !== undefined && currentPeriod !== null && !isRecord(currentPeriod)) {
113
- throw new Error("xAI billing currentPeriod was not an object or null.");
114
- }
115
- if (isRecord(currentPeriod)) {
116
- const type = optionalString(currentPeriod.type, "currentPeriod.type");
117
- const start = optionalTimestamp(currentPeriod.start, "currentPeriod.start");
118
- const end = optionalTimestamp(currentPeriod.end, "currentPeriod.end");
119
- return {
120
- ...(periodLabel(type, start) ? { period: periodLabel(type, start) } : {}),
121
- ...(end !== undefined ? { resetsAt: end } : {}),
122
- };
123
- }
124
- const start = optionalTimestamp(legacyStart, "billingPeriodStart");
125
- const end = optionalTimestamp(legacyEnd, "billingPeriodEnd");
126
- return {
127
- ...(start !== undefined ? { period: "Monthly" } : {}),
128
- ...(end !== undefined ? { resetsAt: end } : {}),
129
- };
106
+ if (currentPeriod !== undefined && currentPeriod !== null && !isRecord(currentPeriod)) {
107
+ throw new Error("xAI billing currentPeriod was not an object or null.");
108
+ }
109
+ if (isRecord(currentPeriod)) {
110
+ const type = optionalString(currentPeriod.type, "currentPeriod.type");
111
+ const start = optionalTimestamp(currentPeriod.start, "currentPeriod.start");
112
+ const end = optionalTimestamp(currentPeriod.end, "currentPeriod.end");
113
+ return {
114
+ ...(periodLabel(type, start) ? { period: periodLabel(type, start) } : {}),
115
+ ...(end !== undefined ? { resetsAt: end } : {}),
116
+ };
117
+ }
118
+ const start = optionalTimestamp(legacyStart, "billingPeriodStart");
119
+ const end = optionalTimestamp(legacyEnd, "billingPeriodEnd");
120
+ return {
121
+ ...(start !== undefined ? { period: "Monthly" } : {}),
122
+ ...(end !== undefined ? { resetsAt: end } : {}),
123
+ };
130
124
  }
131
125
 
132
126
  function periodLabel(type: string | undefined, start: number | undefined): string | undefined {
133
- if (type === "USAGE_PERIOD_TYPE_WEEKLY") return "Weekly";
134
- if (type === "USAGE_PERIOD_TYPE_MONTHLY") return "Monthly";
135
- if (type)
136
- return sanitizeDisplayText(type.replace(/^USAGE_PERIOD_TYPE_/u, "").replaceAll("_", " "), 40);
137
- return start === undefined ? undefined : "Current period";
127
+ if (type === "USAGE_PERIOD_TYPE_WEEKLY") return "Weekly";
128
+ if (type === "USAGE_PERIOD_TYPE_MONTHLY") return "Monthly";
129
+ if (type) return sanitizeDisplayText(type.replace(/^USAGE_PERIOD_TYPE_/u, "").replaceAll("_", " "), 40);
130
+ return start === undefined ? undefined : "Current period";
138
131
  }
139
132
 
140
133
  function optionalUsd(value: unknown, field: string): number | undefined {
141
- if (value === undefined || value === null) return undefined;
142
- if (!isRecord(value)) throw new Error(`xAI billing ${field} was not a cent wrapper.`);
143
- const cents = value.val === undefined ? 0 : value.val;
144
- if (!Number.isSafeInteger(cents) || Math.abs(cents as number) > MAX_SAFE_CENTS) {
145
- throw new Error(`xAI billing ${field}.val was not a safe signed integer.`);
146
- }
147
- return (cents as number) / 100;
134
+ if (value === undefined || value === null) return undefined;
135
+ if (!isRecord(value)) throw new Error(`xAI billing ${field} was not a cent wrapper.`);
136
+ const cents = value.val === undefined ? 0 : value.val;
137
+ if (!Number.isSafeInteger(cents) || Math.abs(cents as number) > MAX_SAFE_CENTS) {
138
+ throw new Error(`xAI billing ${field}.val was not a safe signed integer.`);
139
+ }
140
+ return (cents as number) / 100;
148
141
  }
149
142
 
150
143
  function optionalPercent(value: unknown, field: string): number | undefined {
151
- if (value === undefined || value === null) return undefined;
152
- if (typeof value !== "number" || !Number.isFinite(value) || value < 0 || value > 100) {
153
- throw new Error(`xAI billing ${field} was outside 0–100.`);
154
- }
155
- return value;
144
+ if (value === undefined || value === null) return undefined;
145
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0 || value > 100) {
146
+ throw new Error(`xAI billing ${field} was outside 0–100.`);
147
+ }
148
+ return value;
156
149
  }
157
150
 
158
151
  function optionalTimestamp(value: unknown, field: string): number | undefined {
159
- if (value === undefined || value === null) return undefined;
160
- if (typeof value !== "string" || value.length > 80) {
161
- throw new Error(`xAI billing ${field} was not a bounded timestamp.`);
162
- }
163
- const milliseconds = Date.parse(value);
164
- if (!Number.isFinite(milliseconds)) throw new Error(`xAI billing ${field} was invalid.`);
165
- return Math.floor(milliseconds / 1000);
152
+ if (value === undefined || value === null) return undefined;
153
+ if (typeof value !== "string" || value.length > 80) {
154
+ throw new Error(`xAI billing ${field} was not a bounded timestamp.`);
155
+ }
156
+ const milliseconds = Date.parse(value);
157
+ if (!Number.isFinite(milliseconds)) throw new Error(`xAI billing ${field} was invalid.`);
158
+ return Math.floor(milliseconds / 1000);
166
159
  }
167
160
 
168
161
  function optionalString(value: unknown, field: string): string | undefined {
169
- if (value === undefined || value === null) return undefined;
170
- if (typeof value !== "string" || value.length > 80) {
171
- throw new Error(`xAI billing ${field} was not a bounded string.`);
172
- }
173
- return value;
162
+ if (value === undefined || value === null) return undefined;
163
+ if (typeof value !== "string" || value.length > 80) {
164
+ throw new Error(`xAI billing ${field} was not a bounded string.`);
165
+ }
166
+ return value;
174
167
  }
175
168
 
176
169
  function optionalTier(value: unknown): string | undefined {
177
- if (value === undefined || value === null) return undefined;
178
- if (typeof value !== "string" || value.length > 160) {
179
- throw new Error("xAI subscription tier was not a bounded string or null.");
180
- }
181
- return sanitizeDisplayText(value, 80) || undefined;
170
+ if (value === undefined || value === null) return undefined;
171
+ if (typeof value !== "string" || value.length > 160) {
172
+ throw new Error("xAI subscription tier was not a bounded string or null.");
173
+ }
174
+ return sanitizeDisplayText(value, 80) || undefined;
182
175
  }
183
176
 
184
177
  function isRecord(value: unknown): value is Record<string, unknown> {
185
- return typeof value === "object" && value !== null && !Array.isArray(value);
178
+ return typeof value === "object" && value !== null && !Array.isArray(value);
186
179
  }
@@ -1,94 +1,94 @@
1
1
  // Fixed messages from https://docs.z.ai/api-reference/api-code. Never interpolate provider text:
2
2
  // error messages and their template parameters can contain credentials or terminal controls.
3
3
  const ERROR_MESSAGES: Readonly<Record<string, string>> = {
4
- "1000": "Authentication failed. Check your API key.",
5
- "1001": "Authentication header missing. Check your API key.",
6
- "1003": "Authentication token expired. Obtain a new token.",
7
- "1005": "Two-factor authentication required.",
8
- "1113": "Insufficient balance or no resource package. Recharge your account.",
9
- "1200": "API call error. Try again later.",
10
- "1210": "Invalid API parameter. Check the API documentation.",
11
- "1211": "Unknown model. Check the model ID.",
12
- "1212": "This model does not support the requested method.",
13
- "1213": "A required parameter is missing.",
14
- "1214": "Invalid parameter. Check the API documentation.",
15
- "1215": "Conflicting parameters. Check the API documentation.",
16
- "1220": "Access denied. Check your permissions.",
17
- "1221": "This API has been taken offline.",
18
- "1222": "This API does not exist.",
19
- "1230": "API processing error. Try again later.",
20
- "1234": "Network error. Try again later.",
21
- "1261": "Prompt too long. Reduce the input length.",
22
- "1301": "Content rejected by the safety policy.",
23
- "1302": "Request rate limit reached. Try again later.",
24
- "1305": "Service overloaded. Try again later.",
25
- "1308": "Usage limit reached. Wait for the quota reset.",
26
- "1309": "GLM Coding Plan expired. Renew your subscription.",
27
- "1310": "Weekly or monthly limit exhausted. Wait for the quota reset.",
28
- "1311": "Your subscription does not include this model.",
29
- "1313": "Request frequency restricted by the Fair Usage Policy. Contact support.",
30
- "1314": "Enterprise package expired. Contact your administrator.",
31
- "1315": "This API key requires an enterprise coding package scenario. Replace the key.",
32
- "1316": "5-hour limit reached; insufficient balance for extra usage. Wait for reset.",
33
- "1317": "7-day limit reached; insufficient balance for extra usage. Wait for reset.",
34
- "1318": "5-hour limit reached; extra usage blocked by monthly spend limit.",
35
- "1319": "7-day limit reached; extra usage blocked by monthly spend limit.",
36
- "1320": "5-hour limit reached; extra usage blocked by monthly spend limit.",
37
- "1321": "7-day limit reached; extra usage blocked by monthly spend limit.",
4
+ "1000": "Authentication failed. Check your API key.",
5
+ "1001": "Authentication header missing. Check your API key.",
6
+ "1003": "Authentication token expired. Obtain a new token.",
7
+ "1005": "Two-factor authentication required.",
8
+ "1113": "Insufficient balance or no resource package. Recharge your account.",
9
+ "1200": "API call error. Try again later.",
10
+ "1210": "Invalid API parameter. Check the API documentation.",
11
+ "1211": "Unknown model. Check the model ID.",
12
+ "1212": "This model does not support the requested method.",
13
+ "1213": "A required parameter is missing.",
14
+ "1214": "Invalid parameter. Check the API documentation.",
15
+ "1215": "Conflicting parameters. Check the API documentation.",
16
+ "1220": "Access denied. Check your permissions.",
17
+ "1221": "This API has been taken offline.",
18
+ "1222": "This API does not exist.",
19
+ "1230": "API processing error. Try again later.",
20
+ "1234": "Network error. Try again later.",
21
+ "1261": "Prompt too long. Reduce the input length.",
22
+ "1301": "Content rejected by the safety policy.",
23
+ "1302": "Request rate limit reached. Try again later.",
24
+ "1305": "Service overloaded. Try again later.",
25
+ "1308": "Usage limit reached. Wait for the quota reset.",
26
+ "1309": "GLM Coding Plan expired. Renew your subscription.",
27
+ "1310": "Weekly or monthly limit exhausted. Wait for the quota reset.",
28
+ "1311": "Your subscription does not include this model.",
29
+ "1313": "Request frequency restricted by the Fair Usage Policy. Contact support.",
30
+ "1314": "Enterprise package expired. Contact your administrator.",
31
+ "1315": "This API key requires an enterprise coding package scenario. Replace the key.",
32
+ "1316": "5-hour limit reached; insufficient balance for extra usage. Wait for reset.",
33
+ "1317": "7-day limit reached; insufficient balance for extra usage. Wait for reset.",
34
+ "1318": "5-hour limit reached; extra usage blocked by monthly spend limit.",
35
+ "1319": "7-day limit reached; extra usage blocked by monthly spend limit.",
36
+ "1320": "5-hour limit reached; extra usage blocked by monthly spend limit.",
37
+ "1321": "7-day limit reached; extra usage blocked by monthly spend limit.",
38
38
  };
39
39
 
40
40
  const HTTP_MESSAGES: Readonly<Record<number, string>> = {
41
- 400: "Invalid request. Check the API documentation.",
42
- 401: "Authentication failed. Check your API key.",
43
- 403: "Access denied. Check your permissions.",
44
- 429: "Request or usage limit reached. Try again later.",
45
- 500: "Internal error. Try again later.",
41
+ 400: "Invalid request. Check the API documentation.",
42
+ 401: "Authentication failed. Check your API key.",
43
+ 403: "Access denied. Check your permissions.",
44
+ 429: "Request or usage limit reached. Try again later.",
45
+ 500: "Internal error. Try again later.",
46
46
  };
47
47
 
48
48
  export function zaiPayloadError(payload: unknown): string | undefined {
49
- const object = asObject(payload);
50
- if (!object) return undefined;
51
- const nested = asObject(object.error);
52
- // Missing nested metadata must not hide a top-level code; explicit nested values retain priority.
53
- const rawCode = nested?.code === undefined ? object.code : nested.code;
54
- const code = errorCode(rawCode);
55
- if (
56
- object.error === undefined &&
57
- object.success !== false &&
58
- (rawCode === undefined || code === "0" || code === "200")
59
- ) {
60
- return undefined;
61
- }
62
- return code && code !== "0" && code !== "200"
63
- ? `Z.AI ${code}: ${ERROR_MESSAGES[code] ?? "API request failed."}`
64
- : "Z.AI: API request failed.";
49
+ const object = asObject(payload);
50
+ if (!object) return undefined;
51
+ const nested = asObject(object.error);
52
+ // Missing nested metadata must not hide a top-level code; explicit nested values retain priority.
53
+ const rawCode = nested?.code === undefined ? object.code : nested.code;
54
+ const code = errorCode(rawCode);
55
+ if (
56
+ object.error === undefined &&
57
+ object.success !== false &&
58
+ (rawCode === undefined || code === "0" || code === "200")
59
+ ) {
60
+ return undefined;
61
+ }
62
+ return code && code !== "0" && code !== "200"
63
+ ? `Z.AI ${code}: ${ERROR_MESSAGES[code] ?? "API request failed."}`
64
+ : "Z.AI: API request failed.";
65
65
  }
66
66
 
67
67
  export function zaiResponseError(status: number, text: string): string | undefined {
68
- let payload: unknown;
69
- try {
70
- payload = JSON.parse(text) as unknown;
71
- } catch {
72
- // Do not expose JSON parser excerpts, response bodies, or statusText.
73
- if (status >= 200 && status < 300) return "Z.AI: Invalid JSON response.";
74
- }
75
- const error = zaiPayloadError(payload);
76
- if (error) return error;
77
- if (status < 200 || status >= 300) {
78
- return `Z.AI HTTP ${status}: ${HTTP_MESSAGES[status] ?? "API request failed."}`;
79
- }
80
- return undefined;
68
+ let payload: unknown;
69
+ try {
70
+ payload = JSON.parse(text) as unknown;
71
+ } catch {
72
+ // Do not expose JSON parser excerpts, response bodies, or statusText.
73
+ if (status >= 200 && status < 300) return "Z.AI: Invalid JSON response.";
74
+ }
75
+ const error = zaiPayloadError(payload);
76
+ if (error) return error;
77
+ if (status < 200 || status >= 300) {
78
+ return `Z.AI HTTP ${status}: ${HTTP_MESSAGES[status] ?? "API request failed."}`;
79
+ }
80
+ return undefined;
81
81
  }
82
82
 
83
83
  function errorCode(value: unknown): string | undefined {
84
- if (typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= 9999) {
85
- return String(value);
86
- }
87
- return typeof value === "string" && /^(?:0|[1-9]\d{0,3})$/u.test(value) ? value : undefined;
84
+ if (typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= 9999) {
85
+ return String(value);
86
+ }
87
+ return typeof value === "string" && /^(?:0|[1-9]\d{0,3})$/u.test(value) ? value : undefined;
88
88
  }
89
89
 
90
90
  function asObject(value: unknown): Record<string, unknown> | undefined {
91
- return value !== null && typeof value === "object" && !Array.isArray(value)
92
- ? (value as Record<string, unknown>)
93
- : undefined;
91
+ return value !== null && typeof value === "object" && !Array.isArray(value)
92
+ ? (value as Record<string, unknown>)
93
+ : undefined;
94
94
  }