jeopi-utils 16.2.20 → 16.2.22

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/CHANGELOG.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [16.2.21] - 2026-07-02
6
+
7
+ ### Fixed
8
+
9
+ - Fixed `extractRetryHint()` to parse Cloud Code Assist `Resets in …` quota text and `quotaResetDelay` metadata, preserving the provider's exact daily-quota reset window for callers.
10
+
5
11
  ## [16.2.14] - 2026-07-02
6
12
 
7
13
  ### Added
@@ -8,9 +8,9 @@
8
8
  * - `x-ratelimit-reset-after` (seconds)
9
9
  *
10
10
  * Body patterns:
11
- * - `Your quota will reset after 18h31m10s` / `10m15s` / `39s`
11
+ * - `Your quota will reset after 18h31m10s` / `Resets in 28h48m25s` / `39s`
12
12
  * - `Please retry in 250ms` / `Please retry in 12s`
13
- * - `"retryDelay": "34.074824224s"` (JSON error detail field)
13
+ * - `"retryDelay": "34.074824224s"` / `"quotaResetDelay": "28h48m25.991228095s"`
14
14
  * - `try again in 250ms` / `try again in 12s` / `try again in 5 min` / `try again in ~158 min`
15
15
  *
16
16
  * Returns `undefined` if no signal is found.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "jeopi-utils",
4
- "version": "16.2.20",
4
+ "version": "16.2.22",
5
5
  "description": "Shared utilities for pi packages",
6
6
  "homepage": "https://github.com/akillness/jeopi",
7
7
  "author": "Can Boluk",
@@ -31,7 +31,7 @@
31
31
  "fmt": "biome format --write ."
32
32
  },
33
33
  "dependencies": {
34
- "jeopi-natives": "16.2.20",
34
+ "jeopi-natives": "16.2.22",
35
35
  "handlebars": "^4.7.9",
36
36
  "winston": "^3.19.0",
37
37
  "winston-daily-rotate-file": "^5.0.0"
@@ -1,11 +1,12 @@
1
1
  import { scheduler } from "node:timers/promises";
2
2
 
3
- // "reset after 1h2m3s" / "10m15s" / "39s"
4
- const QUOTA_RESET_PATTERN = /reset after (?:(\d+)h)?(?:(\d+)m)?(\d+(?:\.\d+)?)s/i;
3
+ // "reset after 1h2m3s" / "Resets in 28h48m25.991s" / "10m15s" / "39s"
4
+ const DURATION_TEXT_PATTERN = /([0-9.]+\s*(?:ms|h|m|s)(?:\s*[0-9.]+\s*(?:ms|h|m|s))*)/i;
5
+ const QUOTA_RESET_PATTERN = new RegExp(`(?:reset after|resets?\\s+in)\\s+${DURATION_TEXT_PATTERN.source}`, "i");
6
+ // JSON fields: "retryDelay": "34.074824224s", "quotaResetDelay": "28h48m25.991228095s"
7
+ const DURATION_FIELD_PATTERN = /"(?:retryDelay|quotaResetDelay)":\s*"([^"]+)"/i;
5
8
  // "Please retry in 250ms" / "Please retry in 12s"
6
9
  const PLEASE_RETRY_PATTERN = /Please retry in ([0-9.]+)(ms|s)/i;
7
- // JSON field: "retryDelay": "34.074824224s"
8
- const RETRY_DELAY_FIELD_PATTERN = /"retryDelay":\s*"([0-9.]+)(ms|s)"/i;
9
10
  // "try again in 250ms" / "try again in 12s" / "try again in 12sec" /
10
11
  // "try again in 5 min" / "try again in ~158 min." / "try again in 2h" /
11
12
  // "try again in 90 minutes" / "try again in 1 hour"
@@ -21,9 +22,9 @@ const TRY_AGAIN_PATTERN = /try again in\s+~?\s*([0-9.]+)\s*(ms|sec|s|minutes?|mi
21
22
  * - `x-ratelimit-reset-after` (seconds)
22
23
  *
23
24
  * Body patterns:
24
- * - `Your quota will reset after 18h31m10s` / `10m15s` / `39s`
25
+ * - `Your quota will reset after 18h31m10s` / `Resets in 28h48m25s` / `39s`
25
26
  * - `Please retry in 250ms` / `Please retry in 12s`
26
- * - `"retryDelay": "34.074824224s"` (JSON error detail field)
27
+ * - `"retryDelay": "34.074824224s"` / `"quotaResetDelay": "28h48m25.991228095s"`
27
28
  * - `try again in 250ms` / `try again in 12s` / `try again in 5 min` / `try again in ~158 min`
28
29
  *
29
30
  * Returns `undefined` if no signal is found.
@@ -56,16 +57,16 @@ export function extractRetryHint(source: Response | Headers | null | undefined,
56
57
  if (!body) return undefined;
57
58
 
58
59
  const quotaMatch = QUOTA_RESET_PATTERN.exec(body);
59
- if (quotaMatch) {
60
- const hours = quotaMatch[1] ? Number.parseInt(quotaMatch[1], 10) : 0;
61
- const minutes = quotaMatch[2] ? Number.parseInt(quotaMatch[2], 10) : 0;
62
- const seconds = Number.parseFloat(quotaMatch[3]!);
63
- if (!Number.isNaN(seconds)) {
64
- const totalMs = ((hours * 60 + minutes) * 60 + seconds) * 1000;
65
- if (totalMs > 0) return totalMs;
66
- }
60
+ if (quotaMatch?.[1]) {
61
+ const totalMs = parseDurationTextMs(quotaMatch[1]);
62
+ if (totalMs !== undefined) return totalMs;
63
+ }
64
+ const durationFieldMatch = DURATION_FIELD_PATTERN.exec(body);
65
+ if (durationFieldMatch?.[1]) {
66
+ const totalMs = parseDurationTextMs(durationFieldMatch[1]);
67
+ if (totalMs !== undefined) return totalMs;
67
68
  }
68
- for (const pattern of [PLEASE_RETRY_PATTERN, RETRY_DELAY_FIELD_PATTERN, TRY_AGAIN_PATTERN]) {
69
+ for (const pattern of [PLEASE_RETRY_PATTERN, TRY_AGAIN_PATTERN]) {
69
70
  const match = pattern.exec(body);
70
71
  if (match?.[1]) {
71
72
  const value = Number.parseFloat(match[1]);
@@ -77,6 +78,20 @@ export function extractRetryHint(source: Response | Headers | null | undefined,
77
78
  }
78
79
  return undefined;
79
80
  }
81
+ function parseDurationTextMs(value: string): number | undefined {
82
+ let totalMs = 0;
83
+ let matched = false;
84
+ for (const match of value.matchAll(/([0-9.]+)\s*(ms|h|m|s)/gi)) {
85
+ const amount = Number.parseFloat(match[1] ?? "");
86
+ const unit = match[2];
87
+ if (!Number.isFinite(amount) || !unit) continue;
88
+ const unitMs = unitToMs(unit);
89
+ if (unitMs === undefined) continue;
90
+ totalMs += amount * unitMs;
91
+ matched = true;
92
+ }
93
+ return matched && totalMs > 0 ? totalMs : undefined;
94
+ }
80
95
 
81
96
  function unitToMs(unit: string): number | undefined {
82
97
  switch (unit.toLowerCase()) {