@narumitw/pi-usage 0.60.3 → 0.60.6
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.
- package/README.md +42 -321
- package/dist/index.ts +106 -18
- package/dist/index.ts.map +4 -4
- package/docs/operations.md +31 -0
- package/docs/providers.md +271 -0
- package/package.json +6 -5
- package/src/core.ts +4 -0
- package/src/providers/zai-errors.ts +94 -0
- package/src/providers/zai.ts +5 -4
- package/src/query.ts +8 -0
- package/src/types.ts +6 -0
- package/src/usage.ts +1 -0
package/dist/index.ts
CHANGED
|
@@ -120,6 +120,9 @@ var UsageCache = class {
|
|
|
120
120
|
}
|
|
121
121
|
this.entries.set(key, { createdAt: now, report: report2 });
|
|
122
122
|
}
|
|
123
|
+
delete(providerId, fingerprint) {
|
|
124
|
+
this.entries.delete(cacheKey(providerId, fingerprint));
|
|
125
|
+
}
|
|
123
126
|
clearProvider(providerId) {
|
|
124
127
|
for (const key of this.entries.keys()) {
|
|
125
128
|
if (key.startsWith(`${providerId}:`)) this.entries.delete(key);
|
|
@@ -1730,17 +1733,98 @@ function isRecord2(value) {
|
|
|
1730
1733
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1731
1734
|
}
|
|
1732
1735
|
|
|
1736
|
+
// src/providers/zai-errors.ts
|
|
1737
|
+
var ERROR_MESSAGES = {
|
|
1738
|
+
"1000": "Authentication failed. Check your API key.",
|
|
1739
|
+
"1001": "Authentication header missing. Check your API key.",
|
|
1740
|
+
"1003": "Authentication token expired. Obtain a new token.",
|
|
1741
|
+
"1005": "Two-factor authentication required.",
|
|
1742
|
+
"1113": "Insufficient balance or no resource package. Recharge your account.",
|
|
1743
|
+
"1200": "API call error. Try again later.",
|
|
1744
|
+
"1210": "Invalid API parameter. Check the API documentation.",
|
|
1745
|
+
"1211": "Unknown model. Check the model ID.",
|
|
1746
|
+
"1212": "This model does not support the requested method.",
|
|
1747
|
+
"1213": "A required parameter is missing.",
|
|
1748
|
+
"1214": "Invalid parameter. Check the API documentation.",
|
|
1749
|
+
"1215": "Conflicting parameters. Check the API documentation.",
|
|
1750
|
+
"1220": "Access denied. Check your permissions.",
|
|
1751
|
+
"1221": "This API has been taken offline.",
|
|
1752
|
+
"1222": "This API does not exist.",
|
|
1753
|
+
"1230": "API processing error. Try again later.",
|
|
1754
|
+
"1234": "Network error. Try again later.",
|
|
1755
|
+
"1261": "Prompt too long. Reduce the input length.",
|
|
1756
|
+
"1301": "Content rejected by the safety policy.",
|
|
1757
|
+
"1302": "Request rate limit reached. Try again later.",
|
|
1758
|
+
"1305": "Service overloaded. Try again later.",
|
|
1759
|
+
"1308": "Usage limit reached. Wait for the quota reset.",
|
|
1760
|
+
"1309": "GLM Coding Plan expired. Renew your subscription.",
|
|
1761
|
+
"1310": "Weekly or monthly limit exhausted. Wait for the quota reset.",
|
|
1762
|
+
"1311": "Your subscription does not include this model.",
|
|
1763
|
+
"1313": "Request frequency restricted by the Fair Usage Policy. Contact support.",
|
|
1764
|
+
"1314": "Enterprise package expired. Contact your administrator.",
|
|
1765
|
+
"1315": "This API key requires an enterprise coding package scenario. Replace the key.",
|
|
1766
|
+
"1316": "5-hour limit reached; insufficient balance for extra usage. Wait for reset.",
|
|
1767
|
+
"1317": "7-day limit reached; insufficient balance for extra usage. Wait for reset.",
|
|
1768
|
+
"1318": "5-hour limit reached; extra usage blocked by monthly spend limit.",
|
|
1769
|
+
"1319": "7-day limit reached; extra usage blocked by monthly spend limit.",
|
|
1770
|
+
"1320": "5-hour limit reached; extra usage blocked by monthly spend limit.",
|
|
1771
|
+
"1321": "7-day limit reached; extra usage blocked by monthly spend limit."
|
|
1772
|
+
};
|
|
1773
|
+
var HTTP_MESSAGES = {
|
|
1774
|
+
400: "Invalid request. Check the API documentation.",
|
|
1775
|
+
401: "Authentication failed. Check your API key.",
|
|
1776
|
+
403: "Access denied. Check your permissions.",
|
|
1777
|
+
429: "Request or usage limit reached. Try again later.",
|
|
1778
|
+
500: "Internal error. Try again later."
|
|
1779
|
+
};
|
|
1780
|
+
function zaiPayloadError(payload) {
|
|
1781
|
+
const object = asObject11(payload);
|
|
1782
|
+
if (!object) return void 0;
|
|
1783
|
+
const nested = asObject11(object.error);
|
|
1784
|
+
const rawCode = nested?.code === void 0 ? object.code : nested.code;
|
|
1785
|
+
const code = errorCode(rawCode);
|
|
1786
|
+
if (object.error === void 0 && object.success !== false && (rawCode === void 0 || code === "0" || code === "200")) {
|
|
1787
|
+
return void 0;
|
|
1788
|
+
}
|
|
1789
|
+
return code && code !== "0" && code !== "200" ? `Z.AI ${code}: ${ERROR_MESSAGES[code] ?? "API request failed."}` : "Z.AI: API request failed.";
|
|
1790
|
+
}
|
|
1791
|
+
function zaiResponseError(status, text) {
|
|
1792
|
+
let payload;
|
|
1793
|
+
try {
|
|
1794
|
+
payload = JSON.parse(text);
|
|
1795
|
+
} catch {
|
|
1796
|
+
if (status >= 200 && status < 300) return "Z.AI: Invalid JSON response.";
|
|
1797
|
+
}
|
|
1798
|
+
const error = zaiPayloadError(payload);
|
|
1799
|
+
if (error) return error;
|
|
1800
|
+
if (status < 200 || status >= 300) {
|
|
1801
|
+
return `Z.AI HTTP ${status}: ${HTTP_MESSAGES[status] ?? "API request failed."}`;
|
|
1802
|
+
}
|
|
1803
|
+
return void 0;
|
|
1804
|
+
}
|
|
1805
|
+
function errorCode(value) {
|
|
1806
|
+
if (typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= 9999) {
|
|
1807
|
+
return String(value);
|
|
1808
|
+
}
|
|
1809
|
+
return typeof value === "string" && /^(?:0|[1-9]\d{0,3})$/u.test(value) ? value : void 0;
|
|
1810
|
+
}
|
|
1811
|
+
function asObject11(value) {
|
|
1812
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1733
1815
|
// src/providers/zai.ts
|
|
1734
1816
|
var FIVE_HOUR_WINDOW_MINUTES2 = 300;
|
|
1735
1817
|
var WEEKLY_WINDOW_MINUTES2 = 10080;
|
|
1736
1818
|
function normalizeZaiQuotaPayload(providerId, providerName, payload, capturedAt, plan) {
|
|
1737
|
-
const
|
|
1819
|
+
const error = zaiPayloadError(payload);
|
|
1820
|
+
if (error) throw new Error(error);
|
|
1821
|
+
const data = asObject12(payload.data);
|
|
1738
1822
|
if (!data) throw new Error("Z.AI quota response data was not an object.");
|
|
1739
1823
|
const limits = Array.isArray(data.limits) ? data.limits : [];
|
|
1740
1824
|
const buckets = [];
|
|
1741
1825
|
const metrics = [];
|
|
1742
1826
|
for (const raw of limits) {
|
|
1743
|
-
const limit =
|
|
1827
|
+
const limit = asObject12(raw);
|
|
1744
1828
|
if (!limit) continue;
|
|
1745
1829
|
const type = asString5(limit.type);
|
|
1746
1830
|
const unit = asNonnegativeNumber4(limit.unit);
|
|
@@ -1789,14 +1873,11 @@ function normalizeZaiQuotaPayload(providerId, providerName, payload, capturedAt,
|
|
|
1789
1873
|
};
|
|
1790
1874
|
}
|
|
1791
1875
|
function normalizeZaiSubscriptionPayload(payload) {
|
|
1792
|
-
if (payload
|
|
1793
|
-
if (typeof payload.code === "number" && payload.code !== 0 && payload.code !== 200) {
|
|
1794
|
-
return void 0;
|
|
1795
|
-
}
|
|
1876
|
+
if (zaiPayloadError(payload)) return void 0;
|
|
1796
1877
|
if (!Array.isArray(payload.data)) return void 0;
|
|
1797
1878
|
const candidates = [];
|
|
1798
1879
|
for (const raw of payload.data) {
|
|
1799
|
-
const entry =
|
|
1880
|
+
const entry = asObject12(raw);
|
|
1800
1881
|
if (!entry) continue;
|
|
1801
1882
|
const name = asString5(entry.productName);
|
|
1802
1883
|
if (!name) continue;
|
|
@@ -1874,7 +1955,7 @@ function addCountBucket(buckets, limit, id, label, windowMinutes) {
|
|
|
1874
1955
|
function addUsageDetailMetrics(metrics, value) {
|
|
1875
1956
|
if (!Array.isArray(value)) return;
|
|
1876
1957
|
for (const raw of value) {
|
|
1877
|
-
const detail =
|
|
1958
|
+
const detail = asObject12(raw);
|
|
1878
1959
|
if (!detail) continue;
|
|
1879
1960
|
const label = asString5(detail.modelCode);
|
|
1880
1961
|
const usage = asNonnegativeNumber4(detail.usage);
|
|
@@ -1882,7 +1963,7 @@ function addUsageDetailMetrics(metrics, value) {
|
|
|
1882
1963
|
metrics.push({ id: `mcp-${kebabCase(label)}`, label, value: usage, unit: "count" });
|
|
1883
1964
|
}
|
|
1884
1965
|
}
|
|
1885
|
-
function
|
|
1966
|
+
function asObject12(value) {
|
|
1886
1967
|
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
1887
1968
|
return value;
|
|
1888
1969
|
}
|
|
@@ -2220,6 +2301,7 @@ var SUPPORTED_ADAPTERS = [
|
|
|
2220
2301
|
{
|
|
2221
2302
|
id: "zai",
|
|
2222
2303
|
displayName: "Z.AI",
|
|
2304
|
+
invalidateCacheOnFailure: true,
|
|
2223
2305
|
semantics: { kind: "consumer-subscription", label: "GLM Coding Plan usage" },
|
|
2224
2306
|
async query(auth, signal, timeoutMs, guard) {
|
|
2225
2307
|
return queryZaiUsage("zai", "Z.AI", auth, signal, timeoutMs, guard);
|
|
@@ -2228,6 +2310,7 @@ var SUPPORTED_ADAPTERS = [
|
|
|
2228
2310
|
{
|
|
2229
2311
|
id: "zai-coding-cn",
|
|
2230
2312
|
displayName: "Z.AI Coding CN",
|
|
2313
|
+
invalidateCacheOnFailure: true,
|
|
2231
2314
|
semantics: { kind: "consumer-subscription", label: "GLM Coding Plan usage" },
|
|
2232
2315
|
async query(auth, signal, timeoutMs, guard) {
|
|
2233
2316
|
return queryZaiUsage("zai-coding-cn", "Z.AI Coding CN", auth, signal, timeoutMs, guard);
|
|
@@ -2529,6 +2612,8 @@ async function fetchProviderJson(url, auth, signal, timeoutMs, description, requ
|
|
|
2529
2612
|
);
|
|
2530
2613
|
if (controller.signal.aborted)
|
|
2531
2614
|
throw Object.assign(new Error("Usage query aborted."), { name: "AbortError" });
|
|
2615
|
+
const responseError = request.responseError?.(response.status, text);
|
|
2616
|
+
if (responseError) throw new Error(responseError);
|
|
2532
2617
|
if (!response.ok) {
|
|
2533
2618
|
throw new Error(
|
|
2534
2619
|
`${description} returned ${response.status} ${response.statusText}: ${redactUsageError(text, auth.secrets)}`
|
|
@@ -2605,7 +2690,7 @@ function resolveXaiUsageAuth(auth, model, salt, candidates) {
|
|
|
2605
2690
|
const matches = [];
|
|
2606
2691
|
for (const candidate of candidates) {
|
|
2607
2692
|
try {
|
|
2608
|
-
const credential =
|
|
2693
|
+
const credential = asObject13(candidate);
|
|
2609
2694
|
if (credential?.type !== "oauth") continue;
|
|
2610
2695
|
sawOAuth = true;
|
|
2611
2696
|
if (credential.access !== resolvedAccess) continue;
|
|
@@ -2659,7 +2744,7 @@ function resolveGitHubCopilotUsageAuth(auth, model, salt, candidates, standalone
|
|
|
2659
2744
|
const matches = /* @__PURE__ */ new Map();
|
|
2660
2745
|
for (const candidate of candidates) {
|
|
2661
2746
|
try {
|
|
2662
|
-
const credential =
|
|
2747
|
+
const credential = asObject13(candidate);
|
|
2663
2748
|
if (credential?.type !== "oauth") continue;
|
|
2664
2749
|
sawOAuth = true;
|
|
2665
2750
|
const storedAccess2 = typeof credential.access === "string" && credential.access ? credential.access : void 0;
|
|
@@ -2721,7 +2806,7 @@ function bearerToken(authorization) {
|
|
|
2721
2806
|
const match = /^Bearer\s+(.+)$/iu.exec(authorization ?? "");
|
|
2722
2807
|
return match?.[1];
|
|
2723
2808
|
}
|
|
2724
|
-
function
|
|
2809
|
+
function asObject13(value) {
|
|
2725
2810
|
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
2726
2811
|
return value;
|
|
2727
2812
|
}
|
|
@@ -2850,7 +2935,8 @@ async function queryZaiUsage(providerId, providerName, auth, signal, timeoutMs,
|
|
|
2850
2935
|
zaiMonitorAuth(auth),
|
|
2851
2936
|
signal,
|
|
2852
2937
|
remainingTimeout2(timeoutMs, startedAt, `fetching ${providerName} quota`),
|
|
2853
|
-
`${providerName} quota endpoint
|
|
2938
|
+
`${providerName} quota endpoint`,
|
|
2939
|
+
{ responseError: zaiResponseError }
|
|
2854
2940
|
);
|
|
2855
2941
|
await guard();
|
|
2856
2942
|
const planTimeoutMs = timeoutMs - (Date.now() - startedAt);
|
|
@@ -2865,7 +2951,8 @@ async function fetchZaiPlan(providerName, auth, signal, timeoutMs) {
|
|
|
2865
2951
|
zaiMonitorAuth(auth),
|
|
2866
2952
|
signal,
|
|
2867
2953
|
timeoutMs,
|
|
2868
|
-
`${providerName} plan endpoint
|
|
2954
|
+
`${providerName} plan endpoint`,
|
|
2955
|
+
{ responseError: zaiResponseError }
|
|
2869
2956
|
);
|
|
2870
2957
|
return normalizeZaiSubscriptionPayload(payload);
|
|
2871
2958
|
} catch (error) {
|
|
@@ -3015,7 +3102,7 @@ function normalizeCodexResetCreditsPayload(payload) {
|
|
|
3015
3102
|
if (rawCredits !== void 0 && !Array.isArray(rawCredits)) {
|
|
3016
3103
|
throw new Error("Codex reset credits response returned invalid credits.");
|
|
3017
3104
|
}
|
|
3018
|
-
const options = (rawCredits ?? []).map(
|
|
3105
|
+
const options = (rawCredits ?? []).map(asObject14).filter((credit) => Boolean(credit)).filter((credit) => credit.status === "available" && credit.reset_type === "codex_rate_limits").map(normalizeResetOption).sort(
|
|
3019
3106
|
(left, right) => (left.expiresAt ?? Number.MAX_SAFE_INTEGER) - (right.expiresAt ?? Number.MAX_SAFE_INTEGER)
|
|
3020
3107
|
).slice(0, Math.min(availableCount, MAX_RESET_OPTIONS));
|
|
3021
3108
|
if (availableCount > 0 && options.length === 0) {
|
|
@@ -3030,7 +3117,7 @@ function selectCodexResetCredential(candidates, resolvedAccess, resolvedAccountI
|
|
|
3030
3117
|
const matches = /* @__PURE__ */ new Map();
|
|
3031
3118
|
for (const candidate of candidates) {
|
|
3032
3119
|
try {
|
|
3033
|
-
const credential =
|
|
3120
|
+
const credential = asObject14(candidate);
|
|
3034
3121
|
if (credential?.type !== "oauth") continue;
|
|
3035
3122
|
sawOAuth = true;
|
|
3036
3123
|
const storedAccess = asNonemptyString(credential.access);
|
|
@@ -3069,7 +3156,7 @@ function codexAccountIdFromAccessToken(access) {
|
|
|
3069
3156
|
const parts = access.split(".");
|
|
3070
3157
|
if (parts.length !== 3 || !parts[1]) return void 0;
|
|
3071
3158
|
const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString("utf8"));
|
|
3072
|
-
const claims =
|
|
3159
|
+
const claims = asObject14(asObject14(payload)?.["https://api.openai.com/auth"]);
|
|
3073
3160
|
return validHeaderValue(claims?.chatgpt_account_id);
|
|
3074
3161
|
} catch {
|
|
3075
3162
|
return void 0;
|
|
@@ -3101,7 +3188,7 @@ function normalizeResetOption(credit) {
|
|
|
3101
3188
|
function isCodexResetOutcomeCode(value) {
|
|
3102
3189
|
return value === "reset" || value === "nothing_to_reset" || value === "no_credit" || value === "already_redeemed";
|
|
3103
3190
|
}
|
|
3104
|
-
function
|
|
3191
|
+
function asObject14(value) {
|
|
3105
3192
|
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
3106
3193
|
return value;
|
|
3107
3194
|
}
|
|
@@ -4677,6 +4764,7 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
4677
4764
|
if (failure.until <= now) failureBackoff.delete(key);
|
|
4678
4765
|
}
|
|
4679
4766
|
if (queryId === void 0 || latestQueries.get(failureKey) === queryId) {
|
|
4767
|
+
if (adapter.invalidateCacheOnFailure) cache.delete(adapter.id, queryFingerprint);
|
|
4680
4768
|
setBoundedMap(
|
|
4681
4769
|
failureBackoff,
|
|
4682
4770
|
failureKey,
|