pi-provider-qoder-cn 0.2.16 → 0.2.18

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.
Files changed (3) hide show
  1. package/README.md +4 -0
  2. package/dist/index.js +55 -39
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -129,6 +129,10 @@ Notes:
129
129
  not reserve space for one it does not have.
130
130
  - Colour is suppressed automatically for a `NO_COLOR` environment, a dumb
131
131
  terminal, and any non-TTY output that is not the interactive UI.
132
+ - The gauge and the `%` column are derived from `used / total`, so they always
133
+ agree with the numbers on the same row. The API's own `percentage` field is a
134
+ 0-1 fraction (94% arrives as `0.94`) and is only used — scaled by 100 — when a
135
+ bucket comes back without numbers.
132
136
  - `Expires never` means the API reported the year-9999 sentinel, i.e. the
133
137
  allowance has no expiry date.
134
138
  - `Note plan quota is prorated` appears after a mid-cycle plan change.
package/dist/index.js CHANGED
@@ -1785,6 +1785,27 @@ init_oauth();
1785
1785
 
1786
1786
  // src/auth/usage.ts
1787
1787
  init_region();
1788
+ function num(value) {
1789
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
1790
+ }
1791
+ function round2(value) {
1792
+ return Math.round(value * 100) / 100;
1793
+ }
1794
+ function hasAllowance(bucket) {
1795
+ if (!bucket) return false;
1796
+ return num(bucket.total ?? bucket.cap) > 0 || num(bucket.used) > 0 || num(bucket.remaining) > 0;
1797
+ }
1798
+ function resetTimestamp(expiresAt) {
1799
+ const ms = num(expiresAt);
1800
+ if (ms <= 0) return void 0;
1801
+ const timestamp = ms < 1e12 ? ms * 1e3 : ms;
1802
+ if (new Date(timestamp).getUTCFullYear() >= 9999) return void 0;
1803
+ return new Date(timestamp).toISOString();
1804
+ }
1805
+ function pickBucket(raw, camel) {
1806
+ const snake = camel.replace(/[A-Z]/g, (c) => `_${c.toLowerCase()}`);
1807
+ return raw[snake] ?? raw[camel];
1808
+ }
1788
1809
  async function fetchQoderUsageForMode(credentials, mode) {
1789
1810
  const region = getQoderRegionConfig(mode);
1790
1811
  const response = await fetch(getQoderUsageURL(mode), {
@@ -1799,35 +1820,27 @@ async function fetchQoderUsageForMode(credentials, mode) {
1799
1820
  throw new Error(`Failed to fetch Qoder usage: ${response.status} ${response.statusText}`);
1800
1821
  }
1801
1822
  const raw = await response.json();
1802
- const usageBuckets = [];
1803
- const userQuota = raw.user_quota ?? raw.userQuota;
1804
- const org = raw.org_resource_package ?? raw.orgResourcePackage ?? raw.shared_quota ?? raw.sharedQuota;
1805
- if (userQuota) {
1806
- usageBuckets.push({
1807
- id: "user-quota",
1808
- label: "User Quota",
1809
- usedDisplay: userQuota.used.toFixed(2),
1810
- limitDisplay: (userQuota.total ?? userQuota.cap ?? 0).toFixed(2),
1811
- unit: userQuota.unit,
1812
- resetAt: raw.expiresAt ? new Date(raw.expiresAt).toISOString() : void 0
1813
- });
1814
- }
1815
- const orgLimit = org?.cap ?? org?.total ?? 0;
1816
- if (org && orgLimit > 0) {
1817
- usageBuckets.push({
1818
- id: "org-resource-package",
1819
- label: "Org Resource Package",
1820
- usedDisplay: org.used.toFixed(2),
1821
- limitDisplay: orgLimit.toFixed(2),
1822
- unit: org.unit,
1823
- resetAt: raw.expiresAt ? new Date(raw.expiresAt).toISOString() : void 0
1824
- });
1825
- }
1826
- const remainingText = userQuota ? `${userQuota.remaining.toFixed(2)} ${userQuota.unit} remaining` : org ? `${org.remaining.toFixed(2)} ${org.unit} remaining` : "";
1823
+ const org = pickBucket(raw, "orgResourcePackage") ?? pickBucket(raw, "sharedQuota");
1824
+ const resetAt = resetTimestamp(raw.expiresAt);
1825
+ const buckets = [
1826
+ { id: "user-quota", label: "User Quota", quota: pickBucket(raw, "userQuota") },
1827
+ { id: "add-on-quota", label: "Add-on", quota: pickBucket(raw, "addOnQuota") },
1828
+ { id: "org-resource-package", label: "Org Resource Package", quota: org }
1829
+ ].filter((entry) => hasAllowance(entry.quota));
1830
+ const usageBuckets = buckets.map(({ id, label, quota }) => ({
1831
+ id,
1832
+ label,
1833
+ usedDisplay: round2(num(quota?.used)).toFixed(2),
1834
+ limitDisplay: round2(num(quota?.total ?? quota?.cap)).toFixed(2),
1835
+ unit: quota?.unit || "credits",
1836
+ resetAt
1837
+ }));
1838
+ const remaining = buckets.reduce((sum, { quota }) => sum + Math.max(0, num(quota?.remaining)), 0);
1839
+ const unit = buckets[0]?.quota?.unit || "credits";
1827
1840
  return {
1828
- summary: remainingText,
1841
+ summary: buckets.length > 0 ? `${round2(remaining)} ${unit} remaining` : "",
1829
1842
  subscriptionTitle: region.usageTitle,
1830
- resetAt: raw.expiresAt ? new Date(raw.expiresAt).toISOString() : void 0,
1843
+ resetAt,
1831
1844
  manageUrl: region.manageUrl,
1832
1845
  usageBuckets,
1833
1846
  raw
@@ -2456,6 +2469,10 @@ function formatPercent(value) {
2456
2469
  if (typeof value !== "number" || !Number.isFinite(value)) return void 0;
2457
2470
  return Math.round(value * 10) / 10;
2458
2471
  }
2472
+ function normalizePercent(value) {
2473
+ if (typeof value !== "number" || !Number.isFinite(value)) return void 0;
2474
+ return value >= 0 && value <= 1 ? value * 100 : value;
2475
+ }
2459
2476
  function formatDailyResetCountdown(now = Date.now()) {
2460
2477
  const diffMs = msUntilBeijingMidnight(now);
2461
2478
  const totalMinutes = Math.max(1, Math.round(diffMs / 6e4));
@@ -2512,11 +2529,8 @@ function renderUsageRows(rows, notes, color) {
2512
2529
  return lines.map((line) => line.replace(/ +$/, ""));
2513
2530
  }
2514
2531
  function bucketRow(label, bucket, danger) {
2515
- const rawPercent = bucket?.percentage;
2516
2532
  const derivedPercent = typeof bucket?.used === "number" && typeof bucket?.total === "number" && bucket.total > 0 ? bucket.used / bucket.total * 100 : void 0;
2517
- const percent = formatPercent(
2518
- typeof rawPercent === "number" && Number.isFinite(rawPercent) ? rawPercent : derivedPercent
2519
- );
2533
+ const percent = formatPercent(derivedPercent ?? normalizePercent(bucket?.percentage));
2520
2534
  return {
2521
2535
  label,
2522
2536
  percent,
@@ -2531,7 +2545,7 @@ function hasBucket(bucket) {
2531
2545
  if (!bucket) return false;
2532
2546
  return (bucket.total ?? 0) > 0 || (bucket.used ?? 0) > 0;
2533
2547
  }
2534
- function pickBucket(raw, camel) {
2548
+ function pickBucket2(raw, camel) {
2535
2549
  if (!raw) return void 0;
2536
2550
  const snake = camel.replace(/[A-Z]/g, (c) => `_${c.toLowerCase()}`);
2537
2551
  return raw[snake] ?? raw[camel];
@@ -2541,15 +2555,17 @@ function formatQoderUsage(raw, mode, now = Date.now(), options = {}) {
2541
2555
  const region = getQoderRegionConfig(mode);
2542
2556
  const danger = Boolean(raw?.isQuotaExceeded);
2543
2557
  const userType = raw?.userType ? ` (${raw.userType})` : "";
2544
- const showUserQuota = hasBucket(pickBucket(raw, "userQuota"));
2545
- const showAddOnQuota = hasBucket(pickBucket(raw, "addOnQuota"));
2546
- const org = pickBucket(raw, "orgResourcePackage") ?? pickBucket(raw, "sharedQuota");
2558
+ const showUserQuota = hasBucket(pickBucket2(raw, "userQuota"));
2559
+ const showAddOnQuota = hasBucket(pickBucket2(raw, "addOnQuota"));
2560
+ const org = pickBucket2(raw, "orgResourcePackage") ?? pickBucket2(raw, "sharedQuota");
2547
2561
  const showOrg = hasBucket(org);
2548
2562
  const orgPercent = typeof org?.used === "number" && typeof org?.cap === "number" && org.cap > 0 ? formatPercent(org.used / org.cap * 100) : void 0;
2549
- const overallPercent = formatPercent(showOrg ? raw?.totalUsagePercentage || orgPercent : raw?.totalUsagePercentage);
2563
+ const overallPercent = formatPercent(
2564
+ showOrg ? normalizePercent(raw?.totalUsagePercentage) || orgPercent : normalizePercent(raw?.totalUsagePercentage)
2565
+ );
2550
2566
  const rows = [{ label: "Overall", percent: overallPercent, tail: "used", danger }];
2551
- if (showUserQuota) rows.push(bucketRow("Plan quota", pickBucket(raw, "userQuota"), danger));
2552
- if (showAddOnQuota) rows.push(bucketRow("Add-on", pickBucket(raw, "addOnQuota"), danger));
2567
+ if (showUserQuota) rows.push(bucketRow("Plan quota", pickBucket2(raw, "userQuota"), danger));
2568
+ if (showAddOnQuota) rows.push(bucketRow("Add-on", pickBucket2(raw, "addOnQuota"), danger));
2553
2569
  if (showOrg && org)
2554
2570
  rows.push(bucketRow("Enterprise", { ...org, percentage: void 0, total: org.cap ?? org.total }, danger));
2555
2571
  const isPersonalStandard = raw?.userType === "personal_standard" || raw?.userType === "free";
@@ -2589,7 +2605,7 @@ function formatQoderUsage(raw, mode, now = Date.now(), options = {}) {
2589
2605
  });
2590
2606
  }
2591
2607
  }
2592
- const addOn = pickBucket(raw, "addOnQuota");
2608
+ const addOn = pickBucket2(raw, "addOnQuota");
2593
2609
  if (showAddOnQuota && (addOn?.remaining ?? 0) > 0 && checkin) {
2594
2610
  const latestExpiryMs = checkin.expiresAt ? Date.parse(checkin.expiresAt) : void 0;
2595
2611
  const estimate = estimateRollingAddOnExpiry(addOn, latestExpiryMs, now);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-provider-qoder-cn",
3
- "version": "0.2.16",
3
+ "version": "0.2.18",
4
4
  "description": "Pi extension for Qoder CN — PAT/OAuth auth, /qoder-cn.usage quota command, and enterprise VPC endpoint support",
5
5
  "type": "module",
6
6
  "license": "MIT",