@narumitw/pi-usage 0.60.7 → 0.60.8

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.
@@ -5,281 +5,276 @@ export type MiniMaxProviderId = "minimax" | "minimax-cn";
5
5
  export type MiniMaxUsageKind = "token-plan" | "account-balance";
6
6
 
7
7
  const PROVIDERS = {
8
- minimax: { name: "MiniMax", currency: "USD" },
9
- "minimax-cn": { name: "MiniMax CN", currency: "CNY" },
8
+ minimax: { name: "MiniMax", currency: "USD" },
9
+ "minimax-cn": { name: "MiniMax CN", currency: "CNY" },
10
10
  } as const;
11
11
  const DECIMAL_AMOUNT = /^-?(?:0|[1-9]\d*)(?:\.\d+)?$/u;
12
12
  const PERCENT_TOLERANCE = 1;
13
13
 
14
14
  export function miniMaxUsageKind(apiKey: string): MiniMaxUsageKind {
15
- return apiKey.startsWith("sk-api-") ? "account-balance" : "token-plan";
15
+ return apiKey.startsWith("sk-api-") ? "account-balance" : "token-plan";
16
16
  }
17
17
 
18
18
  export function normalizeMiniMaxUsagePayload(
19
- providerId: MiniMaxProviderId,
20
- kind: MiniMaxUsageKind,
21
- payload: MiniMaxUsagePayload,
22
- capturedAt: number,
19
+ providerId: MiniMaxProviderId,
20
+ kind: MiniMaxUsageKind,
21
+ payload: MiniMaxUsagePayload,
22
+ capturedAt: number,
23
23
  ): UsageReport {
24
- return kind === "account-balance"
25
- ? normalizeBalance(providerId, payload, capturedAt)
26
- : normalizeTokenPlan(providerId, payload, capturedAt);
24
+ return kind === "account-balance"
25
+ ? normalizeBalance(providerId, payload, capturedAt)
26
+ : normalizeTokenPlan(providerId, payload, capturedAt);
27
27
  }
28
28
 
29
29
  function normalizeBalance(
30
- providerId: MiniMaxProviderId,
31
- payload: MiniMaxUsagePayload,
32
- capturedAt: number,
30
+ providerId: MiniMaxProviderId,
31
+ payload: MiniMaxUsagePayload,
32
+ capturedAt: number,
33
33
  ): UsageReport {
34
- assertSuccess(payload);
35
- const provider = PROVIDERS[providerId];
36
- const metrics: UsageMetric[] = [
37
- balanceMetric(
38
- "available-balance",
39
- "Available balance",
40
- payload.available_amount,
41
- provider.currency,
42
- ),
43
- balanceMetric("cash-balance", "Cash balance", payload.cash_balance, provider.currency, true),
44
- balanceMetric("voucher-balance", "Voucher balance", payload.voucher_balance, provider.currency),
45
- balanceMetric("credit-balance", "Credit balance", payload.credit_balance, provider.currency),
46
- balanceMetric("owed-amount", "Owed amount", payload.owed_amount, provider.currency),
47
- ];
48
- return {
49
- providerId,
50
- providerName: provider.name,
51
- capturedAt,
52
- source: "minimax-account-balance",
53
- semantics: { kind: "api-key", label: "MiniMax pay-as-you-go account balance" },
54
- buckets: [],
55
- metrics,
56
- };
34
+ assertSuccess(payload);
35
+ const provider = PROVIDERS[providerId];
36
+ const metrics: UsageMetric[] = [
37
+ balanceMetric("available-balance", "Available balance", payload.available_amount, provider.currency),
38
+ balanceMetric("cash-balance", "Cash balance", payload.cash_balance, provider.currency, true),
39
+ balanceMetric("voucher-balance", "Voucher balance", payload.voucher_balance, provider.currency),
40
+ balanceMetric("credit-balance", "Credit balance", payload.credit_balance, provider.currency),
41
+ balanceMetric("owed-amount", "Owed amount", payload.owed_amount, provider.currency),
42
+ ];
43
+ return {
44
+ providerId,
45
+ providerName: provider.name,
46
+ capturedAt,
47
+ source: "minimax-account-balance",
48
+ semantics: { kind: "api-key", label: "MiniMax pay-as-you-go account balance" },
49
+ buckets: [],
50
+ metrics,
51
+ };
57
52
  }
58
53
 
