@xiaoso/dsh-tool-plus 0.1.16 → 0.1.17-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/lib/{abort-DZ54CaKU.mjs → abort-BTblp0Rt.mjs} +2 -2
  2. package/lib/{alibaba-coding-plan-DODpkcqi.mjs → alibaba-coding-plan-t3R4YKCB.mjs} +3 -3
  3. package/lib/{alibaba-token-plan-C6lpKCda.mjs → alibaba-token-plan-CO4djG1y.mjs} +4 -4
  4. package/lib/{alibaba-token-plan-CHGyWOfX.mjs → alibaba-token-plan-CUjYJX0-.mjs} +1 -1
  5. package/lib/{anthropic-Dv_GGX6g.mjs → anthropic-nf7w6so1.mjs} +48 -21
  6. package/lib/{api-key-validation-MnMf_ihX.mjs → api-key-validation-C_tZ7Y9a.mjs} +2 -2
  7. package/lib/behavior-BwvTUtag.mjs +111 -0
  8. package/lib/{browser-render-BZXt548F.mjs → browser-render-BQaEVK_n.mjs} +4 -4
  9. package/lib/{reference-CtvM4zLU.mjs → bundled-references-CrQ4dQcT.mjs} +57 -4
  10. package/lib/cascade-BWjRTD1q.mjs +30265 -0
  11. package/lib/{cline-pass-BT9CBEOj.mjs → cline-pass-Bjm-O8GG.mjs} +2 -2
  12. package/lib/cloudflare-CKXRJ0l9.mjs +37 -0
  13. package/lib/{cloudflare-ai-gateway-DReN6ohJ.mjs → cloudflare-ai-gateway-BpnBSjwV.mjs} +8 -4
  14. package/lib/cloudflare-workers-ai-odj9XswL.mjs +32 -0
  15. package/lib/collapse-_nkb2dn7.mjs +1945 -0
  16. package/lib/{coreweave-C82HrLpb.mjs → coreweave-CGDZOD8-.mjs} +1 -1
  17. package/lib/{coreweave-C68UgRQO.mjs → coreweave-DwffcgW5.mjs} +4 -4
  18. package/lib/cursor-BgOuV7Qf.mjs +794 -0
  19. package/lib/{cursor-_gHHq-Dq.mjs → cursor-CF1iHHbv.mjs} +68 -19
  20. package/lib/{cursor-proto-Cr_WEMuv.mjs → cursor-proto-BuNTjX3L.mjs} +823 -23
  21. package/lib/{devin-QpfTSl9H.mjs → devin-7OEeEb80.mjs} +112 -31
  22. package/lib/{devin-proto-DYhp4Ty6.mjs → devin-proto-C-QreeAv.mjs} +71 -5
  23. package/lib/{dom-DG3sPESc.mjs → dom-BmbZv4v9.mjs} +598 -182
  24. package/lib/{error-O63pC8zg.mjs → error-8LVo7tug.mjs} +725 -104
  25. package/lib/factory-droid-ChhmsGWb.mjs +71 -0
  26. package/lib/{fs-promises-shim-B2YFWkcd.mjs → fs-promises-shim-CANAxFWh.mjs} +10 -1
  27. package/lib/{github-copilot-BMEcw8N1.mjs → github-copilot-QwevdT30.mjs} +4 -4
  28. package/lib/{github-copilot-headers-DnjGEbko.mjs → github-copilot-headers-CAUZ0rUD.mjs} +138 -26
  29. package/lib/{gitlab-duo-DnHqJjFY.mjs → gitlab-duo-DigV7k_o.mjs} +2 -2
  30. package/lib/{google-antigravity-B1mSckrK.mjs → google-antigravity-D-oe0r8A.mjs} +6 -7
  31. package/lib/{google-gemini-cli-DJwThCCn.mjs → google-gemini-cli-CQBHgGJT.mjs} +5 -6
  32. package/lib/{google-oauth-shared-BctLpVUN.mjs → google-oauth-shared-CvgVIUwJ.mjs} +2 -2
  33. package/lib/google-validation-CdvRj4ec.mjs +126 -0
  34. package/lib/id-EJwVLDHE.mjs +2548 -0
  35. package/lib/index.mjs +248 -258
  36. package/lib/{kilo-BEgEwMJQ.mjs → kilo-5r6OffCa.mjs} +2 -2
  37. package/lib/{kimi-T9NldEIJ.mjs → kimi-DOOtbXmm.mjs} +9 -10
  38. package/lib/{launch-BIWNyrBB.mjs → launch-DkAjib7h.mjs} +1 -1
  39. package/lib/{markit-Dvp2JPdT.mjs → markit-CCcyeAds.mjs} +359 -140
  40. package/lib/{models-DnXMYGcJ.mjs → models-DCpDT_im.mjs} +199203 -112275
  41. package/lib/{muse-code-CNvqqAp6.mjs → muse-code-CYf8zj_B.mjs} +5 -5
  42. package/lib/{openai-codex-D5-CyTxa.mjs → openai-codex-Cj-TvSFh.mjs} +13 -10
  43. package/lib/{perplexity-A2PEoGaC.mjs → perplexity-Dh_Qgsmx.mjs} +68 -25
  44. package/lib/{pkce-BYjmugmw.mjs → pkce-DO-jBJLV.mjs} +1 -1
  45. package/lib/pricing-DusBEhwm.mjs +593 -0
  46. package/lib/{readability-uGhqb4GC.mjs → readability-XHK0o3bv.mjs} +287 -27
  47. package/lib/{scrapers-C1ujjV8k.mjs → scrapers-B6PADOVm.mjs} +13 -13
  48. package/lib/slugify-ynYWBIA5.mjs +11 -0
  49. package/lib/snowflake-B_jap6lz.mjs +666 -0
  50. package/lib/snowflake-Cu1ZyOp6.mjs +151 -0
  51. package/lib/{src-AELUUb5U.mjs → src-BhinCSpi.mjs} +1172 -458
  52. package/lib/{stream-AVOoe5Hj.mjs → stream-D2ivg3GH.mjs} +3740 -5311
  53. package/lib/{turndown-D6fof1OX.mjs → turndown-Vya8SavL.mjs} +82 -30
  54. package/lib/types/tools/hashline/native/index.d.ts +2 -2
  55. package/lib/types/tools/hashline/native/policy.d.ts +19 -21
  56. package/lib/types/tools/omp/session/agent-storage.d.ts +3 -3
  57. package/lib/types/tools/shared/bun-named-shim.d.ts +5 -0
  58. package/lib/{types-BLrLohwK.mjs → types-tH1v9uRm.mjs} +2 -2
  59. package/lib/{utils-cZX3f_Ls.mjs → utils-C_Ncya1r.mjs} +1737 -509
  60. package/lib/utils-D2tK38xq.mjs +34006 -0
  61. package/lib/{utils-I4I8azHN.mjs → utils-DrDwmNtu.mjs} +8 -3
  62. package/lib/{xai-oauth-CbeldUSV.mjs → xai-oauth-DLLzX6my.mjs} +4 -4
  63. package/lib/{xiaomi-Dl3VSs04.mjs → xiaomi-CqK-KivK.mjs} +2 -2
  64. package/lib/{zai-FyjGSypr.mjs → zai-DoJ2xPIH.mjs} +2 -2
  65. package/lib/{zip-DRrycScC.mjs → zip-DG1mUV6X.mjs} +1 -1
  66. package/package.json +8 -8
  67. package/lib/amazon-bedrock-Dx0rr9dY.mjs +0 -996
  68. package/lib/anthropic-ClCw0PV_.mjs +0 -3624
  69. package/lib/anthropic-wire-lbGAFzYb.mjs +0 -927
  70. package/lib/azure-openai-responses-WE_bgFWp.mjs +0 -285
  71. package/lib/bundled-references-DY3EJxZV.mjs +0 -56
  72. package/lib/cloudflare-ai-gateway-CrMa97Ap.mjs +0 -44
  73. package/lib/collapse-CkMNRNr7.mjs +0 -2304
  74. package/lib/cursor-C6jaIJDg.mjs +0 -283
  75. package/lib/cursor-D-XZYDf_.mjs +0 -4544
  76. package/lib/deterministic-id-CUEIMSqR.mjs +0 -17
  77. package/lib/devin-ExGIghmx.mjs +0 -69
  78. package/lib/devin-u0ljw5P6.mjs +0 -628
  79. package/lib/effort-BdtQMN-_.mjs +0 -23
  80. package/lib/event-stream-BKEtb45Y.mjs +0 -167
  81. package/lib/gemini-headers-Co9cVvex.mjs +0 -54
  82. package/lib/google-auth-qAN-6DMW.mjs +0 -232
  83. package/lib/google-gemini-cli-B2Aw1S5w.mjs +0 -844
  84. package/lib/google-lTxuq28n.mjs +0 -38
  85. package/lib/google-shared-Y01j9xOr.mjs +0 -856
  86. package/lib/google-validation-BEkNs-Bx.mjs +0 -18
  87. package/lib/google-vertex-DcM-FRC5.mjs +0 -99
  88. package/lib/hosts-00rjRzUD.mjs +0 -203
  89. package/lib/id-AL5obQzR.mjs +0 -71
  90. package/lib/idle-iterator-D0ZXEcUq.mjs +0 -366
  91. package/lib/ollama-6R0oXZks.mjs +0 -574
  92. package/lib/openai-codex-responses-C8bnBWOe.mjs +0 -3232
  93. package/lib/openai-completions-DNr3mUsn.mjs +0 -1430
  94. package/lib/openai-configuration-update-xMYUjaK_.mjs +0 -150
  95. package/lib/openai-reasoning-fallback-Ju-5pSR8.mjs +0 -380
  96. package/lib/openai-responses-CjpafAEr.mjs +0 -861
  97. package/lib/protobuf-BMgDYu_U.mjs +0 -717
  98. package/lib/provider-response-SqKSkkUc.mjs +0 -22
  99. package/lib/proxy-DxvCQOCA.mjs +0 -230
  100. package/lib/request-debug-BwPOqN_j.mjs +0 -183
  101. package/lib/sanitize-text-BCf5KmCf.mjs +0 -33
  102. package/lib/sse-debug-B-tkLCPW.mjs +0 -11
  103. package/lib/stream-markup-healing-CN3RsqG9.mjs +0 -267
  104. package/lib/strict-tool-validation-CBTz1dAA.mjs +0 -105
  105. package/lib/taxonomy-DSIM99rS.mjs +0 -15392
  106. package/lib/thinking-ngMwf_1Y.mjs +0 -336
  107. package/lib/type-guards-CttAANEx.mjs +0 -11
  108. package/lib/utils-CE3DSebh.mjs +0 -11763
  109. package/lib/vision-guard-BCpXWUj3.mjs +0 -77
@@ -1,20 +1,22 @@
1
1
  import { installBunShim as __installBunShim } from "./bun-shim.mjs";
2
2
  __installBunShim();
3
- import { J as getLogsDir, L as isBunTestRuntime } from "./src-AELUUb5U.mjs";
3
+ import { n as fs_promises_shim_exports } from "./fs-promises-shim-CANAxFWh.mjs";
4
+ import { at as getLogsDir, dt as isEnoent, k as warn, lt as isBunTestRuntime } from "./src-BhinCSpi.mjs";
5
+ import { h as isRecord } from "./cascade-BWjRTD1q.mjs";
6
+ import "./behavior-BwvTUtag.mjs";
4
7
  import * as path$1 from "node:path";
5
8
  import { scheduler } from "node:timers/promises";
6
- //#region node_modules/.pnpm/@oh-my-pi+pi-utils@18.1.17/node_modules/@oh-my-pi/pi-utils/src/fetch-retry.ts
9
+ //#region node_modules/.pnpm/@oh-my-pi+pi-utils@18.12.6/node_modules/@oh-my-pi/pi-utils/src/fetch-retry.ts
7
10
  const QUOTA_RESET_PATTERN = /reset after (?:(\d+)h)?(?:(\d+)m)?(\d+(?:\.\d+)?)s/i;
8
11
  const PLEASE_RETRY_PATTERN = /Please retry in ([0-9.]+)(ms|s)/i;
9
12
  const RETRY_DELAY_FIELD_PATTERN = /"retryDelay":\s*"([0-9.]+)(ms|s)"/i;
10
13
  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
- const WILL_RESET_AT_PATTERN = /(?:will\s+)?reset at\s+([0-9]{4}-[0-9]{2}-[0-9]{2}[ T][0-9]{2}:[0-9]{2}:[0-9]{2}(?:\.[0-9]+)?(?:Z|[+-][0-9]{2}:?[0-9]{2})?)/i;
13
- const CN_RESET_AT_PATTERN = /将在\s*([0-9]{4}-[0-9]{2}-[0-9]{2}\s+[0-9]{2}:[0-9]{2}:[0-9]{2})\s*重置/;
14
+ const WILL_RESET_IN_PATTERN = /(?:will\s+)?resets?\s+in\s+~?\s*([0-9.]+)\s*(ms|sec|s|minutes?|mins?|m|hours?|hrs?|h|days?|d)\b/i;
15
+ const RESET_IN_HR_MIN_PATTERN = /resets?\s+in\s+~?\s*(\d+(?:\.\d+)?)\s*hr\s*(\d+(?:\.\d+)?)\s*min\b/i;
16
+ const RESET_AT_PATTERNS = [/(?:will\s+)?reset at\s+([0-9]{4}-[0-9]{2}-[0-9]{2}[ T][0-9]{2}:[0-9]{2}:[0-9]{2}(?:\.[0-9]+)?(?:Z|[+-][0-9]{2}:?[0-9]{2})?)/i, /将在\s*([0-9]{4}-[0-9]{2}-[0-9]{2}\s+[0-9]{2}:[0-9]{2}:[0-9]{2})\s*重置/];
14
17
  const RETRY_AFTER_MS_BODY_PATTERN = /\bretry-after-ms\s*[:=]\s*([0-9]+)\b/i;
