@xiaoso/dsh-tool-plus 0.1.0-beta.2 → 0.1.0-beta.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/README.en.md +122 -0
- package/README.md +3 -2
- package/THIRD_PARTY_NOTICES.md +4 -35
- package/lib/abort-B7izMMna.mjs +51 -0
- package/lib/amazon-bedrock-D-shAr1X.mjs +917 -0
- package/lib/anthropic-BWsZFzU8.mjs +262 -0
- package/lib/anthropic-CMT1Qj1t.mjs +3115 -0
- package/lib/api-key-login-CpzSezsi.mjs +157 -0
- package/lib/aws-credentials-8Mgn5MAy.mjs +867 -0
- package/lib/azure-openai-responses-CNkHiLUh.mjs +274 -0
- package/lib/bun-shim.mjs +172 -7
- package/lib/bundled-references-CSuBco5p.mjs +55 -0
- package/lib/callback-server-BEFNf-jf.mjs +398 -0
- package/lib/client.js +110 -40
- package/lib/cursor-BIQfBC6V.mjs +135 -0
- package/lib/cursor-DNk6eXDd.mjs +5470 -0
- package/lib/cursor-MjSm74IJ.mjs +273 -0
- package/lib/device-code-OI_tQcmc.mjs +51 -0
- package/lib/devin-CldyPD8K.mjs +95 -0
- package/lib/devin-CzRpTUOA.mjs +114 -0
- package/lib/devin-DXOeNyU7.mjs +542 -0
- package/lib/error-B3bevhrs.mjs +1449 -0
- package/lib/event-stream-BDPi64mK.mjs +151 -0
- package/lib/github-copilot-4GaMtQmg.mjs +208 -0
- package/lib/github-copilot-B2HGSIJK.mjs +104 -0
- package/lib/gitlab-duo-akICyfeD.mjs +155 -0
- package/lib/gitlab-duo-workflow-CSEgYk3N.mjs +91 -0
- package/lib/google-antigravity-Dlkdhahq.mjs +198 -0
- package/lib/google-auth-DhqpOYrX.mjs +232 -0
- package/lib/google-gemini-cli-BG_LRdIV.mjs +850 -0
- package/lib/google-gemini-cli-zJhiOcND.mjs +211 -0
- package/lib/google-oauth-shared-CwNWYdmR.mjs +147 -0
- package/lib/google-shared-DzhCb9b0.mjs +760 -0
- package/lib/google-vertex-kscbkyRb.mjs +99 -0
- package/lib/google-zyDOG9x7.mjs +30 -0
- package/lib/identity-DnB63pS7.mjs +219 -0
- package/lib/idle-iterator-C6XbWcuB.mjs +366 -0
- package/lib/index.mjs +2865 -2745
- package/lib/kimi-DMVI-zrZ.mjs +236 -0
- package/lib/markit-M0jhziqU.mjs +1417 -0
- package/lib/minimax-code-BK-3eBbz.mjs +53 -0
- package/lib/models-CfT4coYT.mjs +104660 -0
- package/lib/ollama-BQMGPxtV.mjs +589 -0
- package/lib/openai-codex-CQL_kvYL.mjs +255 -0
- package/lib/openai-codex-responses-Cz-2qpC7.mjs +3144 -0
- package/lib/openai-completions-i8doczAk.mjs +1360 -0
- package/lib/openai-reasoning-fallback-DGVVLgd1.mjs +286 -0
- package/lib/openai-responses-DTkBiiy-.mjs +867 -0
- package/lib/opencode-DaJjcUbf.mjs +38 -0
- package/lib/perplexity-9b-RQ0mV.mjs +194 -0
- package/lib/proxy-ZtUcl6r5.mjs +190 -0
- package/lib/scrapers-CL-WgdU1.mjs +9198 -0
- package/lib/scrapers-CipyKyCH.mjs +9198 -0
- package/lib/scrapers-DGfrYhtw.mjs +9198 -0
- package/lib/scrapers-DSUPYoUF.mjs +9198 -0
- package/lib/scrapers-DZ2GXmp7.mjs +9198 -0
- package/lib/src-DTid0g4K.mjs +5001 -0
- package/lib/stream-C_RAoxJC.mjs +10236 -0
- package/lib/stream-markup-healing-2toHXWXM.mjs +491 -0
- package/lib/types/client/locales.d.ts +1 -1
- package/lib/types/config/fields.d.ts +1 -1
- package/lib/types/config/settings.d.ts +16 -2
- package/lib/types/tools/omp/sdk.d.ts +6 -0
- package/lib/types/tools/omp/tools/fetch.d.ts +2 -0
- package/lib/types/tools/omp/tools/read.d.ts +2 -8
- package/lib/types/tools/omp/utils/image-loading.d.ts +10 -36
- package/lib/types/tools/read/adapter/index.d.ts +4 -1
- package/lib/types/tools/read-image/index.d.ts +4 -0
- package/lib/types/tools/shared/bun-shim.d.ts +35 -3
- package/lib/types/tools/shared/image-bridge.d.ts +98 -0
- package/lib/types/tools/shared/omp-prompt.d.ts +4 -2
- package/lib/utils-2Wq2eATG.mjs +8119 -0
- package/lib/utils-BnX8O3vi.mjs +12303 -0
- package/lib/utils-CE8N8P4Q.mjs +12315 -0
- package/lib/utils-C_MrQ_0z.mjs +12301 -0
- package/lib/utils-D0Z1fyoO.mjs +12299 -0
- package/lib/utils-DppGHXQB.mjs +51 -0
- package/lib/utils-W8WdSBpX.mjs +12315 -0
- package/lib/vision-guard-BDD4OrKo.mjs +92 -0
- package/lib/wafer-DRU0EjZX.mjs +17 -0
- package/lib/xai-oauth-DPHDXKD7.mjs +446 -0
- package/lib/xiaomi-Dd87KSg5.mjs +157 -0
- package/lib/zai-CFeOt-53.mjs +203 -0
- package/lib/zip-D6QXpvaJ.mjs +1316 -0
- package/package.json +14 -10
- package/presets/package.json +1 -1
- package/presets/tool-plus-ptc/preset.yml +1 -1
- package/presets/tool-plus-standard/preset.yml +1 -1
|
@@ -0,0 +1,1449 @@
|
|
|
1
|
+
import { installBunShim as __installBunShim } from "./bun-shim.mjs";
|
|
2
|
+
__installBunShim();
|
|
3
|
+
import { J as getLogsDir, R as isBunTestRuntime } from "./src-DTid0g4K.mjs";
|
|
4
|
+
import * as path from "node:path";
|
|
5
|
+
import { scheduler } from "node:timers/promises";
|
|
6
|
+
//#region node_modules/.pnpm/@oh-my-pi+pi-utils@17.3.5/node_modules/@oh-my-pi/pi-utils/src/fetch-retry.ts
|
|
7
|
+
const QUOTA_RESET_PATTERN = /reset after (?:(\d+)h)?(?:(\d+)m)?(\d+(?:\.\d+)?)s/i;
|
|
8
|
+
const PLEASE_RETRY_PATTERN = /Please retry in ([0-9.]+)(ms|s)/i;
|
|
9
|
+
const RETRY_DELAY_FIELD_PATTERN = /"retryDelay":\s*"([0-9.]+)(ms|s)"/i;
|
|
10
|
+
const TRY_AGAIN_PATTERN = /try again in\s+~?\s*([0-9.]+)\s*(ms|sec|s|minutes?|mins?|m|hours?|hrs?|h)\b/i;
|
|
11
|
+
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;
|
|
12
|
+
/**
|
|
13
|
+
* Server-suggested retry delay extraction. Merges the patterns historically used
|
|
14
|
+
* by the OpenAI Codex and Google Gemini retry helpers.
|
|
15
|
+
*
|
|
16
|
+
* Header sources (checked in order):
|
|
17
|
+
* - `retry-after-ms` (milliseconds)
|
|
18
|
+
* - `Retry-After` (numeric seconds, or HTTP date)
|
|
19
|
+
* - `x-ratelimit-reset-ms` (delta ms, or Unix epoch ms/s for large values)
|
|
20
|
+
* - `x-ratelimit-reset` (Unix epoch seconds)
|
|
21
|
+
* - `x-ratelimit-reset-after` (seconds)
|
|
22
|
+
*
|
|
23
|
+
* Body patterns:
|
|
24
|
+
* - `Your quota will reset after 18h31m10s` / `10m15s` / `39s`
|
|
25
|
+
* - `Please retry in 250ms` / `Please retry in 12s`
|
|
26
|
+
* - `"retryDelay": "34.074824224s"` (JSON error detail field)
|
|
27
|
+
* - `try again in 250ms` / `try again in 12s` / `try again in 5 min` / `try again in ~158 min`
|
|
28
|
+
*
|
|
29
|
+
* Returns `undefined` if no signal is found.
|
|
30
|
+
*/
|
|
31
|
+
function extractRetryHint(source, body) {
|
|
32
|
+
const headers = source instanceof Headers ? source : source?.headers ?? void 0;
|
|
33
|
+
if (headers) {
|
|
34
|
+
const retryAfterMs = headers.get("retry-after-ms");
|
|
35
|
+
if (retryAfterMs) {
|
|
36
|
+
const ms = Number(retryAfterMs);
|
|
37
|
+
if (Number.isFinite(ms) && ms >= 0) return ms;
|
|
38
|
+
}
|
|
39
|
+
const retryAfter = headers.get("retry-after");
|
|
40
|
+
if (retryAfter) {
|
|
41
|
+
const seconds = Number(retryAfter);
|
|
42
|
+
if (Number.isFinite(seconds)) return Math.max(0, seconds * 1e3);
|
|
43
|
+
const parsedDate = Date.parse(retryAfter);
|
|
44
|
+
if (!Number.isNaN(parsedDate)) return Math.max(0, parsedDate - Date.now());
|
|
45
|
+
}
|
|
46
|
+
const rateLimitResetMs = headers.get("x-ratelimit-reset-ms");
|
|
47
|
+
if (rateLimitResetMs) {
|
|
48
|
+
const value = Number(rateLimitResetMs);
|
|
49
|
+
if (Number.isFinite(value) && value > 0) {
|
|
50
|
+
const targetMs = value > 0xe8d4a51000 ? value : value > 1e9 ? value * 1e3 : void 0;
|
|
51
|
+
if (targetMs === void 0) return value;
|
|
52
|
+
const delta = targetMs - Date.now();
|
|
53
|
+
if (delta > 0) return delta;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const rateLimitReset = headers.get("x-ratelimit-reset");
|
|
57
|
+
if (rateLimitReset) {
|
|
58
|
+
const resetSeconds = Number.parseInt(rateLimitReset, 10);
|
|
59
|
+
if (!Number.isNaN(resetSeconds)) {
|
|
60
|
+
const delta = resetSeconds * 1e3 - Date.now();
|
|
61
|
+
if (delta > 0) return delta;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const rateLimitResetAfter = headers.get("x-ratelimit-reset-after");
|
|
65
|
+
if (rateLimitResetAfter) {
|
|
66
|
+
const seconds = Number(rateLimitResetAfter);
|
|
67
|
+
if (Number.isFinite(seconds) && seconds > 0) return seconds * 1e3;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (!body) return void 0;
|
|
71
|
+
const quotaMatch = QUOTA_RESET_PATTERN.exec(body);
|
|
72
|
+
if (quotaMatch) {
|
|
73
|
+
const hours = quotaMatch[1] ? Number.parseInt(quotaMatch[1], 10) : 0;
|
|
74
|
+
const minutes = quotaMatch[2] ? Number.parseInt(quotaMatch[2], 10) : 0;
|
|
75
|
+
const seconds = Number.parseFloat(quotaMatch[3]);
|
|
76
|
+
if (!Number.isNaN(seconds)) {
|
|
77
|
+
const totalMs = ((hours * 60 + minutes) * 60 + seconds) * 1e3;
|
|
78
|
+
if (totalMs > 0) return totalMs;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
for (const pattern of [
|
|
82
|
+
WILL_RESET_IN_PATTERN,
|
|
83
|
+
PLEASE_RETRY_PATTERN,
|
|
84
|
+
RETRY_DELAY_FIELD_PATTERN,
|
|
85
|
+
TRY_AGAIN_PATTERN
|
|
86
|
+
]) {
|
|
87
|
+
const match = pattern.exec(body);
|
|
88
|
+
if (match?.[1]) {
|
|
89
|
+
const value = Number.parseFloat(match[1]);
|
|
90
|
+
if (Number.isFinite(value) && value > 0) {
|
|
91
|
+
const unitMs = unitToMs(match[2]);
|
|
92
|
+
if (unitMs !== void 0) return value * unitMs;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function unitToMs(unit) {
|
|
98
|
+
switch (unit.toLowerCase()) {
|
|
99
|
+
case "ms": return 1;
|
|
100
|
+
case "s":
|
|
101
|
+
case "sec": return 1e3;
|
|
102
|
+
case "m":
|
|
103
|
+
case "min":
|
|
104
|
+
case "mins":
|
|
105
|
+
case "minute":
|
|
106
|
+
case "minutes": return 6e4;
|
|
107
|
+
case "h":
|
|
108
|
+
case "hr":
|
|
109
|
+
case "hrs":
|
|
110
|
+
case "hour":
|
|
111
|
+
case "hours": return 36e5;
|
|
112
|
+
default: return;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
const DEFAULT_MAX_DELAY_MS = 6e4;
|
|
116
|
+
const DEFAULT_MAX_ATTEMPTS = 5;
|
|
117
|
+
/**
|
|
118
|
+
* Fetch with bounded retries and sensible defaults. Retries on any
|
|
119
|
+
* `isRetryableStatus` (5xx, 408, 429) and on transient network errors. Server
|
|
120
|
+
* `Retry-After`/quota hints are honoured up to `maxDelayMs`; a hint that exceeds
|
|
121
|
+
* the cap returns the current response so the caller can fail fast. Aborts on
|
|
122
|
+
* `init.signal` propagate as `"Request was aborted"`.
|
|
123
|
+
*
|
|
124
|
+
* The caller is responsible for inspecting `!response.ok` once the call returns.
|
|
125
|
+
*/
|
|
126
|
+
async function fetchWithRetry(url, options = {}) {
|
|
127
|
+
const { maxAttempts = DEFAULT_MAX_ATTEMPTS, maxDelayMs = DEFAULT_MAX_DELAY_MS, defaultDelayMs, prepareInit, shouldRetryResponse, fetch: fetchImpl = fetch, timeout = false, ...baseInit } = options;
|
|
128
|
+
const signal = baseInit.signal;
|
|
129
|
+
for (let attempt = 0;; attempt++) {
|
|
130
|
+
if (signal?.aborted) throw new Error("Request was aborted");
|
|
131
|
+
const requestUrl = typeof url === "function" ? url(attempt) : url;
|
|
132
|
+
const init = prepareInit ? mergeInit(baseInit, await prepareInit(attempt), timeout) : "timeout" in options ? {
|
|
133
|
+
...baseInit,
|
|
134
|
+
timeout
|
|
135
|
+
} : baseInit;
|
|
136
|
+
let response;
|
|
137
|
+
try {
|
|
138
|
+
response = await fetchImpl(requestUrl, init);
|
|
139
|
+
} catch (error) {
|
|
140
|
+
if (signal?.aborted) throw new Error("Request was aborted");
|
|
141
|
+
const wrapped = wrapNetworkError(error);
|
|
142
|
+
if (attempt + 1 >= maxAttempts) throw wrapped;
|
|
143
|
+
await waitForRetry(resolveDefaultDelay(defaultDelayMs, attempt, maxDelayMs), signal);
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
if (!isRetryableStatus(response.status)) return response;
|
|
147
|
+
if (attempt + 1 >= maxAttempts) return response;
|
|
148
|
+
const retryBody = await response.clone().text();
|
|
149
|
+
if (shouldRetryResponse && !await shouldRetryResponse(response, retryBody, attempt)) return response;
|
|
150
|
+
const hint = extractRetryHint(response, retryBody);
|
|
151
|
+
if (hint !== void 0 && hint > maxDelayMs) return response;
|
|
152
|
+
await waitForRetry(Math.min(hint ?? resolveDefaultDelay(defaultDelayMs, attempt, maxDelayMs), maxDelayMs), signal);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
function mergeInit(base, overlay, timeout) {
|
|
156
|
+
const merged = {
|
|
157
|
+
...base,
|
|
158
|
+
...overlay,
|
|
159
|
+
timeout
|
|
160
|
+
};
|
|
161
|
+
if (base.headers || overlay.headers) {
|
|
162
|
+
const baseHeaders = new Headers(base.headers ?? void 0);
|
|
163
|
+
new Headers(overlay.headers ?? void 0).forEach((value, key) => {
|
|
164
|
+
baseHeaders.set(key, value);
|
|
165
|
+
});
|
|
166
|
+
merged.headers = baseHeaders;
|
|
167
|
+
}
|
|
168
|
+
return merged;
|
|
169
|
+
}
|
|
170
|
+
async function waitForRetry(delayMs, signal) {
|
|
171
|
+
try {
|
|
172
|
+
await scheduler.wait(delayMs, { signal });
|
|
173
|
+
} catch (error) {
|
|
174
|
+
if (signal?.aborted) throw new Error("Request was aborted");
|
|
175
|
+
throw error;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
function wrapNetworkError(error) {
|
|
179
|
+
if (error instanceof Error) {
|
|
180
|
+
if (error.name === "AbortError" || error.message === "Request was aborted") return /* @__PURE__ */ new Error("Request was aborted");
|
|
181
|
+
if (error.message === "fetch failed" && error.cause instanceof Error) return /* @__PURE__ */ new Error(`Network error: ${error.cause.message}`);
|
|
182
|
+
return error;
|
|
183
|
+
}
|
|
184
|
+
return new Error(String(error));
|
|
185
|
+
}
|
|
186
|
+
function resolveDefaultDelay(option, attempt, maxDelayMs) {
|
|
187
|
+
if (option === void 0) return Math.min(500 * 2 ** attempt, maxDelayMs);
|
|
188
|
+
if (typeof option === "number") return Math.min(option, maxDelayMs);
|
|
189
|
+
if (typeof option === "function") return Math.min(option(attempt), maxDelayMs);
|
|
190
|
+
return Math.min(option[Math.min(attempt, option.length - 1)] ?? 0, maxDelayMs);
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Inspect an arbitrary error value (or its `cause` chain, up to depth 2) for an
|
|
194
|
+
* HTTP status code. Reads `status`, `statusCode`, and `response.status` fields,
|
|
195
|
+
* coerces string values, and falls back to scanning the error message for
|
|
196
|
+
* common patterns like `Error: 401`, `error (429)`, or `HTTP 503`.
|
|
197
|
+
*/
|
|
198
|
+
function extractHttpStatusFromError(error) {
|
|
199
|
+
return extractHttpStatusFromErrorInternal(error, 0);
|
|
200
|
+
}
|
|
201
|
+
function extractHttpStatusFromErrorInternal(error, depth) {
|
|
202
|
+
if (!error || typeof error !== "object" || depth > 2) return void 0;
|
|
203
|
+
const info = error;
|
|
204
|
+
const rawStatus = info.status ?? info.statusCode ?? info.response?.status;
|
|
205
|
+
let status;
|
|
206
|
+
if (typeof rawStatus === "number" && Number.isFinite(rawStatus)) status = rawStatus;
|
|
207
|
+
else if (typeof rawStatus === "string") {
|
|
208
|
+
const parsed = Number(rawStatus);
|
|
209
|
+
if (Number.isFinite(parsed)) status = parsed;
|
|
210
|
+
}
|
|
211
|
+
if (status !== void 0 && status >= 100 && status <= 599) return status;
|
|
212
|
+
if (info.message) {
|
|
213
|
+
const extracted = extractStatusFromMessage(info.message);
|
|
214
|
+
if (extracted !== void 0) return extracted;
|
|
215
|
+
}
|
|
216
|
+
if (info.cause) return extractHttpStatusFromErrorInternal(info.cause, depth + 1);
|
|
217
|
+
}
|
|
218
|
+
const STATUS_MESSAGE_PATTERNS$1 = [
|
|
219
|
+
/\berror\s*[:=]\s*(\d{3})\b/i,
|
|
220
|
+
/error\s*\((\d{3})\)/i,
|
|
221
|
+
/status\s*[:=]?\s*(\d{3})/i,
|
|
222
|
+
/\bhttp\s*(\d{3})\b/i,
|
|
223
|
+
/\b(\d{3})\s*(?:status|error)\b/i
|
|
224
|
+
];
|
|
225
|
+
function extractStatusFromMessage(message) {
|
|
226
|
+
for (const pattern of STATUS_MESSAGE_PATTERNS$1) {
|
|
227
|
+
const match = pattern.exec(message);
|
|
228
|
+
if (!match) continue;
|
|
229
|
+
const value = Number(match[1]);
|
|
230
|
+
if (Number.isFinite(value) && value >= 100 && value <= 599) return value;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* `true` if the given HTTP status code is one we treat as transient: 408
|
|
235
|
+
* (Request Timeout), 429 (Too Many Requests), or any 5xx (server error).
|
|
236
|
+
*/
|
|
237
|
+
function isRetryableStatus(status) {
|
|
238
|
+
return status >= 500 || status === 408 || status === 429;
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* `true` if the message describes an unexpected socket closure — Bun and some
|
|
242
|
+
* proxies surface these for any HTTP/2 stream reset.
|
|
243
|
+
*/
|
|
244
|
+
function isUnexpectedSocketCloseMessage(message) {
|
|
245
|
+
return /\b(?:the\s+)?socket connection (?:was )?closed unexpectedly\b/i.test(message);
|
|
246
|
+
}
|
|
247
|
+
const TRANSIENT_MESSAGE_PATTERN = /overloaded|rate.?limit|too many requests|service.?unavailable|server error|internal error|connection.?error|unable to connect|fetch failed|network error|stream stall|other side closed|HTTP2(?:StreamReset|RefusedStream|EnhanceYourCalm)/i;
|
|
248
|
+
const VALIDATION_MESSAGE_PATTERN = /invalid|validation|bad request|unsupported|schema|missing required|not found|unauthorized|forbidden/i;
|
|
249
|
+
/**
|
|
250
|
+
* Identify errors that should be retried: aborts/timeouts in the error name or
|
|
251
|
+
* message, retryable HTTP statuses (see `isRetryableStatus`), unexpected socket
|
|
252
|
+
* closes, and the standard transient phrases. 4xx statuses other than 408/429
|
|
253
|
+
* and validation-shaped messages short-circuit to `false`.
|
|
254
|
+
*/
|
|
255
|
+
function isRetryableError(error) {
|
|
256
|
+
const info = error;
|
|
257
|
+
const message = info?.message ?? "";
|
|
258
|
+
if ((info?.name ?? "") === "AbortError" || /timeout|timed out|aborted/i.test(message)) return true;
|
|
259
|
+
const status = extractHttpStatusFromError(error);
|
|
260
|
+
if (status !== void 0) {
|
|
261
|
+
if (isRetryableStatus(status)) return true;
|
|
262
|
+
if (status >= 400 && status < 500) return false;
|
|
263
|
+
}
|
|
264
|
+
if (VALIDATION_MESSAGE_PATTERN.test(message)) return false;
|
|
265
|
+
return isUnexpectedSocketCloseMessage(message) || TRANSIENT_MESSAGE_PATTERN.test(message);
|
|
266
|
+
}
|
|
267
|
+
//#endregion
|
|
268
|
+
//#region node_modules/.pnpm/@oh-my-pi+pi-ai@17.3.5/node_modules/@oh-my-pi/pi-ai/src/error/aws.ts
|
|
269
|
+
/** A failure resolving AWS credentials for the Bedrock provider. */
|
|
270
|
+
var AwsCredentialsError = class extends Error {
|
|
271
|
+
kind;
|
|
272
|
+
constructor(message, kind, options) {
|
|
273
|
+
super(message, options?.cause === void 0 ? void 0 : { cause: options.cause });
|
|
274
|
+
this.name = "AwsCredentialsError";
|
|
275
|
+
this.kind = kind;
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
/** A malformed AWS event-stream frame (bad length, CRC mismatch, unknown header type). */
|
|
279
|
+
var EventStreamFrameError = class extends Error {
|
|
280
|
+
constructor(detail) {
|
|
281
|
+
super(`eventstream: ${detail}`);
|
|
282
|
+
this.name = "EventStreamFrameError";
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
//#endregion
|
|
286
|
+
//#region node_modules/.pnpm/@oh-my-pi+pi-ai@17.3.5/node_modules/@oh-my-pi/pi-ai/src/error/classes.ts
|
|
287
|
+
/** Prefix on errors raised when an Anthropic SSE stream envelope is malformed. */
|
|
288
|
+
const STREAM_ENVELOPE_ERROR_PREFIX = "Anthropic stream envelope error:";
|
|
289
|
+
/** Non-2xx HTTP response from a provider. */
|
|
290
|
+
var ProviderHttpError = class extends Error {
|
|
291
|
+
status;
|
|
292
|
+
headers;
|
|
293
|
+
code;
|
|
294
|
+
constructor(message, status, options) {
|
|
295
|
+
super(message, options?.cause === void 0 ? void 0 : { cause: options.cause });
|
|
296
|
+
this.name = "ProviderHttpError";
|
|
297
|
+
this.status = status;
|
|
298
|
+
this.headers = options?.headers;
|
|
299
|
+
this.code = options?.code;
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
/** Non-2xx response from an OpenAI-wire endpoint, with the decoded body attached. */
|
|
303
|
+
var OpenAIHttpError = class extends ProviderHttpError {
|
|
304
|
+
captured;
|
|
305
|
+
constructor(message, captured, code, cause) {
|
|
306
|
+
super(message, captured.status, {
|
|
307
|
+
headers: captured.headers,
|
|
308
|
+
code,
|
|
309
|
+
cause
|
|
310
|
+
});
|
|
311
|
+
this.name = "OpenAIHttpError";
|
|
312
|
+
this.captured = captured;
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Pull a human-readable message and machine code out of an OpenAI-style error
|
|
316
|
+
* envelope (`{ error: { message, code, type } }`), tolerating the flat shapes
|
|
317
|
+
* compat hosts return (`{ error: "..." }`, `{ message: "..." }`) and falling
|
|
318
|
+
* back to the raw body text.
|
|
319
|
+
*/
|
|
320
|
+
static parseEnvelope(bodyJson, bodyText) {
|
|
321
|
+
if (typeof bodyJson === "object" && bodyJson !== null) {
|
|
322
|
+
const envelope = bodyJson;
|
|
323
|
+
const error = envelope.error;
|
|
324
|
+
if (typeof error === "object" && error !== null) {
|
|
325
|
+
const { message, code, type } = error;
|
|
326
|
+
return {
|
|
327
|
+
detail: typeof message === "string" && message.length > 0 ? message : bodyText,
|
|
328
|
+
code: typeof code === "string" ? code : typeof type === "string" ? type : void 0
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
if (typeof error === "string" && error.length > 0) return {
|
|
332
|
+
detail: error,
|
|
333
|
+
code: void 0
|
|
334
|
+
};
|
|
335
|
+
if (typeof envelope.message === "string" && envelope.message.length > 0) return {
|
|
336
|
+
detail: envelope.message,
|
|
337
|
+
code: void 0
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
return {
|
|
341
|
+
detail: bodyText,
|
|
342
|
+
code: void 0
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
/** Non-2xx response from the Anthropic API. */
|
|
347
|
+
const DEFAULT_ANTHROPIC_ERROR_BODY_READ_TIMEOUT_MS = 5e3;
|
|
348
|
+
const MAX_ANTHROPIC_ERROR_BODY_BYTES = 65536;
|
|
349
|
+
const ANTHROPIC_ERROR_BODY_TRUNCATION_MARKER = "\n[Response body truncated after 64 KiB]";
|
|
350
|
+
let anthropicErrorBodyReadTimeoutMs = DEFAULT_ANTHROPIC_ERROR_BODY_READ_TIMEOUT_MS;
|
|
351
|
+
/** Non-2xx response from the Anthropic API. */
|
|
352
|
+
var AnthropicApiError = class AnthropicApiError extends ProviderHttpError {
|
|
353
|
+
requestId;
|
|
354
|
+
constructor(status, message, headers) {
|
|
355
|
+
super(message, status, { headers });
|
|
356
|
+
this.name = "AnthropicApiError";
|
|
357
|
+
this.requestId = headers.get("request-id");
|
|
358
|
+
}
|
|
359
|
+
static async fromResponse(response, signal) {
|
|
360
|
+
const reader = response.body?.locked ? void 0 : response.body?.getReader();
|
|
361
|
+
if (!reader) {
|
|
362
|
+
if (signal?.aborted) throw new AbortError("Request was aborted.");
|
|
363
|
+
return new AnthropicApiError(response.status, `${response.status} status code (no body)`, response.headers);
|
|
364
|
+
}
|
|
365
|
+
let aborted = false;
|
|
366
|
+
let timedOut = false;
|
|
367
|
+
let readerCancelled = false;
|
|
368
|
+
const cancelReader = () => {
|
|
369
|
+
if (readerCancelled) return;
|
|
370
|
+
readerCancelled = true;
|
|
371
|
+
reader.cancel().catch(() => {});
|
|
372
|
+
};
|
|
373
|
+
const onAbort = () => {
|
|
374
|
+
if (aborted) return;
|
|
375
|
+
aborted = true;
|
|
376
|
+
cancelReader();
|
|
377
|
+
};
|
|
378
|
+
if (signal?.aborted) onAbort();
|
|
379
|
+
else signal?.addEventListener("abort", onAbort, { once: true });
|
|
380
|
+
const deadline = performance.now() + anthropicErrorBodyReadTimeoutMs;
|
|
381
|
+
let timeout;
|
|
382
|
+
timeout = setTimeout(() => {
|
|
383
|
+
timedOut = true;
|
|
384
|
+
cancelReader();
|
|
385
|
+
}, anthropicErrorBodyReadTimeoutMs);
|
|
386
|
+
let capturedBytes = 0;
|
|
387
|
+
let truncated = false;
|
|
388
|
+
const bodyChunks = [];
|
|
389
|
+
try {
|
|
390
|
+
const decoder = new TextDecoder();
|
|
391
|
+
let cleanEof = false;
|
|
392
|
+
while (!aborted && !timedOut) {
|
|
393
|
+
if (performance.now() >= deadline) {
|
|
394
|
+
timedOut = true;
|
|
395
|
+
cancelReader();
|
|
396
|
+
break;
|
|
397
|
+
}
|
|
398
|
+
let result;
|
|
399
|
+
try {
|
|
400
|
+
result = await reader.read();
|
|
401
|
+
} catch {
|
|
402
|
+
break;
|
|
403
|
+
}
|
|
404
|
+
if (aborted || timedOut) break;
|
|
405
|
+
if (result.done) {
|
|
406
|
+
cleanEof = true;
|
|
407
|
+
break;
|
|
408
|
+
}
|
|
409
|
+
const chunk = result.value;
|
|
410
|
+
if (!chunk) break;
|
|
411
|
+
const bytesToCapture = Math.min(MAX_ANTHROPIC_ERROR_BODY_BYTES - capturedBytes, chunk.byteLength);
|
|
412
|
+
if (bytesToCapture > 0) {
|
|
413
|
+
bodyChunks.push(decoder.decode(chunk.subarray(0, bytesToCapture), { stream: true }));
|
|
414
|
+
capturedBytes += bytesToCapture;
|
|
415
|
+
}
|
|
416
|
+
if (bytesToCapture < chunk.byteLength) {
|
|
417
|
+
truncated = true;
|
|
418
|
+
cancelReader();
|
|
419
|
+
break;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
if (aborted || signal?.aborted) throw new AbortError("Request was aborted.");
|
|
423
|
+
if (cleanEof) bodyChunks.push(decoder.decode());
|
|
424
|
+
if (truncated) bodyChunks.push(ANTHROPIC_ERROR_BODY_TRUNCATION_MARKER);
|
|
425
|
+
} finally {
|
|
426
|
+
if (timeout !== void 0) clearTimeout(timeout);
|
|
427
|
+
signal?.removeEventListener("abort", onAbort);
|
|
428
|
+
reader.releaseLock();
|
|
429
|
+
}
|
|
430
|
+
const detail = bodyChunks.join("").trim() || "status code (no body)";
|
|
431
|
+
return new AnthropicApiError(response.status, `${response.status} ${detail}`, response.headers);
|
|
432
|
+
}
|
|
433
|
+
};
|
|
434
|
+
/** Network-level failure (DNS, TLS, socket reset) after retries were exhausted. */
|
|
435
|
+
var AnthropicConnectionError = class extends Error {
|
|
436
|
+
constructor(cause) {
|
|
437
|
+
super("Connection error.", { cause });
|
|
438
|
+
this.name = "AnthropicConnectionError";
|
|
439
|
+
}
|
|
440
|
+
};
|
|
441
|
+
/** No response headers arrived within the configured request timeout. */
|
|
442
|
+
var AnthropicConnectionTimeoutError = class extends Error {
|
|
443
|
+
constructor() {
|
|
444
|
+
super("Request timed out.");
|
|
445
|
+
this.name = "AnthropicConnectionTimeoutError";
|
|
446
|
+
}
|
|
447
|
+
};
|
|
448
|
+
/**
|
|
449
|
+
* A malformed Anthropic SSE stream envelope — events arriving out of order
|
|
450
|
+
* (before `message_start`) or otherwise violating the message-event grammar.
|
|
451
|
+
* The message is prefixed with {@link STREAM_ENVELOPE_ERROR_PREFIX} so the
|
|
452
|
+
* shared envelope predicates classify it.
|
|
453
|
+
*/
|
|
454
|
+
var AnthropicStreamEnvelopeError = class extends Error {
|
|
455
|
+
constructor(detail) {
|
|
456
|
+
super(`${STREAM_ENVELOPE_ERROR_PREFIX} ${detail}`);
|
|
457
|
+
this.name = "AnthropicStreamEnvelopeError";
|
|
458
|
+
}
|
|
459
|
+
};
|
|
460
|
+
/** Non-2xx response (or in-stream exception event) from the Bedrock runtime API. */
|
|
461
|
+
var BedrockApiError = class extends ProviderHttpError {
|
|
462
|
+
name = "BedrockApiError";
|
|
463
|
+
};
|
|
464
|
+
/** Non-2xx response (or in-stream error chunk) from the Cloud Code Assist API. */
|
|
465
|
+
var GeminiCliApiError = class extends ProviderHttpError {
|
|
466
|
+
name = "GeminiCliApiError";
|
|
467
|
+
};
|
|
468
|
+
/** Non-2xx response (or in-stream error chunk) from the Google Generative Language / Vertex API. */
|
|
469
|
+
var GoogleApiError = class extends ProviderHttpError {
|
|
470
|
+
name = "GoogleApiError";
|
|
471
|
+
};
|
|
472
|
+
/** Non-2xx response from the Ollama `/api/chat` endpoint. */
|
|
473
|
+
var OllamaApiError = class extends ProviderHttpError {
|
|
474
|
+
name = "OllamaApiError";
|
|
475
|
+
};
|
|
476
|
+
//#endregion
|
|
477
|
+
//#region node_modules/.pnpm/@oh-my-pi+pi-ai@17.3.5/node_modules/@oh-my-pi/pi-ai/src/error/rate-limit.ts
|
|
478
|
+
const ACCOUNT_RATE_LIMIT_PATTERN = /\baccount(?:'s)?\b[^\n]{0,80}\brate.?limit\b|\brate.?limit\b[^\n]{0,80}\baccount\b/i;
|
|
479
|
+
const INSUFFICIENT_BALANCE_PATTERN = /insufficient.?balance/i;
|
|
480
|
+
const SPEND_LIMIT_PATTERN = /spend.?limit/i;
|
|
481
|
+
const SUBSCRIPTION_CAP_PATTERN = /\b(?:subscription|plan|membership)\b[^\n]{0,80}\b(?:rate.?limits?|quota|cap)\b|\b(?:rate.?limits?|quota|cap)\b[^\n]{0,80}\b(?:subscription|plan|membership)\b/i;
|
|
482
|
+
const TRANSIENT_INTERVAL_RATE_LIMIT_PATTERN = /\bper\s+(?:second|minute)\b/i;
|
|
483
|
+
function matchesSubscriptionCapText(errorMessage) {
|
|
484
|
+
return SUBSCRIPTION_CAP_PATTERN.test(errorMessage) && !TRANSIENT_INTERVAL_RATE_LIMIT_PATTERN.test(errorMessage);
|
|
485
|
+
}
|
|
486
|
+
const OPENROUTER_DAILY_FREE_LIMIT_PATTERN = /\bfree[-_ ]models[-_ ]per[-_ ]day\b/i;
|
|
487
|
+
const RESOURCE_EXHAUSTED_PATTERN = /resource.?exhausted/gi;
|
|
488
|
+
const CONCURRENT_LIMIT_PATTERN = /\btoo many\s+concurren\w*\s+(?:requests?|invocations?)\b|\bconcurren\w*\b[^\n]{0,60}\b(?:limit|quota|exceed\w*|reach\w*)\b|\b(?:limit|quota|exceed\w*|reach\w*)\b[^\n]{0,60}\bconcurren\w*\b|\bconcurren[a-z]*[-_](?:[a-z]+[_-])*(?:limit|quota|exceed\w*|reach\w*)/i;
|
|
489
|
+
const ACCOUNT_SCOPED_403_PATTERN = /\b(?:overall|account|organization|team|workspace)\b[^\n]{0,40}\b(?:message |request )?rate.?limit\b|\byour\b[^\n]{0,30}\b(?:limit )?will reset\b/i;
|
|
490
|
+
const CN_QUOTA_EXHAUSTED_PATTERN = /使用.{0,30}?上限|(?:额度|配额)已?(?:用|耗)(?:完|尽)|限额.{0,30}重置|余额不足/;
|
|
491
|
+
const CN_TRANSIENT_CAP_PATTERN = /速率.{0,30}上限|频率.{0,30}上限|每分钟.{0,30}上限|并发.{0,30}上限|使用.{0,30}(?:速率|频率|每分钟|并发).{0,30}上限/;
|
|
492
|
+
const CN_THROTTLE_PATTERN = /速率(?:限制|过快)|频率(?:过高|过快)|过于频繁|稍后[重再]试/;
|
|
493
|
+
const DASHSCOPE_TOKEN_LIMIT_DOC_PATTERN = /error-code[^()\s]*#token-limit/i;
|
|
494
|
+
const DASHSCOPE_TOKEN_LIMIT_MESSAGE_PATTERN = /\byou exceeded your current quota, please check your plan and billing details\b/i;
|
|
495
|
+
/** True for DashScope/Bailian's documented OpenAI-compatible TPM/TPS throttle. */
|
|
496
|
+
function isDashScopeTokenLimitText(errorMessage) {
|
|
497
|
+
return DASHSCOPE_TOKEN_LIMIT_DOC_PATTERN.test(errorMessage) && DASHSCOPE_TOKEN_LIMIT_MESSAGE_PATTERN.test(errorMessage);
|
|
498
|
+
}
|
|
499
|
+
const GOOGLE_RPC_ERROR_INFO_TYPE = "type.googleapis.com/google.rpc.ErrorInfo";
|
|
500
|
+
const LONG_RATE_LIMIT_DELAY_MS = 3e5;
|
|
501
|
+
function asRecord(value) {
|
|
502
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
503
|
+
}
|
|
504
|
+
function parseJsonBody(errorMessage) {
|
|
505
|
+
const start = errorMessage.indexOf("{");
|
|
506
|
+
const end = errorMessage.lastIndexOf("}");
|
|
507
|
+
if (start < 0 || end < start) return void 0;
|
|
508
|
+
try {
|
|
509
|
+
return asRecord(JSON.parse(errorMessage.slice(start, end + 1)));
|
|
510
|
+
} catch {
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* Classify structured Google RESOURCE_EXHAUSTED bodies before consulting text.
|
|
516
|
+
* Cloud Code Assist prefixes the JSON with its HTTP error label, so accept an
|
|
517
|
+
* embedded top-level object as well as a raw JSON body.
|
|
518
|
+
*/
|
|
519
|
+
function parseGoogleRpcRateLimitReason(errorMessage) {
|
|
520
|
+
const error = asRecord(parseJsonBody(errorMessage)?.error);
|
|
521
|
+
if (typeof error?.status !== "string" || error.status.trim().toUpperCase() !== "RESOURCE_EXHAUSTED") return;
|
|
522
|
+
if (!Array.isArray(error.details)) return void 0;
|
|
523
|
+
for (const value of error.details) {
|
|
524
|
+
const detail = asRecord(value);
|
|
525
|
+
if (detail?.["@type"] !== GOOGLE_RPC_ERROR_INFO_TYPE || typeof detail.reason !== "string") continue;
|
|
526
|
+
switch (detail.reason.trim().toUpperCase()) {
|
|
527
|
+
case "QUOTA_EXHAUSTED": return "QUOTA_EXHAUSTED";
|
|
528
|
+
case "INSUFFICIENT_G1_CREDITS_BALANCE": return "INSUFFICIENT_G1_CREDITS_BALANCE";
|
|
529
|
+
case "RATE_LIMIT_EXCEEDED": {
|
|
530
|
+
const retryDelayMs = extractRetryHint(void 0, errorMessage);
|
|
531
|
+
return retryDelayMs !== void 0 && retryDelayMs >= LONG_RATE_LIMIT_DELAY_MS ? "QUOTA_EXHAUSTED" : "RATE_LIMIT_EXCEEDED";
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
function isQuotaExhaustedReason(reason) {
|
|
537
|
+
return reason === "QUOTA_EXHAUSTED" || reason === "INSUFFICIENT_G1_CREDITS_BALANCE";
|
|
538
|
+
}
|
|
539
|
+
/**
|
|
540
|
+
* Classify a rate-limit error message into a reason category.
|
|
541
|
+
* Priority order: explicit details in a resource-exhausted error > QUOTA
|
|
542
|
+
* (Antigravity "quota will reset") > CN quota > DASHSCOPE_TOKEN_LIMIT (TPM/TPS
|
|
543
|
+
* throttle) > CONCURRENT_LIMIT > MODEL_CAPACITY > QUOTA (account) > RATE_LIMIT >
|
|
544
|
+
* QUOTA (generic) > SERVER_ERROR > bare resource-exhausted > UNKNOWN.
|
|
545
|
+
*
|
|
546
|
+
* Bare "resource exhausted" / "resource_exhausted" maps to MODEL_CAPACITY (transient, short wait).
|
|
547
|
+
* Explicit details such as "quota exceeded" retain their normal classification.
|
|
548
|
+
*/
|
|
549
|
+
function parseRateLimitReason(errorMessage) {
|
|
550
|
+
const structuredReason = parseGoogleRpcRateLimitReason(errorMessage);
|
|
551
|
+
if (structuredReason !== void 0) return structuredReason;
|
|
552
|
+
const lowerWithStatus = errorMessage.toLowerCase();
|
|
553
|
+
const lower = lowerWithStatus.replace(RESOURCE_EXHAUSTED_PATTERN, "");
|
|
554
|
+
const hasResourceExhaustedStatus = lower !== lowerWithStatus;
|
|
555
|
+
if (lower.includes("quota will reset") || lower.includes("exhausted your capacity")) return "QUOTA_EXHAUSTED";
|
|
556
|
+
if (CN_QUOTA_EXHAUSTED_PATTERN.test(errorMessage) && !CN_TRANSIENT_CAP_PATTERN.test(errorMessage)) return "QUOTA_EXHAUSTED";
|
|
557
|
+
if (isDashScopeTokenLimitText(errorMessage)) return "RATE_LIMIT_EXCEEDED";
|
|
558
|
+
if (CONCURRENT_LIMIT_PATTERN.test(errorMessage)) return "CONCURRENT_LIMIT";
|
|
559
|
+
if (lower.includes("capacity") || lower.includes("overloaded") || lower.includes("529") || lower.includes("503")) return "MODEL_CAPACITY_EXHAUSTED";
|
|
560
|
+
if (ACCOUNT_RATE_LIMIT_PATTERN.test(errorMessage)) return "QUOTA_EXHAUSTED";
|
|
561
|
+
if (SPEND_LIMIT_PATTERN.test(errorMessage)) return "QUOTA_EXHAUSTED";
|
|
562
|
+
if (matchesSubscriptionCapText(errorMessage)) return "QUOTA_EXHAUSTED";
|
|
563
|
+
if (OPENROUTER_DAILY_FREE_LIMIT_PATTERN.test(errorMessage)) return "QUOTA_EXHAUSTED";
|
|
564
|
+
if (lower.includes("per minute") || lower.includes("rate limit") || lower.includes("too many requests") || lower.includes("presque")) return "RATE_LIMIT_EXCEEDED";
|
|
565
|
+
if (lower.includes("exhausted") || lower.includes("quota") || lower.includes("usage limit") || lower.includes("run out of credits") || lower.includes("out of credits") || lower.includes("spending-limit") || lower.includes("spending limit") || INSUFFICIENT_BALANCE_PATTERN.test(errorMessage)) return "QUOTA_EXHAUSTED";
|
|
566
|
+
if (lower.includes("500") || lower.includes("internal error") || lower.includes("internal server error")) return "SERVER_ERROR";
|
|
567
|
+
if (hasResourceExhaustedStatus) return "MODEL_CAPACITY_EXHAUSTED";
|
|
568
|
+
return "UNKNOWN";
|
|
569
|
+
}
|
|
570
|
+
/** Detect usage/quota limit errors in error messages (persistent, requires credential switch). */
|
|
571
|
+
const USAGE_LIMIT_PATTERN = /usage.?limit|usage_limit_reached|usage_not_included|limit_reached|quota.?(?:exceeded|reached|insufficient)|额度不足|额度耗尽|resource.?exhausted|exhausted your capacity|quota will reset|insufficient.?(?:balance|quota)|balance.?exhausted|run out of credits|out of credits|spending[- _]?limit|personal-team-blocked/i;
|
|
572
|
+
/**
|
|
573
|
+
* HTTP status codes that, absent richer body classification, represent an
|
|
574
|
+
* account-local usage cap rather than a bad credential or a transient blip.
|
|
575
|
+
* HTTP 402 Payment Required is categorically an account-billing cap (xAI
|
|
576
|
+
* Grok Build "usage balance exhausted", DeepSeek "Insufficient Balance",
|
|
577
|
+
* OpenRouter credit exhaustion) — never a transient blip or bad credential.
|
|
578
|
+
* Always combine with {@link isUsageLimitOutcome} when a message is available
|
|
579
|
+
* — a 429 carrying transient rate-limit wording is NOT a usage cap.
|
|
580
|
+
*/
|
|
581
|
+
function isUsageLimitStatus(status) {
|
|
582
|
+
return status === 429 || status === 402;
|
|
583
|
+
}
|
|
584
|
+
/**
|
|
585
|
+
* A usage-limit status body is opaque when it carries no signal beyond the
|
|
586
|
+
* status itself — empty, whitespace-only, the status digits with HTTP/JSON
|
|
587
|
+
* framing, or generic punctuation. Anything else (retry hints, capacity
|
|
588
|
+
* wording, error descriptions) is informative enough to defer to the
|
|
589
|
+
* classifier.
|
|
590
|
+
*/
|
|
591
|
+
function isOpaqueStatusBody(message) {
|
|
592
|
+
const cleaned = message.replace(/\b(?:429|402)\b/g, "").replace(/\b(?:http|https|status|error|code|response|message)\b/gi, "");
|
|
593
|
+
return !/[a-z\d]{3,}/i.test(cleaned) && !CN_QUOTA_EXHAUSTED_PATTERN.test(cleaned) && !CN_TRANSIENT_CAP_PATTERN.test(cleaned) && !CN_THROTTLE_PATTERN.test(cleaned);
|
|
594
|
+
}
|
|
595
|
+
/**
|
|
596
|
+
* Internal text matcher for usage/quota-limit phrasing. NOT part of the public
|
|
597
|
+
* API — callers classify through {@link import("./flags").isUsageLimit} (the
|
|
598
|
+
* flag accessor). `flags.ts` consumes this to populate `Flag.UsageLimit`, and
|
|
599
|
+
* {@link isUsageLimitOutcome} uses it for the account-rotation decision.
|
|
600
|
+
*/
|
|
601
|
+
function matchesUsageLimitText(errorMessage) {
|
|
602
|
+
const structuredReason = parseGoogleRpcRateLimitReason(errorMessage);
|
|
603
|
+
if (structuredReason !== void 0) return isQuotaExhaustedReason(structuredReason);
|
|
604
|
+
if (isDashScopeTokenLimitText(errorMessage)) return false;
|
|
605
|
+
return USAGE_LIMIT_PATTERN.test(errorMessage) || CN_QUOTA_EXHAUSTED_PATTERN.test(errorMessage) && !CN_TRANSIENT_CAP_PATTERN.test(errorMessage) || SPEND_LIMIT_PATTERN.test(errorMessage) || ACCOUNT_RATE_LIMIT_PATTERN.test(errorMessage) || matchesSubscriptionCapText(errorMessage) || OPENROUTER_DAILY_FREE_LIMIT_PATTERN.test(errorMessage);
|
|
606
|
+
}
|
|
607
|
+
/**
|
|
608
|
+
* Account-scoped cap phrasing delivered on a 403 (or a statusless Connect
|
|
609
|
+
* trailer): "Reached overall message rate limit", "Your limit will reset in …".
|
|
610
|
+
* Kept separate from {@link matchesUsageLimitText} because the bare wording is
|
|
611
|
+
* ambiguous without the 403 / statusless-account context; consumed by both
|
|
612
|
+
* {@link isUsageLimitOutcome} (rotation decision) and `flags.ts` (Flag.UsageLimit).
|
|
613
|
+
*/
|
|
614
|
+
function isAccountScopedCapText(message) {
|
|
615
|
+
return ACCOUNT_SCOPED_403_PATTERN.test(message);
|
|
616
|
+
}
|
|
617
|
+
//#endregion
|
|
618
|
+
//#region node_modules/.pnpm/@oh-my-pi+pi-ai@17.3.5/node_modules/@oh-my-pi/pi-ai/src/error/flags.ts
|
|
619
|
+
const Flag = {
|
|
620
|
+
Class: 4096,
|
|
621
|
+
ThinkingLoop: 65536,
|
|
622
|
+
Transient: 131072,
|
|
623
|
+
Timeout: 262144,
|
|
624
|
+
UsageLimit: 524288,
|
|
625
|
+
StaleResponsesItem: 1048576,
|
|
626
|
+
MalformedFunctionCall: 2097152,
|
|
627
|
+
ProviderFinishError: 4194304,
|
|
628
|
+
EmptyResponse: 8192,
|
|
629
|
+
ContentBlocked: 32768,
|
|
630
|
+
/** Account-scoped provider policy denial that may succeed with another credential. */
|
|
631
|
+
AccountPolicy: 16384,
|
|
632
|
+
ContextOverflow: 8388608,
|
|
633
|
+
AuthFailed: 16777216,
|
|
634
|
+
SilentAbort: 33554432,
|
|
635
|
+
UserInterrupt: 67108864,
|
|
636
|
+
Abort: 134217728,
|
|
637
|
+
/** Strict-tool rejection (400): grammar too large, schema too complex, or structured outputs unsupported by the model/endpoint. */
|
|
638
|
+
Grammar: 268435456,
|
|
639
|
+
/** Anthropic model/account does not support fast mode / the `speed` parameter. */
|
|
640
|
+
FastModeUnsupported: 536870912,
|
|
641
|
+
/** OAuth refresh failed definitively — the stored grant is dead, re-login required. */
|
|
642
|
+
OAuthExpiry: 1073741824
|
|
643
|
+
};
|
|
644
|
+
const KIND_MASK = Flag.ThinkingLoop | Flag.Transient | Flag.Timeout | Flag.UsageLimit | Flag.StaleResponsesItem | Flag.MalformedFunctionCall | Flag.ProviderFinishError | Flag.EmptyResponse | Flag.ContentBlocked | Flag.AccountPolicy | Flag.ContextOverflow | Flag.AuthFailed | Flag.SilentAbort | Flag.UserInterrupt | Flag.Abort | Flag.Grammar | Flag.FastModeUnsupported | Flag.OAuthExpiry;
|
|
645
|
+
const RETRIABLE_KINDS = Flag.Transient | Flag.UsageLimit | Flag.ThinkingLoop | Flag.StaleResponsesItem | Flag.ProviderFinishError | Flag.EmptyResponse;
|
|
646
|
+
const OVERFLOW_PATTERNS = [
|
|
647
|
+
/prompt is too long/i,
|
|
648
|
+
/input is too long for requested model/i,
|
|
649
|
+
/exceeds the context window/i,
|
|
650
|
+
/input token count.*exceeds the maximum/i,
|
|
651
|
+
/maximum prompt length is \d+/i,
|
|
652
|
+
/reduce the length of the messages/i,
|
|
653
|
+
/maximum context length is \d+ tokens/i,
|
|
654
|
+
/exceeds the limit of \d+/i,
|
|
655
|
+
/exceeds the available context size/i,
|
|
656
|
+
/requested tokens?.*exceed.*context (window|length|size)/i,
|
|
657
|
+
/context (window|length|size).*(exceeded|overflow|too small)/i,
|
|
658
|
+
/(prompt|input).*(too long|too large).*(context|n_ctx)/i,
|
|
659
|
+
/requested tokens?.*(exceeds?|greater than).*(n_ctx|context)/i,
|
|
660
|
+
/greater than the context length/i,
|
|
661
|
+
/context window exceeds limit/i,
|
|
662
|
+
/exceeded model token limit/i,
|
|
663
|
+
/context[_ ]length[_ ]exceeded/i,
|
|
664
|
+
/too many tokens/i,
|
|
665
|
+
/token limit exceeded/i,
|
|
666
|
+
/request_too_large/i,
|
|
667
|
+
/request exceeds the maximum size/i,
|
|
668
|
+
/payload too large/i,
|
|
669
|
+
/entity too large/i,
|
|
670
|
+
/\b413\b.*\b(request|payload|entity)\b.*\btoo large\b/i,
|
|
671
|
+
/model_context_window_exceeded/i,
|
|
672
|
+
/prompt filled the context window/i
|
|
673
|
+
];
|
|
674
|
+
const OVERFLOW_NO_BODY_PATTERN = /\b4(00|13)\s*(status code)?\s*\(no body\)/i;
|
|
675
|
+
const TIMEOUT_PATTERN = /\b(?:operation\s+)?timed?\s*out\b|\btimeout\b|\bstream stall\b/i;
|
|
676
|
+
const TRANSIENT_ENVELOPE_PATTERN = /anthropic stream envelope error:/i;
|
|
677
|
+
const TRANSIENT_ENVELOPE_BEFORE_START_PATTERN = /before message_start/i;
|
|
678
|
+
const STREAM_READ_ERROR_PATTERN = /stream[_ -]?read[_ -]?error/i;
|
|
679
|
+
const TRANSIENT_TRANSPORT_PATTERN = /\b(?:no[_ -]?capacity|(?:high|peak)[ _-]?demand|(?:at|over|insufficient)[ _-]?capacity|capacity[ _-]?(?:exceeded|exhausted)|peak[ _-]?load)\b|overloaded|provider.?returned.?error|rate.?limit|too many requests|429|500|502|503|504|service.?unavailable|server.?error|internal.?error|retry your request|network.?error|connection.?error|connection.?refused|unable.?to.?connect\.\s*is the computer able to access the url\?|other side closed|fetch failed|upstream.?connect|upstream.?request.?failed|reset before headers|socket hang up|timed? out|timeout|terminated|retry delay|stream stall|no error details in response|HTTP2(?:StreamReset|RefusedStream|EnhanceYourCalm)|nghttp2_(?:internal_error|refused_stream)|stream closed with error code nghttp2_(?:internal_error|refused_stream)|malformed.?function.?call/i;
|
|
680
|
+
const AUTH_FAILURE_PATTERN = /\b(?:401|403|unauthorized|forbidden|authentication|auth[_ ]?unavailable|no auth available|(?:invalid|no)[_ ]?api[_ ]?key)\b/i;
|
|
681
|
+
const MALFORMED_FUNCTION_CALL_PATTERN = /\bmalformed.?function.?call\b/i;
|
|
682
|
+
const PROVIDER_FINISH_ERROR_PATTERN = /\bProvider (?:returned error finish_reason|finish_reason:\s*error)\b/i;
|
|
683
|
+
const EMPTY_RESPONSE_PATTERN = /\bthought-only response without final output\b/i;
|
|
684
|
+
const CONTENT_FILTER_PATTERN = /\b(?:incomplete:\s*)?content_filter\b/i;
|
|
685
|
+
const ACCOUNT_POLICY_PATTERN = /\bcyber_policy\b|trusted access for cyber/i;
|
|
686
|
+
const STALE_RESPONSE_ITEM_PATTERNS = [/\bItem with id ['"][^'"]+['"] not found\.?/i, /previous[ _]?response/i];
|
|
687
|
+
const STALE_RESPONSE_ITEM_DETAIL_PATTERN = /not[ _]?found|invalid|expired|stale|zero[ _-]?data[ _-]?retention/i;
|
|
688
|
+
/**
|
|
689
|
+
* Local llama.cpp / Ollama deterministic tool-call argument JSON parse failure.
|
|
690
|
+
* The model emitted invalid JSON in a tool call and the server returned HTTP 500
|
|
691
|
+
* with this exact text — replaying the same prompt yields the same malformed
|
|
692
|
+
* output, so callers strip {@link Flag.Transient} when this matches.
|
|
693
|
+
*/
|
|
694
|
+
const LLAMA_CPP_TOOL_CALL_PARSE_PATTERN = /failed to parse tool call arguments as json|\[json\.exception\.parse_error\.101\]/i;
|
|
695
|
+
const COPILOT_TRANSIENT_MODEL_CODES = { model_not_supported: true };
|
|
696
|
+
const COPILOT_TRANSIENT_MODEL_PATTERN = /model_not_supported/i;
|
|
697
|
+
const GRAMMAR_TOO_LARGE_PATTERN = /compiled grammar/i;
|
|
698
|
+
const GRAMMAR_TOO_LARGE_DETAIL_PATTERN = /too large/i;
|
|
699
|
+
const SCHEMA_TOO_COMPLEX_PATTERN = /schema/i;
|
|
700
|
+
const SCHEMA_TOO_COMPLEX_DETAIL_PATTERN = /too complex/i;
|
|
701
|
+
const SCHEMA_COMPILE_PATTERN = /compil/i;
|
|
702
|
+
const INVALID_REQUEST_PATTERN = /invalid_request_error/i;
|
|
703
|
+
const STRUCTURED_OUTPUTS_PATTERN = /structured[_ -]?outputs?/i;
|
|
704
|
+
const FEATURE_NOT_SUPPORTED_PATTERN = /not (?:supported|available|enabled)|unsupported|does(?: not|n'?t) support/i;
|
|
705
|
+
const ANTHROPIC_STRICT_FIELD_PATTERN = /\btools\.\d+\.custom\.strict\b/i;
|
|
706
|
+
const EXTRA_INPUTS_NOT_PERMITTED_PATTERN = /extra inputs? (?:are|is) not permitted/i;
|
|
707
|
+
const FAST_MODE_SPEED_PARAM_PATTERN = /\bspeed\b/i;
|
|
708
|
+
const FAST_MODE_NOT_SUPPORTED_PATTERN = /not support/i;
|
|
709
|
+
const FAST_MODE_RATE_LIMIT_PATTERN = /rate_limit_error/i;
|
|
710
|
+
const FAST_MODE_ENTITLEMENT_PATTERN = /fast mode/i;
|
|
711
|
+
function matchesStrictToolsRejection(message, errorStatus) {
|
|
712
|
+
if (errorStatus !== 400) return false;
|
|
713
|
+
if (ANTHROPIC_STRICT_FIELD_PATTERN.test(message) && EXTRA_INPUTS_NOT_PERMITTED_PATTERN.test(message)) return true;
|
|
714
|
+
if (STRUCTURED_OUTPUTS_PATTERN.test(message) && FEATURE_NOT_SUPPORTED_PATTERN.test(message)) return true;
|
|
715
|
+
if (!INVALID_REQUEST_PATTERN.test(message)) return false;
|
|
716
|
+
const grammarTooLarge = GRAMMAR_TOO_LARGE_PATTERN.test(message) && GRAMMAR_TOO_LARGE_DETAIL_PATTERN.test(message);
|
|
717
|
+
const schemaTooComplex = SCHEMA_TOO_COMPLEX_PATTERN.test(message) && SCHEMA_TOO_COMPLEX_DETAIL_PATTERN.test(message) && SCHEMA_COMPILE_PATTERN.test(message);
|
|
718
|
+
return grammarTooLarge || schemaTooComplex;
|
|
719
|
+
}
|
|
720
|
+
function matchesFastModeUnsupported(message, errorStatus) {
|
|
721
|
+
if (errorStatus !== 400 && errorStatus !== 429) return false;
|
|
722
|
+
if (errorStatus === 400 && INVALID_REQUEST_PATTERN.test(message) && FAST_MODE_SPEED_PARAM_PATTERN.test(message) && FAST_MODE_NOT_SUPPORTED_PATTERN.test(message)) return true;
|
|
723
|
+
return errorStatus === 429 && FAST_MODE_RATE_LIMIT_PATTERN.test(message) && FAST_MODE_ENTITLEMENT_PATTERN.test(message);
|
|
724
|
+
}
|
|
725
|
+
Flag.ThinkingLoop, Flag.Transient, Flag.Timeout, Flag.UsageLimit, Flag.StaleResponsesItem, Flag.MalformedFunctionCall, Flag.ProviderFinishError, Flag.EmptyResponse, Flag.ContentBlocked, Flag.AccountPolicy, Flag.ContextOverflow, Flag.AuthFailed, Flag.SilentAbort, Flag.UserInterrupt, Flag.Abort;
|
|
726
|
+
const STATUS_MESSAGE_PATTERNS = [
|
|
727
|
+
/\bstatus(?:_code)?[:=]\s*(\d{3})\b/i,
|
|
728
|
+
/\bstatus\s+(\d{3})\b/i,
|
|
729
|
+
/\bHTTP\s+(\d{3})\b/i,
|
|
730
|
+
/\b(?:error|failed)\s*[:=]?\s*(\d{3})\b/i,
|
|
731
|
+
/(?:^|\s)(\d{3})\s+(?:[A-Z][a-z]+(?:\s+[A-Z][a-z]+)*)/
|
|
732
|
+
];
|
|
733
|
+
function create(...flags) {
|
|
734
|
+
let bits = 0;
|
|
735
|
+
for (const f of flags) bits |= f;
|
|
736
|
+
return bits | Flag.Class;
|
|
737
|
+
}
|
|
738
|
+
function is(id, flag) {
|
|
739
|
+
return ((id ?? 0) & flag) !== 0;
|
|
740
|
+
}
|
|
741
|
+
function retriable(id, opts) {
|
|
742
|
+
if (is(id, Flag.ContentBlocked)) return false;
|
|
743
|
+
if (opts?.replayUnsafe) return false;
|
|
744
|
+
if (is(id, Flag.MalformedFunctionCall)) return true;
|
|
745
|
+
return ((id ?? 0) & RETRIABLE_KINDS) !== 0;
|
|
746
|
+
}
|
|
747
|
+
function isClassified(id) {
|
|
748
|
+
return ((id ?? 0) & Flag.Class) !== 0;
|
|
749
|
+
}
|
|
750
|
+
function statusFromId(id) {
|
|
751
|
+
return id && !isClassified(id) ? id : void 0;
|
|
752
|
+
}
|
|
753
|
+
function status(error) {
|
|
754
|
+
return statusInternal(error, 0);
|
|
755
|
+
}
|
|
756
|
+
function statusInternal(error, depth) {
|
|
757
|
+
if (depth > 2 || error === void 0 || error === null) return void 0;
|
|
758
|
+
if (typeof error === "object") {
|
|
759
|
+
const errObj = error;
|
|
760
|
+
if (typeof errObj.status === "number" && errObj.status >= 100 && errObj.status <= 599) return errObj.status;
|
|
761
|
+
if (typeof errObj.statusCode === "number" && errObj.statusCode >= 100 && errObj.statusCode <= 599) return errObj.statusCode;
|
|
762
|
+
if (typeof errObj.response === "object" && errObj.response !== null) {
|
|
763
|
+
const resp = errObj.response;
|
|
764
|
+
if (typeof resp.status === "number" && resp.status >= 100 && resp.status <= 599) return resp.status;
|
|
765
|
+
}
|
|
766
|
+
if ("cause" in errObj) {
|
|
767
|
+
const nested = statusInternal(errObj.cause, depth + 1);
|
|
768
|
+
if (nested !== void 0) return nested;
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
if (error instanceof Error || typeof error === "object" && error !== null && "message" in error) {
|
|
772
|
+
const message = error.message;
|
|
773
|
+
if (typeof message === "string") for (const pattern of STATUS_MESSAGE_PATTERNS) {
|
|
774
|
+
const match = pattern.exec(message);
|
|
775
|
+
if (match) {
|
|
776
|
+
const code = parseInt(match[1], 10);
|
|
777
|
+
if (code >= 100 && code <= 599) return code;
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
function isStreamReadErrorText(text) {
|
|
783
|
+
return STREAM_READ_ERROR_PATTERN.test(text);
|
|
784
|
+
}
|
|
785
|
+
function isTransientErrorText(text) {
|
|
786
|
+
return isUnexpectedSocketCloseMessage(text) || isStreamReadErrorText(text) || TRANSIENT_ENVELOPE_PATTERN.test(text) && TRANSIENT_ENVELOPE_BEFORE_START_PATTERN.test(text) || TRANSIENT_TRANSPORT_PATTERN.test(text);
|
|
787
|
+
}
|
|
788
|
+
function isTimeoutText(text) {
|
|
789
|
+
return TIMEOUT_PATTERN.test(text);
|
|
790
|
+
}
|
|
791
|
+
function isAuthFailureText(text) {
|
|
792
|
+
return AUTH_FAILURE_PATTERN.test(text);
|
|
793
|
+
}
|
|
794
|
+
function isStaleResponsesText(text) {
|
|
795
|
+
return STALE_RESPONSE_ITEM_PATTERNS[0].test(text) || STALE_RESPONSE_ITEM_PATTERNS[1].test(text) && STALE_RESPONSE_ITEM_DETAIL_PATTERN.test(text);
|
|
796
|
+
}
|
|
797
|
+
function isMalformedFunctionCallText(text) {
|
|
798
|
+
return MALFORMED_FUNCTION_CALL_PATTERN.test(text);
|
|
799
|
+
}
|
|
800
|
+
function isProviderFinishErrorText(text) {
|
|
801
|
+
return PROVIDER_FINISH_ERROR_PATTERN.test(text);
|
|
802
|
+
}
|
|
803
|
+
function isContentBlockedText(text) {
|
|
804
|
+
return CONTENT_FILTER_PATTERN.test(text);
|
|
805
|
+
}
|
|
806
|
+
function matchesOverflowText(text) {
|
|
807
|
+
return OVERFLOW_PATTERNS.some((p) => p.test(text)) || OVERFLOW_NO_BODY_PATTERN.test(text);
|
|
808
|
+
}
|
|
809
|
+
function classifyText(errorMessage, errorStatus, api) {
|
|
810
|
+
let kinds = 0;
|
|
811
|
+
if (errorMessage) {
|
|
812
|
+
if (matchesOverflowText(errorMessage)) kinds |= Flag.ContextOverflow;
|
|
813
|
+
if (isMalformedFunctionCallText(errorMessage)) kinds |= Flag.MalformedFunctionCall;
|
|
814
|
+
if (isProviderFinishErrorText(errorMessage)) kinds |= Flag.ProviderFinishError;
|
|
815
|
+
if (EMPTY_RESPONSE_PATTERN.test(errorMessage)) kinds |= Flag.EmptyResponse | Flag.Transient;
|
|
816
|
+
if (isContentBlockedText(errorMessage)) kinds |= Flag.ContentBlocked;
|
|
817
|
+
if (ACCOUNT_POLICY_PATTERN.test(errorMessage)) kinds |= Flag.AccountPolicy | Flag.ContentBlocked;
|
|
818
|
+
if (isAuthFailureText(errorMessage)) kinds |= Flag.AuthFailed;
|
|
819
|
+
const statusClean = errorStatus ? errorStatus : status({ message: errorMessage }) ?? void 0;
|
|
820
|
+
const cleanMessage = errorMessage;
|
|
821
|
+
const isOpaque = isOpaqueStatusBody(cleanMessage);
|
|
822
|
+
const isLimitStatus = isUsageLimitStatus(statusClean);
|
|
823
|
+
const reason = parseRateLimitReason(cleanMessage);
|
|
824
|
+
const isBillingCapStatus = statusClean === 402;
|
|
825
|
+
if (!(reason === "CONCURRENT_LIMIT" && !isBillingCapStatus) && (matchesUsageLimitText(cleanMessage) || (statusClean === 403 || statusClean === void 0) && isAccountScopedCapText(cleanMessage) || isLimitStatus && (isOpaque || reason === "QUOTA_EXHAUSTED" || isBillingCapStatus && reason === "CONCURRENT_LIMIT"))) kinds |= Flag.UsageLimit;
|
|
826
|
+
if (isTimeoutText(errorMessage)) kinds |= Flag.Transient | Flag.Timeout;
|
|
827
|
+
else if (isTransientErrorText(errorMessage)) kinds |= Flag.Transient;
|
|
828
|
+
if (reason === "CONCURRENT_LIMIT") kinds |= Flag.Transient;
|
|
829
|
+
if ((api === "openai-responses" || api === "openai-codex-responses") && isStaleResponsesText(errorMessage)) kinds |= Flag.StaleResponsesItem;
|
|
830
|
+
if (statusClean === 400 && COPILOT_TRANSIENT_MODEL_PATTERN.test(cleanMessage)) kinds |= Flag.Transient;
|
|
831
|
+
if (matchesStrictToolsRejection(cleanMessage, statusClean)) kinds |= Flag.Grammar;
|
|
832
|
+
if (matchesFastModeUnsupported(cleanMessage, statusClean)) kinds |= Flag.FastModeUnsupported;
|
|
833
|
+
}
|
|
834
|
+
if (kinds !== 0) return create(kinds);
|
|
835
|
+
const fallbackStatus = errorStatus ?? (errorMessage ? status({ message: errorMessage }) : void 0);
|
|
836
|
+
if (fallbackStatus === 401 || fallbackStatus === 403) return create(Flag.AuthFailed);
|
|
837
|
+
return fallbackStatus ?? 0;
|
|
838
|
+
}
|
|
839
|
+
function classify(error, api) {
|
|
840
|
+
let kinds = 0;
|
|
841
|
+
const seen = /* @__PURE__ */ new Set();
|
|
842
|
+
let link = error;
|
|
843
|
+
while (link !== void 0 && link !== null) {
|
|
844
|
+
if (typeof link === "object") {
|
|
845
|
+
if (seen.has(link)) break;
|
|
846
|
+
seen.add(link);
|
|
847
|
+
if ("errorId" in link && typeof link.errorId === "number") kinds |= link.errorId & KIND_MASK;
|
|
848
|
+
if ("code" in link && typeof link.code === "string" && ACCOUNT_POLICY_PATTERN.test(link.code)) kinds |= Flag.AccountPolicy | Flag.ContentBlocked;
|
|
849
|
+
}
|
|
850
|
+
if (link instanceof AwsCredentialsError) kinds |= Flag.AuthFailed;
|
|
851
|
+
else if (link instanceof AnthropicConnectionTimeoutError) kinds |= Flag.Timeout | Flag.Transient;
|
|
852
|
+
else if (link instanceof AnthropicConnectionError) kinds |= Flag.Transient;
|
|
853
|
+
else if (typeof link === "object" && "name" in link && link.name === "CodexWebSocketTransportError") kinds |= Flag.Transient;
|
|
854
|
+
else if (link instanceof Error && link.name === "CodexProviderStreamError" && "retryable" in link && link.retryable === true) kinds |= Flag.Transient;
|
|
855
|
+
else if (link instanceof ProviderHttpError) {
|
|
856
|
+
let linkKinds = 0;
|
|
857
|
+
const { status: codeStatus, code } = link;
|
|
858
|
+
if (code === "usage_limit_reached" || code === "insufficient_quota" && !isDashScopeTokenLimitText(link.message)) linkKinds |= Flag.UsageLimit;
|
|
859
|
+
if (code === "overloaded_error" || code === "rate_limit_error") linkKinds |= Flag.Transient;
|
|
860
|
+
if ((codeStatus === 401 || codeStatus === 403) && !(codeStatus === 403 && parseRateLimitReason(link.message) === "CONCURRENT_LIMIT")) linkKinds |= Flag.AuthFailed;
|
|
861
|
+
else if (codeStatus === 429) {
|
|
862
|
+
if ((linkKinds & Flag.UsageLimit) === 0) linkKinds |= Flag.Transient;
|
|
863
|
+
} else if (codeStatus >= 500) linkKinds |= Flag.Transient;
|
|
864
|
+
kinds |= linkKinds;
|
|
865
|
+
}
|
|
866
|
+
let linkMessage;
|
|
867
|
+
if (link instanceof Error) linkMessage = link.message;
|
|
868
|
+
else if (typeof link === "string") linkMessage = link;
|
|
869
|
+
else if (typeof link === "object" && "message" in link && typeof link.message === "string") linkMessage = link.message;
|
|
870
|
+
const textId = classifyText(linkMessage, status(link), api);
|
|
871
|
+
kinds |= textId & KIND_MASK;
|
|
872
|
+
link = typeof link === "object" && "cause" in link ? link.cause : void 0;
|
|
873
|
+
}
|
|
874
|
+
return kinds !== 0 ? create(kinds) : status(error) ?? 0;
|
|
875
|
+
}
|
|
876
|
+
/**
|
|
877
|
+
* Whether an error (or message string) classifies as an account usage/quota
|
|
878
|
+
* limit — the persistent, credential-rotation-worthy kind. This is the public
|
|
879
|
+
* accessor for {@link Flag.UsageLimit}; prefer it over re-running message
|
|
880
|
+
* regexes at call sites.
|
|
881
|
+
*/
|
|
882
|
+
function isUsageLimit(error, api) {
|
|
883
|
+
return is(classify(error, api), Flag.UsageLimit);
|
|
884
|
+
}
|
|
885
|
+
/**
|
|
886
|
+
* Strict-tool rejection: grammar too large, schema too complex, or structured
|
|
887
|
+
* outputs unsupported by the model/endpoint.
|
|
888
|
+
* Accessor for {@link Flag.Grammar}.
|
|
889
|
+
*/
|
|
890
|
+
function isGrammarError(error) {
|
|
891
|
+
return is(classify(error), Flag.Grammar);
|
|
892
|
+
}
|
|
893
|
+
/**
|
|
894
|
+
* Anthropic model/account does not support fast mode / the `speed` parameter.
|
|
895
|
+
* Accessor for {@link Flag.FastModeUnsupported}.
|
|
896
|
+
*/
|
|
897
|
+
function isFastModeUnsupported(error) {
|
|
898
|
+
return is(classify(error), Flag.FastModeUnsupported);
|
|
899
|
+
}
|
|
900
|
+
/**
|
|
901
|
+
* Depth-bounded search for a provider error `code`. SDK error objects keep the
|
|
902
|
+
* parsed response body on `.error`, and Copilot's body is itself
|
|
903
|
+
* `{ error: { code } }`, so the code sits up to two envelopes below the thrown
|
|
904
|
+
* error depending on which SDK produced it.
|
|
905
|
+
*/
|
|
906
|
+
function providerErrorCode(error) {
|
|
907
|
+
let node = error;
|
|
908
|
+
for (let depth = 0; depth < 3; depth++) {
|
|
909
|
+
if ("code" in node && typeof node.code === "string") return node.code;
|
|
910
|
+
if (!("error" in node)) return void 0;
|
|
911
|
+
const nested = node.error;
|
|
912
|
+
if (!nested || typeof nested !== "object") return void 0;
|
|
913
|
+
node = nested;
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
/**
|
|
917
|
+
* GitHub Copilot 400 `model_not_supported` response for a model advertised by
|
|
918
|
+
* `/models` — transient fleet skew, not a malformed request. Reads the
|
|
919
|
+
* structural `code` through the SDK/body envelopes, then falls back to the
|
|
920
|
+
* stringified body both SDK families put in `message`.
|
|
921
|
+
*/
|
|
922
|
+
function isCopilotTransientModelError(error) {
|
|
923
|
+
if (!error || typeof error !== "object" || status(error) !== 400) return false;
|
|
924
|
+
const code = providerErrorCode(error);
|
|
925
|
+
if (code !== void 0 && Object.hasOwn(COPILOT_TRANSIENT_MODEL_CODES, code)) return true;
|
|
926
|
+
const message = "message" in error ? error.message : void 0;
|
|
927
|
+
return typeof message === "string" && COPILOT_TRANSIENT_MODEL_PATTERN.test(message);
|
|
928
|
+
}
|
|
929
|
+
function classifyMessage(message) {
|
|
930
|
+
const existingId = message.errorId;
|
|
931
|
+
const currentStatus = message.errorStatus ?? statusFromId(existingId);
|
|
932
|
+
const textId = classifyText(message.errorMessage, currentStatus, message.api);
|
|
933
|
+
let kinds = ((existingId ?? 0) | textId) & KIND_MASK;
|
|
934
|
+
if (message.errorMessage && LLAMA_CPP_TOOL_CALL_PARSE_PATTERN.test(message.errorMessage)) kinds &= ~Flag.Transient;
|
|
935
|
+
const id = kinds !== 0 ? create(kinds) : statusFromId(textId) ?? statusFromId(existingId) ?? currentStatus ?? 0;
|
|
936
|
+
message.errorId = id;
|
|
937
|
+
return id;
|
|
938
|
+
}
|
|
939
|
+
function attach(error, id) {
|
|
940
|
+
Object.defineProperty(error, "errorId", {
|
|
941
|
+
value: id,
|
|
942
|
+
enumerable: false,
|
|
943
|
+
configurable: true
|
|
944
|
+
});
|
|
945
|
+
return error;
|
|
946
|
+
}
|
|
947
|
+
const STREAM_PARSE_TRUNCATION_PATTERN = /unterminated string|unexpected end of json input|unexpected end of data|unexpected eof|end of file|eof while parsing|truncated/i;
|
|
948
|
+
const STREAM_PARSE_DIAGNOSTIC_PATTERN = /(?:json parse error:\s*(?:unterminated string|unexpected end of json input|unexpected end of data|unexpected eof|end of file|eof while parsing|truncated)|json\.parse:\s*(?:unterminated string|unexpected end of data)|unexpected end of json input|unexpected eof|eof while parsing)/i;
|
|
949
|
+
const STREAM_EVENT_ORDER_PATTERN = /stream event order|before message_start/i;
|
|
950
|
+
/**
|
|
951
|
+
* Transient stream corruption where the response was truncated mid-JSON.
|
|
952
|
+
*
|
|
953
|
+
* Strings (persisted `stopDetails.explanation`/`errorMessage` diagnostics) are matched with the
|
|
954
|
+
* stricter {@link STREAM_PARSE_DIAGNOSTIC_PATTERN} — bare "truncated"/"end of file" text is too
|
|
955
|
+
* low-signal to trust once detached from a live transport `Error`, which keeps the broad pattern.
|
|
956
|
+
*/
|
|
957
|
+
function isTransientStreamParseError(error) {
|
|
958
|
+
if (typeof error === "string") return STREAM_PARSE_DIAGNOSTIC_PATTERN.test(error);
|
|
959
|
+
return error instanceof Error && STREAM_PARSE_TRUNCATION_PATTERN.test(error.message);
|
|
960
|
+
}
|
|
961
|
+
/** Any malformed stream-envelope error (prefix-tagged or out-of-order events). */
|
|
962
|
+
function isStreamEnvelopeError(error) {
|
|
963
|
+
return error instanceof Error && (error.message.includes("Anthropic stream envelope error:") || STREAM_EVENT_ORDER_PATTERN.test(error.message));
|
|
964
|
+
}
|
|
965
|
+
/** Stream-envelope errors safe to retry against the provider (event ordering only). */
|
|
966
|
+
function isRetryableStreamEnvelopeError(error) {
|
|
967
|
+
return error instanceof Error && STREAM_EVENT_ORDER_PATTERN.test(error.message);
|
|
968
|
+
}
|
|
969
|
+
//#endregion
|
|
970
|
+
//#region node_modules/.pnpm/@oh-my-pi+pi-ai@17.3.5/node_modules/@oh-my-pi/pi-ai/src/error/abort.ts
|
|
971
|
+
/**
|
|
972
|
+
* A request was cancelled — by the caller's `AbortSignal` or a provider-local
|
|
973
|
+
* watchdog. Carries the {@link Flag.Abort} classification structurally so retry
|
|
974
|
+
* logic does not have to regex the message text.
|
|
975
|
+
*
|
|
976
|
+
* The default message is kept byte-identical to the historical
|
|
977
|
+
* `"Request was aborted"` string so any remaining text-based matchers keep
|
|
978
|
+
* working through the migration.
|
|
979
|
+
*/
|
|
980
|
+
var AbortError = class extends Error {
|
|
981
|
+
constructor(message = "Request was aborted", options) {
|
|
982
|
+
super(message, options?.cause === void 0 ? void 0 : { cause: options.cause });
|
|
983
|
+
this.name = "AbortError";
|
|
984
|
+
attach(this, create(Flag.Abort));
|
|
985
|
+
}
|
|
986
|
+
};
|
|
987
|
+
//#endregion
|
|
988
|
+
//#region node_modules/.pnpm/@oh-my-pi+pi-ai@17.3.5/node_modules/@oh-my-pi/pi-ai/src/error/auth.ts
|
|
989
|
+
/**
|
|
990
|
+
* No API key / credential was available to dispatch a request.
|
|
991
|
+
*
|
|
992
|
+
* The default message preserves the historical `"No API key for provider: X"`
|
|
993
|
+
* wording, which {@link Flag.AuthFailed}'s regex (`no api key`) keys off — but
|
|
994
|
+
* the flag is also attached structurally so classification never depends on the
|
|
995
|
+
* exact phrasing.
|
|
996
|
+
*/
|
|
997
|
+
var MissingApiKeyError = class extends Error {
|
|
998
|
+
provider;
|
|
999
|
+
constructor(provider, message) {
|
|
1000
|
+
super(message ?? (provider ? `No API key for provider: ${provider}` : "No API key available"));
|
|
1001
|
+
this.name = "MissingApiKeyError";
|
|
1002
|
+
this.provider = provider;
|
|
1003
|
+
attach(this, create(Flag.AuthFailed));
|
|
1004
|
+
}
|
|
1005
|
+
};
|
|
1006
|
+
/** A user-facing login flow required an `onPrompt` callback that was not supplied. */
|
|
1007
|
+
var OnPromptRequiredError = class extends Error {
|
|
1008
|
+
constructor(providerLabel) {
|
|
1009
|
+
super(`${providerLabel} login requires onPrompt callback`);
|
|
1010
|
+
this.name = "OnPromptRequiredError";
|
|
1011
|
+
}
|
|
1012
|
+
};
|
|
1013
|
+
/** An interactive login asked for an API key but the user supplied an empty value. */
|
|
1014
|
+
var ApiKeyRequiredError = class extends Error {
|
|
1015
|
+
constructor(message = "API key is required") {
|
|
1016
|
+
super(message);
|
|
1017
|
+
this.name = "ApiKeyRequiredError";
|
|
1018
|
+
}
|
|
1019
|
+
};
|
|
1020
|
+
/**
|
|
1021
|
+
* A user cancelled an interactive login / device flow. Classified as an abort
|
|
1022
|
+
* so it is never surfaced as a retryable transient failure.
|
|
1023
|
+
*/
|
|
1024
|
+
var LoginCancelledError = class extends Error {
|
|
1025
|
+
constructor(message = "Login cancelled") {
|
|
1026
|
+
super(message);
|
|
1027
|
+
this.name = "LoginCancelledError";
|
|
1028
|
+
attach(this, create(Flag.Abort));
|
|
1029
|
+
}
|
|
1030
|
+
};
|
|
1031
|
+
//#endregion
|
|
1032
|
+
//#region node_modules/.pnpm/@oh-my-pi+pi-ai@17.3.5/node_modules/@oh-my-pi/pi-ai/src/utils/retry-after.ts
|
|
1033
|
+
const RETRY_AFTER_HINT = "retry-after-ms=";
|
|
1034
|
+
function formatErrorMessageWithRetryAfter(error, headers) {
|
|
1035
|
+
const message = error instanceof Error ? error.message : JSON.stringify(error);
|
|
1036
|
+
if (message.includes(RETRY_AFTER_HINT)) return message;
|
|
1037
|
+
const retryAfterMs = getRetryAfterMsFromHeaders(headers ?? getHeadersFromError(error));
|
|
1038
|
+
if (retryAfterMs === void 0) return message;
|
|
1039
|
+
return `${message} ${RETRY_AFTER_HINT}${retryAfterMs}`;
|
|
1040
|
+
}
|
|
1041
|
+
function getRetryAfterMsFromHeaders(headers) {
|
|
1042
|
+
if (!headers) return void 0;
|
|
1043
|
+
const candidates = [
|
|
1044
|
+
parseRetryAfterMsHeader(getHeaderValue(headers, "retry-after-ms")),
|
|
1045
|
+
parseRetryAfterHeader(getHeaderValue(headers, "retry-after")),
|
|
1046
|
+
parseResetHeader(getHeaderValue(headers, "x-ratelimit-reset-ms"), "ms"),
|
|
1047
|
+
parseResetHeader(getHeaderValue(headers, "x-ratelimit-reset"), "s")
|
|
1048
|
+
].filter((value) => value !== void 0);
|
|
1049
|
+
if (candidates.length === 0) return void 0;
|
|
1050
|
+
return Math.max(...candidates);
|
|
1051
|
+
}
|
|
1052
|
+
function getHeadersFromError(error) {
|
|
1053
|
+
if (!error || typeof error !== "object") return void 0;
|
|
1054
|
+
const record = error;
|
|
1055
|
+
const direct = extractHeaders(record.headers) ?? extractHeaders(record.response?.headers);
|
|
1056
|
+
if (direct) return direct;
|
|
1057
|
+
if (record.cause) return getHeadersFromError(record.cause);
|
|
1058
|
+
}
|
|
1059
|
+
function extractHeaders(value) {
|
|
1060
|
+
if (!value) return void 0;
|
|
1061
|
+
if (value instanceof Headers) return value;
|
|
1062
|
+
if (typeof value === "object") return value;
|
|
1063
|
+
}
|
|
1064
|
+
function getHeaderValue(headers, name) {
|
|
1065
|
+
if (headers instanceof Headers) return headers.get(name) ?? void 0;
|
|
1066
|
+
const target = name.toLowerCase();
|
|
1067
|
+
for (const [key, value] of Object.entries(headers)) if (key.toLowerCase() === target && typeof value === "string") return value;
|
|
1068
|
+
}
|
|
1069
|
+
/** `retry-after-ms` (Anthropic-style): a plain millisecond delta. */
|
|
1070
|
+
function parseRetryAfterMsHeader(value) {
|
|
1071
|
+
if (!value) return void 0;
|
|
1072
|
+
const ms = Number(value.trim());
|
|
1073
|
+
if (!Number.isFinite(ms) || ms <= 0) return void 0;
|
|
1074
|
+
return Math.ceil(ms);
|
|
1075
|
+
}
|
|
1076
|
+
function parseRetryAfterHeader(value) {
|
|
1077
|
+
if (!value) return void 0;
|
|
1078
|
+
const trimmed = value.trim();
|
|
1079
|
+
if (!trimmed) return void 0;
|
|
1080
|
+
const numeric = Number(trimmed);
|
|
1081
|
+
if (Number.isFinite(numeric)) {
|
|
1082
|
+
if (numeric <= 0) return void 0;
|
|
1083
|
+
return Math.ceil(numeric * 1e3);
|
|
1084
|
+
}
|
|
1085
|
+
const dateMs = Date.parse(trimmed);
|
|
1086
|
+
if (!Number.isNaN(dateMs)) {
|
|
1087
|
+
const delay = dateMs - Date.now();
|
|
1088
|
+
return delay > 0 ? Math.ceil(delay) : void 0;
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
function parseResetHeader(value, unit) {
|
|
1092
|
+
if (!value) return void 0;
|
|
1093
|
+
const numeric = Number(value);
|
|
1094
|
+
if (!Number.isFinite(numeric) || numeric <= 0) return void 0;
|
|
1095
|
+
const nowMs = Date.now();
|
|
1096
|
+
let targetMs;
|
|
1097
|
+
if (unit === "ms") {
|
|
1098
|
+
if (numeric > 0xe8d4a51000) targetMs = numeric;
|
|
1099
|
+
else if (numeric > 1e9) targetMs = numeric * 1e3;
|
|
1100
|
+
else return Math.ceil(numeric);
|
|
1101
|
+
} else if (numeric > 0xe8d4a51000) targetMs = numeric;
|
|
1102
|
+
else if (numeric > 1e9) targetMs = numeric * 1e3;
|
|
1103
|
+
else return Math.ceil(numeric * 1e3);
|
|
1104
|
+
if (targetMs <= nowMs) return void 0;
|
|
1105
|
+
return Math.ceil(targetMs - nowMs);
|
|
1106
|
+
}
|
|
1107
|
+
//#endregion
|
|
1108
|
+
//#region node_modules/.pnpm/@oh-my-pi+pi-ai@17.3.5/node_modules/@oh-my-pi/pi-ai/src/utils/retry.ts
|
|
1109
|
+
const COPILOT_MODEL_RETRY_MAX_ATTEMPTS = 8;
|
|
1110
|
+
const COPILOT_GENERIC_RETRY_MAX_ATTEMPTS = 3;
|
|
1111
|
+
const COPILOT_MODEL_RETRY_BASE_DELAY_MS = 400;
|
|
1112
|
+
/** Longest server-requested backoff we are willing to sit out before giving up. */
|
|
1113
|
+
const COPILOT_RETRY_AFTER_MAX_WAIT_MS = 3e4;
|
|
1114
|
+
/**
|
|
1115
|
+
* Wrap an initial Copilot request so transient `model_not_supported` 400s are
|
|
1116
|
+
* retried a small number of times. No-op for non-Copilot providers.
|
|
1117
|
+
*
|
|
1118
|
+
* The callback **MUST** create a fresh in-flight request each invocation — a
|
|
1119
|
+
* once-consumed AsyncIterable cannot be re-iterated.
|
|
1120
|
+
*/
|
|
1121
|
+
async function callWithCopilotModelRetry(fn, options) {
|
|
1122
|
+
if (options.provider !== "github-copilot") return fn();
|
|
1123
|
+
let lastError;
|
|
1124
|
+
const retryBaseDelayMs = options.retryBaseDelayMs ?? COPILOT_MODEL_RETRY_BASE_DELAY_MS;
|
|
1125
|
+
for (let attempt = 0; attempt < COPILOT_MODEL_RETRY_MAX_ATTEMPTS; attempt++) try {
|
|
1126
|
+
return await fn();
|
|
1127
|
+
} catch (error) {
|
|
1128
|
+
lastError = error;
|
|
1129
|
+
if (options.signal?.aborted) throw error;
|
|
1130
|
+
const transientModelError = isCopilotTransientModelError(error);
|
|
1131
|
+
if (!transientModelError && !isRetryableError(error)) throw error;
|
|
1132
|
+
if (attempt >= (transientModelError ? COPILOT_MODEL_RETRY_MAX_ATTEMPTS : COPILOT_GENERIC_RETRY_MAX_ATTEMPTS) - 1) break;
|
|
1133
|
+
let delayMs = transientModelError ? retryBaseDelayMs : retryBaseDelayMs * (attempt + 1);
|
|
1134
|
+
if (!transientModelError) {
|
|
1135
|
+
if (status(error) !== void 0) {
|
|
1136
|
+
const retryAfterMs = getRetryAfterMsFromHeaders(getHeadersFromError(error));
|
|
1137
|
+
if (retryAfterMs === void 0 || retryAfterMs > COPILOT_RETRY_AFTER_MAX_WAIT_MS) throw error;
|
|
1138
|
+
delayMs = Math.max(delayMs, retryAfterMs);
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
await scheduler.wait(delayMs, { signal: options.signal });
|
|
1142
|
+
}
|
|
1143
|
+
throw lastError;
|
|
1144
|
+
}
|
|
1145
|
+
//#endregion
|
|
1146
|
+
//#region node_modules/.pnpm/@oh-my-pi+pi-ai@17.3.5/node_modules/@oh-my-pi/pi-ai/src/utils/http-inspector.ts
|
|
1147
|
+
const SENSITIVE_HEADERS = [
|
|
1148
|
+
"authorization",
|
|
1149
|
+
"x-api-key",
|
|
1150
|
+
"api-key",
|
|
1151
|
+
"cookie",
|
|
1152
|
+
"set-cookie",
|
|
1153
|
+
"proxy-authorization"
|
|
1154
|
+
];
|
|
1155
|
+
/**
|
|
1156
|
+
* Build the JSON persisted for a rejected request. Request fields stay at the
|
|
1157
|
+
* top level (so existing dump parsers still read `body`); the provider's error
|
|
1158
|
+
* is added under `errorResponse` so a failed request is diagnosable from the
|
|
1159
|
+
* dump file rather than the request alone.
|
|
1160
|
+
*/
|
|
1161
|
+
function buildHttp400DumpPayload(dump, error, message) {
|
|
1162
|
+
return {
|
|
1163
|
+
...sanitizeDump(dump),
|
|
1164
|
+
errorResponse: {
|
|
1165
|
+
status: status(error),
|
|
1166
|
+
message
|
|
1167
|
+
}
|
|
1168
|
+
};
|
|
1169
|
+
}
|
|
1170
|
+
/** HTTP statuses whose rejected request we persist for post-hoc diagnosis: the
|
|
1171
|
+
* request-content rejections that wedge a session. 400 (bad request) and 413
|
|
1172
|
+
* (payload too large — an oversized image / snapcompact frame payload that 413s
|
|
1173
|
+
* and empties the turn). Auth (401/403), not-found (404), rate limits and 5xx
|
|
1174
|
+
* are excluded: 429/5xx are retried, so persisting them here would write one
|
|
1175
|
+
* dump per attempt. */
|
|
1176
|
+
function shouldDumpRejectedRequest(error) {
|
|
1177
|
+
const status$2 = status(error);
|
|
1178
|
+
return status$2 === 400 || status$2 === 413;
|
|
1179
|
+
}
|
|
1180
|
+
async function appendRawHttpRequestDumpFor400(message, error, dump) {
|
|
1181
|
+
if (!dump || isBunTestRuntime() || !shouldDumpRejectedRequest(error)) return message;
|
|
1182
|
+
const payload = buildHttp400DumpPayload(dump, error, message);
|
|
1183
|
+
const fileName = `${Date.now()}-${Bun.hash(JSON.stringify(payload)).toString(36)}.json`;
|
|
1184
|
+
const filePath = path.join(getLogsDir(), "http-400-requests", fileName);
|
|
1185
|
+
try {
|
|
1186
|
+
await Bun.write(filePath, `${JSON.stringify(payload, null, 2)}\n`);
|
|
1187
|
+
return `${message}\nraw-http-request=${filePath}`;
|
|
1188
|
+
} catch (writeError) {
|
|
1189
|
+
return `${message}\nraw-http-request-save-failed=${writeError instanceof Error ? writeError.message : String(writeError)}`;
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
async function finalizeErrorMessage(error, rawRequestDump, capturedErrorResponse) {
|
|
1193
|
+
let message = formatErrorMessageWithRetryAfter(error, capturedErrorResponse?.headers);
|
|
1194
|
+
const capturedMessage = formatCapturedHttpError(capturedErrorResponse);
|
|
1195
|
+
if (capturedMessage) {
|
|
1196
|
+
if (/\bstatus code\s*\(no body\)/i.test(message)) message = `${capturedErrorResponse?.status ?? "HTTP"} status code: ${capturedMessage}`;
|
|
1197
|
+
else if (!message.includes(capturedMessage)) message = `${message}\n${capturedMessage}`;
|
|
1198
|
+
}
|
|
1199
|
+
return appendRawHttpRequestDumpFor400(message, error, rawRequestDump);
|
|
1200
|
+
}
|
|
1201
|
+
/**
|
|
1202
|
+
* Rewrite error message for GitHub Copilot request failures.
|
|
1203
|
+
* Must run AFTER finalizeErrorMessage since it replaces the message entirely.
|
|
1204
|
+
*
|
|
1205
|
+
* 400 `model_not_supported` = Copilot fleet skew. A model that `/models`
|
|
1206
|
+
* advertises can flap between 200 and 400 because only part of
|
|
1207
|
+
* Copilot's fleet has it in the integrator allowlist. After the
|
|
1208
|
+
* in-request retry exhausts, surface guidance rather than the raw error.
|
|
1209
|
+
* 401 = token invalid/expired → credential removal is safe, prompt re-login.
|
|
1210
|
+
* 403 = token valid but access denied (plan, model policy, org restriction) →
|
|
1211
|
+
* do NOT reuse the auth-failed string (which triggers credential removal).
|
|
1212
|
+
*/
|
|
1213
|
+
function rewriteCopilotError(errorMessage, error, provider) {
|
|
1214
|
+
if (provider !== "github-copilot") return errorMessage;
|
|
1215
|
+
const status$1 = status(error);
|
|
1216
|
+
if (status$1 === 401) return `GitHub Copilot authentication failed (HTTP 401). Your token may have been revoked. Please re-login with /login github-copilot`;
|
|
1217
|
+
if (status$1 === 403) return `GitHub Copilot access denied (HTTP 403). Your account may not have access to this model or feature. Check your Copilot plan or model policy settings.`;
|
|
1218
|
+
if (isCopilotTransientModelError(error)) return `GitHub Copilot rejected this model (HTTP 400) after retries: only part of its fleet currently serves this model id, even though /models advertises it. Try again in a few seconds or switch to a model Copilot serves fleet-wide (claude-opus-4.7, claude-sonnet-4.5, gpt-4.1).`;
|
|
1219
|
+
return errorMessage;
|
|
1220
|
+
}
|
|
1221
|
+
function sanitizeDump(dump) {
|
|
1222
|
+
return {
|
|
1223
|
+
...dump,
|
|
1224
|
+
headers: redactHeaders(dump.headers)
|
|
1225
|
+
};
|
|
1226
|
+
}
|
|
1227
|
+
function redactHeaders(headers) {
|
|
1228
|
+
if (!headers) return;
|
|
1229
|
+
const redacted = {};
|
|
1230
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
1231
|
+
if (SENSITIVE_HEADERS.includes(key.toLowerCase())) {
|
|
1232
|
+
redacted[key] = "[redacted]";
|
|
1233
|
+
continue;
|
|
1234
|
+
}
|
|
1235
|
+
redacted[key] = value;
|
|
1236
|
+
}
|
|
1237
|
+
return redacted;
|
|
1238
|
+
}
|
|
1239
|
+
function formatCapturedHttpError(captured) {
|
|
1240
|
+
if (!captured) return void 0;
|
|
1241
|
+
const bodyText = captured.bodyText?.trim();
|
|
1242
|
+
if (!bodyText) return void 0;
|
|
1243
|
+
const payload = parseCapturedErrorPayload(captured);
|
|
1244
|
+
if (!payload) return bodyText;
|
|
1245
|
+
const errorPayload = getObjectProperty(payload, "error") ?? payload;
|
|
1246
|
+
const stringError = errorPayload === payload ? getStringProperty(payload, "error") : void 0;
|
|
1247
|
+
const message = getStringProperty(errorPayload, "message") ?? getStringProperty(payload, "message") ?? stringError ?? bodyText;
|
|
1248
|
+
const extras = [
|
|
1249
|
+
getStringProperty(errorPayload, "type") ?? getStringProperty(payload, "type"),
|
|
1250
|
+
getStringProperty(errorPayload, "param") ?? getStringProperty(payload, "param"),
|
|
1251
|
+
getStringProperty(errorPayload, "code") ?? getStringProperty(payload, "code")
|
|
1252
|
+
].filter(Boolean).map((value, index) => {
|
|
1253
|
+
if (index === 0) return `type=${value}`;
|
|
1254
|
+
if (index === 1) return `param=${value}`;
|
|
1255
|
+
return `code=${value}`;
|
|
1256
|
+
});
|
|
1257
|
+
return extras.length > 0 ? `${message} (${extras.join(" ")})` : message;
|
|
1258
|
+
}
|
|
1259
|
+
function parseCapturedErrorPayload(captured) {
|
|
1260
|
+
if (isObject(captured.bodyJson)) return captured.bodyJson;
|
|
1261
|
+
if (!captured.bodyText) return void 0;
|
|
1262
|
+
try {
|
|
1263
|
+
const parsed = JSON.parse(captured.bodyText);
|
|
1264
|
+
return isObject(parsed) ? parsed : void 0;
|
|
1265
|
+
} catch {
|
|
1266
|
+
return;
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
function getObjectProperty(value, key) {
|
|
1270
|
+
const property = value[key];
|
|
1271
|
+
return isObject(property) ? property : void 0;
|
|
1272
|
+
}
|
|
1273
|
+
function getStringProperty(value, key) {
|
|
1274
|
+
const property = value[key];
|
|
1275
|
+
return typeof property === "string" && property.trim().length > 0 ? property : void 0;
|
|
1276
|
+
}
|
|
1277
|
+
function isObject(value) {
|
|
1278
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1279
|
+
}
|
|
1280
|
+
//#endregion
|
|
1281
|
+
//#region node_modules/.pnpm/@oh-my-pi+pi-ai@17.3.5/node_modules/@oh-my-pi/pi-ai/src/error/format.ts
|
|
1282
|
+
function rewriteOllamaToolCallJsonError(message) {
|
|
1283
|
+
if (!LLAMA_CPP_TOOL_CALL_PARSE_PATTERN.test(message)) return message;
|
|
1284
|
+
return `Local Ollama model emitted malformed tool-call JSON and llama.cpp rejected it (HTTP 500). This is usually a deterministic model-output failure after context degradation, not a transient server outage; reload the model or reduce context, then retry.\n${message}`;
|
|
1285
|
+
}
|
|
1286
|
+
/**
|
|
1287
|
+
* Format a provider error into a user-facing message, unifying the three
|
|
1288
|
+
* formatters: lightweight retry-after extraction, the raw-dump finalizer, and
|
|
1289
|
+
* the copilot rewrite.
|
|
1290
|
+
*
|
|
1291
|
+
* Selection is driven by inputs, not a mode flag: a `rawRequestDump` routes
|
|
1292
|
+
* through {@link finalizeErrorMessage} (retry-after + raw dump + captured body),
|
|
1293
|
+
* otherwise the lightweight {@link formatErrorMessageWithRetryAfter} is used.
|
|
1294
|
+
*/
|
|
1295
|
+
async function formatMessage(error, opts = {}) {
|
|
1296
|
+
let message = opts.rawRequestDump ? await finalizeErrorMessage(error, opts.rawRequestDump, opts.capturedErrorResponse) : formatErrorMessageWithRetryAfter(error);
|
|
1297
|
+
if (opts.provider === "github-copilot") message = rewriteCopilotError(message, error, opts.provider);
|
|
1298
|
+
if (opts.provider === "ollama") message = rewriteOllamaToolCallJsonError(message);
|
|
1299
|
+
return message;
|
|
1300
|
+
}
|
|
1301
|
+
//#endregion
|
|
1302
|
+
//#region node_modules/.pnpm/@oh-my-pi+pi-ai@17.3.5/node_modules/@oh-my-pi/pi-ai/src/error/finalize.ts
|
|
1303
|
+
/**
|
|
1304
|
+
* Build the complete error bundle for a provider catch block, replacing the
|
|
1305
|
+
* `stopReason` / `errorStatus` / `errorId` / `errorMessage` boilerplate.
|
|
1306
|
+
*
|
|
1307
|
+
* `stopReason` comes from the abort tracker (caller intent dominates) or, when
|
|
1308
|
+
* no tracker is supplied, the raw `signal.aborted`. A local abort reason (e.g. a
|
|
1309
|
+
* first-event timeout) supersedes the formatted message. Message formatting is
|
|
1310
|
+
* wrapped so a formatter throw can never skip the caller's `stream.end()`.
|
|
1311
|
+
*/
|
|
1312
|
+
async function finalize(error, opts = {}) {
|
|
1313
|
+
const aborted = opts.abortTracker ? opts.abortTracker.wasCallerAbort() : opts.signal?.aborted === true;
|
|
1314
|
+
const currentStatus = status(error) ?? opts.capturedErrorResponse?.status;
|
|
1315
|
+
let message;
|
|
1316
|
+
try {
|
|
1317
|
+
message = (opts.abortTracker?.getLocalAbortReason())?.message ?? await formatMessage(error, opts);
|
|
1318
|
+
} catch {
|
|
1319
|
+
message = error instanceof Error ? error.message : String(error);
|
|
1320
|
+
}
|
|
1321
|
+
return {
|
|
1322
|
+
id: classifyMessage({
|
|
1323
|
+
api: opts.api,
|
|
1324
|
+
errorId: classify(error, opts.api),
|
|
1325
|
+
errorMessage: message,
|
|
1326
|
+
errorStatus: currentStatus
|
|
1327
|
+
}),
|
|
1328
|
+
status: currentStatus,
|
|
1329
|
+
stopReason: aborted ? "aborted" : "error",
|
|
1330
|
+
message
|
|
1331
|
+
};
|
|
1332
|
+
}
|
|
1333
|
+
//#endregion
|
|
1334
|
+
//#region node_modules/.pnpm/@oh-my-pi+pi-ai@17.3.5/node_modules/@oh-my-pi/pi-ai/src/error/oauth.ts
|
|
1335
|
+
/**
|
|
1336
|
+
* A failure inside an interactive OAuth / device-code login flow. The `kind`
|
|
1337
|
+
* pinpoints the stage. Timeout/polling are classified transient; everything
|
|
1338
|
+
* else is a hard auth failure so the credential layer does not silently retry.
|
|
1339
|
+
*/
|
|
1340
|
+
var OAuthError = class extends Error {
|
|
1341
|
+
kind;
|
|
1342
|
+
provider;
|
|
1343
|
+
status;
|
|
1344
|
+
constructor(message, options = {}) {
|
|
1345
|
+
super(message, options.cause === void 0 ? void 0 : { cause: options.cause });
|
|
1346
|
+
this.name = "OAuthError";
|
|
1347
|
+
this.kind = options.kind ?? "http";
|
|
1348
|
+
this.provider = options.provider;
|
|
1349
|
+
this.status = options.status;
|
|
1350
|
+
attach(this, this.kind === "timeout" || this.kind === "polling" ? create(Flag.Transient) : create(Flag.AuthFailed));
|
|
1351
|
+
}
|
|
1352
|
+
};
|
|
1353
|
+
//#endregion
|
|
1354
|
+
//#region node_modules/.pnpm/@oh-my-pi+pi-ai@17.3.5/node_modules/@oh-my-pi/pi-ai/src/error/provider.ts
|
|
1355
|
+
/**
|
|
1356
|
+
* A non-HTTP provider failure: a truncated stream, an error stop reason, an
|
|
1357
|
+
* empty body, a malformed envelope, or a runtime fault. For non-2xx HTTP
|
|
1358
|
+
* responses use {@link ProviderHttpError} (or a provider subclass) instead.
|
|
1359
|
+
*/
|
|
1360
|
+
var ProviderResponseError = class extends Error {
|
|
1361
|
+
provider;
|
|
1362
|
+
kind;
|
|
1363
|
+
constructor(message, options = {}) {
|
|
1364
|
+
super(message, options.cause === void 0 ? void 0 : { cause: options.cause });
|
|
1365
|
+
this.name = "ProviderResponseError";
|
|
1366
|
+
this.provider = options.provider;
|
|
1367
|
+
this.kind = options.kind ?? "output";
|
|
1368
|
+
if (this.kind === "content-blocked") attach(this, create(Flag.ContentBlocked));
|
|
1369
|
+
else if (this.kind === "empty-output") attach(this, create(Flag.Transient, Flag.EmptyResponse));
|
|
1370
|
+
else if (this.kind === "incomplete-stream" || this.kind === "empty-body") attach(this, create(Flag.Transient));
|
|
1371
|
+
}
|
|
1372
|
+
};
|
|
1373
|
+
/** Non-2xx response from the Devin API. */
|
|
1374
|
+
var DevinApiError = class extends ProviderHttpError {
|
|
1375
|
+
name = "DevinApiError";
|
|
1376
|
+
};
|
|
1377
|
+
//#endregion
|
|
1378
|
+
//#region node_modules/.pnpm/@oh-my-pi+pi-ai@17.3.5/node_modules/@oh-my-pi/pi-ai/src/error/retryable.ts
|
|
1379
|
+
/**
|
|
1380
|
+
* Whether a numeric HTTP status is in the canonical transient/retryable set:
|
|
1381
|
+
* 408 (Request Timeout), 429 (Too Many Requests), and any 5xx.
|
|
1382
|
+
*
|
|
1383
|
+
* This is a pure predicate over a status code already in hand — distinct from
|
|
1384
|
+
* {@link classify}, which inspects a whole error (including message text) and
|
|
1385
|
+
* may match more. Use this when you only have a `status: number`.
|
|
1386
|
+
*/
|
|
1387
|
+
function isTransientStatus(status) {
|
|
1388
|
+
return status !== void 0 && (status === 408 || status === 429 || status >= 500);
|
|
1389
|
+
}
|
|
1390
|
+
const PROVIDER_TRANSIENT_EXTRA_PATTERN = /bad record mac|stream error.*received from peer|1302/i;
|
|
1391
|
+
function isTransientTransportMessage(message) {
|
|
1392
|
+
return message.includes("tls: bad record mac") || message.includes("type=server_error");
|
|
1393
|
+
}
|
|
1394
|
+
/**
|
|
1395
|
+
* Whether a provider stream error should be retried against the same credential.
|
|
1396
|
+
*
|
|
1397
|
+
* Account-level usage/quota limits are deliberately treated as **non**-retryable
|
|
1398
|
+
* here — they are owned by the credential-rotation layer (auth-gateway /
|
|
1399
|
+
* `streamSimple` a/b/c policy), not this seconds-scale provider backoff.
|
|
1400
|
+
*
|
|
1401
|
+
* Provider-specific transient cases are injected via {@link ProviderRetryableHooks}
|
|
1402
|
+
* so this stays free of provider imports.
|
|
1403
|
+
*/
|
|
1404
|
+
function isProviderRetryableError(error, hooks = {}) {
|
|
1405
|
+
if (!(error instanceof Error)) return false;
|
|
1406
|
+
if (hooks.isProviderTransient?.(error)) return true;
|
|
1407
|
+
if (isUsageLimit(error)) return false;
|
|
1408
|
+
const httpStatus = status(error);
|
|
1409
|
+
if (httpStatus !== void 0 && httpStatus >= 400 && httpStatus < 500 && httpStatus !== 408 && httpStatus !== 429) return false;
|
|
1410
|
+
const msg = error.message.toLowerCase();
|
|
1411
|
+
if (isUnexpectedSocketCloseMessage(msg) || isTransientTransportMessage(msg) || TRANSIENT_TRANSPORT_PATTERN.test(msg) || PROVIDER_TRANSIENT_EXTRA_PATTERN.test(msg) || isTransientStreamParseError(error) || isRetryableStreamEnvelopeError(error)) return true;
|
|
1412
|
+
return isRetryableError(error);
|
|
1413
|
+
}
|
|
1414
|
+
//#endregion
|
|
1415
|
+
//#region node_modules/.pnpm/@oh-my-pi+pi-ai@17.3.5/node_modules/@oh-my-pi/pi-ai/src/error/validation.ts
|
|
1416
|
+
/**
|
|
1417
|
+
* Caller-supplied input failed validation before/while building a provider
|
|
1418
|
+
* request: bad request body, malformed tool arguments, unsupported content
|
|
1419
|
+
* type, a schema that cannot be normalized, an unknown tool, etc.
|
|
1420
|
+
*
|
|
1421
|
+
* This is a programmer/config/contract error, not a transient provider fault —
|
|
1422
|
+
* it is never retried.
|
|
1423
|
+
*/
|
|
1424
|
+
var ValidationError = class extends Error {
|
|
1425
|
+
constructor(message, options) {
|
|
1426
|
+
super(message, options?.cause === void 0 ? void 0 : { cause: options.cause });
|
|
1427
|
+
this.name = "ValidationError";
|
|
1428
|
+
}
|
|
1429
|
+
};
|
|
1430
|
+
/**
|
|
1431
|
+
* Provider/auth configuration was missing or malformed (env var pointing at a
|
|
1432
|
+
* missing file, missing projectId, bad bind string, mTLS half-configured, …).
|
|
1433
|
+
*/
|
|
1434
|
+
var ConfigurationError = class extends Error {
|
|
1435
|
+
constructor(message, options) {
|
|
1436
|
+
super(message, options?.cause === void 0 ? void 0 : { cause: options.cause });
|
|
1437
|
+
this.name = "ConfigurationError";
|
|
1438
|
+
}
|
|
1439
|
+
};
|
|
1440
|
+
/** A request was abandoned because it exceeded a stream/idle/first-event deadline. */
|
|
1441
|
+
var StreamTimeoutError = class extends Error {
|
|
1442
|
+
constructor(message = "Request timed out.", options) {
|
|
1443
|
+
super(message, options?.cause === void 0 ? void 0 : { cause: options.cause });
|
|
1444
|
+
this.name = "StreamTimeoutError";
|
|
1445
|
+
attach(this, create(Flag.Transient, Flag.Timeout));
|
|
1446
|
+
}
|
|
1447
|
+
};
|
|
1448
|
+
//#endregion
|
|
1449
|
+
export { isTransientStreamParseError as A, OpenAIHttpError as B, classifyMessage as C, isFastModeUnsupported as D, isCopilotTransientModelError as E, AnthropicStreamEnvelopeError as F, fetchWithRetry as G, AwsCredentialsError as H, BedrockApiError as I, GeminiCliApiError as L, AnthropicApiError as M, AnthropicConnectionError as N, isGrammarError as O, AnthropicConnectionTimeoutError as P, GoogleApiError as R, classify as S, is as T, EventStreamFrameError as U, ProviderHttpError as V, extractHttpStatusFromError as W, OnPromptRequiredError as _, isTransientStatus as a, LLAMA_CPP_TOOL_CALL_PARSE_PATTERN as b, OAuthError as c, callWithCopilotModelRetry as d, getHeadersFromError as f, MissingApiKeyError as g, LoginCancelledError as h, isProviderRetryableError as i, retriable as j, isStreamEnvelopeError as k, finalize as l, ApiKeyRequiredError as m, StreamTimeoutError as n, DevinApiError as o, getRetryAfterMsFromHeaders as p, ValidationError as r, ProviderResponseError as s, ConfigurationError as t, finalizeErrorMessage as u, AbortError as v, create as w, attach as x, Flag as y, OllamaApiError as z };
|