59
54
  function normalizeTokenPlan(
60
- providerId: MiniMaxProviderId,
61
- payload: MiniMaxUsagePayload,
62
- capturedAt: number,
55
+ providerId: MiniMaxProviderId,
56
+ payload: MiniMaxUsagePayload,
57
+ capturedAt: number,
63
58
  ): UsageReport {
64
- assertSuccess(payload);
65
- if (!Array.isArray(payload.model_remains) || payload.model_remains.length === 0) {
66
- throw new Error("MiniMax Token Plan returned no quota rows.");
67
- }
68
- const provider = PROVIDERS[providerId];
69
- const buckets: UsageBucket[] = [];
70
- const groups = new Set<string>();
71
- for (const [index, raw] of payload.model_remains.entries()) {
72
- const row = asObject(raw);
73
- if (!row) throw new Error("MiniMax Token Plan quota row was not an object.");
74
- const groupLabel = safeLabel(row.model_name, `Quota ${index + 1}`);
75
- const groupId = uniqueGroupId(groupLabel, index, groups);
76
- buckets.push(
77
- normalizeWindow(row, {
78
- id: `${groupId}:interval`,
79
- label: "Rolling window",
80
- groupId,
81
- groupLabel,
82
- countField: "current_interval_usage_count",
83
- totalField: "current_interval_total_count",
84
- percentField: "current_interval_remaining_percent",
85
- statusField: "current_interval_status",
86
- startField: "start_time",
87
- endField: "end_time",
88
- }),
89
- normalizeWindow(row, {
90
- id: `${groupId}:weekly`,
91
- label: "Weekly window",
92
- groupId,
93
- groupLabel,
94
- countField: "current_weekly_usage_count",
95
- totalField: "current_weekly_total_count",
96
- percentField: "current_weekly_remaining_percent",
97
- statusField: "current_weekly_status",
98
- startField: "weekly_start_time",
99
- endField: "weekly_end_time",
100
- boostPermille: row.weekly_boost_permille,
101
- }),
102
- );
103
- }
104
- return {
105
- providerId,
106
- providerName: provider.name,
107
- capturedAt,
108
- source: "minimax-token-plan",
109
- semantics: { kind: "consumer-subscription", label: "MiniMax Token Plan quota" },
110
- buckets,
111
- metrics: [],
112
- };
59
+ assertSuccess(payload);
60
+ if (!Array.isArray(payload.model_remains) || payload.model_remains.length === 0) {
61
+ throw new Error("MiniMax Token Plan returned no quota rows.");
62
+ }
63
+ const provider = PROVIDERS[providerId];
64
+ const buckets: UsageBucket[] = [];
65
+ const groups = new Set<string>();
66
+ for (const [index, raw] of payload.model_remains.entries()) {
67
+ const row = asObject(raw);
68
+ if (!row) throw new Error("MiniMax Token Plan quota row was not an object.");
69
+ const groupLabel = safeLabel(row.model_name, `Quota ${index + 1}`);
70
+ const groupId = uniqueGroupId(groupLabel, index, groups);
71
+ buckets.push(
72
+ normalizeWindow(row, {
73
+ id: `${groupId}:interval`,
74
+ label: "Rolling window",
75
+ groupId,
76
+ groupLabel,
77
+ countField: "current_interval_usage_count",
78
+ totalField: "current_interval_total_count",
79
+ percentField: "current_interval_remaining_percent",
80
+ statusField: "current_interval_status",
81
+ startField: "start_time",
82
+ endField: "end_time",
83
+ }),
84
+ normalizeWindow(row, {
85
+ id: `${groupId}:weekly`,
86
+ label: "Weekly window",
87
+ groupId,
88
+ groupLabel,
89
+ countField: "current_weekly_usage_count",
90
+ totalField: "current_weekly_total_count",
91
+ percentField: "current_weekly_remaining_percent",
92
+ statusField: "current_weekly_status",
93
+ startField: "weekly_start_time",
94
+ endField: "weekly_end_time",
95
+ boostPermille: row.weekly_boost_permille,
96
+ }),
97
+ );
98
+ }
99
+ return {
100
+ providerId,
101
+ providerName: provider.name,
102
+ capturedAt,
103
+ source: "minimax-token-plan",
104
+ semantics: { kind: "consumer-subscription", label: "MiniMax Token Plan quota" },
105
+ buckets,
106
+ metrics: [],
107
+ };
113
108
  }
114
109
 
