@oh-my-pi/pi-utils 18.1.2 → 18.1.3

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
+ ## [18.1.3] - 2026-09-02
6
+
7
+ ### Fixed
8
+
9
+ - Fixed retry-hint extraction for body-level millisecond hints and absolute quota-reset timestamps ([#10325](https://github.com/can1357/oh-my-pi/pull/10325) by [@usr-bin-roygbiv](https://github.com/usr-bin-roygbiv)).
10
+
5
11
  ## [18.1.0] - 2026-09-01
6
12
 
7
13
  ### Added
@@ -14,6 +14,8 @@
14
14
  * - `Please retry in 250ms` / `Please retry in 12s`
15
15
  * - `"retryDelay": "34.074824224s"` (JSON error detail field)
16
16
  * - `try again in 250ms` / `try again in 12s` / `try again in 5 min` / `try again in ~158 min`
17
+ * - `retry-after-ms=98497000`
18
+ * - `Your limit will reset at 2026-09-01 09:44:51` / `将在 2026-09-01 09:44:51 重置`
17
19
  *
18
20
  * Returns `undefined` if no signal is found.
19
21
  */
@@ -15,6 +15,7 @@ export * from "./json-parse.js";
15
15
  export * as logger from "./logger.js";
16
16
  export * from "./loop-phase.js";
17
17
  export * from "./math-delimiters.js";
18
+ export * from "./materialize-string.js";
18
19
  export * from "./mermaid-ascii.js";
19
20
  export * from "./mime.js";
20
21
  export * from "./path.js";
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Copy text into independent UTF-16 backing storage before a bounded substring outlives its source.
3
+ *
4
+ * The UTF-16 round trip preserves every JavaScript code unit, including lone
5
+ * surrogates that a UTF-8 round trip would replace.
6
+ */
7
+ export declare function materializeString(text: string): string;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-utils",
4
- "version": "18.1.2",
4
+ "version": "18.1.3",
5
5
  "description": "Shared utilities for pi packages",
6
6
  "homepage": "https://omp.sh",
7
7
  "author": "Stencil Labs, Inc.",
@@ -31,7 +31,7 @@
31
31
  "fmt": "oxfmt --no-error-on-unmatched-pattern 'src/**/*.{ts,tsx}' '{test,bench,examples,scripts}/**/*.ts' '*.ts'"
32
32
  },
33
33
  "dependencies": {
34
- "@oh-my-pi/pi-natives": "18.1.2"
34
+ "@oh-my-pi/pi-natives": "18.1.3"
35
35
  },
36
36
  "devDependencies": {
37
37
  "@types/bun": "^1.3.14"
@@ -12,6 +12,12 @@ const RETRY_DELAY_FIELD_PATTERN = /"retryDelay":\s*"([0-9.]+)(ms|s)"/i;
12
12
  const TRY_AGAIN_PATTERN = /try again in\s+~?\s*([0-9.]+)\s*(ms|sec|s|minutes?|mins?|m|hours?|hrs?|h)\b/i;
13
13
  // "Your limit will reset in 13 minutes" / "reset in 13 minutes" / "will reset in 2h"
14
14
  const WILL_RESET_IN_PATTERN = /(?:will\s+)?reset in\s+~?\s*([0-9.]+)\s*(ms|sec|s|minutes?|mins?|m|hours?|hrs?|h)\b/i;
15
+ // "Your limit will reset at 2026-09-01 09:44:51" / "reset at 2026-09-01T09:44:51Z"
16
+ const WILL_RESET_AT_PATTERN =
17
+ /(?:will\s+)?reset at\s+([0-9]{4}-[0-9]{2}-[0-9]{2}[ T][0-9]{2}:[0-9]{2}:[0-9]{2}(?:\.[0-9]+)?(?:Z|[+-][0-9]{2}:?[0-9]{2})?)/i;
18
+ const CN_RESET_AT_PATTERN = /将在\s*([0-9]{4}-[0-9]{2}-[0-9]{2}\s+[0-9]{2}:[0-9]{2}:[0-9]{2})\s*重置/;
19
+ // "retry-after-ms=98497000"
20
+ const RETRY_AFTER_MS_BODY_PATTERN = /\bretry-after-ms=([0-9]+)\b/i;
15
21
 
16
22
  /**
17
23
  * Server-suggested retry delay extraction. Merges the patterns historically used
@@ -29,6 +35,8 @@ const WILL_RESET_IN_PATTERN = /(?:will\s+)?reset in\s+~?\s*([0-9.]+)\s*(ms|sec|s
29
35
  * - `Please retry in 250ms` / `Please retry in 12s`
30
36
  * - `"retryDelay": "34.074824224s"` (JSON error detail field)
31
37
  * - `try again in 250ms` / `try again in 12s` / `try again in 5 min` / `try again in ~158 min`
38
+ * - `retry-after-ms=98497000`
39
+ * - `Your limit will reset at 2026-09-01 09:44:51` / `将在 2026-09-01 09:44:51 重置`
32
40
  *
33
41
  * Returns `undefined` if no signal is found.
34
42
  */
@@ -85,11 +93,38 @@ export function extractRetryHint(source: Response | Headers | null | undefined,
85
93
  if (totalMs > 0) return totalMs;
86
94
  }
87
95
  }
96
+ for (const pattern of [WILL_RESET_AT_PATTERN, CN_RESET_AT_PATTERN]) {
97
+ const match = pattern.exec(body);
98
+ if (match?.[1]) {
99
+ // Provider timestamps without an explicit offset are interpreted as UTC.
100
+ const normalized = match[1].replace(" ", "T");
101
+ const hasOffset = /(?:Z|[+-][0-9]{2}:?[0-9]{2})$/i.test(normalized);
102
+ const parsed = Date.parse(hasOffset ? normalized : `${normalized}Z`);
103
+ if (!Number.isNaN(parsed) && parsed > Date.now()) {
104
+ return parsed - Date.now();
105
+ }
106
+ }
107
+ }
88
108
  // Account-reset hints ("will reset in …") take precedence over short
89
109
  // retry hints ("please retry in 5s"): a body carrying both must honour the
90
110
  // longer account window, not the shorter generic one. QUOTA_RESET_PATTERN
91
111
  // ("reset after …") above already runs first and stays first.
92
- for (const pattern of [WILL_RESET_IN_PATTERN, PLEASE_RETRY_PATTERN, RETRY_DELAY_FIELD_PATTERN, TRY_AGAIN_PATTERN]) {
112
+ const accountResetMatch = WILL_RESET_IN_PATTERN.exec(body);
113
+ if (accountResetMatch?.[1]) {
114
+ const value = Number.parseFloat(accountResetMatch[1]);
115
+ if (Number.isFinite(value) && value > 0) {
116
+ const unitMs = unitToMs(accountResetMatch[2]!);
117
+ if (unitMs !== undefined) return value * unitMs;
118
+ }
119
+ }
120
+
121
+ const retryAfterMsMatch = RETRY_AFTER_MS_BODY_PATTERN.exec(body);
122
+ if (retryAfterMsMatch?.[1]) {
123
+ const ms = Number(retryAfterMsMatch[1]);
124
+ if (Number.isFinite(ms) && ms > 0) return ms;
125
+ }
126
+
127
+ for (const pattern of [PLEASE_RETRY_PATTERN, RETRY_DELAY_FIELD_PATTERN, TRY_AGAIN_PATTERN]) {
93
128
  const match = pattern.exec(body);
94
129
  if (match?.[1]) {
95
130
  const value = Number.parseFloat(match[1]);
package/src/index.ts CHANGED
@@ -15,6 +15,7 @@ export * from "./json-parse";
15
15
  export * as logger from "./logger";
16
16
  export * from "./loop-phase";
17
17
  export * from "./math-delimiters";
18
+ export * from "./materialize-string";
18
19
  export * from "./mermaid-ascii";
19
20
  export * from "./mime";
20
21
  export * from "./path";
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Copy text into independent UTF-16 backing storage before a bounded substring outlives its source.
3
+ *
4
+ * The UTF-16 round trip preserves every JavaScript code unit, including lone
5
+ * surrogates that a UTF-8 round trip would replace.
6
+ */
7
+ export function materializeString(text: string): string {
8
+ if (text.length === 0) return "";
9
+ return Buffer.from(text, "utf16le").toString("utf16le");
10
+ }