pi-provider-qoder-cn 0.2.17 → 0.2.19

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 +8 -0
  2. package/dist/index.js +74 -49
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -133,6 +133,14 @@ Notes:
133
133
  agree with the numbers on the same row. The API's own `percentage` field is a
134
134
  0-1 fraction (94% arrives as `0.94`) and is only used — scaled by 100 — when a
135
135
  bucket comes back without numbers.
136
+ - `Add-on expiry` is only printed when a real check-in grant exists. A pool that
137
+ is a whole stack of daily grant packs (never more packs than the validity
138
+ window holds) is laid out as `Rolling 30d · earliest of N daily 100-credit
139
+ packs ~DATE`. A pool that is not one (a purchased top-up, or a leftover
140
+ remainder) prints only the expiry the campaign actually reported, `DATE ·
141
+ latest grant`, and an account that has never claimed prints no expiry line at
142
+ all, because with no grant there is nothing to date from, and inventing one
143
+ would show dates the API never sent.
136
144
  - `Expires never` means the API reported the year-9999 sentinel, i.e. the
137
145
  allowance has no expiry date.
138
146
  - `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
@@ -2389,30 +2402,39 @@ function isCredentialExpiredError(error) {
2389
2402
 
2390
2403
  // src/commands/usage.ts
2391
2404
  init_region();
2392
- function estimateRollingAddOnExpiry(addOn, latestExpiryMs, now = Date.now()) {
2405
+ var DEFAULT_GRANT_CREDITS = 100;
2406
+ var DEFAULT_GRANT_VALIDITY_DAYS = 30;
2407
+ function positiveOr(value, fallback) {
2408
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : fallback;
2409
+ }
2410
+ function estimateRollingAddOnExpiry(addOn, grant, now = Date.now()) {
2393
2411
  if (!addOn || !addOn.total || addOn.total <= 0 || !addOn.remaining || addOn.remaining <= 0) {
2394
2412
  return null;
2395
2413
  }
2396
- const packSize = 100;
2397
- const totalPacks = Math.max(1, Math.round(addOn.total / packSize));
2398
- if (totalPacks > 50) return null;
2414
+ const shape = typeof grant === "number" ? { expiryMs: grant } : grant ?? {};
2415
+ const packSize = positiveOr(shape.amount, DEFAULT_GRANT_CREDITS);
2416
+ const validityDays = positiveOr(shape.validityDays, DEFAULT_GRANT_VALIDITY_DAYS);
2417
+ const anchorMs = shape.expiryMs;
2418
+ if (typeof anchorMs !== "number" || !Number.isFinite(anchorMs) || anchorMs <= now) return null;
2419
+ if (addOn.total % packSize !== 0) return null;
2420
+ const totalPacks = Math.round(addOn.total / packSize);
2421
+ if (totalPacks < 1 || totalPacks > validityDays) return null;
2399
2422
  const used = Math.max(0, addOn.used ?? 0);
2400
2423
  const consumedPacks = Math.min(totalPacks - 1, Math.floor(used / packSize));
2401
- const earliestActiveIndex = consumedPacks;
2402
- const daysAgoClaimed = totalPacks - 1 - earliestActiveIndex;
2403
- const anchorExpiryMs = latestExpiryMs && Number.isFinite(latestExpiryMs) ? latestExpiryMs : now + 30 * 864e5;
2404
- const earliestMs = anchorExpiryMs - daysAgoClaimed * 864e5;
2424
+ const daysAgoClaimed = totalPacks - 1 - consumedPacks;
2425
+ const earliestMs = anchorMs - daysAgoClaimed * 864e5;
2405
2426
  const daysRemaining = Math.max(0, Math.round((earliestMs - now) / 864e5));
2406
2427
  const earliestRemaining = Math.min(addOn.remaining, packSize - used % packSize);
2407
2428
  const d = new Date(earliestMs);
2408
- const earliestDate = d.toISOString().slice(0, 10);
2409
2429
  return {
2410
- earliestDate,
2430
+ earliestDate: d.toISOString().slice(0, 10),
2411
2431
  earliestMs,
2412
2432
  earliestRemaining,
2413
2433
  totalPacks,
2414
2434
  consumedPacks,
2415
- daysRemaining
2435
+ daysRemaining,
2436
+ packSize,
2437
+ validityDays
2416
2438
  };
2417
2439
  }
2418
2440
  var ANSI2 = {
@@ -2532,7 +2554,7 @@ function hasBucket(bucket) {
2532
2554
  if (!bucket) return false;
2533
2555
  return (bucket.total ?? 0) > 0 || (bucket.used ?? 0) > 0;
2534
2556
  }
2535
- function pickBucket(raw, camel) {
2557
+ function pickBucket2(raw, camel) {
2536
2558
  if (!raw) return void 0;
2537
2559
  const snake = camel.replace(/[A-Z]/g, (c) => `_${c.toLowerCase()}`);
2538
2560
  return raw[snake] ?? raw[camel];
@@ -2542,17 +2564,17 @@ function formatQoderUsage(raw, mode, now = Date.now(), options = {}) {
2542
2564
  const region = getQoderRegionConfig(mode);
2543
2565
  const danger = Boolean(raw?.isQuotaExceeded);
2544
2566
  const userType = raw?.userType ? ` (${raw.userType})` : "";
2545
- const showUserQuota = hasBucket(pickBucket(raw, "userQuota"));
2546
- const showAddOnQuota = hasBucket(pickBucket(raw, "addOnQuota"));
2547
- const org = pickBucket(raw, "orgResourcePackage") ?? pickBucket(raw, "sharedQuota");
2567
+ const showUserQuota = hasBucket(pickBucket2(raw, "userQuota"));
2568
+ const showAddOnQuota = hasBucket(pickBucket2(raw, "addOnQuota"));
2569
+ const org = pickBucket2(raw, "orgResourcePackage") ?? pickBucket2(raw, "sharedQuota");
2548
2570
  const showOrg = hasBucket(org);
2549
2571
  const orgPercent = typeof org?.used === "number" && typeof org?.cap === "number" && org.cap > 0 ? formatPercent(org.used / org.cap * 100) : void 0;
2550
2572
  const overallPercent = formatPercent(
2551
2573
  showOrg ? normalizePercent(raw?.totalUsagePercentage) || orgPercent : normalizePercent(raw?.totalUsagePercentage)
2552
2574
  );
2553
2575
  const rows = [{ label: "Overall", percent: overallPercent, tail: "used", danger }];
2554
- if (showUserQuota) rows.push(bucketRow("Plan quota", pickBucket(raw, "userQuota"), danger));
2555
- if (showAddOnQuota) rows.push(bucketRow("Add-on", pickBucket(raw, "addOnQuota"), danger));
2576
+ if (showUserQuota) rows.push(bucketRow("Plan quota", pickBucket2(raw, "userQuota"), danger));
2577
+ if (showAddOnQuota) rows.push(bucketRow("Add-on", pickBucket2(raw, "addOnQuota"), danger));
2556
2578
  if (showOrg && org)
2557
2579
  rows.push(bucketRow("Enterprise", { ...org, percentage: void 0, total: org.cap ?? org.total }, danger));
2558
2580
  const isPersonalStandard = raw?.userType === "personal_standard" || raw?.userType === "free";
@@ -2592,16 +2614,19 @@ function formatQoderUsage(raw, mode, now = Date.now(), options = {}) {
2592
2614
  });
2593
2615
  }
2594
2616
  }
2595
- const addOn = pickBucket(raw, "addOnQuota");
2617
+ const addOn = pickBucket2(raw, "addOnQuota");
2596
2618
  if (showAddOnQuota && (addOn?.remaining ?? 0) > 0 && checkin) {
2597
- const latestExpiryMs = checkin.expiresAt ? Date.parse(checkin.expiresAt) : void 0;
2598
- const estimate = estimateRollingAddOnExpiry(addOn, latestExpiryMs, now);
2619
+ const expiryMs = checkin.expiresAt ? Date.parse(checkin.expiresAt) : void 0;
2620
+ const grant = { expiryMs, amount: checkin.amount, validityDays: checkin.validityDays };
2621
+ const estimate = estimateRollingAddOnExpiry(addOn, grant, now);
2599
2622
  if (estimate) {
2600
- const earliestText = estimate.totalPacks > 1 ? `earliest active pack ~${estimate.earliestDate} \xB7 in ~${estimate.daysRemaining}d (~${formatAmount(estimate.earliestRemaining)} credits)` : `${estimate.earliestDate} (in ${estimate.daysRemaining}d)`;
2623
+ const expiryText = estimate.totalPacks > 1 ? `earliest of ${estimate.totalPacks} daily ${estimate.packSize}-credit packs ~${estimate.earliestDate} (in ~${estimate.daysRemaining}d, ~${formatAmount(estimate.earliestRemaining)} credits)` : `${estimate.earliestDate} (in ${estimate.daysRemaining}d)`;
2601
2624
  notes.push({
2602
2625
  label: "Add-on expiry",
2603
- value: `Rolling 30d (${earliestText})`
2626
+ value: `Rolling ${estimate.validityDays}d \xB7 ${expiryText}`
2604
2627
  });
2628
+ } else if (typeof expiryMs === "number" && Number.isFinite(expiryMs) && expiryMs > now) {
2629
+ notes.push({ label: "Add-on expiry", value: `${formatResetTime(expiryMs, now)} \xB7 latest grant` });
2605
2630
  }
2606
2631
  }
2607
2632
  notes.push({ label: "Expires", value: formatResetTime(raw?.expiresAt, now) });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-provider-qoder-cn",
3
- "version": "0.2.17",
3
+ "version": "0.2.19",
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",