115
110
  type WindowFields = {
116
- id: string;
117
- label: string;
118
- groupId: string;
119
- groupLabel: string;
120
- countField: string;
121
- totalField: string;
122
- percentField: string;
123
- statusField: string;
124
- startField: string;
125
- endField: string;
126
- boostPermille?: unknown;
111
+ id: string;
112
+ label: string;
113
+ groupId: string;
114
+ groupLabel: string;
115
+ countField: string;
116
+ totalField: string;
117
+ percentField: string;
118
+ statusField: string;
119
+ startField: string;
120
+ endField: string;
121
+ boostPermille?: unknown;
127
122
  };
128
123
 
129
124
  function normalizeWindow(row: Record<string, unknown>, fields: WindowFields): UsageBucket {
130
- const status = optionalInteger(row[fields.statusField], fields.statusField);
131
- if (status !== undefined && ![1, 2, 3].includes(status)) {
132
- throw new Error(`MiniMax Token Plan ${fields.label} status was unsupported.`);
133
- }
134
- const percent = optionalPercent(row[fields.percentField], fields.percentField);
135
- validateBoost(fields.boostPermille);
136
- const start = timestamp(row[fields.startField], fields.startField);
137
- const end = timestamp(row[fields.endField], fields.endField);
138
- if (end < start) throw new Error(`MiniMax Token Plan ${fields.label} timestamps were reversed.`);
139
- const resetsAt = Math.floor(end / 1_000);
140
- const windowMinutes = Math.max(1, Math.round((end - start) / 60_000));
141
- if (status === 3) {
142
- return {
143
- id: fields.id,
144
- label: fields.label,
145
- groupId: fields.groupId,
146
- groupLabel: fields.groupLabel,
147
- remaining: 100,
148
- unit: "percent",
149
- period: "unlimited",
150
- windowMinutes,
151
- };
152
- }
153
- const total = nonnegativeInteger(row[fields.totalField], fields.totalField);
154
- const count = nonnegativeInteger(row[fields.countField], fields.countField);
155
- if (total === 0) {
156
- if (count !== 0) {
157
- throw new Error(`MiniMax Token Plan ${fields.label} counts were inconsistent.`);
158
- }
159
- if (percent === undefined) {
160
- throw new Error(`MiniMax Token Plan ${fields.label} returned no quota and no percent.`);
161
- }
162
- // Token Plan reports remaining_percent as the quota when both counts are 0.
163
- return {
164
- id: fields.id,
165
- label: fields.label,
166
- groupId: fields.groupId,
167
- groupLabel: fields.groupLabel,
168
- remaining: percent,
169
- used: 100 - percent,
170
- limit: 0,
171
- unit: "percent",
172
- windowMinutes,
173
- resetsAt,
174
- };
175
- }
176
- const resolved = resolveQuotaCounts(count, total, percent);
177
- if (!resolved) throw new Error(`MiniMax Token Plan ${fields.label} counts were inconsistent.`);
178
- return {
179
- id: fields.id,
180
- label: fields.label,
181
- groupId: fields.groupId,
182
- groupLabel: fields.groupLabel,
183
- ...resolved,
184
- unit: "count",
185
- windowMinutes,
186
- resetsAt,
187
- };
125
+ const status = optionalInteger(row[fields.statusField], fields.statusField);
126
+ if (status !== undefined && ![1, 2, 3].includes(status)) {
127
+ throw new Error(`MiniMax Token Plan ${fields.label} status was unsupported.`);
128
+ }
129
+ const percent = optionalPercent(row[fields.percentField], fields.percentField);
130
+ validateBoost(fields.boostPermille);
131
+ const start = timestamp(row[fields.startField], fields.startField);
132
+ const end = timestamp(row[fields.endField], fields.endField);
133
+ if (end < start) throw new Error(`MiniMax Token Plan ${fields.label} timestamps were reversed.`);
134
+ const resetsAt = Math.floor(end / 1_000);
135
+ const windowMinutes = Math.max(1, Math.round((end - start) / 60_000));
136
+ if (status === 3) {
137
+ return {
138
+ id: fields.id,
139
+ label: fields.label,
140
+ groupId: fields.groupId,
141
+ groupLabel: fields.groupLabel,
142
+ remaining: 100,
143
+ unit: "percent",
144
+ period: "unlimited",
145
+ windowMinutes,
146
+ };
147
+ }
148
+ const total = nonnegativeInteger(row[fields.totalField], fields.totalField);
149
+ const count = nonnegativeInteger(row[fields.countField], fields.countField);
150
+ if (total === 0) {
151
+ if (count !== 0) {
152
+ throw new Error(`MiniMax Token Plan ${fields.label} counts were inconsistent.`);
153
+ }
154
+ if (percent === undefined) {
155
+ throw new Error(`MiniMax Token Plan ${fields.label} returned no quota and no percent.`);
156
+ }
157
+ // Token Plan reports remaining_percent as the quota when both counts are 0.
158
+ return {
159
+ id: fields.id,
160
+ label: fields.label,
161
+ groupId: fields.groupId,
162
+ groupLabel: fields.groupLabel,
163
+ remaining: percent,
164
+ used: 100 - percent,
165
+ limit: 0,
166
+ unit: "percent",
167
+ windowMinutes,
168
+ resetsAt,
169
+ };
170
+ }
171
+ const resolved = resolveQuotaCounts(count, total, percent);
172
+ if (!resolved) throw new Error(`MiniMax Token Plan ${fields.label} counts were inconsistent.`);
173
+ return {
174
+ id: fields.id,
175
+ label: fields.label,
176
+ groupId: fields.groupId,
177
+ groupLabel: fields.groupLabel,
178
+ ...resolved,
179
+ unit: "count",
180
+ windowMinutes,
181
+ resetsAt,
182
+ };
188
183
  }
