@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/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 data = asObject11(payload.data);
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 = asObject11(raw);
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.success === false) return void 0;
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 = asObject11(raw);
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 = asObject11(raw);
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 asObject11(value) {
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 = asObject12(candidate);
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 = asObject12(candidate);
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 asObject12(value) {
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(asObject13).filter((credit) => Boolean(credit)).filter((credit) => credit.status === "available" && credit.reset_type === "codex_rate_limits").map(normalizeResetOption).sort(
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 = asObject13(candidate);
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 = asObject13(asObject13(payload)?.["https://api.openai.com/auth"]);
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 asObject13(value) {
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,