15
18
  /**
16
19
  * Server-suggested retry delay extraction. Merges the patterns historically used
17
- * by the OpenAI Codex and Google Gemini retry helpers.
18
20
  *
19
21
  * Header sources (checked in order):
20
22
  * - `retry-after-ms` (milliseconds)
@@ -30,12 +32,14 @@ const RETRY_AFTER_MS_BODY_PATTERN = /\bretry-after-ms\s*[:=]\s*([0-9]+)\b/i;
30
32
  * - `try again in 250ms` / `try again in 12s` / `try again in 5 min` / `try again in ~158 min`
31
33
  * - `retry-after-ms=98497000` / `retry-after-ms: 7200000` / `retry-after-ms = 7200000`
32
34
  * - `Your limit will reset at 2026-09-01 09:44:51` / `将在 2026-09-01 09:44:51 重置`
35
+ * (a provider offset makes a naive wall clock authoritative; otherwise it
36
+ * resolves only when no relative signal is present)
33
37
  *
34
38
  * Returns `undefined` if no signal is found, or `0` when the provider
35
39
  * explicitly asks for an immediate retry (`retry-after…=0`, or an absolute
36
40
  * reset timestamp that has already elapsed).
37
41
  */
38
- function extractRetryHint(source, body) {
42
+ function extractRetryHint(source, body, options) {
39
43
  const headers = source instanceof Headers ? source : source?.headers ?? void 0;
40
44
  if (headers) {
41
45
  const retryAfterMs = headers.get("retry-after-ms");
@@ -77,6 +81,10 @@ function extractRetryHint(source, body) {
77
81
  if (!body) return void 0;
78
82
  let longestMs;
79
83
  let retryNow = false;
84
+ let longestNaiveMs;
85
+ const considerNaive = (ms) => {
86
+ if (ms !== void 0 && ms > 0 && (longestNaiveMs === void 0 || ms > longestNaiveMs)) longestNaiveMs = ms;
87
+ };
80
88
  const consider = (ms) => {
81
89
  if (ms !== void 0 && ms > 0 && (longestMs === void 0 || ms > longestMs)) longestMs = ms;
82
90
  };
@@ -95,15 +103,24 @@ function extractRetryHint(source, body) {
95
103
  consider(totalMs > 0 ? totalMs : void 0);
96
104
  }
97
105
  }
98
- for (const pattern of [WILL_RESET_AT_PATTERN, CN_RESET_AT_PATTERN]) {
106
+ for (const pattern of RESET_AT_PATTERNS) {
99
107
  const match = pattern.exec(body);
100
- if (match?.[1]) {
101
- const normalized = match[1].replace(" ", "T");
102
- const hasOffset = /(?:Z|[+-][0-9]{2}:?[0-9]{2})$/i.test(normalized);
103
- const parsed = Date.parse(hasOffset ? normalized : `${normalized}Z`);
104
- if (!Number.isNaN(parsed) && parsed > Date.now()) consider(parsed - Date.now());
108
+ if (!match?.[1]) continue;
109
+ const normalized = match[1].replace(" ", "T");
110
+ const hasOffset = /(?:Z|[+-][0-9]{2}:?[0-9]{2})$/i.test(normalized);
111
+ const configuredOffset = options?.naiveResetTimezoneOffset;
112
+ const parsed = Date.parse(hasOffset ? normalized : `${normalized}${configuredOffset ?? "Z"}`);
113
+ if (!Number.isNaN(parsed) && parsed > Date.now()) {
114
+ if (hasOffset || configuredOffset !== void 0) consider(parsed - Date.now());
115
+ else considerNaive(parsed - Date.now());
105
116
  }
106
117
  }
118
+ const compoundResetMatch = RESET_IN_HR_MIN_PATTERN.exec(body);
119
+ if (compoundResetMatch?.[1] && compoundResetMatch[2]) {
120
+ const hours = Number.parseFloat(compoundResetMatch[1]);
121
+ const minutes = Number.parseFloat(compoundResetMatch[2]);
122
+ if (Number.isFinite(hours) && Number.isFinite(minutes) && hours >= 0 && minutes > 0) consider(hours * 60 * 6e4 + minutes * 6e4);
123
+ }
107
124
  const accountResetMatch = WILL_RESET_IN_PATTERN.exec(body);
108
125
  if (accountResetMatch?.[1]) {
109
126
  const value = Number.parseFloat(accountResetMatch[1]);
@@ -151,7 +168,7 @@ function extractRetryHint(source, body) {
151
168
  const resetSeconds = Number(resetMatch[1]);
152
169
  if (!Number.isNaN(resetSeconds)) considerClamped(resetSeconds > 1e9 ? resetSeconds * 1e3 - Date.now() : resetSeconds * 1e3);
153
170
  }
154
- return longestMs ?? (retryNow ? 0 : void 0);
171
+ return longestMs ?? longestNaiveMs ?? (retryNow ? 0 : void 0);
155
172
  }
156
173
  function unitToMs(unit) {
157
174
  switch (unit.toLowerCase()) {
@@ -168,6 +185,9 @@ function unitToMs(unit) {
168
185
  case "hrs":
169
186
  case "hour":
170
187
  case "hours": return 36e5;
188
+ case "d":
189
+ case "day":
190
+ case "days": return 864e5;
171
191
  default: return;
172
192
  }
173
193
  }
@@ -202,13 +222,15 @@ async function fetchWithRetry(url, options = {}) {
202
222
  await waitForRetry(resolveDefaultDelay(defaultDelayMs, attempt, maxDelayMs), signal);
203
223
  continue;
204
224
  }
205
- if (!isRetryableStatus(response.status)) return response;
225
+ if (!isRetryableStatus$1(response.status)) return response;
206
226
  if (attempt + 1 >= maxAttempts) return response;
207
227
  const retryBody = await response.clone().text();
208
228
  if (shouldRetryResponse && !await shouldRetryResponse(response, retryBody, attempt)) return response;
209
229
  const hint = extractRetryHint(response, retryBody);
210
230
  if (hint !== void 0 && hint > maxDelayMs) return response;
211
- await waitForRetry(Math.min(hint ?? resolveDefaultDelay(defaultDelayMs, attempt, maxDelayMs), maxDelayMs), signal);
231
+ const delayMs = Math.min(hint ?? resolveDefaultDelay(defaultDelayMs, attempt, maxDelayMs), maxDelayMs);
232
+ if (!response.bodyUsed) response.body?.cancel().catch(() => {});
233
+ await waitForRetry(delayMs, signal);
212
234
  }
213
235
  }
214
236
  function mergeInit(base, overlay, timeout) {
@@ -237,7 +259,7 @@ async function waitForRetry(delayMs, signal) {
237
259
  function wrapNetworkError(error) {
238
260
  if (error instanceof Error) {
239
261
  if (error.name === "AbortError" || error.message === "Request was aborted") return /* @__PURE__ */ new Error("Request was aborted");
240
- if (error.message === "fetch failed" && error.cause instanceof Error) return /* @__PURE__ */ new Error(`Network error: ${error.cause.message}`);
262
+ if (error.message === "fetch failed" && error.cause instanceof Error) return new Error(`Network error: ${error.cause.message}`, { cause: error });
241
263
  return error;
242
264
  }
243
265
  return new Error(String(error));
@@ -293,7 +315,7 @@ function extractStatusFromMessage(message) {
293
315
  * `true` if the given HTTP status code is one we treat as transient: 408
294
316
  * (Request Timeout), 429 (Too Many Requests), or any 5xx (server error).
295
317
  */
296
- function isRetryableStatus(status) {
318
+ function isRetryableStatus$1(status) {
297
319
  return status >= 500 || status === 408 || status === 429;
298
320
  }
299
321
  /**
@@ -317,14 +339,14 @@ function isRetryableError(error) {
317
339
  if ((info?.name ?? "") === "AbortError" || /timeout|timed out|aborted/i.test(message)) return true;
318
340
  const status = extractHttpStatusFromError(error);
319
341
  if (status !== void 0) {
320
- if (isRetryableStatus(status)) return true;
342
+ if (isRetryableStatus$1(status)) return true;
321
343
  if (status >= 400 && status < 500) return false;
322
344
  }
323
345
  if (VALIDATION_MESSAGE_PATTERN.test(message)) return false;
324
346
  return isUnexpectedSocketCloseMessage(message) || TRANSIENT_MESSAGE_PATTERN.test(message);
325
347
  }
326
348
  //#endregion
327
- //#region node_modules/.pnpm/@oh-my-pi+pi-ai@18.1.17/node_modules/@oh-my-pi/pi-ai/src/error/aws.ts
349
+ //#region node_modules/.pnpm/@oh-my-pi+pi-ai@18.12.6/node_modules/@oh-my-pi/pi-ai/src/error/aws.ts
328
350
  /** A failure resolving AWS credentials for the Bedrock provider. */
329
351
  var AwsCredentialsError = class extends Error {
330
352
  kind;
@@ -342,7 +364,136 @@ var EventStreamFrameError = class extends Error {
342
364
  }
343
365
  };
344
366
  //#endregion
345
- //#region node_modules/.pnpm/@oh-my-pi+pi-ai@18.1.17/node_modules/@oh-my-pi/pi-ai/src/error/classes.ts
367
+ //#region node_modules/.pnpm/@oh-my-pi+pi-ai@18.12.6/node_modules/@oh-my-pi/pi-ai/src/error/network-cause.ts
368
+ const MAX_CAUSE_DEPTH = 8;
369
+ const MAX_AGGREGATE_BRANCH = 4;
370
+ const MAX_LINKS = 16;
371
+ const MAX_DETAIL_LENGTH = 120;
372
+ const UNKNOWN_HOST = "the API host";
373
+ const OFFLINE_CODE_PATTERN = /^(?:ETIMEOUT|EAI_AGAIN|ENETUNREACH|EHOSTUNREACH)$/i;
374
+ const OFFLINE_TEXT_PATTERN = /\b(?:ETIMEOUT|EAI_AGAIN|ENETUNREACH|EHOSTUNREACH)\b/i;
375
+ const UNREACHABLE_CODE_PATTERN = /^(?:ENOTFOUND|FailedToOpenSocket)$/i;
376
+ const UNREACHABLE_TEXT_PATTERN = /\b(?:ENOTFOUND|FailedToOpenSocket|getaddrinfo)\b/i;
377
+ const REFUSED_CODE_PATTERN = /^(?:ECONNREFUSED|ConnectionRefused)$/i;
378
+ const REFUSED_TEXT_PATTERN = /\bECONNREFUSED\b|\bConnectionRefused\b|\bUnable to connect\b/i;
379
+ const TLS_CODE_PATTERN = /^(?:CERT_|SELF_SIGNED|UNABLE_TO_GET_ISSUER_CERT|UNABLE_TO_VERIFY|DEPTH_ZERO_SELF_SIGNED_CERT|ERR_TLS_CERT)/i;
380
+ const TLS_TEXT_PATTERN = /unable to verify|certificate verif(?:y|ication)|self[- ]signed certificate|\bCERT_[A-Z_]+\b|\bSELF_SIGNED[A-Z_]*\b|\bUNABLE_TO_GET_ISSUER_CERT[A-Z_]*\b|\bUNABLE_TO_VERIFY_LEAF_SIGNATURE\b/i;
381
+ const GETADDRINFO_HOST_PATTERN = /\bgetaddrinfo\s+[A-Z_]+\s+([^\s,)]+)/i;
382
+ const CONNECT_HOST_PATTERN = /\b(?:connect|connection to)\s+[A-Z_]+\s+([^\s,)]+?)(?::\d+)?(?=[\s,)]|$)/i;
383
+ const BARE_HOST_PATTERN = /\b((?:[a-z0-9-]+\.)+[a-z]{2,})\b/i;
384
+ const GETADDRINFO_DETAIL_PATTERN = /\bgetaddrinfo\s+[A-Z_]+/i;
385
+ const ERRNO_DETAIL_PATTERN = /\b(?:ETIMEOUT|ENOTFOUND|EAI_AGAIN|ENETUNREACH|EHOSTUNREACH|FailedToOpenSocket)\b/i;
386
+ const HOST_PATTERNS = [
387
+ GETADDRINFO_HOST_PATTERN,
388
+ CONNECT_HOST_PATTERN,
389
+ BARE_HOST_PATTERN
390
+ ];
391
+ function kindOf(code, message) {
392
+ if (code !== void 0 && TLS_CODE_PATTERN.test(code)) return "tls";
393
+ if (message !== void 0 && TLS_TEXT_PATTERN.test(message)) return "tls";
394
+ if (code !== void 0 && REFUSED_CODE_PATTERN.test(code)) return "refused";
395
+ if (message !== void 0 && REFUSED_TEXT_PATTERN.test(message)) return "refused";
396
+ if (code !== void 0 && OFFLINE_CODE_PATTERN.test(code)) return "offline";
397
+ if (message !== void 0 && OFFLINE_TEXT_PATTERN.test(message)) return "offline";
398
+ if (code !== void 0 && UNREACHABLE_CODE_PATTERN.test(code)) return "unreachable";
399
+ if (message !== void 0 && UNREACHABLE_TEXT_PATTERN.test(message)) return "unreachable";
400
+ }
401
+ /** First host named by any message seen so far, most specific pattern first. */
402
+ function hostFromTexts(texts) {
403
+ for (const pattern of HOST_PATTERNS) for (const text of texts) {
404
+ const host = pattern.exec(text)?.[1];
405
+ if (host) return host;
406
+ }
407
+ }
408
+ function detailOf(kind, code, message) {
409
+ if (kind === "refused") return void 0;
410
+ if (kind === "tls" && code !== void 0) return code;
411
+ const matched = message === void 0 ? void 0 : (kind === "tls" ? TLS_TEXT_PATTERN : GETADDRINFO_DETAIL_PATTERN).exec(message)?.[0];
412
+ if (matched !== void 0) return matched.replace(/\s+/g, " ").trim().slice(0, MAX_DETAIL_LENGTH);
413
+ if (kind === "tls") return void 0;
414
+ return code ?? (message === void 0 ? void 0 : ERRNO_DETAIL_PATTERN.exec(message)?.[0]);
415
+ }
416
+ /**
417
+ * Walk an error's `cause`/`errors` graph (bounded depth, cycle-safe) and report
418
+ * the outermost link that describes a local transport failure. Returns
419
+ * `undefined` for anything else, including HTTP-level provider errors.
420
+ */
421
+ function classifyNetworkCause(err) {
422
+ const seen = /* @__PURE__ */ new Set();
423
+ const queue = [{
424
+ value: err,
425
+ depth: 0
426
+ }];
427
+ const texts = [];
428
+ let links = 0;
429
+ while (links < MAX_LINKS) {
430
+ const link = queue.shift();
431
+ if (link === void 0) break;
432
+ links++;
433
+ const { value, depth } = link;
434
+ let code;
435
+ let message;
436
+ const isObject = typeof value === "object" && value !== null;
437
+ if (typeof value === "string") message = value;
438
+ else if (isObject) {
439
+ if (seen.has(value)) continue;
440
+ seen.add(value);
441
+ if ("code" in value && typeof value.code === "string") code = value.code;
442
+ if ("message" in value && typeof value.message === "string") message = value.message;
443
+ } else continue;
444
+ if (message !== void 0) texts.push(message);
445
+ const kind = kindOf(code, message);
446
+ if (kind !== void 0) return {
447
+ kind,
448
+ code,
449
+ detail: detailOf(kind, code, message),
450
+ host: hostFromTexts(texts)
451
+ };
452
+ if (!isObject || depth >= MAX_CAUSE_DEPTH) continue;
453
+ if ("cause" in value) queue.push({
454
+ value: value.cause,
455
+ depth: depth + 1
456
+ });
457
+ if ("errors" in value && Array.isArray(value.errors)) for (const nested of value.errors.slice(0, MAX_AGGREGATE_BRANCH)) queue.push({
458
+ value: nested,
459
+ depth: depth + 1
460
+ });
461
+ }
462
+ }
463
+ /**
464
+ * User-visible sentence for a network failure, or `undefined` when the error is
465
+ * not one. `requestUrl` wins over any host parsed out of the error text.
466
+ */
467
+ function networkFailureMessage(err, requestUrl) {
468
+ const failure = classifyNetworkCause(err);
469
+ if (failure === void 0) return void 0;
470
+ let urlHost;
471
+ if (typeof requestUrl === "string" && requestUrl.length > 0) try {
472
+ const { host } = new URL(requestUrl);
473
+ if (host.length > 0) urlHost = host;
474
+ } catch {
475
+ urlHost = void 0;
476
+ }
477
+ const host = urlHost ?? failure.host ?? UNKNOWN_HOST;
478
+ const detail = failure.detail === void 0 ? "" : ` (${failure.detail})`;
479
+ switch (failure.kind) {
480
+ case "offline": return `No internet connection: couldn't reach ${host}${detail}. Check your network or proxy and try again.`;
481
+ case "unreachable": return `Couldn't reach ${host}${detail}. Check the URL, your network, or proxy.`;
482
+ case "refused": return `Couldn't connect to ${host} (connection refused). Check that the server is running and reachable.`;
483
+ case "tls": return `TLS certificate verification failed for ${host}${detail}. If you're behind a TLS-inspecting proxy, its CA may need to be trusted.`;
484
+ }
485
+ }
486
+ /**
487
+ * Re-describe a rejected `fetch` with {@link networkFailureMessage}, keeping the
488
+ * original error as `cause`. Anything that is not a local transport failure is
489
+ * returned untouched, so provider/HTTP errors keep their identity.
490
+ */
491
+ function networkFailureError(err, requestUrl) {
492
+ const message = networkFailureMessage(err, requestUrl);
493
+ return message === void 0 ? err : new Error(message, { cause: err });
494
+ }
495
+ //#endregion
496
+ //#region node_modules/.pnpm/@oh-my-pi+pi-ai@18.12.6/node_modules/@oh-my-pi/pi-ai/src/error/classes.ts
346
497
  /** Prefix on errors raised when an Anthropic SSE stream envelope is malformed. */
347
498
  const STREAM_ENVELOPE_ERROR_PREFIX = "Anthropic stream envelope error:";
348
499
  /** Non-2xx HTTP response from a provider. */
@@ -410,8 +561,12 @@ let anthropicErrorBodyReadTimeoutMs = DEFAULT_ANTHROPIC_ERROR_BODY_READ_TIMEOUT_
410
561
  /** Non-2xx response from the Anthropic API. */
411
562
  var AnthropicApiError = class AnthropicApiError extends ProviderHttpError {
412
563
  requestId;
413
- constructor(status, message, headers) {
414
- super(message, status, { headers });
564
+ constructor(status, message, headers, options) {
565
+ super(message, status, {
566
+ headers,
567
+ code: options?.code,
568
+ cause: options?.cause
569
+ });
415
570
  this.name = "AnthropicApiError";
416
571
  this.requestId = headers.get("request-id");
417
572
  }
@@ -486,13 +641,24 @@ var AnthropicApiError = class AnthropicApiError extends ProviderHttpError {
486
641
  reader.releaseLock();
487
642
  }
488
643
  const detail = bodyChunks.join("").trim() || "status code (no body)";
489
- return new AnthropicApiError(response.status, `${response.status} ${detail}`, response.headers);
644
+ let code;
645
+ try {
646
+ const parsed = JSON.parse(detail);
647
+ if (parsed && typeof parsed === "object" && "error" in parsed) {
648
+ const err = parsed.error;
649
+ if (err && typeof err === "object") {
650
+ if ("details" in err && err.details && typeof err.details === "object" && "error_code" in err.details && typeof err.details.error_code === "string") code = err.details.error_code;
651
+ else if ("type" in err && typeof err.type === "string") code = err.type;
652
+ }
653
+ }
654
+ } catch {}
655
+ return new AnthropicApiError(response.status, `${response.status} ${detail}`, response.headers, { code });
490
656
  }
491
657
  };
492
658
  /** Network-level failure (DNS, TLS, socket reset) after retries were exhausted. */
493
659
  var AnthropicConnectionError = class extends Error {
494
- constructor(cause) {
495
- super("Connection error.", { cause });
660
+ constructor(cause, requestUrl) {
661
+ super(networkFailureMessage(cause, requestUrl) ?? "Connection error.", { cause });
496
662
  this.name = "AnthropicConnectionError";
497
663
  }
498
664
  };
@@ -532,11 +698,13 @@ var OllamaApiError = class extends ProviderHttpError {
532
698
  name = "OllamaApiError";
533
699
  };
534
700
  //#endregion
535
- //#region node_modules/.pnpm/@oh-my-pi+pi-ai@18.1.17/node_modules/@oh-my-pi/pi-ai/src/error/rate-limit.ts
701
+ //#region node_modules/.pnpm/@oh-my-pi+pi-ai@18.12.6/node_modules/@oh-my-pi/pi-ai/src/error/rate-limit.ts
536
702
  const ACCOUNT_RATE_LIMIT_PATTERN = /\baccount(?:'s)?\b[^\n]{0,80}\brate.?limit\b|\brate.?limit\b[^\n]{0,80}\baccount\b/i;
537
703
  const INSUFFICIENT_BALANCE_PATTERN = /insufficient.?balance/i;
538
704
  const CREDITS_EXHAUSTED_PATTERN = /\b(?:exceed\w*|insufficient|not enough)\b[^\n]{0,40}\bcredits?\b|\bcredits?\b[^\n]{0,40}\b(?:exhausted|depleted)\b/i;
539
- const SPEND_LIMIT_PATTERN = /spend.?limit/i;
705
+ const ANTHROPIC_CREDITS_REQUIRED_PATTERN = /\busage credits are required\b|\bcredits_required\b/i;
706
+ const PREPAID_BALANCE_EXHAUSTED_PATTERN = /\busage_pricing_required\b|\bprepaid balance\b[^\n]{0,40}\b(?:used up|exhausted|depleted)\b/i;
707
+ const SPEND_LIMIT_PATTERN = /spend(?:ing)?[\s_-]?(?:limit|cap)\b/i;
540
708
  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;
541
709
  const TRANSIENT_INTERVAL_RATE_LIMIT_PATTERN = /\bper\s+(?:second|minute)\b/i;
542
710
  function matchesSubscriptionCapText(errorMessage) {
@@ -547,7 +715,7 @@ const CLINE_PASS_QUOTA_PATTERN = /clinepass limit|free limit reached on model/i;
547
715
  const RESOURCE_EXHAUSTED_PATTERN = /resource.?exhausted/gi;
548
716
  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;
549
717
  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;
550
- const CN_QUOTA_EXHAUSTED_PATTERN = /使用.{0,30}?上限|(?:额度|配额)已?(?:用|耗)(?:完|尽)|限额.{0,30}重置|余额不足/;
718
+ const CN_QUOTA_EXHAUSTED_PATTERN = /(?:使用|用量).{0,30}?上限|(?:额度|配额)已?(?:用|耗)(?:完|尽)|限额.{0,30}重置|余额不足/;
551
719
  const CN_TRANSIENT_CAP_PATTERN = /速率.{0,30}上限|频率.{0,30}上限|每分钟.{0,30}上限|并发.{0,30}上限|使用.{0,30}(?:速率|频率|每分钟|并发).{0,30}上限/;
552
720
  const CN_THROTTLE_PATTERN = /速率(?:限制|过快)|频率(?:过高|过快)|过于频繁|稍后[重再]试/;
553
721
  const DASHSCOPE_TOKEN_LIMIT_DOC_PATTERN = /error-code[^()\s]*#token-limit/i;
@@ -556,6 +724,7 @@ const DASHSCOPE_TOKEN_LIMIT_MESSAGE_PATTERN = /\byou exceeded your current quota
556
724
  function isDashScopeTokenLimitText(errorMessage) {
557
725
  return DASHSCOPE_TOKEN_LIMIT_DOC_PATTERN.test(errorMessage) && DASHSCOPE_TOKEN_LIMIT_MESSAGE_PATTERN.test(errorMessage);
558
726
  }
727
+ const TPM_RPM_THROTTLE_PATTERN = /\b(?:tpm|rpm)\b[^\n]{0,40}\b(?:exhaust\w*|exceed\w*|limit\w*|throttl\w*|reach\w*)\b|\b(?:exhaust\w*|exceed\w*|limit\w*|throttl\w*|reach\w*)\b[^\n]{0,40}\b(?:tpm|rpm)\b|\bRateLimitExceeded\.(?:Endpoint)?(?:TPM|RPM)\w*/i;
559
728
  const GOOGLE_RPC_ERROR_INFO_TYPE = "type.googleapis.com/google.rpc.ErrorInfo";
560
729
  const ANTIGRAVITY_MODEL_QUOTA_PATTERN = /\bexhausted your capacity on this model\b/i;
561
730
  const LONG_RATE_LIMIT_DELAY_MS = 3e5;
@@ -602,8 +771,9 @@ function isQuotaExhaustedReason(reason) {
602
771
  * Classify a rate-limit error message into a reason category.
603
772
  * Priority order: explicit details in a resource-exhausted error > QUOTA
604
773
  * (Antigravity "quota will reset") > CN quota > DASHSCOPE_TOKEN_LIMIT (TPM/TPS
605
- * throttle) > CONCURRENT_LIMIT > MODEL_CAPACITY > QUOTA (account) > RATE_LIMIT >
606
- * QUOTA (generic) > SERVER_ERROR > bare resource-exhausted > UNKNOWN.
774
+ * throttle) > CONCURRENT_LIMIT > MODEL_CAPACITY > QUOTA (account) > RATE_LIMIT
775
+ * (including TPM/RPM rolling windows) > QUOTA (generic) > SERVER_ERROR > bare
776
+ * resource-exhausted > UNKNOWN.
607
777
  *
608
778
  * Bare "resource exhausted" / "resource_exhausted" maps to MODEL_CAPACITY (transient, short wait).
609
779
  * Explicit details such as "quota exceeded" retain their normal classification.
@@ -624,28 +794,31 @@ function parseRateLimitReason(errorMessage) {
624
794
  if (matchesSubscriptionCapText(errorMessage)) return "QUOTA_EXHAUSTED";
625
795
  if (OPENROUTER_DAILY_FREE_LIMIT_PATTERN.test(errorMessage)) return "QUOTA_EXHAUSTED";
626
796
  if (CLINE_PASS_QUOTA_PATTERN.test(errorMessage)) return "QUOTA_EXHAUSTED";
627
- if (lower.includes("per minute") || lower.includes("rate limit") || lower.includes("too many requests") || lower.includes("presque")) return "RATE_LIMIT_EXCEEDED";
628
- 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) || CREDITS_EXHAUSTED_PATTERN.test(errorMessage)) return "QUOTA_EXHAUSTED";
797
+ if (ANTHROPIC_CREDITS_REQUIRED_PATTERN.test(errorMessage)) return "QUOTA_EXHAUSTED";
798
+ if (PREPAID_BALANCE_EXHAUSTED_PATTERN.test(errorMessage)) return "QUOTA_EXHAUSTED";
799
+ if (lower.includes("per minute") || lower.includes("rate limit") || lower.includes("too many requests") || lower.includes("presque") || TPM_RPM_THROTTLE_PATTERN.test(errorMessage)) return "RATE_LIMIT_EXCEEDED";
800
+ 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") || lower.includes("access_terminated_error") || INSUFFICIENT_BALANCE_PATTERN.test(errorMessage) || CREDITS_EXHAUSTED_PATTERN.test(errorMessage)) return "QUOTA_EXHAUSTED";
629
801
  if (lower.includes("500") || lower.includes("internal error") || lower.includes("internal server error")) return "SERVER_ERROR";
630
802
  if (hasResourceExhaustedStatus) return "MODEL_CAPACITY_EXHAUSTED";
631
803
  return "UNKNOWN";
632
804
  }
633
805
  /** Detect usage/quota limit errors in error messages (persistent, requires credential switch). */
634
- 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|clinepass limit|free limit reached on model/i;
806
+ 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|out of (?:extra )?usage|spending[- _]?limit|personal-team-blocked|clinepass limit|free limit reached on model|access_terminated_error/i;
635
807
  /**
636
808
  * HTTP status codes that, absent richer body classification, represent an
637
809
  * account-local usage cap rather than a bad credential or a transient blip.
638
810
  * HTTP 402 Payment Required represents an account-billing cap (xAI
639
811
  * Grok Build "usage balance exhausted", DeepSeek "Insufficient Balance",
640
- * OpenRouter credit exhaustion) when opaque, payment/deactivation/balance-worded,
641
- * or QUOTA_EXHAUSTED/CONCURRENT_LIMIT, while informative non-quota 402s (e.g.
812
+ * OpenCode Go "Insufficient account funds", OpenRouter credit exhaustion)
813
+ * when opaque, payment/deactivation/balance/funds-worded, or
814
+ * QUOTA_EXHAUSTED/CONCURRENT_LIMIT. Informative non-quota 402s (e.g.
642
815
  * endpoint subscription requirements) remain non-usage-limits. Always combine
643
816
  * with {@link isUsageLimitOutcome} when a message is available.
644
817
  */
645
818
  function isUsageLimitStatus(status) {
646
819
  return status === 429 || status === 402;
647
820
  }
648
- const STATUS_402_QUOTA_PATTERN = /\b(?:payment(?:\s+is)?[-_.\s]*required|deactivated_workspace|insufficient.?balance)\b/i;
821
+ const STATUS_402_QUOTA_PATTERN = /\b(?:payment(?:\s+is)?[-_.\s]*required|deactivated_workspace|insufficient.?(?:balance|account.?funds))\b/i;
649
822
  function is402BillingCapBody(message) {
650
823
  if (message === void 0 || isOpaqueStatusBody(message)) return true;
651
824
  if (STATUS_402_QUOTA_PATTERN.test(message)) return true;
@@ -673,7 +846,8 @@ function matchesUsageLimitText(errorMessage) {
673
846
  const structuredReason = parseGoogleRpcRateLimitReason(errorMessage);
674
847
  if (structuredReason !== void 0) return isQuotaExhaustedReason(structuredReason);
675
848
  if (isDashScopeTokenLimitText(errorMessage)) return false;
676
- return USAGE_LIMIT_PATTERN.test(errorMessage) || CREDITS_EXHAUSTED_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);
849
+ if (TPM_RPM_THROTTLE_PATTERN.test(errorMessage) && parseRateLimitReason(errorMessage) === "RATE_LIMIT_EXCEEDED") return false;
850
+ return USAGE_LIMIT_PATTERN.test(errorMessage) || ANTHROPIC_CREDITS_REQUIRED_PATTERN.test(errorMessage) || PREPAID_BALANCE_EXHAUSTED_PATTERN.test(errorMessage) || CREDITS_EXHAUSTED_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);
677
851
  }
678
852
  /**
679
853
  * Account-scoped cap phrasing delivered on a 403 (or a statusless Connect
@@ -686,7 +860,7 @@ function isAccountScopedCapText(message) {
686
860
  return ACCOUNT_SCOPED_403_PATTERN.test(message);
687
861
  }
688
862
  //#endregion
689
- //#region node_modules/.pnpm/@oh-my-pi+pi-ai@18.1.17/node_modules/@oh-my-pi/pi-ai/src/error/flags.ts
863
+ //#region node_modules/.pnpm/@oh-my-pi+pi-ai@18.12.6/node_modules/@oh-my-pi/pi-ai/src/error/flags.ts
690
864
  const Flag = {
691
865
  Class: 4096,
692
866
  ThinkingLoop: 65536,
@@ -711,10 +885,20 @@ const Flag = {
711
885
  FastModeUnsupported: 536870912,
712
886
  /** OAuth refresh failed definitively — the stored grant is dead, re-login required. */
713
887
  OAuthExpiry: 1073741824,
888
+ /**
889
+ * An Anthropic-compatible endpoint rejected the `cache_control` field with a
890
+ * 400, so prompt-cache breakpoints must be dropped for it.
891
+ *
892
+ * Every bit from {@link Flag.Class} up is already taken, so this claims a low
893
+ * bit instead. Those belong to the raw-status lane — an unclassified id is an
894
+ * HTTP status (100-599, see `statusFromId`) — and 0x800 is 2048, above every
895
+ * status value, so no raw status id can set it.
896
+ */
897
+ CacheControlUnsupported: 2048,
714
898
  /** HTTP 413 byte/media rejection — token compaction cannot shrink bytes or media budgets (#9235). */
715
899
  PayloadRejected: 2147483648
716
900
  };
717
- 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.PayloadRejected | Flag.SilentAbort | Flag.UserInterrupt | Flag.Abort | Flag.Grammar | Flag.FastModeUnsupported | Flag.OAuthExpiry;
901
+ 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.PayloadRejected | Flag.SilentAbort | Flag.UserInterrupt | Flag.Abort | Flag.Grammar | Flag.FastModeUnsupported | Flag.CacheControlUnsupported | Flag.OAuthExpiry;
718
902
  const RETRIABLE_KINDS = Flag.Transient | Flag.UsageLimit | Flag.ThinkingLoop | Flag.StaleResponsesItem | Flag.ProviderFinishError | Flag.EmptyResponse;
719
903
  const CONTEXT_OVERFLOW_EVIDENCE_PATTERNS = [
720
904
  /prompt is too long/i,
@@ -725,6 +909,8 @@ const CONTEXT_OVERFLOW_EVIDENCE_PATTERNS = [
725
909
  /reduce the length of the messages/i,
726
910
  /maximum context length is \d+ tokens/i,
727
911
  /exceeds the available context size/i,
912
+ /\bprompt\s*\(\s*\d+\s+tokens\s*\)\s*\+\s*max\s+tokens\s*\(\s*\d+\s*\)\s+exceeds\s+the\s+context\s*\(\s*\d+\s*\)/i,
913
+ /\bprompt\s*\(\s*\d+\s+tokens\s*\)\s+leaves\s+no\s+room\s+to\s+answer\s+in\s+the\s+context\s*\(\s*\d+\s*\)/i,
728
914
  /requested tokens?.*exceed.*context (window|length|size)/i,
729
915
  /context (window|length|size).*(exceeded|overflow|too small)/i,
730
916
  /(prompt|input).*(too long|too large).*(context|n_ctx)/i,
@@ -786,19 +972,66 @@ function matchesPayloadRejectionText(text) {
786
972
  }
787
973
  const TIMEOUT_PATTERN = /\b(?:operation\s+)?timed?\s*out\b|\btimeout\b|\bstream stall\b/i;
788
974
  const TRANSIENT_ENVELOPE_PATTERN = /anthropic stream envelope error:/i;
789
- const TRANSIENT_ENVELOPE_TRUNCATION_PATTERN = /before message_(?:start|stop)/i;
975
+ const TRANSIENT_ENVELOPE_TRUNCATION_PATTERN = /before message_(?:start|delta|stop)/i;
790
976
  const STREAM_READ_ERROR_PATTERN = /stream[_ -]?read[_ -]?error/i;
791
977
  /** Python h2/httpx diagnostics forwarded through provider or proxy error events. */
792
978
  const PYTHON_HTTP2_STREAM_RESET_PATTERN = /<StreamReset stream_id:\d+, error_code:(?:2|7), remote_reset:True>/;
793
979
  /** Python h11/httpx EOF while reading an HTTP/1.1 chunked response body. */
794
980
  const PYTHON_HTTP_INCOMPLETE_CHUNK_PATTERN = /peer closed connection without sending complete message body \(incomplete chunked read\)/;
795
- 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|auth-gateway\s+5\d{2}(?=[:\s]|$)|\b(?:429|500|502|503|504)\b|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;
981
+ /** reqwest body-frame failures forwarded by the Codex HTTP proxy. */
982
+ const CODEX_HTTP_BODY_READ_ERROR_PATTERN = /\btransport error reading codex response body\b/i;
983
+ const RESPONSES_REQUEST_BODY_READ_TIMEOUT_PATTERN = /\btimed out reading request body\b/i;
984
+ /** Exact HTTP request-body-read timeout diagnostic. */
985
+ function isRequestBodyReadTimeout(status, message) {
986
+ return status === 408 && RESPONSES_REQUEST_BODY_READ_TIMEOUT_PATTERN.test(message ?? "");
987
+ }
988
+ /**
989
+ * Codex code for the experimental native turn lane refusing a mid-response
990
+ * `response.steer`; the server then drops the in-flight response and closes
991
+ * the socket.
992
+ */
993
+ const CODEX_NATIVE_LANE_STEER_REJECTED_CODE = "unsupported_native_inflight_message";
994
+ /**
995
+ * A Codex turn the native turn lane dropped because omp steered it. The
996
+ * rejection answers our own `response.steer`, not the model's health: the
997
+ * provider stops steering the session, so the same model replays cleanly.
998
+ */
999
+ function isCodexSteerRejection(message) {
1000
+ return message.api === "openai-codex-responses" && message.errorMessage?.includes(`code=unsupported_native_inflight_message`) === true;
1001
+ }
1002
+ /**
1003
+ * Transport-failure wording that marks an error transient. Covers
1004
+ * diagnostics forwarded verbatim by gateways/proxies in other runtimes, e.g.
1005
+ * undici's "other side closed" and Go's `net.ErrClosed` ("use of closed
1006
+ * network connection", #15385). Also covers OMP's own network-failure
1007
+ * sentences from `networkFailureMessage` (offline / unreachable / refused /
1008
+ * TLS), which must stay at least as retryable as the bare `Connection error.`
1009
+ * text they replaced.
1010
+ */
1011
+ 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|auth-gateway\s+5\d{2}(?=[:\s]|$)|\b(?:429|500|502|503|504)\b|service.?unavailable|temporar(?:y|ily)[ _-]?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|use of closed network connection|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)|\bE(?:TIMEOUT|NOTFOUND|NETUNREACH|HOSTUNREACH)\b|\bFailedToOpenSocket\b|no internet connection|couldn.?t reach|couldn.?t connect to|tls certificate verification failed|malformed.?function.?call/i;
796
1012
  const AUTH_FAILURE_PATTERN = /\b(?:401|403|unauthorized|forbidden|authentication|auth[_ ]?unavailable|no auth available|(?:invalid|no)[_ ]?api[_ ]?key)\b/i;
797
1013
  const MALFORMED_FUNCTION_CALL_PATTERN = /\bmalformed.?function.?call\b/i;
798
1014
  const PROVIDER_FINISH_ERROR_PATTERN = /\bProvider (?:returned error finish_reason|finish_reason:\s*error)\b/i;
799
1015
  const EMPTY_RESPONSE_PATTERN = /\bthought-only response without final output\b/i;
800
1016
  const CONTENT_FILTER_PATTERN = /\b(?:incomplete:\s*)?content_filter\b/i;
801
1017
  const ACCOUNT_POLICY_PATTERN = /\bcyber_policy\b|trusted access for cyber/i;
1018
+ const ANTHROPIC_ACCOUNT_POLICY_PATTERN = /\b(?:oauth_not_allowed_for_organization|permission_error)\b|\bOAuth authentication is currently not allowed for this organization\b/i;
1019
+ /** Whether an error message represents an Anthropic account-scoped permission/policy denial. */
1020
+ function isAnthropicAccountPolicyText(text, provider, statusArg) {
1021
+ if (provider !== void 0 && provider !== "anthropic") return false;
1022
+ const statusCandidate = statusArg ?? (text ? status({ message: text }) : void 0);
1023
+ if (statusCandidate !== void 0 && statusCandidate !== 403) return false;
1024
+ return ANTHROPIC_ACCOUNT_POLICY_PATTERN.test(text);
1025
+ }
1026
+ const IMMUTABLE_LATEST_THINKING_PATTERN = /messages\.\d+\.content\.\d+.*\b(?:thinking|redacted_thinking)\b.*\blatest assistant message cannot be modified\b/is;
1027
+ /**
1028
+ * Anthropic's rejection of a replayed latest assistant turn whose `thinking` or
1029
+ * `redacted_thinking` blocks no longer verify. Resending the same turn fails the
1030
+ * same way; only a request that changes that turn's reasoning can succeed.
1031
+ */
1032
+ function isImmutableLatestThinkingText(text) {
1033
+ return IMMUTABLE_LATEST_THINKING_PATTERN.test(text);
1034
+ }
802
1035
  const CODEX_CHATGPT_ACCOUNT_MODEL_POLICY_PATTERN = /\bThe ['"]([^'"\r\n]+)['"] model is not supported when using Codex with a ChatGPT account\./i;
803
1036
  const CODEX_CHATGPT_ACCOUNT_MODEL_MAX_LENGTH = 256;
804
1037
  const CURSOR_PLAN_POLICY_MARKER_PATTERN = /\bERROR_RATE_LIMITED_CHANGEABLE\b/i;
@@ -843,14 +1076,20 @@ const STRUCTURED_OUTPUTS_PATTERN = /structured[_ -]?outputs?/i;
843
1076
  const FEATURE_NOT_SUPPORTED_PATTERN = /not (?:supported|available|enabled)|unsupported|does(?: not|n'?t) support/i;
844
1077
  const ANTHROPIC_STRICT_FIELD_PATTERN = /\btools\.\d+\.custom\.strict\b/i;
845
1078
  const EXTRA_INPUTS_NOT_PERMITTED_PATTERN = /extra inputs? (?:are|is) not permitted/i;
1079
+ const STRICT_TOOL_SCHEMA_REJECTION_PATTERN = /invalid schema for function|invalid tool parameters schema/i;
846
1080
  const FAST_MODE_SPEED_PARAM_PATTERN = /\bspeed\b/i;
847
1081
  const FAST_MODE_NOT_SUPPORTED_PATTERN = /not support/i;
848
1082
  const FAST_MODE_RATE_LIMIT_PATTERN = /rate_limit_error/i;
849
1083
  const FAST_MODE_ENTITLEMENT_PATTERN = /fast mode/i;
1084
+ const CACHE_CONTROL_FIELD_PATTERN = /\bcache_control\b/i;
1085
+ const CACHE_CONTROL_REJECTION_PATTERN = /\bunexpected\b|\bunrecognized\b|\bnot permitted\b|\bnot allowed\b|\bnot recognized\b|\bnot supported\b|\bunsupported\b|\binvalid[_ ]field\b|\bextra (?:inputs?|fields?)\b/i;
1086
+ const CACHE_CONTROL_MEMBER_REFUSAL_PATTERN = /\b(?:un(?:known|permitted|supported|recognized|expected)|invalid|bad|not (?:an? )?(?:known|valid|accepted|supported|permitted|allowed|recognized))[_ -](?:field|name|param(?:eter)?|argument|key|propert(?:y|ies)|attribute|member|option|input)s?\b/i;
1087
+ const CACHE_CONTROL_EMPTY_TEXT_PATTERN = /empty text block|text content block.*non-(?:empty|whitespace)|text.{0,40}must (?:be non-empty|not be empty)/i;
850
1088
  function matchesStrictToolsRejection(message, errorStatus) {
851
1089
  if (errorStatus !== 400) return false;
852
1090
  if (ANTHROPIC_STRICT_FIELD_PATTERN.test(message) && EXTRA_INPUTS_NOT_PERMITTED_PATTERN.test(message)) return true;
853
1091
  if (STRUCTURED_OUTPUTS_PATTERN.test(message) && FEATURE_NOT_SUPPORTED_PATTERN.test(message)) return true;
1092
+ if (STRICT_TOOL_SCHEMA_REJECTION_PATTERN.test(message)) return true;
854
1093
  if (!INVALID_REQUEST_PATTERN.test(message)) return false;
855
1094
  const grammarTooLarge = GRAMMAR_TOO_LARGE_PATTERN.test(message) && GRAMMAR_TOO_LARGE_DETAIL_PATTERN.test(message);
856
1095
  const schemaTooComplex = SCHEMA_TOO_COMPLEX_PATTERN.test(message) && SCHEMA_TOO_COMPLEX_DETAIL_PATTERN.test(message) && SCHEMA_COMPILE_PATTERN.test(message);
@@ -861,6 +1100,12 @@ function matchesFastModeUnsupported(message, errorStatus) {
861
1100
  if (errorStatus === 400 && INVALID_REQUEST_PATTERN.test(message) && FAST_MODE_SPEED_PARAM_PATTERN.test(message) && FAST_MODE_NOT_SUPPORTED_PATTERN.test(message)) return true;
862
1101
  return errorStatus === 429 && FAST_MODE_RATE_LIMIT_PATTERN.test(message) && FAST_MODE_ENTITLEMENT_PATTERN.test(message);
863
1102
  }
1103
+ function matchesCacheControlRejection(message, errorStatus) {
1104
+ if (errorStatus !== 400) return false;
1105
+ if (!CACHE_CONTROL_FIELD_PATTERN.test(message)) return false;
1106
+ if (CACHE_CONTROL_EMPTY_TEXT_PATTERN.test(message)) return false;
1107
+ return CACHE_CONTROL_REJECTION_PATTERN.test(message) || CACHE_CONTROL_MEMBER_REFUSAL_PATTERN.test(message);
1108
+ }
864
1109
  Flag.ThinkingLoop, Flag.Transient, Flag.Timeout, Flag.UsageLimit, Flag.StaleResponsesItem, Flag.MalformedFunctionCall, Flag.ProviderFinishError, Flag.EmptyResponse, Flag.ContentBlocked, Flag.AccountPolicy, Flag.ContextOverflow, Flag.PayloadRejected, Flag.AuthFailed, Flag.SilentAbort, Flag.UserInterrupt, Flag.Abort;
865
1110
  const STATUS_MESSAGE_PATTERNS = [
866
1111
  /\bstatus(?:_code)?[:=]\s*(\d{3})\b/i,
@@ -899,6 +1144,7 @@ function statusInternal(error, depth) {
899
1144
  const errObj = error;
900
1145
  if (typeof errObj.status === "number" && errObj.status >= 100 && errObj.status <= 599) return errObj.status;
901
1146
  if (typeof errObj.statusCode === "number" && errObj.statusCode >= 100 && errObj.statusCode <= 599) return errObj.statusCode;
1147
+ if (typeof errObj.errorStatus === "number" && errObj.errorStatus >= 100 && errObj.errorStatus <= 599) return errObj.errorStatus;
902
1148
  if (typeof errObj.response === "object" && errObj.response !== null) {
903
1149
  const resp = errObj.response;
904
1150
  if (typeof resp.status === "number" && resp.status >= 100 && resp.status <= 599) return resp.status;
@@ -946,6 +1192,16 @@ function isContentBlockedText(text) {
946
1192
  function matchesOverflowText(text) {
947
1193
  return OVERFLOW_PATTERNS.some((p) => p.test(text)) || OVERFLOW_NO_BODY_PATTERN.test(text);
948
1194
  }
1195
+ /**
1196
+ * A 4xx the provider rejected as a deterministic client error — every 4xx
1197
+ * except 408 (Request Timeout) and 429 (Too Many Requests), the retryable
1198
+ * pair. Mirrors the 4xx policy in {@link isProviderRetryableError}: such a
1199
+ * request replays identically, so a transient signal riding on it (e.g. a
1200
+ * truncation phrase in the body) must not flip it to retryable.
1201
+ */
1202
+ function isTerminalClientErrorStatus(status) {
1203
+ return status !== void 0 && status >= 400 && status < 500 && status !== 408 && status !== 429;
1204
+ }
949
1205
  function classifyText(errorMessage, errorStatus, priorTokenOverflowEvidence = false, api, provider, modelId) {
950
1206
  let kinds = 0;
951
1207
  if (errorMessage) {
@@ -955,22 +1211,31 @@ function classifyText(errorMessage, errorStatus, priorTokenOverflowEvidence = fa
955
1211
  if (isProviderFinishErrorText(errorMessage)) kinds |= Flag.ProviderFinishError;
956
1212
  if (EMPTY_RESPONSE_PATTERN.test(errorMessage)) kinds |= Flag.EmptyResponse | Flag.Transient;
957
1213
  if (isContentBlockedText(errorMessage)) kinds |= Flag.ContentBlocked;
1214
+ const statusClean = errorStatus ? errorStatus : status({ message: errorMessage }) ?? void 0;
958
1215
  if (ACCOUNT_POLICY_PATTERN.test(errorMessage) || isCodexChatGPTAccountPolicyText(errorMessage, provider, modelId) || provider === "cursor" && isCursorPlanPolicyText(errorMessage)) kinds |= Flag.AccountPolicy | Flag.ContentBlocked;
1216
+ if (isAnthropicAccountPolicyText(errorMessage, provider, statusClean)) kinds |= Flag.AccountPolicy;
959
1217
  if (isAuthFailureText(errorMessage)) kinds |= Flag.AuthFailed;
960
- const statusClean = errorStatus ? errorStatus : status({ message: errorMessage }) ?? void 0;
961
1218
  const cleanMessage = errorMessage;
962
1219
  const isOpaque = isOpaqueStatusBody(cleanMessage);
963
1220
  const isLimitStatus = isUsageLimitStatus(statusClean);
964
1221
  const reason = parseRateLimitReason(cleanMessage);
965
1222
  const is402BillingCap = statusClean === 402 && is402BillingCapBody(cleanMessage);
966
1223
  if (!(reason === "CONCURRENT_LIMIT" && statusClean !== 402) && (is402BillingCap || matchesUsageLimitText(cleanMessage) || (statusClean === 403 || statusClean === void 0) && isAccountScopedCapText(cleanMessage) || isLimitStatus && (isOpaque || reason === "QUOTA_EXHAUSTED"))) kinds |= Flag.UsageLimit;
967
- if (isTimeoutText(errorMessage)) kinds |= Flag.Transient | Flag.Timeout;
968
- else if (isTransientErrorText(errorMessage)) kinds |= Flag.Transient;
1224
+ if (!isTerminalClientErrorStatus(statusClean)) {
1225
+ if (isTimeoutText(errorMessage)) kinds |= Flag.Transient | Flag.Timeout;
1226
+ else if (isTransientErrorText(errorMessage)) kinds |= Flag.Transient;
1227
+ if (isTransientStreamParseError(errorMessage) || isTransientStreamDropError(errorMessage) || CODEX_HTTP_BODY_READ_ERROR_PATTERN.test(errorMessage)) kinds |= Flag.Transient;
1228
+ }
969
1229
  if (reason === "CONCURRENT_LIMIT") kinds |= Flag.Transient;
970
1230
  if ((api === "openai-responses" || api === "openai-codex-responses") && isStaleResponsesText(errorMessage)) kinds |= Flag.StaleResponsesItem;
1231
+ if (isCodexSteerRejection({
1232
+ api,
1233
+ errorMessage
1234
+ })) kinds |= Flag.Transient;
971
1235
  if (statusClean === 400 && GENERATION_NAN_PATTERN.test(cleanMessage)) kinds |= Flag.Transient;
972
1236
  if (matchesStrictToolsRejection(cleanMessage, statusClean)) kinds |= Flag.Grammar;
973
1237
  if (matchesFastModeUnsupported(cleanMessage, statusClean)) kinds |= Flag.FastModeUnsupported;
1238
+ if (matchesCacheControlRejection(cleanMessage, statusClean)) kinds |= Flag.CacheControlUnsupported;
974
1239
  }
975
1240
  const statusEvidence = errorStatus ?? (errorMessage ? status({ message: errorMessage }) : void 0);
976
1241
  if (statusEvidence === 413 && !priorTokenOverflowEvidence && !(errorMessage && hasTokenContextOverflowEvidence(errorMessage))) kinds |= Flag.PayloadRejected;
@@ -985,12 +1250,16 @@ function classify(error, api) {
985
1250
  const seen = /* @__PURE__ */ new Set();
986
1251
  const causeTokenEvidence = hasCauseTokenContextOverflowEvidence(error);
987
1252
  let link = error;
1253
+ let governingTerminalStatus;
988
1254
  while (link !== void 0 && link !== null) {
989
1255
  if (typeof link === "object") {
990
1256
  if (seen.has(link)) break;
991
1257
  seen.add(link);
992
1258
  if ("errorId" in link && typeof link.errorId === "number") kinds |= link.errorId & KIND_MASK;
993
- if ("code" in link && typeof link.code === "string" && ACCOUNT_POLICY_PATTERN.test(link.code)) kinds |= Flag.AccountPolicy | Flag.ContentBlocked;
1259
+ if ("code" in link && typeof link.code === "string") {
1260
+ if (ACCOUNT_POLICY_PATTERN.test(link.code)) kinds |= Flag.AccountPolicy | Flag.ContentBlocked;
1261
+ else if (ANTHROPIC_ACCOUNT_POLICY_PATTERN.test(link.code)) kinds |= Flag.AccountPolicy;
1262
+ }
994
1263
  }
995
1264
  if (link instanceof AwsCredentialsError) kinds |= Flag.AuthFailed;
996
1265
  else if (link instanceof AnthropicConnectionTimeoutError) kinds |= Flag.Timeout | Flag.Transient;
@@ -1000,8 +1269,9 @@ function classify(error, api) {
1000
1269
  else if (link instanceof ProviderHttpError) {
1001
1270
  let linkKinds = 0;
1002
1271
  const { status: codeStatus, code } = link;
1003
- if (code === "usage_limit_reached" || code === "insufficient_quota" && !isDashScopeTokenLimitText(link.message) || codeStatus === 402 && (code === "payment_required" || code === "deactivated_workspace" || is402BillingCapBody(link.message))) linkKinds |= Flag.UsageLimit;
1272
+ if (code === "usage_limit_reached" || code === "insufficient_quota" && !isDashScopeTokenLimitText(link.message) || codeStatus === 402 && (is402BillingCapBody(link.message) || code !== void 0 && !isOpaqueStatusBody(code) && is402BillingCapBody(code))) linkKinds |= Flag.UsageLimit;
1004
1273
  if (code === "overloaded_error" || code === "rate_limit_error") linkKinds |= Flag.Transient;
1274
+ if (code === "oauth_not_allowed_for_organization" || code === "permission_error" || codeStatus === 403 && ANTHROPIC_ACCOUNT_POLICY_PATTERN.test(link.message)) linkKinds |= Flag.AccountPolicy;
1005
1275
  if ((codeStatus === 401 || codeStatus === 403) && !(codeStatus === 403 && parseRateLimitReason(link.message) === "CONCURRENT_LIMIT")) linkKinds |= Flag.AuthFailed;
1006
1276
  else if (codeStatus === 429) {
1007
1277
  if ((linkKinds & Flag.UsageLimit) === 0) linkKinds |= Flag.Transient;
@@ -1011,9 +1281,14 @@ function classify(error, api) {
1011
1281
  let linkMessage;
1012
1282
  if (link instanceof Error) linkMessage = link.message;
1013
1283
  else if (typeof link === "string") linkMessage = link;
1014
- else if (typeof link === "object" && "message" in link && typeof link.message === "string") linkMessage = link.message;
1015
- const textId = classifyText(linkMessage, status(link), causeTokenEvidence, api);
1284
+ else if (typeof link === "object") {
1285
+ if ("message" in link && typeof link.message === "string") linkMessage = link.message;
1286
+ else if ("errorMessage" in link && typeof link.errorMessage === "string") linkMessage = link.errorMessage;
1287
+ }
1288
+ const linkStatus = status(link);
1289
+ const textId = classifyText(linkMessage, linkStatus ?? governingTerminalStatus, causeTokenEvidence, api);
1016
1290
  kinds |= textId & KIND_MASK;
1291
+ if (isTerminalClientErrorStatus(linkStatus)) governingTerminalStatus = linkStatus;
1017
1292
  link = typeof link === "object" && "cause" in link ? link.cause : void 0;
1018
1293
  }
1019
1294
  return kinds !== 0 ? create(kinds) : status(error) ?? 0;
@@ -1042,6 +1317,22 @@ function isGrammarError(error) {
1042
1317
  function isFastModeUnsupported(error) {
1043
1318
  return is(classify(error), Flag.FastModeUnsupported);
1044
1319
  }
1320
+ /**
1321
+ * An Anthropic-compatible endpoint returned a 400 rejecting something about
1322
+ * `cache_control`. Whether it refused the field or one nested option is not
1323
+ * decidable here (see {@link CACHE_CONTROL_FIELD_PATTERN}); the caller's retry
1324
+ * ladder settles it by replaying.
1325
+ * Accessor for {@link Flag.CacheControlUnsupported}.
1326
+ *
1327
+ * Unlike the high-bit accessors this also requires a classified id. The flag
1328
+ * sits in the raw-status lane (see {@link Flag.CacheControlUnsupported}), and
1329
+ * while no HTTP status reaches 0x800, `is` is a bare bit test — the extra gate
1330
+ * keeps a stray non-HTTP status out of the retry path.
1331
+ */
1332
+ function isCacheControlUnsupported(error) {
1333
+ const id = classify(error);
1334
+ return isClassified(id) && is(id, Flag.CacheControlUnsupported);
1335
+ }
1045
1336
  const CLINE_PASS_SURFACE_GATE_PATTERN = /only available via cline product surfaces/i;
1046
1337
  /**
1047
1338
  * Cline's gateway gates some roster entries (certain free-tier models) to its
@@ -1087,6 +1378,28 @@ function isTransientStreamParseError(error) {
1087
1378
  if (typeof error === "string") return STREAM_PARSE_DIAGNOSTIC_PATTERN.test(error);
1088
1379
  return error instanceof Error && STREAM_PARSE_TRUNCATION_PATTERN.test(error.message);
1089
1380
  }
1381
+ /**
1382
+ * Transport-level stream drops: the connection or upstream stream ended before a
1383
+ * terminal event, with no JSON-parse signal and no retryable status attached.
1384
+ *
1385
+ * Distinct from {@link STREAM_PARSE_TRUNCATION_PATTERN} (mid-body JSON
1386
+ * truncation) — these name the transport itself dropping (proxy/gateway closing
1387
+ * the SSE stream, socket dying before the TLS handshake completes). The wording
1388
+ * is the statusless twin of a `408 stream disconnected`, which the status path
1389
+ * already retries; an identical replay recovers it, so callers under a
1390
+ * non-terminal status treat it as transient (#11805).
1391
+ */
1392
+ const STREAM_DROP_PATTERN = /stream disconnected before completion|(?:stream|websocket) closed before response\.completed|stream was interrupted|stream ended before (?:terminal (?:chunk|completion event)|message_(?:start|delta|stop))|socket disconnected before secure tls connection/i;
1393
+ /**
1394
+ * Transport stream-drop diagnostic (see {@link STREAM_DROP_PATTERN}). Unlike
1395
+ * {@link isTransientStreamParseError}, one pattern serves both the live `Error`
1396
+ * and the persisted-string forms: the phrasings are high-signal enough to trust
1397
+ * detached from a transport `Error`.
1398
+ */
1399
+ function isTransientStreamDropError(error) {
1400
+ if (typeof error === "string") return STREAM_DROP_PATTERN.test(error);
1401
+ return error instanceof Error && STREAM_DROP_PATTERN.test(error.message);
1402
+ }
1090
1403
  /** Any malformed stream-envelope error (prefix-tagged or out-of-order events). */
1091
1404
  function isStreamEnvelopeError(error) {
1092
1405
  return error instanceof Error && (error.message.includes("Anthropic stream envelope error:") || STREAM_EVENT_ORDER_PATTERN.test(error.message));
@@ -1096,7 +1409,7 @@ function isRetryableStreamEnvelopeError(error) {
1096
1409
  return error instanceof Error && STREAM_EVENT_ORDER_PATTERN.test(error.message);
1097
1410
  }
1098
1411
  //#endregion
1099
- //#region node_modules/.pnpm/@oh-my-pi+pi-ai@18.1.17/node_modules/@oh-my-pi/pi-ai/src/error/abort.ts
1412
+ //#region node_modules/.pnpm/@oh-my-pi+pi-ai@18.12.6/node_modules/@oh-my-pi/pi-ai/src/error/abort.ts
1100
1413
  /**
1101
1414
  * A request was cancelled — by the caller's `AbortSignal` or a provider-local
1102
1415
  * watchdog. Carries the {@link Flag.Abort} classification structurally so retry
@@ -1114,7 +1427,7 @@ var AbortError = class extends Error {
1114
1427
  }
1115
1428
  };
1116
1429
  //#endregion
1117
- //#region node_modules/.pnpm/@oh-my-pi+pi-ai@18.1.17/node_modules/@oh-my-pi/pi-ai/src/error/auth.ts
1430
+ //#region node_modules/.pnpm/@oh-my-pi+pi-ai@18.12.6/node_modules/@oh-my-pi/pi-ai/src/error/auth.ts
1118
1431
  /**
1119
1432
  * No API key / credential was available to dispatch a request.
1120
1433
  *
@@ -1158,7 +1471,7 @@ var LoginCancelledError = class extends Error {
1158
1471
  }
1159
1472
  };
1160
1473
  //#endregion
1161
- //#region node_modules/.pnpm/@oh-my-pi+pi-ai@18.1.17/node_modules/@oh-my-pi/pi-ai/src/error/oauth.ts
1474
+ //#region node_modules/.pnpm/@oh-my-pi+pi-ai@18.12.6/node_modules/@oh-my-pi/pi-ai/src/error/oauth.ts
1162
1475
  /**
1163
1476
  * A failure inside an interactive OAuth / device-code login flow. The `kind`
1164
1477
  * pinpoints the stage. Timeout/polling are classified transient; everything
@@ -1179,7 +1492,259 @@ var OAuthError = class extends Error {
1179
1492
  }
1180
1493
  };
1181
1494
  //#endregion
1182
- //#region node_modules/.pnpm/@oh-my-pi+pi-ai@18.1.17/node_modules/@oh-my-pi/pi-ai/src/utils/retry-after.ts
1495
+ //#region node_modules/.pnpm/@oh-my-pi+pi-ai@18.12.6/node_modules/@oh-my-pi/pi-ai/src/error/provider.ts
1496
+ /**
1497
+ * A non-HTTP provider failure: a truncated stream, an error stop reason, an
1498
+ * empty body, a malformed envelope, or a runtime fault. For non-2xx HTTP
1499
+ * responses use {@link ProviderHttpError} (or a provider subclass) instead.
1500
+ */
1501
+ var ProviderResponseError = class extends Error {
1502
+ provider;
1503
+ kind;
1504
+ constructor(message, options = {}) {
1505
+ super(message, options.cause === void 0 ? void 0 : { cause: options.cause });
1506
+ this.name = "ProviderResponseError";
1507
+ this.provider = options.provider;
1508
+ this.kind = options.kind ?? "output";
1509
+ if (this.kind === "content-blocked") attach(this, create(Flag.ContentBlocked));
1510
+ else if (this.kind === "empty-output") attach(this, create(Flag.Transient, Flag.EmptyResponse));
1511
+ else if (this.kind === "incomplete-stream" || this.kind === "empty-body") attach(this, create(Flag.Transient));
1512
+ }
1513
+ };
1514
+ /** Non-2xx response from the Devin API. */
1515
+ var DevinApiError = class extends ProviderHttpError {
1516
+ name = "DevinApiError";
1517
+ };
1518
+ //#endregion
1519
+ //#region node_modules/.pnpm/@oh-my-pi+pi-ai@18.12.6/node_modules/@oh-my-pi/pi-ai/src/error/body-error.ts
1520
+ /**
1521
+ * In-band provider failures: upstream 429/5xx payloads that arrive inside an
1522
+ * HTTP 200 response body, or mid-stream after the SSE headers were already sent.
1523
+ *
1524
+ * Providers that front a retry/queue layer — Azure OpenAI, LiteLLM-style
1525
+ * aggregators, Bedrock-compatible shims — answer a throttled request with
1526
+ * `200 OK` + `text/event-stream` and put the real status in the payload:
1527
+ * `data: {"error":{"type":"rate_limit_error"}}`, `data: {"code":429}`, or a bare
1528
+ * non-JSON frame such as `data: 429 Too Many Requests` / an nginx throttle page.
1529
+ * Those bodies used to be either dropped silently (the stream then looked like a
1530
+ * successful empty completion) or surfaced as an unclassified
1531
+ * {@link ProviderResponseError} whose `errorId` stayed 0 — so `AIError.retriable`
1532
+ * answered "terminal" and `retry.fallbackChains` never advanced, pinning the
1533
+ * session on a provider that was merely busy.
1534
+ *
1535
+ * Both probes hand the classifier the structured signal it already trusts for an
1536
+ * out-of-band failure: a {@link ProviderHttpError} carrying a status the upstream
1537
+ * *reported*, so the two routes reach one code path. The invariants that make
1538
+ * that safe, and that callers rely on:
1539
+ *
1540
+ * - **No invented HTTP metadata.** A status is taken only from an error
1541
+ * `status`/`code` *field* (numeric, or a 3-digit string as compat hosts send)
1542
+ * or from {@link RETRYABLE_STATUS_BY_CODE}, and only for `429`/`5xx`. A status
1543
+ * mentioned inside error prose is never promoted to a status: 401/403 wording
1544
+ * stays text and cannot route the failure into the auth-retry lane.
1545
+ * - **No credential rotation on an unreadable body.** A `429` whose body is
1546
+ * empty, `{}`, or framing-only is opaque, and an opaque 429 is the conservative
1547
+ * rotate-to-a-sibling-credential signal. That verdict belongs to a body the
1548
+ * server actually sent, not to a payload we synthesised, so
1549
+ * {@link formatInBandMessage} substitutes {@link IN_BAND_DETAIL_PLACEHOLDER}
1550
+ * and the composed message always stays informative.
1551
+ * - **Unknown envelopes fall through.** Anything without a retryable status
1552
+ * field, a retryable code, or unambiguous throttle wording returns
1553
+ * `undefined` and keeps its pre-existing handling and message.
1554
+ *
1555
+ * Where no numeric status exists, the returned error keeps the upstream wording
1556
+ * and code visible in its message instead of asserting HTTP metadata the provider
1557
+ * never sent, and carries {@link Flag.Transient} directly: throttle spellings like
1558
+ * `Throttled` / `Please retry` are what this probe recognises but the transport
1559
+ * text pattern is not required to match, so the retry decision is stated rather
1560
+ * than hoped for.
1561
+ */
1562
+ /** Cap on synthesized message length, mirroring the transport-level `MAX_DETAIL_CHARS`. */
1563
+ const MAX_IN_BAND_DETAIL_CHARS = 4096;
1564
+ /**
1565
+ * Filler used when an in-band failure frame carries no readable detail. It has
1566
+ * to be informative prose on purpose: an opaque message on a `429` is read as
1567
+ * "the server gave us nothing" and rotates a credential, and that judgement must
1568
+ * never be triggered by wording of ours.
1569
+ */
1570
+ const IN_BAND_DETAIL_PLACEHOLDER = "Provider returned an in-band provider error";
1571
+ /**
1572
+ * Machine error codes that mean "shed this request and back off", mapped to the
1573
+ * HTTP status the upstream would have used had it not wrapped the failure in a
1574
+ * 200. Keys are compared after lower-casing, splitting camel/Pascal word
1575
+ * boundaries, and collapsing `_`/`-`/`.`/spaces, so `Throttling.AllocationQuota`
1576
+ * and `ThrottlingAllocationQuota` both resolve. The list is deliberately limited
1577
+ * to throttle/overload spellings so an unlisted code keeps its pre-existing
1578
+ * classification: request-validation failures (`invalid_request_error`) and
1579
+ * account caps (`insufficient_quota`, `usage_limit_reached`) are never
1580
+ * reinterpreted as retries.
1581
+ */
1582
+ const RETRYABLE_STATUS_BY_CODE = {
1583
+ rate_limit_error: 429,
1584
+ rate_limit_exceeded: 429,
1585
+ rate_limit: 429,
1586
+ rate_limit_reached: 429,
1587
+ rate_limited: 429,
1588
+ ratelimit: 429,
1589
+ too_many_requests: 429,
1590
+ request_throttled: 429,
1591
+ throttled: 429,
1592
+ throttling: 429,
1593
+ throttling_error: 429,
1594
+ throttling_exception: 429,
1595
+ throttling_allocation_quota: 429,
1596
+ request_limit_exceeded: 429,
1597
+ retry_later: 429,
1598
+ overloaded_error: 503,
1599
+ server_overloaded: 503,
1600
+ model_overloaded: 503,
1601
+ overloaded: 503,
1602
+ service_unavailable: 503,
1603
+ server_busy: 503,
1604
+ high_demand: 503,
1605
+ capacity_exceeded: 503
1606
+ };
1607
+ /**
1608
+ * Wording that identifies a throttle/overload in an error code or body. Kept as
1609
+ * an explicit list rather than reusing the classifier's pattern, so this probe
1610
+ * stays independent of the text rules it feeds.
1611
+ */
1612
+ const IN_BAND_RETRYABLE_TEXT_PATTERN = /\brate.?limit|too many requests|too\s+many\s+concurren|service.{0,20}unavailable|temporarily\s+unavailable|server.?error|internal.?error|overloaded|capacity|throttl|retry\s+(?:your\s+)?request|please\s+retry/i;
1613
+ /**
1614
+ * A status in the position a proxy error page puts it: the very first token,
1615
+ * optionally after an `HTTP/1.1 ` prefix. Delimited by a word boundary so
1616
+ * identifiers (`chatcmpl-500321`, `gpt-500x`, `req500502`) cannot fabricate a
1617
+ * status — the same hazard `error-transient-status-boundary.test.ts` guards.
1618
+ * Prose that merely *mentions* a number (`Too many requests (401 from …)`) is
1619
+ * deliberately not read as status metadata.
1620
+ */
1621
+ const LEADING_STATUS_PATTERN = /^\s*(?:HTTP[/.]\d(?:\.\d)?\s+)?([45]\d{2})(?:\b|$)/i;
1622
+ /** Codes that mean a persistent account/billing cap or a bad request; never shed-and-retry. */
1623
+ const NON_RETRYABLE_CODE_PATTERN = /insufficient.?quota|usage.?limit|quota.?(?:exceeded|reached|insufficient)|invalid_request|content_filter|context_length|context_window|billing|balance/i;
1624
+ /** Flags this module asserts for a body it has itself recognised as shed-and-retry. */
1625
+ const IN_BAND_FLAGS = create(Flag.Transient);
1626
+ function normalizeCodeToken(value) {
1627
+ if (typeof value === "number" && Number.isFinite(value)) return String(value);
1628
+ if (typeof value !== "string") return void 0;
1629
+ const collapsed = value.trim().replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase().replace(/[-.\s]+/g, "_").replace(/_+/g, "_");
1630
+ return collapsed.length > 0 ? collapsed : void 0;
1631
+ }
1632
+ function readInBandDetail(value) {
1633
+ if (typeof value !== "string") return void 0;
1634
+ const flattened = value.replace(/<[^>]*>/g, " ").replace(/[\u0000-\u001f\u007f]+/g, " ").replace(/\s+/g, " ").trim();
1635
+ if (flattened.length === 0) return void 0;
1636
+ return flattened.length > MAX_IN_BAND_DETAIL_CHARS ? flattened.slice(0, MAX_IN_BAND_DETAIL_CHARS) : flattened;
1637
+ }
1638
+ /** A numeric HTTP status field, accepting the `"429"` string form compat hosts emit. */
1639
+ function readStatusField(value) {
1640
+ if (typeof value === "number" && Number.isInteger(value) && value >= 100 && value <= 599) return value;
1641
+ if (typeof value === "string" && /^\d{3}$/.test(value.trim())) {
1642
+ const parsed = Number(value.trim());
1643
+ return Number.isInteger(parsed) && parsed >= 100 && parsed <= 599 ? parsed : void 0;
1644
+ }
1645
+ }
1646
+ /** Only `429` and genuine server faults are shed-and-retry; 4xx of any other kind is not. */
1647
+ function isRetryableStatus(status) {
1648
+ return status === 429 || status !== void 0 && status >= 500 && status <= 599;
1649
+ }
1650
+ function readLeadingStatus(text) {
1651
+ if (text === void 0) return void 0;
1652
+ const match = LEADING_STATUS_PATTERN.exec(text);
1653
+ return match?.[1] ? Number(match[1]) : void 0;
1654
+ }
1655
+ /**
1656
+ * Pull the failure signal out of an OpenAI-wire frame. Accepts the nested
1657
+ * `{ error: { code, type, status, message } }` shape, the `response.error`
1658
+ * position of Responses-API terminal events, the flat `{ code, status, message }`
1659
+ * bodies compat hosts emit, and string envelopes (`{ error: "..." }`).
1660
+ *
1661
+ * `undefined` means "not an in-band failure worth retrying". The probe is
1662
+ * intentionally narrow: a bare `type` is present on every Responses event and so
1663
+ * never counts as a signal by itself, statuses come only from fields (never from
1664
+ * prose), and a frame with neither a retryable status nor throttle wording is
1665
+ * left to the caller's existing handling.
1666
+ */
1667
+ function readInBandSignal(frame) {
1668
+ if (typeof frame !== "object" || frame === null || Array.isArray(frame)) return void 0;
1669
+ const root = frame;
1670
+ const nested = root.error ?? root.response?.error;
1671
+ let error;
1672
+ if (typeof nested === "object" && nested !== null) {
1673
+ error = nested;
1674
+ for (let depth = 0; depth < 2; depth++) {
1675
+ const inner = error.error;
1676
+ if (typeof inner !== "object" || inner === null) break;
1677
+ error = inner;
1678
+ }
1679
+ }
1680
+ const code = normalizeCodeToken(error?.code ?? root.code) ?? normalizeCodeToken(error?.type ?? root.type);
1681
+ if (code !== void 0 && NON_RETRYABLE_CODE_PATTERN.test(code)) return void 0;
1682
+ if (!(code !== void 0 && IN_BAND_RETRYABLE_TEXT_PATTERN.test(code)) && nested === void 0 && root.status === void 0 && root.code === void 0 && root.message === void 0) return;
1683
+ const detail = readInBandDetail(error?.message) ?? readInBandDetail(root.message) ?? (typeof nested === "string" ? readInBandDetail(nested) : void 0);
1684
+ const holder = error ?? root;
1685
+ const status = (holder === root ? [root.status, root.code] : [
1686
+ holder.status,
1687
+ holder.code,
1688
+ root.status,
1689
+ root.code
1690
+ ]).map(readStatusField).find(isRetryableStatus) ?? (code !== void 0 && Object.hasOwn(RETRYABLE_STATUS_BY_CODE, code) ? readStatusField(RETRYABLE_STATUS_BY_CODE[code]) : void 0);
1691
+ if (status !== void 0) return {
1692
+ status,
1693
+ code,
1694
+ detail
1695
+ };
1696
+ if (detail === void 0 || !IN_BAND_RETRYABLE_TEXT_PATTERN.test(detail)) return void 0;
1697
+ return {
1698
+ code,
1699
+ detail
1700
+ };
1701
+ }
1702
+ /**
1703
+ * Compose the message for a status-bearing in-band failure. The numeric status
1704
+ * leads (matching `captureOpenAIHttpError`'s `"<status> <detail>"` phrasing)
1705
+ * unless the detail already carries it as its leading token; the machine code is
1706
+ * appended only when it adds information the text classifier or a human reader
1707
+ * can use. A detail that leaves the whole line opaque is replaced by the
1708
+ * placeholder, so a body we generated can never be read as "the server said
1709
+ * nothing".
1710
+ */
1711
+ function formatInBandMessage(status, detail, code) {
1712
+ const body = detail ?? IN_BAND_DETAIL_PLACEHOLDER;
1713
+ const suffix = code !== void 0 && !/^\d+$/.test(code) && !body.toLowerCase().includes(code.toLowerCase()) ? ` (${code})` : "";
1714
+ let message = readLeadingStatus(body) === status ? body : `${status} ${body}`;
1715
+ if (isOpaqueStatusBody(message)) message = `${status} ${IN_BAND_DETAIL_PLACEHOLDER}`;
1716
+ return `${message}${suffix}`;
1717
+ }
1718
+ /**
1719
+ * Build the classified error for an in-band failure frame, or `undefined` when
1720
+ * the frame is not a retryable in-band failure (in which case the caller keeps
1721
+ * its existing handling and message).
1722
+ *
1723
+ * @param frame decoded SSE `data:` payload, or the `{ error, response }` subset of one
1724
+ */
1725
+ function createInBandProviderError(frame) {
1726
+ const signal = readInBandSignal(frame);
1727
+ if (!signal) return void 0;
1728
+ const { status, code, detail } = signal;
1729
+ if (isRetryableStatus(status)) return attach(new ProviderHttpError(formatInBandMessage(status, detail, code), status, { code }), IN_BAND_FLAGS);
1730
+ if (detail === void 0 && code === void 0) return void 0;
1731
+ return attach(new ProviderResponseError(`${detail ?? IN_BAND_DETAIL_PLACEHOLDER}${code ? ` (${code})` : ""}`, { kind: "runtime" }), IN_BAND_FLAGS);
1732
+ }
1733
+ /**
1734
+ * Build the classified error for a non-JSON SSE frame: gateways and reverse
1735
+ * proxies that answer `data: 429 Too Many Requests` or an HTML throttle page
1736
+ * instead of an OpenAI envelope. `undefined` when the text is not recognisable
1737
+ * as a throttle, so genuinely malformed payloads keep failing loudly.
1738
+ */
1739
+ function createInBandProviderErrorFromText(text) {
1740
+ const detail = readInBandDetail(text);
1741
+ if (detail === void 0 || !IN_BAND_RETRYABLE_TEXT_PATTERN.test(detail)) return void 0;
1742
+ const status = readLeadingStatus(detail);
1743
+ if (isRetryableStatus(status)) return attach(new ProviderHttpError(formatInBandMessage(status, detail, void 0), status), IN_BAND_FLAGS);
1744
+ return attach(new ProviderResponseError(detail, { kind: "runtime" }), IN_BAND_FLAGS);
1745
+ }
1746
+ //#endregion
1747
+ //#region node_modules/.pnpm/@oh-my-pi+pi-ai@18.12.6/node_modules/@oh-my-pi/pi-ai/src/utils/retry-after.ts
1183
1748
  const RETRY_AFTER_HINT = "retry-after-ms=";
1184
1749
  function formatErrorMessageWithRetryAfter(error, headers) {
1185
1750
  const message = error instanceof Error ? error.message : JSON.stringify(error);
@@ -1255,15 +1820,17 @@ function parseResetHeader(value, unit) {
1255
1820
  return Math.ceil(targetMs - nowMs);
1256
1821
  }
1257
1822
  //#endregion
1258
- //#region node_modules/.pnpm/@oh-my-pi+pi-ai@18.1.17/node_modules/@oh-my-pi/pi-ai/src/utils/http-inspector.ts
1259
- const SENSITIVE_HEADERS = [
1260
- "authorization",
1261
- "x-api-key",
1262
- "api-key",
1263
- "cookie",
1264
- "set-cookie",
1265
- "proxy-authorization"
1266
- ];
1823
+ //#region node_modules/.pnpm/@oh-my-pi+pi-ai@18.12.6/node_modules/@oh-my-pi/pi-ai/src/utils/http-inspector.ts
1824
+ /**
1825
+ * Matches any header name whose value carries a credential, so a persisted dump
1826
+ * never leaks one. A substring match — not a hand-maintained allow-list — so
1827
+ * provider-specific auth headers (`x-goog-api-key`, `x-amz-security-token`, …)
1828
+ * are redacted without enumerating every provider's spelling; the fixed list it
1829
+ * replaced silently leaked any auth header it did not name. Redacting a benign
1830
+ * header that happens to match is harmless: dumps exist to diagnose the request
1831
+ * body, not its transport headers.
1832
+ */
1833
+ const SENSITIVE_HEADER_PATTERN = /key|token|secret|auth|credential|cookie/i;
1267
1834
  /**
1268
1835
  * Build the JSON persisted for a rejected request. Request fields stay at the
1269
1836
  * top level (so existing dump parsers still read `body`); the provider's error
@@ -1289,16 +1856,72 @@ function shouldDumpRejectedRequest(error) {
1289
1856
  const status$1 = status(error);
1290
1857
  return status$1 === 400 || status$1 === 413;
1291
1858
  }
1859
+ const RAW_HTTP_REQUEST_LINE = "raw-http-request=";
1860
+ const RAW_HTTP_REQUEST_SAVE_FAILED_LINE = "raw-http-request-save-failed=";
1292
1861
  async function appendRawHttpRequestDumpFor400(message, error, dump) {
1293
1862
  if (!dump || isBunTestRuntime() || !shouldDumpRejectedRequest(error)) return message;
1294
1863
  const payload = buildHttp400DumpPayload(dump, error, message);
1295
1864
  const fileName = `${Date.now()}-${Bun.hash(JSON.stringify(payload)).toString(36)}.json`;
1296
- const filePath = path$1.join(getLogsDir(), "http-400-requests", fileName);
1865
+ const dumpDir = path$1.join(getLogsDir(), HTTP_DUMP_DIR_NAME);
1866
+ const filePath = path$1.join(dumpDir, fileName);
1297
1867
  try {
1298
1868
  await Bun.write(filePath, `${JSON.stringify(payload, null, 2)}\n`);
1299
- return `${message}\nraw-http-request=${filePath}`;
1869
+ pruneHttpRequestDumps(dumpDir, { keep: fileName }).catch((err) => {
1870
+ warn("Failed to prune HTTP request dumps", {
1871
+ dir: dumpDir,
1872
+ err
1873
+ });
1874
+ });
1875
+ return `${message}\n${RAW_HTTP_REQUEST_LINE}${filePath}`;
1300
1876
  } catch (writeError) {
1301
- return `${message}\nraw-http-request-save-failed=${writeError instanceof Error ? writeError.message : String(writeError)}`;
1877
+ const writeMessage = writeError instanceof Error ? writeError.message : String(writeError);
1878
+ return `${message}\n${RAW_HTTP_REQUEST_SAVE_FAILED_LINE}${writeMessage}`;
1879
+ }
1880
+ }
1881
+ const HTTP_DUMP_DIR_NAME = "http-400-requests";
1882
+ const HTTP_DUMP_MAX_AGE_MS = 6048e5;
1883
+ const HTTP_DUMP_MAX_TOTAL_BYTES = 67108864;
1884
+ /**
1885
+ * Bound the rejected-request dump directory: delete `*.json` dumps older than
1886
+ * the age limit, then the oldest survivors until the total fits the size cap.
1887
+ */
1888
+ async function pruneHttpRequestDumps(dir, retention = {}) {
1889
+ const maxAgeMs = retention.maxAgeMs ?? HTTP_DUMP_MAX_AGE_MS;
1890
+ const maxTotalBytes = retention.maxTotalBytes ?? HTTP_DUMP_MAX_TOTAL_BYTES;
1891
+ const now = retention.now ?? Date.now();
1892
+ let names;
1893
+ try {
1894
+ names = await fs_promises_shim_exports.readdir(dir);
1895
+ } catch (err) {
1896
+ if (isEnoent(err)) return;
1897
+ throw err;
1898
+ }
1899
+ const dumps = [];
1900
+ for (const name of names) {
1901
+ if (!name.endsWith(".json")) continue;
1902
+ try {
1903
+ const stat = await fs_promises_shim_exports.stat(path$1.join(dir, name));
1904
+ if (stat.isFile()) dumps.push({
1905
+ name,
1906
+ mtimeMs: stat.mtimeMs,
1907
+ size: stat.size
1908
+ });
1909
+ } catch (err) {
1910
+ if (!isEnoent(err)) throw err;
1911
+ }
1912
+ }
1913
+ dumps.sort((a, b) => b.mtimeMs - a.mtimeMs || (a.name < b.name ? 1 : a.name > b.name ? -1 : 0));
1914
+ let retainedBytes = 0;
1915
+ for (const dump of dumps) {
1916
+ if (dump.name === retention.keep) {
1917
+ retainedBytes += dump.size;
1918
+ continue;
1919
+ }
1920
+ if (now - dump.mtimeMs <= maxAgeMs && retainedBytes + dump.size <= maxTotalBytes) {
1921
+ retainedBytes += dump.size;
1922
+ continue;
1923
+ }
1924
+ await fs_promises_shim_exports.rm(path$1.join(dir, dump.name), { force: true });
1302
1925
  }
1303
1926
  }
1304
1927
  async function finalizeErrorMessage(error, rawRequestDump, capturedErrorResponse) {
@@ -1360,14 +1983,34 @@ function rewriteClinePassError(errorMessage, provider) {
1360
1983
  function sanitizeDump(dump) {
1361
1984
  return {
1362
1985
  ...dump,
1986
+ url: redactUrlQuery(dump.url),
1363
1987
  headers: redactHeaders(dump.headers)
1364
1988
  };
1365
1989
  }
1990
+ /**
1991
+ * Strips a persisted dump's query string entirely rather than picking sensitive
1992
+ * params by name: a configurable `baseUrl` (e.g. Bedrock's gateway routing) can
1993
+ * carry an arbitrary query-based credential the way `SENSITIVE_HEADER_PATTERN`
1994
+ * matches arbitrary header names, and dumps exist to diagnose the request body,
1995
+ * not the query.
1996
+ */
1997
+ function redactUrlQuery(url) {
1998
+ if (!url) return url;
1999
+ try {
2000
+ const parsed = new URL(url);
2001
+ if (!parsed.search) return url;
2002
+ parsed.search = "";
2003
+ return `${parsed.toString()}[redacted-query]`;
2004
+ } catch {
2005
+ return url;
2006
+ }
2007
+ }
2008
+ /** Copy of `headers` with every credential value replaced by `[redacted]`; shared by every persisted dump. */
1366
2009
  function redactHeaders(headers) {
1367
2010
  if (!headers) return;
1368
2011
  const redacted = {};
1369
2012
  for (const [key, value] of Object.entries(headers)) {
1370
- if (SENSITIVE_HEADERS.includes(key.toLowerCase())) {
2013
+ if (SENSITIVE_HEADER_PATTERN.test(key)) {
1371
2014
  redacted[key] = "[redacted]";
1372
2015
  continue;
1373
2016
  }
@@ -1396,28 +2039,25 @@ function formatCapturedHttpError(captured) {
1396
2039
  return extras.length > 0 ? `${message} (${extras.join(" ")})` : message;
1397
2040
  }
1398
2041
  function parseCapturedErrorPayload(captured) {
1399
- if (isObject(captured.bodyJson)) return captured.bodyJson;
2042
+ if (isRecord(captured.bodyJson)) return captured.bodyJson;
1400
2043
  if (!captured.bodyText) return void 0;
1401
2044
  try {
1402
2045
  const parsed = JSON.parse(captured.bodyText);
1403
- return isObject(parsed) ? parsed : void 0;
2046
+ return isRecord(parsed) ? parsed : void 0;
1404
2047
  } catch {
1405
2048
  return;
1406
2049
  }
1407
2050
  }
1408
2051
  function getObjectProperty(value, key) {
1409
2052
  const property = value[key];
1410
- return isObject(property) ? property : void 0;
2053
+ return isRecord(property) ? property : void 0;
1411
2054
  }
1412
2055
  function getStringProperty(value, key) {
1413
2056
  const property = value[key];
1414
2057
  return typeof property === "string" && property.trim().length > 0 ? property : void 0;
1415
2058
  }
1416
- function isObject(value) {
1417
- return typeof value === "object" && value !== null && !Array.isArray(value);
1418
- }
1419
2059
  //#endregion
1420
- //#region node_modules/.pnpm/@oh-my-pi+pi-ai@18.1.17/node_modules/@oh-my-pi/pi-ai/src/error/format.ts
2060
+ //#region node_modules/.pnpm/@oh-my-pi+pi-ai@18.12.6/node_modules/@oh-my-pi/pi-ai/src/error/format.ts
1421
2061
  function rewriteOllamaToolCallJsonError(message) {
1422
2062
  if (!LLAMA_CPP_TOOL_CALL_PARSE_PATTERN.test(message)) return message;
1423
2063
  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}`;
@@ -1439,7 +2079,7 @@ async function formatMessage(error, opts = {}) {
1439
2079
  return message;
1440
2080
  }
1441
2081
  //#endregion
1442
- //#region node_modules/.pnpm/@oh-my-pi+pi-ai@18.1.17/node_modules/@oh-my-pi/pi-ai/src/error/finalize.ts
2082
+ //#region node_modules/.pnpm/@oh-my-pi+pi-ai@18.12.6/node_modules/@oh-my-pi/pi-ai/src/error/finalize.ts
1443
2083
  /**
1444
2084
  * Build the complete error bundle for a provider catch block, replacing the
1445
2085
  * `stopReason` / `errorStatus` / `errorId` / `errorMessage` boilerplate.
@@ -1451,19 +2091,24 @@ async function formatMessage(error, opts = {}) {
1451
2091
  */
1452
2092
  async function finalize(error, opts = {}) {
1453
2093
  const aborted = opts.abortTracker ? opts.abortTracker.wasCallerAbort() : opts.signal?.aborted === true;
1454
- const currentStatus = status(error) ?? opts.capturedErrorResponse?.status;
2094
+ const errorStatus = status(error);
2095
+ const currentStatus = errorStatus ?? opts.capturedErrorResponse?.status;
1455
2096
  let message;
1456
2097
  try {
1457
2098
  message = (opts.abortTracker?.getLocalAbortReason())?.message ?? await formatMessage(error, opts);
1458
2099
  } catch {
1459
2100
  message = error instanceof Error ? error.message : String(error);
1460
2101
  }
2102
+ const classificationError = errorStatus === void 0 && currentStatus !== void 0 ? {
2103
+ status: currentStatus,
2104
+ cause: error
2105
+ } : error;
1461
2106
  return {
1462
2107
  id: classifyMessage({
1463
2108
  api: opts.api,
1464
2109
  provider: opts.provider,
1465
2110
  model: opts.model,
1466
- errorId: classify(error, opts.api),
2111
+ errorId: classify(classificationError, opts.api),
1467
2112
  errorMessage: message,
1468
2113
  errorStatus: currentStatus
1469
2114
  }),
@@ -1473,31 +2118,7 @@ async function finalize(error, opts = {}) {
1473
2118
  };
1474
2119
  }
1475
2120
  //#endregion
1476
- //#region node_modules/.pnpm/@oh-my-pi+pi-ai@18.1.17/node_modules/@oh-my-pi/pi-ai/src/error/provider.ts
1477
- /**
1478
- * A non-HTTP provider failure: a truncated stream, an error stop reason, an
1479
- * empty body, a malformed envelope, or a runtime fault. For non-2xx HTTP
1480
- * responses use {@link ProviderHttpError} (or a provider subclass) instead.
1481
- */
1482
- var ProviderResponseError = class extends Error {
1483
- provider;
1484
- kind;
1485
- constructor(message, options = {}) {
1486
- super(message, options.cause === void 0 ? void 0 : { cause: options.cause });
1487
- this.name = "ProviderResponseError";
1488
- this.provider = options.provider;
1489
- this.kind = options.kind ?? "output";
1490
- if (this.kind === "content-blocked") attach(this, create(Flag.ContentBlocked));
1491
- else if (this.kind === "empty-output") attach(this, create(Flag.Transient, Flag.EmptyResponse));
1492
- else if (this.kind === "incomplete-stream" || this.kind === "empty-body") attach(this, create(Flag.Transient));
1493
- }
1494
- };
1495
- /** Non-2xx response from the Devin API. */
1496
- var DevinApiError = class extends ProviderHttpError {
1497
- name = "DevinApiError";
1498
- };
1499
- //#endregion
1500
- //#region node_modules/.pnpm/@oh-my-pi+pi-ai@18.1.17/node_modules/@oh-my-pi/pi-ai/src/error/retryable.ts
2121
+ //#region node_modules/.pnpm/@oh-my-pi+pi-ai@18.12.6/node_modules/@oh-my-pi/pi-ai/src/error/retryable.ts
1501
2122
  /**
1502
2123
  * Whether a numeric HTTP status is in the canonical transient/retryable set:
1503
2124
  * 408 (Request Timeout), 429 (Too Many Requests), and any 5xx.
@@ -1529,11 +2150,11 @@ function isProviderRetryableError(error) {
1529
2150
  const httpStatus = status(error);
1530
2151
  if (httpStatus !== void 0 && httpStatus >= 400 && httpStatus < 500 && httpStatus !== 408 && httpStatus !== 429) return false;
1531
2152
  const msg = error.message.toLowerCase();
1532
- if (isUnexpectedSocketCloseMessage(msg) || isTransientTransportMessage(msg) || TRANSIENT_TRANSPORT_PATTERN.test(msg) || PROVIDER_TRANSIENT_EXTRA_PATTERN.test(msg) || isTransientStreamParseError(error) || isRetryableStreamEnvelopeError(error)) return true;
2153
+ if (isUnexpectedSocketCloseMessage(msg) || isTransientTransportMessage(msg) || TRANSIENT_TRANSPORT_PATTERN.test(msg) || CODEX_HTTP_BODY_READ_ERROR_PATTERN.test(msg) || PROVIDER_TRANSIENT_EXTRA_PATTERN.test(msg) || isTransientStreamParseError(error) || isTransientStreamDropError(error) || isRetryableStreamEnvelopeError(error)) return true;
1533
2154
  return isRetryableError(error);
1534
2155
  }
1535
2156
  //#endregion
1536
- //#region node_modules/.pnpm/@oh-my-pi+pi-ai@18.1.17/node_modules/@oh-my-pi/pi-ai/src/error/validation.ts
2157
+ //#region node_modules/.pnpm/@oh-my-pi+pi-ai@18.12.6/node_modules/@oh-my-pi/pi-ai/src/error/validation.ts
1537
2158
  /**
1538
2159
  * Caller-supplied input failed validation before/while building a provider
1539
2160
  * request: bad request body, malformed tool arguments, unsupported content
@@ -1567,4 +2188,4 @@ var StreamTimeoutError = class extends Error {
1567
2188
  }
1568
2189
  };
1569
2190
  //#endregion
1570
- export { isTransientStreamParseError as A, OpenAIHttpError as B, classify as C, isFastModeUnsupported as D, is 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, attach as S, create as T, EventStreamFrameError as U, ProviderHttpError as V, extractHttpStatusFromError as W, AbortError as _, isTransientStatus as a, PYTHON_HTTP2_STREAM_RESET_PATTERN as b, finalize as c, getRetryAfterMsFromHeaders as d, OAuthError as f, OnPromptRequiredError as g, MissingApiKeyError as h, isProviderRetryableError as i, retriable as j, isStreamEnvelopeError as k, finalizeErrorMessage as l, LoginCancelledError as m, StreamTimeoutError as n, DevinApiError as o, ApiKeyRequiredError as p, ValidationError as r, ProviderResponseError as s, ConfigurationError as t, getHeadersFromError as u, Flag as v, classifyMessage as w, PYTHON_HTTP_INCOMPLETE_CHUNK_PATTERN as x, LLAMA_CPP_TOOL_CALL_PARSE_PATTERN as y, OllamaApiError as z };
2191
+ export { EventStreamFrameError as $, is as A, isUsageLimitStatus as B, LLAMA_CPP_TOOL_CALL_PARSE_PATTERN as C, classify as D, attach as E, isRequestBodyReadTimeout as F, BedrockApiError as G, AnthropicConnectionError as H, isStreamEnvelopeError as I, OllamaApiError as J, GeminiCliApiError as K, isTransientStreamParseError as L, isFastModeUnsupported as M, isGrammarError as N, classifyMessage as O, isImmutableLatestThinkingText as P, AwsCredentialsError as Q, retriable as R, Flag as S, PYTHON_HTTP_INCOMPLETE_CHUNK_PATTERN as T, AnthropicConnectionTimeoutError as U, AnthropicApiError as V, AnthropicStreamEnvelopeError as W, ProviderHttpError as X, OpenAIHttpError as Y, networkFailureError as Z, LoginCancelledError as _, isTransientStatus as a, AbortError as b, redactHeaders as c, createInBandProviderError as d, extractHttpStatusFromError as et, createInBandProviderErrorFromText as f, ApiKeyRequiredError as g, OAuthError as h, isProviderRetryableError as i, isCacheControlUnsupported as j, create as k, getHeadersFromError as l, ProviderResponseError as m, StreamTimeoutError as n, fetchWithRetry as nt, finalize as o, DevinApiError as p, GoogleApiError as q, ValidationError as r, isUnexpectedSocketCloseMessage as rt, finalizeErrorMessage as s, ConfigurationError as t, extractRetryHint as tt, getRetryAfterMsFromHeaders as u, MissingApiKeyError as v, PYTHON_HTTP2_STREAM_RESET_PATTERN as w, CODEX_NATIVE_LANE_STEER_REJECTED_CODE as x, OnPromptRequiredError as y, status as z };