189
184
 
190
185
  function resolveQuotaCounts(
191
- reportedCount: number,
192
- total: number,
193
- remainingPercent: number | undefined,
186
+ reportedCount: number,
187
+ total: number,
188
+ remainingPercent: number | undefined,
194
189
  ): Pick<UsageBucket, "used" | "remaining" | "limit"> | undefined {
195
- if (total <= 0 || reportedCount > total) return undefined;
196
- let remaining = reportedCount;
197
- if (remainingPercent !== undefined) {
198
- const asRemaining = (reportedCount / total) * 100;
199
- const asUsed = ((total - reportedCount) / total) * 100;
200
- const remainingDistance = Math.abs(asRemaining - remainingPercent);
201
- const usedDistance = Math.abs(asUsed - remainingPercent);
202
- if (Math.min(remainingDistance, usedDistance) > PERCENT_TOLERANCE) return undefined;
203
- if (usedDistance < remainingDistance) remaining = total - reportedCount;
204
- }
205
- return { used: total - remaining, remaining, limit: total };
190
+ if (total <= 0 || reportedCount > total) return undefined;
191
+ let remaining = reportedCount;
192
+ if (remainingPercent !== undefined) {
193
+ const asRemaining = (reportedCount / total) * 100;
194
+ const asUsed = ((total - reportedCount) / total) * 100;
195
+ const remainingDistance = Math.abs(asRemaining - remainingPercent);
196
+ const usedDistance = Math.abs(asUsed - remainingPercent);
197
+ if (Math.min(remainingDistance, usedDistance) > PERCENT_TOLERANCE) return undefined;
198
+ if (usedDistance < remainingDistance) remaining = total - reportedCount;
199
+ }
200
+ return { used: total - remaining, remaining, limit: total };
206
201
  }
207
202
 
208
203
  function assertSuccess(payload: MiniMaxUsagePayload): void {
209
- const base = asObject(payload.base_resp);
210
- if (base?.status_code !== 0) {
211
- throw new Error("MiniMax usage response did not report success.");
212
- }
204
+ const base = asObject(payload.base_resp);
205
+ if (base?.status_code !== 0) {
206
+ throw new Error("MiniMax usage response did not report success.");
207
+ }
213
208
  }
214
209
 
215
210
  function balanceMetric(
216
- id: string,
217
- label: string,
218
- value: unknown,
219
- currency: "CNY" | "USD",
220
- allowNegative = false,
211
+ id: string,
212
+ label: string,
213
+ value: unknown,
214
+ currency: "CNY" | "USD",
215
+ allowNegative = false,
221
216
  ): UsageMetric {
222
- if (
223
- typeof value !== "string" ||
224
- value.length > 64 ||
225
- !DECIMAL_AMOUNT.test(value) ||
226
- (!allowNegative && value.startsWith("-"))
227
- ) {
228
- throw new Error(`MiniMax ${label.toLowerCase()} was not a valid amount.`);
229
- }
230
- return { id, label, value, unit: "currency", currency };
217
+ if (
218
+ typeof value !== "string" ||
219
+ value.length > 64 ||
220
+ !DECIMAL_AMOUNT.test(value) ||
221
+ (!allowNegative && value.startsWith("-"))
222
+ ) {
223
+ throw new Error(`MiniMax ${label.toLowerCase()} was not a valid amount.`);
224
+ }
225
+ return { id, label, value, unit: "currency", currency };
231
226
  }
