@narumitw/pi-usage 0.60.4 → 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/dist/index.ts +112 -33
- package/dist/index.ts.map +4 -4
- package/docs/providers.md +8 -1
- package/package.json +1 -1
- package/src/core.ts +4 -4
- package/src/providers/zai-errors.ts +94 -0
- package/src/providers/zai.ts +7 -13
- package/src/query.ts +10 -11
- package/src/types.ts +6 -1
- package/src/usage.ts +6 -12
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);
|
|
@@ -239,8 +242,6 @@ function redactUsageError(value, secrets = []) {
|
|
|
239
242
|
function errorMessage(error) {
|
|
240
243
|
return sanitizeDisplayText(error instanceof Error ? error.message : String(error), 600);
|
|
241
244
|
}
|
|
242
|
-
var UsageUnsupportedError = class extends Error {
|
|
243
|
-
};
|
|
244
245
|
function abortError() {
|
|
245
246
|
return Object.assign(new Error("Usage query aborted."), { name: "AbortError" });
|
|
246
247
|
}
|
|
@@ -1732,22 +1733,98 @@ function isRecord2(value) {
|
|
|
1732
1733
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1733
1734
|
}
|
|
1734
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
|
+
|
|
1735
1815
|
// src/providers/zai.ts
|
|
1736
1816
|
var FIVE_HOUR_WINDOW_MINUTES2 = 300;
|
|
1737
1817
|
var WEEKLY_WINDOW_MINUTES2 = 10080;
|
|
1738
1818
|
function normalizeZaiQuotaPayload(providerId, providerName, payload, capturedAt, plan) {
|
|
1739
|
-
const
|
|
1740
|
-
if (
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
`No GLM Coding Plan on this Z.AI credential; API usage is not metered.${reason ? ` Z.AI reported: ${reason}` : ""}`
|
|
1744
|
-
);
|
|
1745
|
-
}
|
|
1819
|
+
const error = zaiPayloadError(payload);
|
|
1820
|
+
if (error) throw new Error(error);
|
|
1821
|
+
const data = asObject12(payload.data);
|
|
1822
|
+
if (!data) throw new Error("Z.AI quota response data was not an object.");
|
|
1746
1823
|
const limits = Array.isArray(data.limits) ? data.limits : [];
|
|
1747
1824
|
const buckets = [];
|
|
1748
1825
|
const metrics = [];
|
|
1749
1826
|
for (const raw of limits) {
|
|
1750
|
-
const limit =
|
|
1827
|
+
const limit = asObject12(raw);
|
|
1751
1828
|
if (!limit) continue;
|
|
1752
1829
|
const type = asString5(limit.type);
|
|
1753
1830
|
const unit = asNonnegativeNumber4(limit.unit);
|
|
@@ -1796,14 +1873,11 @@ function normalizeZaiQuotaPayload(providerId, providerName, payload, capturedAt,
|
|
|
1796
1873
|
};
|
|
1797
1874
|
}
|
|
1798
1875
|
function normalizeZaiSubscriptionPayload(payload) {
|
|
1799
|
-
if (payload
|
|
1800
|
-
if (typeof payload.code === "number" && payload.code !== 0 && payload.code !== 200) {
|
|
1801
|
-
return void 0;
|
|
1802
|
-
}
|
|
1876
|
+
if (zaiPayloadError(payload)) return void 0;
|
|
1803
1877
|
if (!Array.isArray(payload.data)) return void 0;
|
|
1804
1878
|
const candidates = [];
|
|
1805
1879
|
for (const raw of payload.data) {
|
|
1806
|
-
const entry =
|
|
1880
|
+
const entry = asObject12(raw);
|
|
1807
1881
|
if (!entry) continue;
|
|
1808
1882
|
const name = asString5(entry.productName);
|
|
1809
1883
|
if (!name) continue;
|
|
@@ -1881,7 +1955,7 @@ function addCountBucket(buckets, limit, id, label, windowMinutes) {
|
|
|
1881
1955
|
function addUsageDetailMetrics(metrics, value) {
|
|
1882
1956
|
if (!Array.isArray(value)) return;
|
|
1883
1957
|
for (const raw of value) {
|
|
1884
|
-
const detail =
|
|
1958
|
+
const detail = asObject12(raw);
|
|
1885
1959
|
if (!detail) continue;
|
|
1886
1960
|
const label = asString5(detail.modelCode);
|
|
1887
1961
|
const usage = asNonnegativeNumber4(detail.usage);
|
|
@@ -1889,7 +1963,7 @@ function addUsageDetailMetrics(metrics, value) {
|
|
|
1889
1963
|
metrics.push({ id: `mcp-${kebabCase(label)}`, label, value: usage, unit: "count" });
|
|
1890
1964
|
}
|
|
1891
1965
|
}
|
|
1892
|
-
function
|
|
1966
|
+
function asObject12(value) {
|
|
1893
1967
|
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
1894
1968
|
return value;
|
|
1895
1969
|
}
|
|
@@ -2227,6 +2301,7 @@ var SUPPORTED_ADAPTERS = [
|
|
|
2227
2301
|
{
|
|
2228
2302
|
id: "zai",
|
|
2229
2303
|
displayName: "Z.AI",
|
|
2304
|
+
invalidateCacheOnFailure: true,
|
|
2230
2305
|
semantics: { kind: "consumer-subscription", label: "GLM Coding Plan usage" },
|
|
2231
2306
|
async query(auth, signal, timeoutMs, guard) {
|
|
2232
2307
|
return queryZaiUsage("zai", "Z.AI", auth, signal, timeoutMs, guard);
|
|
@@ -2235,6 +2310,7 @@ var SUPPORTED_ADAPTERS = [
|
|
|
2235
2310
|
{
|
|
2236
2311
|
id: "zai-coding-cn",
|
|
2237
2312
|
displayName: "Z.AI Coding CN",
|
|
2313
|
+
invalidateCacheOnFailure: true,
|
|
2238
2314
|
semantics: { kind: "consumer-subscription", label: "GLM Coding Plan usage" },
|
|
2239
2315
|
async query(auth, signal, timeoutMs, guard) {
|
|
2240
2316
|
return queryZaiUsage("zai-coding-cn", "Z.AI Coding CN", auth, signal, timeoutMs, guard);
|
|
@@ -2455,8 +2531,7 @@ async function queryProviderUsage(adapter, auth, signal, timeoutMs, guard, targe
|
|
|
2455
2531
|
);
|
|
2456
2532
|
} catch (error) {
|
|
2457
2533
|
if (isStaleExtensionContextError(error) || isAbortError(error)) throw error;
|
|
2458
|
-
|
|
2459
|
-
throw error instanceof UsageUnsupportedError ? new UsageUnsupportedError(message) : new Error(message);
|
|
2534
|
+
throw new Error(redactUsageError(errorMessage(error), auth.secrets));
|
|
2460
2535
|
}
|
|
2461
2536
|
}
|
|
2462
2537
|
function providerIsConfigured(ctx, providerId) {
|
|
@@ -2537,6 +2612,8 @@ async function fetchProviderJson(url, auth, signal, timeoutMs, description, requ
|
|
|
2537
2612
|
);
|
|
2538
2613
|
if (controller.signal.aborted)
|
|
2539
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);
|
|
2540
2617
|
if (!response.ok) {
|
|
2541
2618
|
throw new Error(
|
|
2542
2619
|
`${description} returned ${response.status} ${response.statusText}: ${redactUsageError(text, auth.secrets)}`
|
|
@@ -2613,7 +2690,7 @@ function resolveXaiUsageAuth(auth, model, salt, candidates) {
|
|
|
2613
2690
|
const matches = [];
|
|
2614
2691
|
for (const candidate of candidates) {
|
|
2615
2692
|
try {
|
|
2616
|
-
const credential =
|
|
2693
|
+
const credential = asObject13(candidate);
|
|
2617
2694
|
if (credential?.type !== "oauth") continue;
|
|
2618
2695
|
sawOAuth = true;
|
|
2619
2696
|
if (credential.access !== resolvedAccess) continue;
|
|
@@ -2667,7 +2744,7 @@ function resolveGitHubCopilotUsageAuth(auth, model, salt, candidates, standalone
|
|
|
2667
2744
|
const matches = /* @__PURE__ */ new Map();
|
|
2668
2745
|
for (const candidate of candidates) {
|
|
2669
2746
|
try {
|
|
2670
|
-
const credential =
|
|
2747
|
+
const credential = asObject13(candidate);
|
|
2671
2748
|
if (credential?.type !== "oauth") continue;
|
|
2672
2749
|
sawOAuth = true;
|
|
2673
2750
|
const storedAccess2 = typeof credential.access === "string" && credential.access ? credential.access : void 0;
|
|
@@ -2729,7 +2806,7 @@ function bearerToken(authorization) {
|
|
|
2729
2806
|
const match = /^Bearer\s+(.+)$/iu.exec(authorization ?? "");
|
|
2730
2807
|
return match?.[1];
|
|
2731
2808
|
}
|
|
2732
|
-
function
|
|
2809
|
+
function asObject13(value) {
|
|
2733
2810
|
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
2734
2811
|
return value;
|
|
2735
2812
|
}
|
|
@@ -2858,7 +2935,8 @@ async function queryZaiUsage(providerId, providerName, auth, signal, timeoutMs,
|
|
|
2858
2935
|
zaiMonitorAuth(auth),
|
|
2859
2936
|
signal,
|
|
2860
2937
|
remainingTimeout2(timeoutMs, startedAt, `fetching ${providerName} quota`),
|
|
2861
|
-
`${providerName} quota endpoint
|
|
2938
|
+
`${providerName} quota endpoint`,
|
|
2939
|
+
{ responseError: zaiResponseError }
|
|
2862
2940
|
);
|
|
2863
2941
|
await guard();
|
|
2864
2942
|
const planTimeoutMs = timeoutMs - (Date.now() - startedAt);
|
|
@@ -2873,7 +2951,8 @@ async function fetchZaiPlan(providerName, auth, signal, timeoutMs) {
|
|
|
2873
2951
|
zaiMonitorAuth(auth),
|
|
2874
2952
|
signal,
|
|
2875
2953
|
timeoutMs,
|
|
2876
|
-
`${providerName} plan endpoint
|
|
2954
|
+
`${providerName} plan endpoint`,
|
|
2955
|
+
{ responseError: zaiResponseError }
|
|
2877
2956
|
);
|
|
2878
2957
|
return normalizeZaiSubscriptionPayload(payload);
|
|
2879
2958
|
} catch (error) {
|
|
@@ -3023,7 +3102,7 @@ function normalizeCodexResetCreditsPayload(payload) {
|
|
|
3023
3102
|
if (rawCredits !== void 0 && !Array.isArray(rawCredits)) {
|
|
3024
3103
|
throw new Error("Codex reset credits response returned invalid credits.");
|
|
3025
3104
|
}
|
|
3026
|
-
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(
|
|
3027
3106
|
(left, right) => (left.expiresAt ?? Number.MAX_SAFE_INTEGER) - (right.expiresAt ?? Number.MAX_SAFE_INTEGER)
|
|
3028
3107
|
).slice(0, Math.min(availableCount, MAX_RESET_OPTIONS));
|
|
3029
3108
|
if (availableCount > 0 && options.length === 0) {
|
|
@@ -3038,7 +3117,7 @@ function selectCodexResetCredential(candidates, resolvedAccess, resolvedAccountI
|
|
|
3038
3117
|
const matches = /* @__PURE__ */ new Map();
|
|
3039
3118
|
for (const candidate of candidates) {
|
|
3040
3119
|
try {
|
|
3041
|
-
const credential =
|
|
3120
|
+
const credential = asObject14(candidate);
|
|
3042
3121
|
if (credential?.type !== "oauth") continue;
|
|
3043
3122
|
sawOAuth = true;
|
|
3044
3123
|
const storedAccess = asNonemptyString(credential.access);
|
|
@@ -3077,7 +3156,7 @@ function codexAccountIdFromAccessToken(access) {
|
|
|
3077
3156
|
const parts = access.split(".");
|
|
3078
3157
|
if (parts.length !== 3 || !parts[1]) return void 0;
|
|
3079
3158
|
const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString("utf8"));
|
|
3080
|
-
const claims =
|
|
3159
|
+
const claims = asObject14(asObject14(payload)?.["https://api.openai.com/auth"]);
|
|
3081
3160
|
return validHeaderValue(claims?.chatgpt_account_id);
|
|
3082
3161
|
} catch {
|
|
3083
3162
|
return void 0;
|
|
@@ -3109,7 +3188,7 @@ function normalizeResetOption(credit) {
|
|
|
3109
3188
|
function isCodexResetOutcomeCode(value) {
|
|
3110
3189
|
return value === "reset" || value === "nothing_to_reset" || value === "no_credit" || value === "already_redeemed";
|
|
3111
3190
|
}
|
|
3112
|
-
function
|
|
3191
|
+
function asObject14(value) {
|
|
3113
3192
|
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
3114
3193
|
return value;
|
|
3115
3194
|
}
|
|
@@ -4568,7 +4647,7 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
4568
4647
|
providerId: adapter.id,
|
|
4569
4648
|
providerName,
|
|
4570
4649
|
displayState,
|
|
4571
|
-
status:
|
|
4650
|
+
status: "query-failed",
|
|
4572
4651
|
message: previousDiscoveryFailure.message
|
|
4573
4652
|
},
|
|
4574
4653
|
fingerprint: auth.fingerprint,
|
|
@@ -4627,7 +4706,7 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
4627
4706
|
providerId: adapter.id,
|
|
4628
4707
|
providerName,
|
|
4629
4708
|
displayState,
|
|
4630
|
-
status:
|
|
4709
|
+
status: "query-failed",
|
|
4631
4710
|
message: previousFailure.message
|
|
4632
4711
|
},
|
|
4633
4712
|
fingerprint: auth.fingerprint,
|
|
@@ -4680,16 +4759,16 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
4680
4759
|
);
|
|
4681
4760
|
}
|
|
4682
4761
|
const message = errorMessage(error);
|
|
4683
|
-
const status = error instanceof UsageUnsupportedError ? "unsupported" : "query-failed";
|
|
4684
4762
|
const now = Date.now();
|
|
4685
4763
|
for (const [key, failure] of failureBackoff) {
|
|
4686
4764
|
if (failure.until <= now) failureBackoff.delete(key);
|
|
4687
4765
|
}
|
|
4688
4766
|
if (queryId === void 0 || latestQueries.get(failureKey) === queryId) {
|
|
4767
|
+
if (adapter.invalidateCacheOnFailure) cache.delete(adapter.id, queryFingerprint);
|
|
4689
4768
|
setBoundedMap(
|
|
4690
4769
|
failureBackoff,
|
|
4691
4770
|
failureKey,
|
|
4692
|
-
{ until: now + FAILURE_BACKOFF_MS, message
|
|
4771
|
+
{ until: now + FAILURE_BACKOFF_MS, message },
|
|
4693
4772
|
MAX_ACCOUNT_STATES
|
|
4694
4773
|
);
|
|
4695
4774
|
}
|
|
@@ -4698,7 +4777,7 @@ function usageExtension(pi, dependencies = {}) {
|
|
|
4698
4777
|
providerId: adapter.id,
|
|
4699
4778
|
providerName,
|
|
4700
4779
|
displayState,
|
|
4701
|
-
status,
|
|
4780
|
+
status: "query-failed",
|
|
4702
4781
|
message
|
|
4703
4782
|
},
|
|
4704
4783
|
fingerprint: auth.fingerprint,
|