|
| 1 | +import { asFiniteNumberInRange, parseStrictFiniteNumber } from "openclaw/plugin-sdk/number-runtime"; |
1 | 2 | import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime"; |
2 | 3 | |
| 4 | +const MAX_SAFE_RETRY_AFTER_SECONDS = Number.MAX_SAFE_INTEGER / 1000; |
| 5 | + |
3 | 6 | export function formatUnknownError(err: unknown): string { |
4 | 7 | if (err instanceof Error) { |
5 | 8 | return err.message; |
@@ -96,17 +99,25 @@ function extractRetryAfterMs(err: unknown): number | null {
|
96 | 99 | } |
97 | 100 | |
98 | 101 | const direct = err.retryAfterMs ?? err.retry_after_ms; |
99 | | -if (typeof direct === "number" && Number.isFinite(direct) && direct >= 0) { |
100 | | -return direct; |
| 102 | +const directMs = asFiniteNumberInRange(direct, { |
| 103 | +min: 0, |
| 104 | +max: Number.MAX_SAFE_INTEGER, |
| 105 | +}); |
| 106 | +if (directMs !== undefined) { |
| 107 | +return directMs; |
101 | 108 | } |
102 | 109 | |
103 | 110 | const retryAfter = err.retryAfter ?? err.retry_after; |
104 | | -if (typeof retryAfter === "number" && Number.isFinite(retryAfter)) { |
105 | | -return retryAfter >= 0 ? retryAfter * 1000 : null; |
| 111 | +const retryAfterSeconds = asFiniteNumberInRange(retryAfter, { |
| 112 | +min: 0, |
| 113 | +max: MAX_SAFE_RETRY_AFTER_SECONDS, |
| 114 | +}); |
| 115 | +if (retryAfterSeconds !== undefined) { |
| 116 | +return retryAfterSeconds * 1000; |
106 | 117 | } |
107 | 118 | if (typeof retryAfter === "string") { |
108 | 119 | const parsed = parseNonNegativeRetryAfterSeconds(retryAfter); |
109 | | -if (Number.isFinite(parsed) && parsed >= 0) { |
| 120 | +if (parsed !== undefined) { |
110 | 121 | return parsed * 1000; |
111 | 122 | } |
112 | 123 | } |
@@ -125,7 +136,7 @@ function extractRetryAfterMs(err: unknown): number | null {
|
125 | 136 | const raw = headers["retry-after"] ?? headers["Retry-After"]; |
126 | 137 | if (typeof raw === "string") { |
127 | 138 | const parsed = parseNonNegativeRetryAfterSeconds(raw); |
128 | | -if (Number.isFinite(parsed) && parsed >= 0) { |
| 139 | +if (parsed !== undefined) { |
129 | 140 | return parsed * 1000; |
130 | 141 | } |
131 | 142 | } |
@@ -141,7 +152,7 @@ function extractRetryAfterMs(err: unknown): number | null {
|
141 | 152 | const raw = (headers as { get: (name: string) => string | null }).get("retry-after"); |
142 | 153 | if (raw) { |
143 | 154 | const parsed = parseNonNegativeRetryAfterSeconds(raw); |
144 | | -if (Number.isFinite(parsed) && parsed >= 0) { |
| 155 | +if (parsed !== undefined) { |
145 | 156 | return parsed * 1000; |
146 | 157 | } |
147 | 158 | } |
@@ -150,12 +161,15 @@ function extractRetryAfterMs(err: unknown): number | null {
|
150 | 161 | return null; |
151 | 162 | } |
152 | 163 | |
153 | | -function parseNonNegativeRetryAfterSeconds(raw: string): number { |
| 164 | +function parseNonNegativeRetryAfterSeconds(raw: string): number | undefined { |
154 | 165 | const trimmed = raw.trim(); |
155 | 166 | if (!/^\d+(?:\.\d+)?$/.test(trimmed)) { |
156 | | -return Number.NaN; |
| 167 | +return undefined; |
157 | 168 | } |
158 | | -return Number(trimmed); |
| 169 | +return asFiniteNumberInRange(parseStrictFiniteNumber(trimmed), { |
| 170 | +min: 0, |
| 171 | +max: MAX_SAFE_RETRY_AFTER_SECONDS, |
| 172 | +}); |
159 | 173 | } |
160 | 174 | |
161 | 175 | type MSTeamsSendErrorKind = |
|