232
227
 
233
228
  function asObject(value: unknown): Record<string, unknown> | undefined {
234
- if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
235
- return value as Record<string, unknown>;
229
+ if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
230
+ return value as Record<string, unknown>;
236
231
  }
237
232
 
238
233
  function safeLabel(value: unknown, fallback: string): string {
239
- if (typeof value !== "string") throw new Error("MiniMax Token Plan model name was not a string.");
240
- return sanitizeDisplayText(value, 80) || fallback;
234
+ if (typeof value !== "string") throw new Error("MiniMax Token Plan model name was not a string.");
235
+ return sanitizeDisplayText(value, 80) || fallback;
241
236
  }
242
237
 
243
238
  function uniqueGroupId(label: string, index: number, groups: Set<string>): string {
244
- const base =
245
- label
246
- .toLowerCase()
247
- .replace(/[^a-z0-9]+/gu, "-")
248
- .replace(/^-|-$/gu, "") || "quota";
249
- const id = groups.has(base) ? `${base}-${index + 1}` : base;
250
- groups.add(id);
251
- return id;
239
+ const base =
240
+ label
241
+ .toLowerCase()
242
+ .replace(/[^a-z0-9]+/gu, "-")
243
+ .replace(/^-|-$/gu, "") || "quota";
244
+ const id = groups.has(base) ? `${base}-${index + 1}` : base;
245
+ groups.add(id);
246
+ return id;
252
247
  }
253
248
 
254
249
  function nonnegativeInteger(value: unknown, field: string): number {
255
- if (!Number.isSafeInteger(value) || (value as number) < 0) {
256
- throw new Error(`MiniMax Token Plan ${field} was not a nonnegative safe integer.`);
257
- }
258
- return value as number;
250
+ if (!Number.isSafeInteger(value) || (value as number) < 0) {
251
+ throw new Error(`MiniMax Token Plan ${field} was not a nonnegative safe integer.`);
252
+ }
253
+ return value as number;
259
254
  }
260
255
 
261
256
  function optionalInteger(value: unknown, field: string): number | undefined {
262
- if (value === undefined || value === null) return undefined;
263
- return nonnegativeInteger(value, field);
257
+ if (value === undefined || value === null) return undefined;
258
+ return nonnegativeInteger(value, field);
264
259
  }
265
260
 
266
261
  function optionalPercent(value: unknown, field: string): number | undefined {
267
- if (value === undefined || value === null) return undefined;
268
- if (typeof value !== "number" || !Number.isFinite(value) || value < 0 || value > 100) {
269
- throw new Error(`MiniMax Token Plan ${field} was not a percentage.`);
270
- }
271
- return value;
262
+ if (value === undefined || value === null) return undefined;
263
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0 || value > 100) {
264
+ throw new Error(`MiniMax Token Plan ${field} was not a percentage.`);
265
+ }
266
+ return value;
272
267
  }
273
268
 
274
269
  function validateBoost(boost: unknown): void {
275
- if (boost === undefined || boost === null) return;
276
- const permille = nonnegativeInteger(boost, "weekly_boost_permille");
277
- if (permille > 10_000) throw new Error("MiniMax Token Plan weekly boost was unreasonable.");
270
+ if (boost === undefined || boost === null) return;
271
+ const permille = nonnegativeInteger(boost, "weekly_boost_permille");
272
+ if (permille > 10_000) throw new Error("MiniMax Token Plan weekly boost was unreasonable.");
278
273
  }
279
274
 
280
275
  function timestamp(value: unknown, field: string): number {
281
- if (!Number.isSafeInteger(value) || (value as number) <= 0) {
282
- throw new Error(`MiniMax Token Plan ${field} was not a valid timestamp.`);
283
- }
284
- return value as number;
276
+ if (!Number.isSafeInteger(value) || (value as number) <= 0) {
277
+ throw new Error(`MiniMax Token Plan ${field} was not a valid timestamp.`);
278
+ }
279
+ return value as number;
285
280
  }
@@ -3,62 +3,57 @@ import type { MoonshotBalancePayload, UsageMetric, UsageReport } from "../types.
3
3
  export type MoonshotProviderId = "moonshotai" | "moonshotai-cn";
4
4
 
5
5
  const PROVIDERS = {
6
- moonshotai: { name: "Moonshot AI", currency: "USD" },
7
- "moonshotai-cn": { name: "Moonshot AI CN", currency: "CNY" },
6
+ moonshotai: { name: "Moonshot AI", currency: "USD" },
7
+ "moonshotai-cn": { name: "Moonshot AI CN", currency: "CNY" },
8
8
  } as const;
9
9
 
10
10
  export function normalizeMoonshotBalancePayload(
11
- providerId: MoonshotProviderId,
12
- payload: MoonshotBalancePayload,
13
- capturedAt: number,
11
+ providerId: MoonshotProviderId,
12
+ payload: MoonshotBalancePayload,
13
+ capturedAt: number,
14
14
  ): UsageReport {
15
- if (payload.code !== 0 || payload.status !== true) {
16
- throw new Error("Moonshot AI balance response did not report success.");
17
- }
18
- const data = asObject(payload.data);
19
- if (!data) throw new Error("Moonshot AI balance response data was not an object.");
20
- const provider = PROVIDERS[providerId];
21
- const available = amount(data.available_balance, "available balance", false);
22
- const voucher = amount(data.voucher_balance, "voucher balance", false);
23
- const cash = amount(data.cash_balance, "cash balance", true);
24
- const metrics: UsageMetric[] = [
25
- currencyMetric("available-balance", "Available balance", available, provider.currency),
26
- currencyMetric("voucher-balance", "Voucher balance", voucher, provider.currency),
27
- currencyMetric("cash-balance", "Cash balance", cash, provider.currency),
28
- ];
29
- return {
30
- providerId,
31
- providerName: provider.name,
32
- capturedAt,
33
- source: "moonshot-balance",
34
- semantics: { kind: "api-key", label: "Moonshot API account balance" },
35
- buckets: [],
36
- metrics,
37
- };
15
+ if (payload.code !== 0 || payload.status !== true) {
16
+ throw new Error("Moonshot AI balance response did not report success.");
17
+ }
18
+ const data = asObject(payload.data);
19
+ if (!data) throw new Error("Moonshot AI balance response data was not an object.");
20
+ const provider = PROVIDERS[providerId];
21
+ const available = amount(data.available_balance, "available balance", false);
22
+ const voucher = amount(data.voucher_balance, "voucher balance", false);
23
+ const cash = amount(data.cash_balance, "cash balance", true);
24
+ const metrics: UsageMetric[] = [
25
+ currencyMetric("available-balance", "Available balance", available, provider.currency),
26
+ currencyMetric("voucher-balance", "Voucher balance", voucher, provider.currency),
27
+ currencyMetric("cash-balance", "Cash balance", cash, provider.currency),
28
+ ];
29
+ return {
30
+ providerId,
31
+ providerName: provider.name,
32
+ capturedAt,
33
+ source: "moonshot-balance",
34
+ semantics: { kind: "api-key", label: "Moonshot API account balance" },
35
+ buckets: [],
36
+ metrics,
37
+ };
38
38
  }
39
39
 
40
- function currencyMetric(
41
- id: string,
42
- label: string,
43
- value: string,
44
- currency: "CNY" | "USD",
45
- ): UsageMetric {
46
- return { id, label, value, unit: "currency", currency };
40
+ function currencyMetric(id: string, label: string, value: string, currency: "CNY" | "USD"): UsageMetric {
41
+ return { id, label, value, unit: "currency", currency };
47
42
  }
48
43
 
49
44
  function asObject(value: unknown): Record<string, unknown> | undefined {
50
- if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
51
- return value as Record<string, unknown>;
45
+ if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
46
+ return value as Record<string, unknown>;
52
47
  }
53
48
 
54
49
  function amount(value: unknown, label: string, allowNegative: boolean): string {
55
- if (
56
- typeof value !== "number" ||
57
- !Number.isFinite(value) ||
58
- (!allowNegative && value < 0) ||
59
- Math.abs(value) > Number.MAX_SAFE_INTEGER
60
- ) {
61
- throw new Error(`Moonshot AI ${label} was not a valid amount.`);
62
- }
63
- return String(value);
50
+ if (
51
+ typeof value !== "number" ||
52
+ !Number.isFinite(value) ||
53
+ (!allowNegative && value < 0) ||
54
+ Math.abs(value) > Number.MAX_SAFE_INTEGER
55
+ ) {
56
+ throw new Error(`Moonshot AI ${label} was not a valid amount.`);
57
+ }
58
+ return String(value);
64
59
  }