@asm-agent/ai 0.8.2

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 (210) hide show
  1. package/README.md +1333 -0
  2. package/dist/api-registry.d.ts +20 -0
  3. package/dist/api-registry.d.ts.map +1 -0
  4. package/dist/api-registry.js +44 -0
  5. package/dist/api-registry.js.map +1 -0
  6. package/dist/bedrock-provider.d.ts +5 -0
  7. package/dist/bedrock-provider.d.ts.map +1 -0
  8. package/dist/bedrock-provider.js +6 -0
  9. package/dist/bedrock-provider.js.map +1 -0
  10. package/dist/cache-pricing.d.ts +13 -0
  11. package/dist/cache-pricing.d.ts.map +1 -0
  12. package/dist/cache-pricing.js +38 -0
  13. package/dist/cache-pricing.js.map +1 -0
  14. package/dist/cli.d.ts +3 -0
  15. package/dist/cli.d.ts.map +1 -0
  16. package/dist/cli.js +116 -0
  17. package/dist/cli.js.map +1 -0
  18. package/dist/env-api-keys.d.ts +19 -0
  19. package/dist/env-api-keys.d.ts.map +1 -0
  20. package/dist/env-api-keys.js +180 -0
  21. package/dist/env-api-keys.js.map +1 -0
  22. package/dist/index.d.ts +30 -0
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +18 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/log.d.ts +27 -0
  27. package/dist/log.d.ts.map +1 -0
  28. package/dist/log.js +69 -0
  29. package/dist/log.js.map +1 -0
  30. package/dist/mcp/catalog.d.ts +19 -0
  31. package/dist/mcp/catalog.d.ts.map +1 -0
  32. package/dist/mcp/catalog.js +42 -0
  33. package/dist/mcp/catalog.js.map +1 -0
  34. package/dist/mcp/index.d.ts +5 -0
  35. package/dist/mcp/index.d.ts.map +1 -0
  36. package/dist/mcp/index.js +3 -0
  37. package/dist/mcp/index.js.map +1 -0
  38. package/dist/mcp/oauth.d.ts +15 -0
  39. package/dist/mcp/oauth.d.ts.map +1 -0
  40. package/dist/mcp/oauth.js +489 -0
  41. package/dist/mcp/oauth.js.map +1 -0
  42. package/dist/mcp.d.ts +2 -0
  43. package/dist/mcp.d.ts.map +1 -0
  44. package/dist/mcp.js +2 -0
  45. package/dist/mcp.js.map +1 -0
  46. package/dist/models.d.ts +18 -0
  47. package/dist/models.d.ts.map +1 -0
  48. package/dist/models.generated.d.ts +26696 -0
  49. package/dist/models.generated.d.ts.map +1 -0
  50. package/dist/models.generated.js +22192 -0
  51. package/dist/models.generated.js.map +1 -0
  52. package/dist/models.js +72 -0
  53. package/dist/models.js.map +1 -0
  54. package/dist/oauth.d.ts +2 -0
  55. package/dist/oauth.d.ts.map +1 -0
  56. package/dist/oauth.js +2 -0
  57. package/dist/oauth.js.map +1 -0
  58. package/dist/openrouter-reasoning.d.ts +11 -0
  59. package/dist/openrouter-reasoning.d.ts.map +1 -0
  60. package/dist/openrouter-reasoning.js +52 -0
  61. package/dist/openrouter-reasoning.js.map +1 -0
  62. package/dist/providers/amazon-bedrock.d.ts +38 -0
  63. package/dist/providers/amazon-bedrock.d.ts.map +1 -0
  64. package/dist/providers/amazon-bedrock.js +767 -0
  65. package/dist/providers/amazon-bedrock.js.map +1 -0
  66. package/dist/providers/anthropic.d.ts +54 -0
  67. package/dist/providers/anthropic.d.ts.map +1 -0
  68. package/dist/providers/anthropic.js +999 -0
  69. package/dist/providers/anthropic.js.map +1 -0
  70. package/dist/providers/azure-openai-responses.d.ts +12 -0
  71. package/dist/providers/azure-openai-responses.d.ts.map +1 -0
  72. package/dist/providers/azure-openai-responses.js +206 -0
  73. package/dist/providers/azure-openai-responses.js.map +1 -0
  74. package/dist/providers/cloudflare.d.ts +10 -0
  75. package/dist/providers/cloudflare.d.ts.map +1 -0
  76. package/dist/providers/cloudflare.js +23 -0
  77. package/dist/providers/cloudflare.js.map +1 -0
  78. package/dist/providers/faux.d.ts +56 -0
  79. package/dist/providers/faux.d.ts.map +1 -0
  80. package/dist/providers/faux.js +368 -0
  81. package/dist/providers/faux.js.map +1 -0
  82. package/dist/providers/github-copilot-headers.d.ts +8 -0
  83. package/dist/providers/github-copilot-headers.d.ts.map +1 -0
  84. package/dist/providers/github-copilot-headers.js +26 -0
  85. package/dist/providers/github-copilot-headers.js.map +1 -0
  86. package/dist/providers/google-shared.d.ts +55 -0
  87. package/dist/providers/google-shared.d.ts.map +1 -0
  88. package/dist/providers/google-shared.js +312 -0
  89. package/dist/providers/google-shared.js.map +1 -0
  90. package/dist/providers/google-vertex.d.ts +15 -0
  91. package/dist/providers/google-vertex.d.ts.map +1 -0
  92. package/dist/providers/google-vertex.js +421 -0
  93. package/dist/providers/google-vertex.js.map +1 -0
  94. package/dist/providers/google.d.ts +13 -0
  95. package/dist/providers/google.d.ts.map +1 -0
  96. package/dist/providers/google.js +370 -0
  97. package/dist/providers/google.js.map +1 -0
  98. package/dist/providers/mistral.d.ts +20 -0
  99. package/dist/providers/mistral.d.ts.map +1 -0
  100. package/dist/providers/mistral.js +536 -0
  101. package/dist/providers/mistral.js.map +1 -0
  102. package/dist/providers/openai-codex-responses.d.ts +30 -0
  103. package/dist/providers/openai-codex-responses.d.ts.map +1 -0
  104. package/dist/providers/openai-codex-responses.js +1013 -0
  105. package/dist/providers/openai-codex-responses.js.map +1 -0
  106. package/dist/providers/openai-completions.d.ts +21 -0
  107. package/dist/providers/openai-completions.d.ts.map +1 -0
  108. package/dist/providers/openai-completions.js +1019 -0
  109. package/dist/providers/openai-completions.js.map +1 -0
  110. package/dist/providers/openai-responses-shared.d.ts +18 -0
  111. package/dist/providers/openai-responses-shared.d.ts.map +1 -0
  112. package/dist/providers/openai-responses-shared.js +488 -0
  113. package/dist/providers/openai-responses-shared.js.map +1 -0
  114. package/dist/providers/openai-responses.d.ts +10 -0
  115. package/dist/providers/openai-responses.d.ts.map +1 -0
  116. package/dist/providers/openai-responses.js +218 -0
  117. package/dist/providers/openai-responses.js.map +1 -0
  118. package/dist/providers/register-builtins.d.ts +35 -0
  119. package/dist/providers/register-builtins.d.ts.map +1 -0
  120. package/dist/providers/register-builtins.js +243 -0
  121. package/dist/providers/register-builtins.js.map +1 -0
  122. package/dist/providers/simple-options.d.ts +8 -0
  123. package/dist/providers/simple-options.d.ts.map +1 -0
  124. package/dist/providers/simple-options.js +40 -0
  125. package/dist/providers/simple-options.js.map +1 -0
  126. package/dist/providers/transform-messages.d.ts +8 -0
  127. package/dist/providers/transform-messages.d.ts.map +1 -0
  128. package/dist/providers/transform-messages.js +174 -0
  129. package/dist/providers/transform-messages.js.map +1 -0
  130. package/dist/session-resources.d.ts +4 -0
  131. package/dist/session-resources.d.ts.map +1 -0
  132. package/dist/session-resources.js +22 -0
  133. package/dist/session-resources.js.map +1 -0
  134. package/dist/stream.d.ts +8 -0
  135. package/dist/stream.d.ts.map +1 -0
  136. package/dist/stream.js +27 -0
  137. package/dist/stream.js.map +1 -0
  138. package/dist/types.d.ts +411 -0
  139. package/dist/types.d.ts.map +1 -0
  140. package/dist/types.js +2 -0
  141. package/dist/types.js.map +1 -0
  142. package/dist/utils/diagnostics.d.ts +19 -0
  143. package/dist/utils/diagnostics.d.ts.map +1 -0
  144. package/dist/utils/diagnostics.js +25 -0
  145. package/dist/utils/diagnostics.js.map +1 -0
  146. package/dist/utils/event-stream.d.ts +21 -0
  147. package/dist/utils/event-stream.d.ts.map +1 -0
  148. package/dist/utils/event-stream.js +78 -0
  149. package/dist/utils/event-stream.js.map +1 -0
  150. package/dist/utils/hash.d.ts +2 -0
  151. package/dist/utils/hash.d.ts.map +1 -0
  152. package/dist/utils/hash.js +13 -0
  153. package/dist/utils/hash.js.map +1 -0
  154. package/dist/utils/headers.d.ts +2 -0
  155. package/dist/utils/headers.d.ts.map +1 -0
  156. package/dist/utils/headers.js +8 -0
  157. package/dist/utils/headers.js.map +1 -0
  158. package/dist/utils/json-parse.d.ts +16 -0
  159. package/dist/utils/json-parse.d.ts.map +1 -0
  160. package/dist/utils/json-parse.js +113 -0
  161. package/dist/utils/json-parse.js.map +1 -0
  162. package/dist/utils/oauth/anthropic.d.ts +25 -0
  163. package/dist/utils/oauth/anthropic.d.ts.map +1 -0
  164. package/dist/utils/oauth/anthropic.js +335 -0
  165. package/dist/utils/oauth/anthropic.js.map +1 -0
  166. package/dist/utils/oauth/github-copilot.d.ts +24 -0
  167. package/dist/utils/oauth/github-copilot.d.ts.map +1 -0
  168. package/dist/utils/oauth/github-copilot.js +279 -0
  169. package/dist/utils/oauth/github-copilot.js.map +1 -0
  170. package/dist/utils/oauth/index.d.ts +45 -0
  171. package/dist/utils/oauth/index.d.ts.map +1 -0
  172. package/dist/utils/oauth/index.js +99 -0
  173. package/dist/utils/oauth/index.js.map +1 -0
  174. package/dist/utils/oauth/oauth-page.d.ts +3 -0
  175. package/dist/utils/oauth/oauth-page.d.ts.map +1 -0
  176. package/dist/utils/oauth/oauth-page.js +134 -0
  177. package/dist/utils/oauth/oauth-page.js.map +1 -0
  178. package/dist/utils/oauth/openai-codex.d.ts +34 -0
  179. package/dist/utils/oauth/openai-codex.d.ts.map +1 -0
  180. package/dist/utils/oauth/openai-codex.js +385 -0
  181. package/dist/utils/oauth/openai-codex.js.map +1 -0
  182. package/dist/utils/oauth/pkce.d.ts +13 -0
  183. package/dist/utils/oauth/pkce.d.ts.map +1 -0
  184. package/dist/utils/oauth/pkce.js +26 -0
  185. package/dist/utils/oauth/pkce.js.map +1 -0
  186. package/dist/utils/oauth/types.d.ts +57 -0
  187. package/dist/utils/oauth/types.d.ts.map +1 -0
  188. package/dist/utils/oauth/types.js +2 -0
  189. package/dist/utils/oauth/types.js.map +1 -0
  190. package/dist/utils/overflow.d.ts +55 -0
  191. package/dist/utils/overflow.d.ts.map +1 -0
  192. package/dist/utils/overflow.js +144 -0
  193. package/dist/utils/overflow.js.map +1 -0
  194. package/dist/utils/sanitize-unicode.d.ts +22 -0
  195. package/dist/utils/sanitize-unicode.d.ts.map +1 -0
  196. package/dist/utils/sanitize-unicode.js +26 -0
  197. package/dist/utils/sanitize-unicode.js.map +1 -0
  198. package/dist/utils/stream-failure.d.ts +55 -0
  199. package/dist/utils/stream-failure.d.ts.map +1 -0
  200. package/dist/utils/stream-failure.js +179 -0
  201. package/dist/utils/stream-failure.js.map +1 -0
  202. package/dist/utils/typebox-helpers.d.ts +17 -0
  203. package/dist/utils/typebox-helpers.d.ts.map +1 -0
  204. package/dist/utils/typebox-helpers.js +21 -0
  205. package/dist/utils/typebox-helpers.js.map +1 -0
  206. package/dist/utils/validation.d.ts +18 -0
  207. package/dist/utils/validation.d.ts.map +1 -0
  208. package/dist/utils/validation.js +281 -0
  209. package/dist/utils/validation.js.map +1 -0
  210. package/package.json +113 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/utils/oauth/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAEjD,MAAM,MAAM,gBAAgB,GAAG;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC;AAErC,8CAA8C;AAC9C,MAAM,MAAM,aAAa,GAAG,eAAe,CAAC;AAE5C,MAAM,MAAM,WAAW,GAAG;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,iBAAiB,EAAE,CAAC;CAC7B,CAAC;AAEF,MAAM,WAAW,mBAAmB;IACnC,MAAM,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,IAAI,CAAC;IACtC,QAAQ,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACnD,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,iBAAiB,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IAC1C,8FAA8F;IAC9F,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,iBAAiB,KAAK,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IACtE,MAAM,CAAC,EAAE,WAAW,CAAC;CACrB;AAED,MAAM,WAAW,sBAAsB;IACtC,QAAQ,CAAC,EAAE,EAAE,eAAe,CAAC;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAEtB,wDAAwD;IACxD,KAAK,CAAC,SAAS,EAAE,mBAAmB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAEjE,iFAAiF;IACjF,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAE7B,yEAAyE;IACzE,YAAY,CAAC,WAAW,EAAE,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAEvE,6DAA6D;IAC7D,SAAS,CAAC,WAAW,EAAE,gBAAgB,GAAG,MAAM,CAAC;IAEjD,uEAAuE;IACvE,YAAY,CAAC,CAAC,MAAM,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,EAAE,WAAW,EAAE,gBAAgB,GAAG,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;CACjF;AAED,qDAAqD;AACrD,MAAM,WAAW,iBAAiB;IACjC,EAAE,EAAE,eAAe,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,OAAO,CAAC;CACnB","sourcesContent":["import type { Api, Model } from \"../../types.js\";\n\nexport type OAuthCredentials = {\n\trefresh: string;\n\taccess: string;\n\texpires: number;\n\t[key: string]: unknown;\n};\n\nexport type OAuthProviderId = string;\n\n/** @deprecated Use OAuthProviderId instead */\nexport type OAuthProvider = OAuthProviderId;\n\nexport type OAuthPrompt = {\n\tmessage: string;\n\tplaceholder?: string;\n\tallowEmpty?: boolean;\n};\n\nexport type OAuthAuthInfo = {\n\turl: string;\n\tinstructions?: string;\n};\n\nexport type OAuthSelectOption = {\n\tid: string;\n\tlabel: string;\n};\n\nexport type OAuthSelectPrompt = {\n\tmessage: string;\n\toptions: OAuthSelectOption[];\n};\n\nexport interface OAuthLoginCallbacks {\n\tonAuth: (info: OAuthAuthInfo) => void;\n\tonPrompt: (prompt: OAuthPrompt) => Promise<string>;\n\tonProgress?: (message: string) => void;\n\tonManualCodeInput?: () => Promise<string>;\n\t/** Show an interactive selector and return the selected option id, or undefined on cancel. */\n\tonSelect?: (prompt: OAuthSelectPrompt) => Promise<string | undefined>;\n\tsignal?: AbortSignal;\n}\n\nexport interface OAuthProviderInterface {\n\treadonly id: OAuthProviderId;\n\treadonly name: string;\n\n\t/** Run the login flow, return credentials to persist */\n\tlogin(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials>;\n\n\t/** Whether login uses a local callback server and supports manual code input. */\n\tusesCallbackServer?: boolean;\n\n\t/** Refresh expired credentials, return updated credentials to persist */\n\trefreshToken(credentials: OAuthCredentials): Promise<OAuthCredentials>;\n\n\t/** Convert credentials to API key string for the provider */\n\tgetApiKey(credentials: OAuthCredentials): string;\n\n\t/** Optional: modify models for this provider (e.g., update baseUrl) */\n\tmodifyModels?(models: Model<Api>[], credentials: OAuthCredentials): Model<Api>[];\n}\n\n/** @deprecated Use OAuthProviderInterface instead */\nexport interface OAuthProviderInfo {\n\tid: OAuthProviderId;\n\tname: string;\n\tavailable: boolean;\n}\n"]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/utils/oauth/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { Api, Model } from \"../../types.js\";\n\nexport type OAuthCredentials = {\n\trefresh: string;\n\taccess: string;\n\texpires: number;\n\t[key: string]: unknown;\n};\n\nexport type OAuthProviderId = string;\n\n/** @deprecated Use OAuthProviderId instead */\nexport type OAuthProvider = OAuthProviderId;\n\nexport type OAuthPrompt = {\n\tmessage: string;\n\tplaceholder?: string;\n\tallowEmpty?: boolean;\n};\n\nexport type OAuthAuthInfo = {\n\turl: string;\n\tinstructions?: string;\n};\n\nexport type OAuthSelectOption = {\n\tid: string;\n\tlabel: string;\n};\n\nexport type OAuthSelectPrompt = {\n\tmessage: string;\n\toptions: OAuthSelectOption[];\n};\n\nexport interface OAuthLoginCallbacks {\n\tonAuth: (info: OAuthAuthInfo) => void;\n\tonPrompt: (prompt: OAuthPrompt) => Promise<string>;\n\tonProgress?: (message: string) => void;\n\tonManualCodeInput?: () => Promise<string>;\n\t/** Show an interactive selector and return the selected option id, or undefined on cancel. */\n\tonSelect?: (prompt: OAuthSelectPrompt) => Promise<string | undefined>;\n\tsignal?: AbortSignal;\n}\n\nexport interface OAuthProviderInterface {\n\treadonly id: OAuthProviderId;\n\treadonly name: string;\n\n\t/** Run the login flow, return credentials to persist */\n\tlogin(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials>;\n\n\t/** Whether login uses a local callback server and supports manual code input. */\n\tusesCallbackServer?: boolean;\n\n\t/** Refresh expired credentials, return updated credentials to persist */\n\trefreshToken(credentials: OAuthCredentials): Promise<OAuthCredentials>;\n\n\t/** Convert credentials to API key string for the provider */\n\tgetApiKey(credentials: OAuthCredentials): string;\n\n\t/** Optional: modify models for this provider (e.g., update baseUrl) */\n\tmodifyModels?(models: Model<Api>[], credentials: OAuthCredentials): Model<Api>[];\n}\n\n/** @deprecated Use OAuthProviderInterface instead */\nexport interface OAuthProviderInfo {\n\tid: OAuthProviderId;\n\tname: string;\n\tavailable: boolean;\n}\n"]}
@@ -0,0 +1,55 @@
1
+ import type { AssistantMessage } from "../types.js";
2
+ /**
3
+ * Check if an assistant message represents a context overflow error.
4
+ *
5
+ * This handles two cases:
6
+ * 1. Error-based overflow: Most providers return stopReason "error" with a
7
+ * specific error message pattern.
8
+ * 2. Silent overflow: Some providers accept overflow requests and return
9
+ * successfully. For these, we check if usage.input exceeds the context window.
10
+ *
11
+ * ## Reliability by Provider
12
+ *
13
+ * **Reliable detection (returns error with detectable message):**
14
+ * - Anthropic: "prompt is too long: X tokens > Y maximum" or "request_too_large"
15
+ * - OpenAI (Completions & Responses): "exceeds the context window"
16
+ * - Google Gemini: "input token count exceeds the maximum"
17
+ * - xAI (Grok): "maximum prompt length is X but request contains Y"
18
+ * - Groq: "reduce the length of the messages"
19
+ * - Cerebras: 400/413 status code (no body)
20
+ * - Mistral: "Prompt contains X tokens ... too large for model with Y maximum context length"
21
+ * - OpenRouter (all backends): "maximum context length is X tokens"
22
+ * - llama.cpp: "exceeds the available context size"
23
+ * - LM Studio: "greater than the context length"
24
+ * - Kimi For Coding: "exceeded model token limit: X (requested: Y)"
25
+ *
26
+ * **Unreliable detection:**
27
+ * - z.ai: Sometimes accepts overflow silently (detectable via usage.input > contextWindow),
28
+ * sometimes returns rate limit errors. Pass contextWindow param to detect silent overflow.
29
+ * - Xiaomi MiMo: Truncates input to fit contextWindow then returns stopReason "length" with
30
+ * output=0. Pass contextWindow param to detect via the "filled context + zero output" signal.
31
+ * - Ollama: May truncate input silently for some setups, but may also return explicit
32
+ * overflow errors that match the patterns above. Silent truncation still cannot be
33
+ * detected here because we do not know the expected token count.
34
+ *
35
+ * ## Custom Providers
36
+ *
37
+ * If you've added custom models via settings.json, this function may not detect
38
+ * overflow errors from those providers. To add support:
39
+ *
40
+ * 1. Send a request that exceeds the model's context window
41
+ * 2. Check the errorMessage in the response
42
+ * 3. Create a regex pattern that matches the error
43
+ * 4. The pattern should be added to OVERFLOW_PATTERNS in this file, or
44
+ * check the errorMessage yourself before calling this function
45
+ *
46
+ * @param message - The assistant message to check
47
+ * @param contextWindow - Optional context window size for detecting silent overflow (z.ai)
48
+ * @returns true if the message indicates a context overflow
49
+ */
50
+ export declare function isContextOverflow(message: AssistantMessage, contextWindow?: number): boolean;
51
+ /**
52
+ * Get the overflow patterns for testing purposes.
53
+ */
54
+ export declare function getOverflowPatterns(): RegExp[];
55
+ //# sourceMappingURL=overflow.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"overflow.d.ts","sourceRoot":"","sources":["../../src/utils/overflow.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAoEpD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,gBAAgB,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,OAAO,CA2B5F;AAED;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,EAAE,CAE9C","sourcesContent":["import type { AssistantMessage } from \"../types.js\";\n\n/**\n * Regex patterns to detect context overflow errors from different providers.\n *\n * These patterns match error messages returned when the input exceeds\n * the model's context window.\n *\n * Provider-specific patterns (with example error messages):\n *\n * - Anthropic: \"prompt is too long: 213462 tokens > 200000 maximum\"\n * - Anthropic: \"413 {\\\"error\\\":{\\\"type\\\":\\\"request_too_large\\\",\\\"message\\\":\\\"Request exceeds the maximum size\\\"}}\"\n * - OpenAI: \"Your input exceeds the context window of this model\"\n * - Google: \"The input token count (1196265) exceeds the maximum number of tokens allowed (1048575)\"\n * - xAI: \"This model's maximum prompt length is 131072 but the request contains 537812 tokens\"\n * - Groq: \"Please reduce the length of the messages or completion\"\n * - OpenRouter: \"This endpoint's maximum context length is X tokens. However, you requested about Y tokens\"\n * - llama.cpp: \"the request exceeds the available context size, try increasing it\"\n * - LM Studio: \"tokens to keep from the initial prompt is greater than the context length\"\n * - GitHub Copilot: \"prompt token count of X exceeds the limit of Y\"\n * - MiniMax: \"invalid params, context window exceeds limit\"\n * - Kimi For Coding: \"Your request exceeded model token limit: X (requested: Y)\"\n * - Cerebras: \"400/413 status code (no body)\"\n * - Mistral: \"Prompt contains X tokens ... too large for model with Y maximum context length\"\n * - z.ai: Does NOT error, accepts overflow silently - handled via usage.input > contextWindow\n * - Xiaomi MiMo: Truncates input to fill contextWindow exactly, then returns finish_reason \"length\"\n * with output=0 (no room left to generate). Detected via stopReason \"length\" + zero output +\n * input filling the context window.\n * - Ollama: Some deployments truncate silently, others return errors like \"prompt too long; exceeded max context length by X tokens\"\n */\nconst OVERFLOW_PATTERNS = [\n\t/prompt is too long/i, // Anthropic token overflow\n\t/request_too_large/i, // Anthropic request byte-size overflow (HTTP 413)\n\t/input is too long for requested model/i, // Amazon Bedrock\n\t/exceeds the context window/i, // OpenAI (Completions & Responses API)\n\t/input token count.*exceeds the maximum/i, // Google (Gemini)\n\t/maximum prompt length is \\d+/i, // xAI (Grok)\n\t/reduce the length of the messages/i, // Groq\n\t/maximum context length is \\d+ tokens/i, // OpenRouter (all backends)\n\t/exceeds the limit of \\d+/i, // GitHub Copilot\n\t/exceeds the available context size/i, // llama.cpp server\n\t/greater than the context length/i, // LM Studio\n\t/context window exceeds limit/i, // MiniMax\n\t/exceeded model token limit/i, // Kimi For Coding\n\t/too large for model with \\d+ maximum context length/i, // Mistral\n\t/model_context_window_exceeded/i, // z.ai non-standard finish_reason surfaced as error text\n\t/prompt too long; exceeded (?:max )?context length/i, // Ollama explicit overflow error\n\t/context[_ ]length[_ ]exceeded/i, // Generic fallback\n\t/too many tokens/i, // Generic fallback\n\t/token limit exceeded/i, // Generic fallback\n\t/^4(?:00|13)\\s*(?:status code)?\\s*\\(no body\\)/i, // Cerebras: 400/413 with no body\n];\n\n/**\n * Patterns that indicate non-overflow errors (e.g. rate limiting, server errors).\n * Error messages matching any of these are excluded from overflow detection\n * even if they also match an OVERFLOW_PATTERN.\n *\n * Example: Bedrock formats throttling errors as \"ThrottlingException: Too many tokens,\n * please wait before trying again.\" which would match the /too many tokens/i overflow\n * pattern without this exclusion.\n */\nconst NON_OVERFLOW_PATTERNS = [\n\t/^(Throttling error|Service unavailable):/i, // AWS Bedrock non-overflow errors (human-readable prefixes from formatBedrockError)\n\t/rate limit/i, // Generic rate limiting\n\t/too many requests/i, // Generic HTTP 429 style\n];\n\n/**\n * Check if an assistant message represents a context overflow error.\n *\n * This handles two cases:\n * 1. Error-based overflow: Most providers return stopReason \"error\" with a\n * specific error message pattern.\n * 2. Silent overflow: Some providers accept overflow requests and return\n * successfully. For these, we check if usage.input exceeds the context window.\n *\n * ## Reliability by Provider\n *\n * **Reliable detection (returns error with detectable message):**\n * - Anthropic: \"prompt is too long: X tokens > Y maximum\" or \"request_too_large\"\n * - OpenAI (Completions & Responses): \"exceeds the context window\"\n * - Google Gemini: \"input token count exceeds the maximum\"\n * - xAI (Grok): \"maximum prompt length is X but request contains Y\"\n * - Groq: \"reduce the length of the messages\"\n * - Cerebras: 400/413 status code (no body)\n * - Mistral: \"Prompt contains X tokens ... too large for model with Y maximum context length\"\n * - OpenRouter (all backends): \"maximum context length is X tokens\"\n * - llama.cpp: \"exceeds the available context size\"\n * - LM Studio: \"greater than the context length\"\n * - Kimi For Coding: \"exceeded model token limit: X (requested: Y)\"\n *\n * **Unreliable detection:**\n * - z.ai: Sometimes accepts overflow silently (detectable via usage.input > contextWindow),\n * sometimes returns rate limit errors. Pass contextWindow param to detect silent overflow.\n * - Xiaomi MiMo: Truncates input to fit contextWindow then returns stopReason \"length\" with\n * output=0. Pass contextWindow param to detect via the \"filled context + zero output\" signal.\n * - Ollama: May truncate input silently for some setups, but may also return explicit\n * overflow errors that match the patterns above. Silent truncation still cannot be\n * detected here because we do not know the expected token count.\n *\n * ## Custom Providers\n *\n * If you've added custom models via settings.json, this function may not detect\n * overflow errors from those providers. To add support:\n *\n * 1. Send a request that exceeds the model's context window\n * 2. Check the errorMessage in the response\n * 3. Create a regex pattern that matches the error\n * 4. The pattern should be added to OVERFLOW_PATTERNS in this file, or\n * check the errorMessage yourself before calling this function\n *\n * @param message - The assistant message to check\n * @param contextWindow - Optional context window size for detecting silent overflow (z.ai)\n * @returns true if the message indicates a context overflow\n */\nexport function isContextOverflow(message: AssistantMessage, contextWindow?: number): boolean {\n\tif (message.stopReason === \"error\" && message.errorMessage) {\n\t\t// Skip messages matching known non-overflow patterns (e.g. throttling / rate-limit)\n\t\tconst isNonOverflow = NON_OVERFLOW_PATTERNS.some((p) => p.test(message.errorMessage!));\n\t\tif (!isNonOverflow && OVERFLOW_PATTERNS.some((p) => p.test(message.errorMessage!))) {\n\t\t\treturn true;\n\t\t}\n\t}\n\n\tif (contextWindow && message.stopReason === \"stop\") {\n\t\tconst inputTokens = message.usage.input + message.usage.cacheRead;\n\t\tif (inputTokens > contextWindow) {\n\t\t\treturn true;\n\t\t}\n\t}\n\n\t// Case 3: Length-stop overflow (Xiaomi MiMo style) - server truncates oversized input\n\t// to fit the context window, leaving no room for output. Returns stopReason \"length\"\n\t// with output=0 and input+cacheRead filling the context window.\n\tif (contextWindow && message.stopReason === \"length\" && message.usage.output === 0) {\n\t\tconst inputTokens = message.usage.input + message.usage.cacheRead;\n\t\tif (inputTokens >= contextWindow * 0.99) {\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n}\n\n/**\n * Get the overflow patterns for testing purposes.\n */\nexport function getOverflowPatterns(): RegExp[] {\n\treturn [...OVERFLOW_PATTERNS];\n}\n"]}
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Regex patterns to detect context overflow errors from different providers.
3
+ *
4
+ * These patterns match error messages returned when the input exceeds
5
+ * the model's context window.
6
+ *
7
+ * Provider-specific patterns (with example error messages):
8
+ *
9
+ * - Anthropic: "prompt is too long: 213462 tokens > 200000 maximum"
10
+ * - Anthropic: "413 {\"error\":{\"type\":\"request_too_large\",\"message\":\"Request exceeds the maximum size\"}}"
11
+ * - OpenAI: "Your input exceeds the context window of this model"
12
+ * - Google: "The input token count (1196265) exceeds the maximum number of tokens allowed (1048575)"
13
+ * - xAI: "This model's maximum prompt length is 131072 but the request contains 537812 tokens"
14
+ * - Groq: "Please reduce the length of the messages or completion"
15
+ * - OpenRouter: "This endpoint's maximum context length is X tokens. However, you requested about Y tokens"
16
+ * - llama.cpp: "the request exceeds the available context size, try increasing it"
17
+ * - LM Studio: "tokens to keep from the initial prompt is greater than the context length"
18
+ * - GitHub Copilot: "prompt token count of X exceeds the limit of Y"
19
+ * - MiniMax: "invalid params, context window exceeds limit"
20
+ * - Kimi For Coding: "Your request exceeded model token limit: X (requested: Y)"
21
+ * - Cerebras: "400/413 status code (no body)"
22
+ * - Mistral: "Prompt contains X tokens ... too large for model with Y maximum context length"
23
+ * - z.ai: Does NOT error, accepts overflow silently - handled via usage.input > contextWindow
24
+ * - Xiaomi MiMo: Truncates input to fill contextWindow exactly, then returns finish_reason "length"
25
+ * with output=0 (no room left to generate). Detected via stopReason "length" + zero output +
26
+ * input filling the context window.
27
+ * - Ollama: Some deployments truncate silently, others return errors like "prompt too long; exceeded max context length by X tokens"
28
+ */
29
+ const OVERFLOW_PATTERNS = [
30
+ /prompt is too long/i, // Anthropic token overflow
31
+ /request_too_large/i, // Anthropic request byte-size overflow (HTTP 413)
32
+ /input is too long for requested model/i, // Amazon Bedrock
33
+ /exceeds the context window/i, // OpenAI (Completions & Responses API)
34
+ /input token count.*exceeds the maximum/i, // Google (Gemini)
35
+ /maximum prompt length is \d+/i, // xAI (Grok)
36
+ /reduce the length of the messages/i, // Groq
37
+ /maximum context length is \d+ tokens/i, // OpenRouter (all backends)
38
+ /exceeds the limit of \d+/i, // GitHub Copilot
39
+ /exceeds the available context size/i, // llama.cpp server
40
+ /greater than the context length/i, // LM Studio
41
+ /context window exceeds limit/i, // MiniMax
42
+ /exceeded model token limit/i, // Kimi For Coding
43
+ /too large for model with \d+ maximum context length/i, // Mistral
44
+ /model_context_window_exceeded/i, // z.ai non-standard finish_reason surfaced as error text
45
+ /prompt too long; exceeded (?:max )?context length/i, // Ollama explicit overflow error
46
+ /context[_ ]length[_ ]exceeded/i, // Generic fallback
47
+ /too many tokens/i, // Generic fallback
48
+ /token limit exceeded/i, // Generic fallback
49
+ /^4(?:00|13)\s*(?:status code)?\s*\(no body\)/i, // Cerebras: 400/413 with no body
50
+ ];
51
+ /**
52
+ * Patterns that indicate non-overflow errors (e.g. rate limiting, server errors).
53
+ * Error messages matching any of these are excluded from overflow detection
54
+ * even if they also match an OVERFLOW_PATTERN.
55
+ *
56
+ * Example: Bedrock formats throttling errors as "ThrottlingException: Too many tokens,
57
+ * please wait before trying again." which would match the /too many tokens/i overflow
58
+ * pattern without this exclusion.
59
+ */
60
+ const NON_OVERFLOW_PATTERNS = [
61
+ /^(Throttling error|Service unavailable):/i, // AWS Bedrock non-overflow errors (human-readable prefixes from formatBedrockError)
62
+ /rate limit/i, // Generic rate limiting
63
+ /too many requests/i, // Generic HTTP 429 style
64
+ ];
65
+ /**
66
+ * Check if an assistant message represents a context overflow error.
67
+ *
68
+ * This handles two cases:
69
+ * 1. Error-based overflow: Most providers return stopReason "error" with a
70
+ * specific error message pattern.
71
+ * 2. Silent overflow: Some providers accept overflow requests and return
72
+ * successfully. For these, we check if usage.input exceeds the context window.
73
+ *
74
+ * ## Reliability by Provider
75
+ *
76
+ * **Reliable detection (returns error with detectable message):**
77
+ * - Anthropic: "prompt is too long: X tokens > Y maximum" or "request_too_large"
78
+ * - OpenAI (Completions & Responses): "exceeds the context window"
79
+ * - Google Gemini: "input token count exceeds the maximum"
80
+ * - xAI (Grok): "maximum prompt length is X but request contains Y"
81
+ * - Groq: "reduce the length of the messages"
82
+ * - Cerebras: 400/413 status code (no body)
83
+ * - Mistral: "Prompt contains X tokens ... too large for model with Y maximum context length"
84
+ * - OpenRouter (all backends): "maximum context length is X tokens"
85
+ * - llama.cpp: "exceeds the available context size"
86
+ * - LM Studio: "greater than the context length"
87
+ * - Kimi For Coding: "exceeded model token limit: X (requested: Y)"
88
+ *
89
+ * **Unreliable detection:**
90
+ * - z.ai: Sometimes accepts overflow silently (detectable via usage.input > contextWindow),
91
+ * sometimes returns rate limit errors. Pass contextWindow param to detect silent overflow.
92
+ * - Xiaomi MiMo: Truncates input to fit contextWindow then returns stopReason "length" with
93
+ * output=0. Pass contextWindow param to detect via the "filled context + zero output" signal.
94
+ * - Ollama: May truncate input silently for some setups, but may also return explicit
95
+ * overflow errors that match the patterns above. Silent truncation still cannot be
96
+ * detected here because we do not know the expected token count.
97
+ *
98
+ * ## Custom Providers
99
+ *
100
+ * If you've added custom models via settings.json, this function may not detect
101
+ * overflow errors from those providers. To add support:
102
+ *
103
+ * 1. Send a request that exceeds the model's context window
104
+ * 2. Check the errorMessage in the response
105
+ * 3. Create a regex pattern that matches the error
106
+ * 4. The pattern should be added to OVERFLOW_PATTERNS in this file, or
107
+ * check the errorMessage yourself before calling this function
108
+ *
109
+ * @param message - The assistant message to check
110
+ * @param contextWindow - Optional context window size for detecting silent overflow (z.ai)
111
+ * @returns true if the message indicates a context overflow
112
+ */
113
+ export function isContextOverflow(message, contextWindow) {
114
+ if (message.stopReason === "error" && message.errorMessage) {
115
+ // Skip messages matching known non-overflow patterns (e.g. throttling / rate-limit)
116
+ const isNonOverflow = NON_OVERFLOW_PATTERNS.some((p) => p.test(message.errorMessage));
117
+ if (!isNonOverflow && OVERFLOW_PATTERNS.some((p) => p.test(message.errorMessage))) {
118
+ return true;
119
+ }
120
+ }
121
+ if (contextWindow && message.stopReason === "stop") {
122
+ const inputTokens = message.usage.input + message.usage.cacheRead;
123
+ if (inputTokens > contextWindow) {
124
+ return true;
125
+ }
126
+ }
127
+ // Case 3: Length-stop overflow (Xiaomi MiMo style) - server truncates oversized input
128
+ // to fit the context window, leaving no room for output. Returns stopReason "length"
129
+ // with output=0 and input+cacheRead filling the context window.
130
+ if (contextWindow && message.stopReason === "length" && message.usage.output === 0) {
131
+ const inputTokens = message.usage.input + message.usage.cacheRead;
132
+ if (inputTokens >= contextWindow * 0.99) {
133
+ return true;
134
+ }
135
+ }
136
+ return false;
137
+ }
138
+ /**
139
+ * Get the overflow patterns for testing purposes.
140
+ */
141
+ export function getOverflowPatterns() {
142
+ return [...OVERFLOW_PATTERNS];
143
+ }
144
+ //# sourceMappingURL=overflow.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"overflow.js","sourceRoot":"","sources":["../../src/utils/overflow.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,iBAAiB,GAAG;IACzB,qBAAqB,EAAE,2BAA2B;IAClD,oBAAoB,EAAE,kDAAkD;IACxE,wCAAwC,EAAE,iBAAiB;IAC3D,6BAA6B,EAAE,uCAAuC;IACtE,yCAAyC,EAAE,kBAAkB;IAC7D,+BAA+B,EAAE,aAAa;IAC9C,oCAAoC,EAAE,OAAO;IAC7C,uCAAuC,EAAE,4BAA4B;IACrE,2BAA2B,EAAE,iBAAiB;IAC9C,qCAAqC,EAAE,mBAAmB;IAC1D,kCAAkC,EAAE,YAAY;IAChD,+BAA+B,EAAE,UAAU;IAC3C,6BAA6B,EAAE,kBAAkB;IACjD,sDAAsD,EAAE,UAAU;IAClE,gCAAgC,EAAE,yDAAyD;IAC3F,oDAAoD,EAAE,iCAAiC;IACvF,gCAAgC,EAAE,mBAAmB;IACrD,kBAAkB,EAAE,mBAAmB;IACvC,uBAAuB,EAAE,mBAAmB;IAC5C,+CAA+C,EAAE,iCAAiC;CAClF,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,qBAAqB,GAAG;IAC7B,2CAA2C,EAAE,oFAAoF;IACjI,aAAa,EAAE,wBAAwB;IACvC,oBAAoB,EAAE,yBAAyB;CAC/C,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,MAAM,UAAU,iBAAiB,CAAC,OAAyB,EAAE,aAAsB,EAAW;IAC7F,IAAI,OAAO,CAAC,UAAU,KAAK,OAAO,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC;QAC5D,oFAAoF;QACpF,MAAM,aAAa,GAAG,qBAAqB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,YAAa,CAAC,CAAC,CAAC;QACvF,IAAI,CAAC,aAAa,IAAI,iBAAiB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,YAAa,CAAC,CAAC,EAAE,CAAC;YACpF,OAAO,IAAI,CAAC;QACb,CAAC;IACF,CAAC;IAED,IAAI,aAAa,IAAI,OAAO,CAAC,UAAU,KAAK,MAAM,EAAE,CAAC;QACpD,MAAM,WAAW,GAAG,OAAO,CAAC,KAAK,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,SAAS,CAAC;QAClE,IAAI,WAAW,GAAG,aAAa,EAAE,CAAC;YACjC,OAAO,IAAI,CAAC;QACb,CAAC;IACF,CAAC;IAED,sFAAsF;IACtF,qFAAqF;IACrF,gEAAgE;IAChE,IAAI,aAAa,IAAI,OAAO,CAAC,UAAU,KAAK,QAAQ,IAAI,OAAO,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACpF,MAAM,WAAW,GAAG,OAAO,CAAC,KAAK,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,SAAS,CAAC;QAClE,IAAI,WAAW,IAAI,aAAa,GAAG,IAAI,EAAE,CAAC;YACzC,OAAO,IAAI,CAAC;QACb,CAAC;IACF,CAAC;IAED,OAAO,KAAK,CAAC;AAAA,CACb;AAED;;GAEG;AACH,MAAM,UAAU,mBAAmB,GAAa;IAC/C,OAAO,CAAC,GAAG,iBAAiB,CAAC,CAAC;AAAA,CAC9B","sourcesContent":["import type { AssistantMessage } from \"../types.js\";\n\n/**\n * Regex patterns to detect context overflow errors from different providers.\n *\n * These patterns match error messages returned when the input exceeds\n * the model's context window.\n *\n * Provider-specific patterns (with example error messages):\n *\n * - Anthropic: \"prompt is too long: 213462 tokens > 200000 maximum\"\n * - Anthropic: \"413 {\\\"error\\\":{\\\"type\\\":\\\"request_too_large\\\",\\\"message\\\":\\\"Request exceeds the maximum size\\\"}}\"\n * - OpenAI: \"Your input exceeds the context window of this model\"\n * - Google: \"The input token count (1196265) exceeds the maximum number of tokens allowed (1048575)\"\n * - xAI: \"This model's maximum prompt length is 131072 but the request contains 537812 tokens\"\n * - Groq: \"Please reduce the length of the messages or completion\"\n * - OpenRouter: \"This endpoint's maximum context length is X tokens. However, you requested about Y tokens\"\n * - llama.cpp: \"the request exceeds the available context size, try increasing it\"\n * - LM Studio: \"tokens to keep from the initial prompt is greater than the context length\"\n * - GitHub Copilot: \"prompt token count of X exceeds the limit of Y\"\n * - MiniMax: \"invalid params, context window exceeds limit\"\n * - Kimi For Coding: \"Your request exceeded model token limit: X (requested: Y)\"\n * - Cerebras: \"400/413 status code (no body)\"\n * - Mistral: \"Prompt contains X tokens ... too large for model with Y maximum context length\"\n * - z.ai: Does NOT error, accepts overflow silently - handled via usage.input > contextWindow\n * - Xiaomi MiMo: Truncates input to fill contextWindow exactly, then returns finish_reason \"length\"\n * with output=0 (no room left to generate). Detected via stopReason \"length\" + zero output +\n * input filling the context window.\n * - Ollama: Some deployments truncate silently, others return errors like \"prompt too long; exceeded max context length by X tokens\"\n */\nconst OVERFLOW_PATTERNS = [\n\t/prompt is too long/i, // Anthropic token overflow\n\t/request_too_large/i, // Anthropic request byte-size overflow (HTTP 413)\n\t/input is too long for requested model/i, // Amazon Bedrock\n\t/exceeds the context window/i, // OpenAI (Completions & Responses API)\n\t/input token count.*exceeds the maximum/i, // Google (Gemini)\n\t/maximum prompt length is \\d+/i, // xAI (Grok)\n\t/reduce the length of the messages/i, // Groq\n\t/maximum context length is \\d+ tokens/i, // OpenRouter (all backends)\n\t/exceeds the limit of \\d+/i, // GitHub Copilot\n\t/exceeds the available context size/i, // llama.cpp server\n\t/greater than the context length/i, // LM Studio\n\t/context window exceeds limit/i, // MiniMax\n\t/exceeded model token limit/i, // Kimi For Coding\n\t/too large for model with \\d+ maximum context length/i, // Mistral\n\t/model_context_window_exceeded/i, // z.ai non-standard finish_reason surfaced as error text\n\t/prompt too long; exceeded (?:max )?context length/i, // Ollama explicit overflow error\n\t/context[_ ]length[_ ]exceeded/i, // Generic fallback\n\t/too many tokens/i, // Generic fallback\n\t/token limit exceeded/i, // Generic fallback\n\t/^4(?:00|13)\\s*(?:status code)?\\s*\\(no body\\)/i, // Cerebras: 400/413 with no body\n];\n\n/**\n * Patterns that indicate non-overflow errors (e.g. rate limiting, server errors).\n * Error messages matching any of these are excluded from overflow detection\n * even if they also match an OVERFLOW_PATTERN.\n *\n * Example: Bedrock formats throttling errors as \"ThrottlingException: Too many tokens,\n * please wait before trying again.\" which would match the /too many tokens/i overflow\n * pattern without this exclusion.\n */\nconst NON_OVERFLOW_PATTERNS = [\n\t/^(Throttling error|Service unavailable):/i, // AWS Bedrock non-overflow errors (human-readable prefixes from formatBedrockError)\n\t/rate limit/i, // Generic rate limiting\n\t/too many requests/i, // Generic HTTP 429 style\n];\n\n/**\n * Check if an assistant message represents a context overflow error.\n *\n * This handles two cases:\n * 1. Error-based overflow: Most providers return stopReason \"error\" with a\n * specific error message pattern.\n * 2. Silent overflow: Some providers accept overflow requests and return\n * successfully. For these, we check if usage.input exceeds the context window.\n *\n * ## Reliability by Provider\n *\n * **Reliable detection (returns error with detectable message):**\n * - Anthropic: \"prompt is too long: X tokens > Y maximum\" or \"request_too_large\"\n * - OpenAI (Completions & Responses): \"exceeds the context window\"\n * - Google Gemini: \"input token count exceeds the maximum\"\n * - xAI (Grok): \"maximum prompt length is X but request contains Y\"\n * - Groq: \"reduce the length of the messages\"\n * - Cerebras: 400/413 status code (no body)\n * - Mistral: \"Prompt contains X tokens ... too large for model with Y maximum context length\"\n * - OpenRouter (all backends): \"maximum context length is X tokens\"\n * - llama.cpp: \"exceeds the available context size\"\n * - LM Studio: \"greater than the context length\"\n * - Kimi For Coding: \"exceeded model token limit: X (requested: Y)\"\n *\n * **Unreliable detection:**\n * - z.ai: Sometimes accepts overflow silently (detectable via usage.input > contextWindow),\n * sometimes returns rate limit errors. Pass contextWindow param to detect silent overflow.\n * - Xiaomi MiMo: Truncates input to fit contextWindow then returns stopReason \"length\" with\n * output=0. Pass contextWindow param to detect via the \"filled context + zero output\" signal.\n * - Ollama: May truncate input silently for some setups, but may also return explicit\n * overflow errors that match the patterns above. Silent truncation still cannot be\n * detected here because we do not know the expected token count.\n *\n * ## Custom Providers\n *\n * If you've added custom models via settings.json, this function may not detect\n * overflow errors from those providers. To add support:\n *\n * 1. Send a request that exceeds the model's context window\n * 2. Check the errorMessage in the response\n * 3. Create a regex pattern that matches the error\n * 4. The pattern should be added to OVERFLOW_PATTERNS in this file, or\n * check the errorMessage yourself before calling this function\n *\n * @param message - The assistant message to check\n * @param contextWindow - Optional context window size for detecting silent overflow (z.ai)\n * @returns true if the message indicates a context overflow\n */\nexport function isContextOverflow(message: AssistantMessage, contextWindow?: number): boolean {\n\tif (message.stopReason === \"error\" && message.errorMessage) {\n\t\t// Skip messages matching known non-overflow patterns (e.g. throttling / rate-limit)\n\t\tconst isNonOverflow = NON_OVERFLOW_PATTERNS.some((p) => p.test(message.errorMessage!));\n\t\tif (!isNonOverflow && OVERFLOW_PATTERNS.some((p) => p.test(message.errorMessage!))) {\n\t\t\treturn true;\n\t\t}\n\t}\n\n\tif (contextWindow && message.stopReason === \"stop\") {\n\t\tconst inputTokens = message.usage.input + message.usage.cacheRead;\n\t\tif (inputTokens > contextWindow) {\n\t\t\treturn true;\n\t\t}\n\t}\n\n\t// Case 3: Length-stop overflow (Xiaomi MiMo style) - server truncates oversized input\n\t// to fit the context window, leaving no room for output. Returns stopReason \"length\"\n\t// with output=0 and input+cacheRead filling the context window.\n\tif (contextWindow && message.stopReason === \"length\" && message.usage.output === 0) {\n\t\tconst inputTokens = message.usage.input + message.usage.cacheRead;\n\t\tif (inputTokens >= contextWindow * 0.99) {\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n}\n\n/**\n * Get the overflow patterns for testing purposes.\n */\nexport function getOverflowPatterns(): RegExp[] {\n\treturn [...OVERFLOW_PATTERNS];\n}\n"]}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Removes unpaired Unicode surrogate characters from a string.
3
+ *
4
+ * Unpaired surrogates (high surrogates 0xD800-0xDBFF without matching low surrogates 0xDC00-0xDFFF,
5
+ * or vice versa) cause JSON serialization errors in many API providers.
6
+ *
7
+ * Valid emoji and other characters outside the Basic Multilingual Plane use properly paired
8
+ * surrogates and will NOT be affected by this function.
9
+ *
10
+ * @param text - The text to sanitize
11
+ * @returns The sanitized text with unpaired surrogates removed
12
+ *
13
+ * @example
14
+ * // Valid emoji (properly paired surrogates) are preserved
15
+ * sanitizeSurrogates("Hello 🙈 World") // => "Hello 🙈 World"
16
+ *
17
+ * // Unpaired high surrogate is removed
18
+ * const unpaired = String.fromCharCode(0xD83D); // high surrogate without low
19
+ * sanitizeSurrogates(`Text ${unpaired} here`) // => "Text here"
20
+ */
21
+ export declare function sanitizeSurrogates(text: string): string;
22
+ //# sourceMappingURL=sanitize-unicode.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sanitize-unicode.d.ts","sourceRoot":"","sources":["../../src/utils/sanitize-unicode.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAIvD","sourcesContent":["/**\n * Removes unpaired Unicode surrogate characters from a string.\n *\n * Unpaired surrogates (high surrogates 0xD800-0xDBFF without matching low surrogates 0xDC00-0xDFFF,\n * or vice versa) cause JSON serialization errors in many API providers.\n *\n * Valid emoji and other characters outside the Basic Multilingual Plane use properly paired\n * surrogates and will NOT be affected by this function.\n *\n * @param text - The text to sanitize\n * @returns The sanitized text with unpaired surrogates removed\n *\n * @example\n * // Valid emoji (properly paired surrogates) are preserved\n * sanitizeSurrogates(\"Hello 🙈 World\") // => \"Hello 🙈 World\"\n *\n * // Unpaired high surrogate is removed\n * const unpaired = String.fromCharCode(0xD83D); // high surrogate without low\n * sanitizeSurrogates(`Text ${unpaired} here`) // => \"Text here\"\n */\nexport function sanitizeSurrogates(text: string): string {\n\t// Replace unpaired high surrogates (0xD800-0xDBFF not followed by low surrogate)\n\t// Replace unpaired low surrogates (0xDC00-0xDFFF not preceded by high surrogate)\n\treturn text.replace(/[\\uD800-\\uDBFF](?![\\uDC00-\\uDFFF])|(?<![\\uD800-\\uDBFF])[\\uDC00-\\uDFFF]/g, \"\");\n}\n"]}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Removes unpaired Unicode surrogate characters from a string.
3
+ *
4
+ * Unpaired surrogates (high surrogates 0xD800-0xDBFF without matching low surrogates 0xDC00-0xDFFF,
5
+ * or vice versa) cause JSON serialization errors in many API providers.
6
+ *
7
+ * Valid emoji and other characters outside the Basic Multilingual Plane use properly paired
8
+ * surrogates and will NOT be affected by this function.
9
+ *
10
+ * @param text - The text to sanitize
11
+ * @returns The sanitized text with unpaired surrogates removed
12
+ *
13
+ * @example
14
+ * // Valid emoji (properly paired surrogates) are preserved
15
+ * sanitizeSurrogates("Hello 🙈 World") // => "Hello 🙈 World"
16
+ *
17
+ * // Unpaired high surrogate is removed
18
+ * const unpaired = String.fromCharCode(0xD83D); // high surrogate without low
19
+ * sanitizeSurrogates(`Text ${unpaired} here`) // => "Text here"
20
+ */
21
+ export function sanitizeSurrogates(text) {
22
+ // Replace unpaired high surrogates (0xD800-0xDBFF not followed by low surrogate)
23
+ // Replace unpaired low surrogates (0xDC00-0xDFFF not preceded by high surrogate)
24
+ return text.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g, "");
25
+ }
26
+ //# sourceMappingURL=sanitize-unicode.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sanitize-unicode.js","sourceRoot":"","sources":["../../src/utils/sanitize-unicode.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAAY,EAAU;IACxD,iFAAiF;IACjF,iFAAiF;IACjF,OAAO,IAAI,CAAC,OAAO,CAAC,yEAAyE,EAAE,EAAE,CAAC,CAAC;AAAA,CACnG","sourcesContent":["/**\n * Removes unpaired Unicode surrogate characters from a string.\n *\n * Unpaired surrogates (high surrogates 0xD800-0xDBFF without matching low surrogates 0xDC00-0xDFFF,\n * or vice versa) cause JSON serialization errors in many API providers.\n *\n * Valid emoji and other characters outside the Basic Multilingual Plane use properly paired\n * surrogates and will NOT be affected by this function.\n *\n * @param text - The text to sanitize\n * @returns The sanitized text with unpaired surrogates removed\n *\n * @example\n * // Valid emoji (properly paired surrogates) are preserved\n * sanitizeSurrogates(\"Hello 🙈 World\") // => \"Hello 🙈 World\"\n *\n * // Unpaired high surrogate is removed\n * const unpaired = String.fromCharCode(0xD83D); // high surrogate without low\n * sanitizeSurrogates(`Text ${unpaired} here`) // => \"Text here\"\n */\nexport function sanitizeSurrogates(text: string): string {\n\t// Replace unpaired high surrogates (0xD800-0xDBFF not followed by low surrogate)\n\t// Replace unpaired low surrogates (0xDC00-0xDFFF not preceded by high surrogate)\n\treturn text.replace(/[\\uD800-\\uDBFF](?![\\uDC00-\\uDFFF])|(?<![\\uD800-\\uDBFF])[\\uDC00-\\uDFFF]/g, \"\");\n}\n"]}
@@ -0,0 +1,55 @@
1
+ import type { AssistantMessage } from "../types.js";
2
+ /**
3
+ * Shared classification and reporting for provider stream failures, so no
4
+ * provider collapses a specific cause (refusal, safety filter, overload, ...)
5
+ * into a generic string before it is logged and persisted.
6
+ */
7
+ export type StreamFailureKind = "refusal" | "safety" | "overloaded" | "rate_limit" | "server_error" | "auth" | "invalid_request" | "malformed_response" | "unknown";
8
+ export interface StreamFailureInfo {
9
+ kind: StreamFailureKind;
10
+ /** Provider's own error/stop identifier, e.g. "overloaded_error" or "SAFETY". */
11
+ providerErrorType?: string;
12
+ status?: number;
13
+ requestId?: string;
14
+ /** Truncated raw provider payload for post-mortems. */
15
+ raw?: string;
16
+ }
17
+ export declare class StreamFailureError extends Error {
18
+ readonly info: StreamFailureInfo;
19
+ constructor(message: string, info: StreamFailureInfo);
20
+ }
21
+ /** Build a user-facing message like "Provider overloaded (overloaded_error, 529) [request_id: req_abc]". */
22
+ export declare function streamFailureMessage(info: StreamFailureInfo, detail?: string): string;
23
+ export declare function classifyStreamFailure(providerErrorType?: string, status?: number): StreamFailureKind;
24
+ /**
25
+ * Failure for a stream that terminated with a provider stop/finish reason that
26
+ * maps to "error" (e.g. Anthropic "refusal", Gemini "SAFETY"). Providers call
27
+ * this instead of throwing a generic error, so the raw reason survives.
28
+ */
29
+ export declare function streamFailureFromStopReason(rawStopReason: string | undefined, extra?: Pick<StreamFailureInfo, "requestId">): StreamFailureError;
30
+ export declare function truncateRawPayload(raw: string): string;
31
+ /**
32
+ * Best-effort extraction of structured failure info from any thrown value:
33
+ * StreamFailureError, provider SDK errors (Anthropic/OpenAI APIError, AWS SDK
34
+ * exceptions, Google ApiError), or plain errors.
35
+ */
36
+ export declare function extractStreamFailureInfo(error: unknown): StreamFailureInfo;
37
+ /**
38
+ * User-facing message for a thrown stream error: a classified one-liner with
39
+ * the provider's own short message, never the raw payload/trace. Unrecognized
40
+ * errors pass through verbatim so their text (which downstream retry matching
41
+ * may depend on) is preserved.
42
+ */
43
+ export declare function formatStreamFailureMessage(error: unknown): string;
44
+ /**
45
+ * Record a terminal stream failure on the message (structured diagnostic that
46
+ * persists to session JSONL) and emit one structured log line. Call from the
47
+ * provider's terminal catch after stopReason/errorMessage are set; no-op for
48
+ * user-initiated aborts.
49
+ */
50
+ export declare function recordStreamFailure(model: {
51
+ provider: string;
52
+ id: string;
53
+ api: string;
54
+ }, output: AssistantMessage, error: unknown): void;
55
+ //# sourceMappingURL=stream-failure.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stream-failure.d.ts","sourceRoot":"","sources":["../../src/utils/stream-failure.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAGpD;;;;GAIG;AAEH,MAAM,MAAM,iBAAiB,GAC1B,SAAS,GACT,QAAQ,GACR,YAAY,GACZ,YAAY,GACZ,cAAc,GACd,MAAM,GACN,iBAAiB,GACjB,oBAAoB,GACpB,SAAS,CAAC;AAEb,MAAM,WAAW,iBAAiB;IACjC,IAAI,EAAE,iBAAiB,CAAC;IACxB,iFAAiF;IACjF,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uDAAuD;IACvD,GAAG,CAAC,EAAE,MAAM,CAAC;CACb;AAED,qBAAa,kBAAmB,SAAQ,KAAK;IAC5C,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAC;IAEjC,YAAY,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,EAInD;CACD;AAcD,4GAA4G;AAC5G,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,iBAAiB,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CASrF;AAED,wBAAgB,qBAAqB,CAAC,iBAAiB,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,iBAAiB,CAsBpG;AAED;;;;GAIG;AACH,wBAAgB,2BAA2B,CAC1C,aAAa,EAAE,MAAM,GAAG,SAAS,EACjC,KAAK,CAAC,EAAE,IAAI,CAAC,iBAAiB,EAAE,WAAW,CAAC,GAC1C,kBAAkB,CAWpB;AAID,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEtD;AA2DD;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,OAAO,GAAG,iBAAiB,CAE1E;AAED;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAOjE;AAID;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAClC,KAAK,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,EACpD,MAAM,EAAE,gBAAgB,EACxB,KAAK,EAAE,OAAO,GACZ,IAAI,CAsBN","sourcesContent":["import { getLogger } from \"../log.js\";\nimport type { AssistantMessage } from \"../types.js\";\nimport { appendAssistantMessageDiagnostic, extractDiagnosticError } from \"./diagnostics.js\";\n\n/**\n * Shared classification and reporting for provider stream failures, so no\n * provider collapses a specific cause (refusal, safety filter, overload, ...)\n * into a generic string before it is logged and persisted.\n */\n\nexport type StreamFailureKind =\n\t| \"refusal\"\n\t| \"safety\"\n\t| \"overloaded\"\n\t| \"rate_limit\"\n\t| \"server_error\"\n\t| \"auth\"\n\t| \"invalid_request\"\n\t| \"malformed_response\"\n\t| \"unknown\";\n\nexport interface StreamFailureInfo {\n\tkind: StreamFailureKind;\n\t/** Provider's own error/stop identifier, e.g. \"overloaded_error\" or \"SAFETY\". */\n\tproviderErrorType?: string;\n\tstatus?: number;\n\trequestId?: string;\n\t/** Truncated raw provider payload for post-mortems. */\n\traw?: string;\n}\n\nexport class StreamFailureError extends Error {\n\treadonly info: StreamFailureInfo;\n\n\tconstructor(message: string, info: StreamFailureInfo) {\n\t\tsuper(message);\n\t\tthis.name = \"StreamFailureError\";\n\t\tthis.info = info;\n\t}\n}\n\nconst KIND_MESSAGES: Record<StreamFailureKind, string> = {\n\trefusal: \"Model refused to respond\",\n\tsafety: \"Response blocked by provider safety filters\",\n\toverloaded: \"Provider overloaded\",\n\trate_limit: \"Provider rate limit exceeded\",\n\tserver_error: \"Provider server error\",\n\tauth: \"Provider authentication failed\",\n\tinvalid_request: \"Provider rejected the request\",\n\tmalformed_response: \"Provider returned a malformed response\",\n\tunknown: \"Provider stream failed\",\n};\n\n/** Build a user-facing message like \"Provider overloaded (overloaded_error, 529) [request_id: req_abc]\". */\nexport function streamFailureMessage(info: StreamFailureInfo, detail?: string): string {\n\tconst qualifiers = [info.providerErrorType, info.status !== undefined ? String(info.status) : undefined]\n\t\t.filter(Boolean)\n\t\t.join(\", \");\n\tlet message = KIND_MESSAGES[info.kind];\n\tif (qualifiers) message += ` (${qualifiers})`;\n\tif (detail) message += `: ${detail}`;\n\tif (info.requestId) message += ` [request_id: ${info.requestId}]`;\n\treturn message;\n}\n\nexport function classifyStreamFailure(providerErrorType?: string, status?: number): StreamFailureKind {\n\tconst type = providerErrorType?.toLowerCase() ?? \"\";\n\tif (type === \"refusal\") return \"refusal\";\n\tif (/sensitive|safety|prohibited_content|blocklist|spii|recitation|content.?filter|guardrail|flagged/.test(type)) {\n\t\treturn \"safety\";\n\t}\n\tif (type.includes(\"overloaded\") || status === 529) return \"overloaded\";\n\tif (type.includes(\"rate_limit\") || type.includes(\"throttl\") || status === 429) return \"rate_limit\";\n\tif (/authentication|permission|unauthorized/.test(type) || status === 401 || status === 403) return \"auth\";\n\tif (type.includes(\"invalid_request\") || type.includes(\"not_found_error\") || status === 400 || status === 404) {\n\t\treturn \"invalid_request\";\n\t}\n\tif (type.includes(\"malformed\")) return \"malformed_response\";\n\tif (\n\t\ttype.includes(\"api_error\") ||\n\t\ttype.includes(\"server_error\") ||\n\t\ttype.includes(\"unavailable\") ||\n\t\t(status !== undefined && status >= 500)\n\t) {\n\t\treturn \"server_error\";\n\t}\n\treturn \"unknown\";\n}\n\n/**\n * Failure for a stream that terminated with a provider stop/finish reason that\n * maps to \"error\" (e.g. Anthropic \"refusal\", Gemini \"SAFETY\"). Providers call\n * this instead of throwing a generic error, so the raw reason survives.\n */\nexport function streamFailureFromStopReason(\n\trawStopReason: string | undefined,\n\textra?: Pick<StreamFailureInfo, \"requestId\">,\n): StreamFailureError {\n\tconst info: StreamFailureInfo = {\n\t\tkind: rawStopReason ? classifyStreamFailure(rawStopReason) : \"unknown\",\n\t\tproviderErrorType: rawStopReason,\n\t\trequestId: extra?.requestId,\n\t};\n\tif (info.kind === \"unknown\" && /malformed/i.test(rawStopReason ?? \"\")) info.kind = \"malformed_response\";\n\tconst message = rawStopReason\n\t\t? streamFailureMessage(info)\n\t\t: streamFailureMessage(info, \"stream ended with an error and no stop reason\");\n\treturn new StreamFailureError(message, info);\n}\n\nconst MAX_RAW_LENGTH = 2000;\n\nexport function truncateRawPayload(raw: string): string {\n\treturn raw.length > MAX_RAW_LENGTH ? `${raw.slice(0, MAX_RAW_LENGTH)}…` : raw;\n}\n\nfunction extractStreamFailureParts(error: unknown): { info: StreamFailureInfo; detail?: string } {\n\tif (error instanceof StreamFailureError) return { info: error.info };\n\tif (!(error instanceof Error)) return { info: { kind: \"unknown\" } };\n\n\tconst err = error as Error & {\n\t\tstatus?: unknown;\n\t\tstatusCode?: unknown;\n\t\tcode?: unknown;\n\t\trequestID?: unknown;\n\t\trequest_id?: unknown;\n\t\theaders?: unknown;\n\t\terror?: unknown;\n\t\t$metadata?: { requestId?: unknown };\n\t};\n\n\tconst status =\n\t\ttypeof err.status === \"number\" ? err.status : typeof err.statusCode === \"number\" ? err.statusCode : undefined;\n\n\t// Error bodies come nested differently per SDK: Anthropic/OpenAI expose\n\t// `error.error = {type|code, message}` (sometimes doubly nested).\n\tlet body = err.error as { type?: unknown; code?: unknown; message?: unknown; error?: unknown } | undefined;\n\tif (body && typeof body === \"object\" && body.error && typeof body.error === \"object\") {\n\t\tbody = body.error as { type?: unknown; code?: unknown; message?: unknown };\n\t}\n\tconst bodyType = body && typeof body === \"object\" ? (body.type ?? body.code) : undefined;\n\tconst bodyMessage = body && typeof body === \"object\" ? body.message : undefined;\n\tconst providerErrorType =\n\t\ttypeof bodyType === \"string\"\n\t\t\t? bodyType\n\t\t\t: typeof err.code === \"string\"\n\t\t\t\t? err.code\n\t\t\t\t: err.name !== \"Error\" && err.name !== \"StreamFailureError\"\n\t\t\t\t\t? err.name\n\t\t\t\t\t: undefined;\n\n\tconst headers = err.headers;\n\tconst headerRequestId =\n\t\theaders && typeof (headers as Headers).get === \"function\"\n\t\t\t? ((headers as Headers).get(\"request-id\") ?? (headers as Headers).get(\"x-request-id\"))\n\t\t\t: headers && typeof headers === \"object\"\n\t\t\t\t? ((headers as Record<string, unknown>)[\"request-id\"] ??\n\t\t\t\t\t(headers as Record<string, unknown>)[\"x-request-id\"])\n\t\t\t\t: undefined;\n\tconst rawRequestId = err.requestID ?? err.request_id ?? err.$metadata?.requestId ?? headerRequestId;\n\tconst requestId = typeof rawRequestId === \"string\" ? rawRequestId : undefined;\n\n\treturn {\n\t\tinfo: {\n\t\t\tkind: classifyStreamFailure(providerErrorType ?? error.message, status),\n\t\t\tproviderErrorType,\n\t\t\tstatus,\n\t\t\trequestId,\n\t\t},\n\t\tdetail: typeof bodyMessage === \"string\" ? bodyMessage : undefined,\n\t};\n}\n\n/**\n * Best-effort extraction of structured failure info from any thrown value:\n * StreamFailureError, provider SDK errors (Anthropic/OpenAI APIError, AWS SDK\n * exceptions, Google ApiError), or plain errors.\n */\nexport function extractStreamFailureInfo(error: unknown): StreamFailureInfo {\n\treturn extractStreamFailureParts(error).info;\n}\n\n/**\n * User-facing message for a thrown stream error: a classified one-liner with\n * the provider's own short message, never the raw payload/trace. Unrecognized\n * errors pass through verbatim so their text (which downstream retry matching\n * may depend on) is preserved.\n */\nexport function formatStreamFailureMessage(error: unknown): string {\n\tif (error instanceof StreamFailureError) return error.message;\n\tconst { info, detail } = extractStreamFailureParts(error);\n\tif (info.kind === \"unknown\") {\n\t\treturn error instanceof Error ? error.message : JSON.stringify(error);\n\t}\n\treturn streamFailureMessage(info, detail);\n}\n\nconst log = getLogger(\"ai.provider\");\n\n/**\n * Record a terminal stream failure on the message (structured diagnostic that\n * persists to session JSONL) and emit one structured log line. Call from the\n * provider's terminal catch after stopReason/errorMessage are set; no-op for\n * user-initiated aborts.\n */\nexport function recordStreamFailure(\n\tmodel: { provider: string; id: string; api: string },\n\toutput: AssistantMessage,\n\terror: unknown,\n): void {\n\tif (output.stopReason !== \"error\") return;\n\tconst info = extractStreamFailureInfo(error);\n\tappendAssistantMessageDiagnostic(output, {\n\t\ttype: \"provider_stream_failure\",\n\t\ttimestamp: Date.now(),\n\t\terror: extractDiagnosticError(error),\n\t\tdetails: { ...info },\n\t});\n\tconst rawMessage = error instanceof Error ? error.message : String(error);\n\tlog.error(\"provider stream failure\", {\n\t\tprovider: model.provider,\n\t\tmodel: model.id,\n\t\tapi: model.api,\n\t\tkind: info.kind,\n\t\tproviderErrorType: info.providerErrorType,\n\t\tstatus: info.status,\n\t\trequestId: info.requestId,\n\t\tmessage: output.errorMessage,\n\t\t// errorMessage is user-facing and concise; keep the raw cause for debugging.\n\t\tcause: rawMessage === output.errorMessage ? undefined : truncateRawPayload(rawMessage),\n\t});\n}\n"]}
@@ -0,0 +1,179 @@
1
+ import { getLogger } from "../log.js";
2
+ import { appendAssistantMessageDiagnostic, extractDiagnosticError } from "./diagnostics.js";
3
+ export class StreamFailureError extends Error {
4
+ info;
5
+ constructor(message, info) {
6
+ super(message);
7
+ this.name = "StreamFailureError";
8
+ this.info = info;
9
+ }
10
+ }
11
+ const KIND_MESSAGES = {
12
+ refusal: "Model refused to respond",
13
+ safety: "Response blocked by provider safety filters",
14
+ overloaded: "Provider overloaded",
15
+ rate_limit: "Provider rate limit exceeded",
16
+ server_error: "Provider server error",
17
+ auth: "Provider authentication failed",
18
+ invalid_request: "Provider rejected the request",
19
+ malformed_response: "Provider returned a malformed response",
20
+ unknown: "Provider stream failed",
21
+ };
22
+ /** Build a user-facing message like "Provider overloaded (overloaded_error, 529) [request_id: req_abc]". */
23
+ export function streamFailureMessage(info, detail) {
24
+ const qualifiers = [info.providerErrorType, info.status !== undefined ? String(info.status) : undefined]
25
+ .filter(Boolean)
26
+ .join(", ");
27
+ let message = KIND_MESSAGES[info.kind];
28
+ if (qualifiers)
29
+ message += ` (${qualifiers})`;
30
+ if (detail)
31
+ message += `: ${detail}`;
32
+ if (info.requestId)
33
+ message += ` [request_id: ${info.requestId}]`;
34
+ return message;
35
+ }
36
+ export function classifyStreamFailure(providerErrorType, status) {
37
+ const type = providerErrorType?.toLowerCase() ?? "";
38
+ if (type === "refusal")
39
+ return "refusal";
40
+ if (/sensitive|safety|prohibited_content|blocklist|spii|recitation|content.?filter|guardrail|flagged/.test(type)) {
41
+ return "safety";
42
+ }
43
+ if (type.includes("overloaded") || status === 529)
44
+ return "overloaded";
45
+ if (type.includes("rate_limit") || type.includes("throttl") || status === 429)
46
+ return "rate_limit";
47
+ if (/authentication|permission|unauthorized/.test(type) || status === 401 || status === 403)
48
+ return "auth";
49
+ if (type.includes("invalid_request") || type.includes("not_found_error") || status === 400 || status === 404) {
50
+ return "invalid_request";
51
+ }
52
+ if (type.includes("malformed"))
53
+ return "malformed_response";
54
+ if (type.includes("api_error") ||
55
+ type.includes("server_error") ||
56
+ type.includes("unavailable") ||
57
+ (status !== undefined && status >= 500)) {
58
+ return "server_error";
59
+ }
60
+ return "unknown";
61
+ }
62
+ /**
63
+ * Failure for a stream that terminated with a provider stop/finish reason that
64
+ * maps to "error" (e.g. Anthropic "refusal", Gemini "SAFETY"). Providers call
65
+ * this instead of throwing a generic error, so the raw reason survives.
66
+ */
67
+ export function streamFailureFromStopReason(rawStopReason, extra) {
68
+ const info = {
69
+ kind: rawStopReason ? classifyStreamFailure(rawStopReason) : "unknown",
70
+ providerErrorType: rawStopReason,
71
+ requestId: extra?.requestId,
72
+ };
73
+ if (info.kind === "unknown" && /malformed/i.test(rawStopReason ?? ""))
74
+ info.kind = "malformed_response";
75
+ const message = rawStopReason
76
+ ? streamFailureMessage(info)
77
+ : streamFailureMessage(info, "stream ended with an error and no stop reason");
78
+ return new StreamFailureError(message, info);
79
+ }
80
+ const MAX_RAW_LENGTH = 2000;
81
+ export function truncateRawPayload(raw) {
82
+ return raw.length > MAX_RAW_LENGTH ? `${raw.slice(0, MAX_RAW_LENGTH)}…` : raw;
83
+ }
84
+ function extractStreamFailureParts(error) {
85
+ if (error instanceof StreamFailureError)
86
+ return { info: error.info };
87
+ if (!(error instanceof Error))
88
+ return { info: { kind: "unknown" } };
89
+ const err = error;
90
+ const status = typeof err.status === "number" ? err.status : typeof err.statusCode === "number" ? err.statusCode : undefined;
91
+ // Error bodies come nested differently per SDK: Anthropic/OpenAI expose
92
+ // `error.error = {type|code, message}` (sometimes doubly nested).
93
+ let body = err.error;
94
+ if (body && typeof body === "object" && body.error && typeof body.error === "object") {
95
+ body = body.error;
96
+ }
97
+ const bodyType = body && typeof body === "object" ? (body.type ?? body.code) : undefined;
98
+ const bodyMessage = body && typeof body === "object" ? body.message : undefined;
99
+ const providerErrorType = typeof bodyType === "string"
100
+ ? bodyType
101
+ : typeof err.code === "string"
102
+ ? err.code
103
+ : err.name !== "Error" && err.name !== "StreamFailureError"
104
+ ? err.name
105
+ : undefined;
106
+ const headers = err.headers;
107
+ const headerRequestId = headers && typeof headers.get === "function"
108
+ ? (headers.get("request-id") ?? headers.get("x-request-id"))
109
+ : headers && typeof headers === "object"
110
+ ? (headers["request-id"] ??
111
+ headers["x-request-id"])
112
+ : undefined;
113
+ const rawRequestId = err.requestID ?? err.request_id ?? err.$metadata?.requestId ?? headerRequestId;
114
+ const requestId = typeof rawRequestId === "string" ? rawRequestId : undefined;
115
+ return {
116
+ info: {
117
+ kind: classifyStreamFailure(providerErrorType ?? error.message, status),
118
+ providerErrorType,
119
+ status,
120
+ requestId,
121
+ },
122
+ detail: typeof bodyMessage === "string" ? bodyMessage : undefined,
123
+ };
124
+ }
125
+ /**
126
+ * Best-effort extraction of structured failure info from any thrown value:
127
+ * StreamFailureError, provider SDK errors (Anthropic/OpenAI APIError, AWS SDK
128
+ * exceptions, Google ApiError), or plain errors.
129
+ */
130
+ export function extractStreamFailureInfo(error) {
131
+ return extractStreamFailureParts(error).info;
132
+ }
133
+ /**
134
+ * User-facing message for a thrown stream error: a classified one-liner with
135
+ * the provider's own short message, never the raw payload/trace. Unrecognized
136
+ * errors pass through verbatim so their text (which downstream retry matching
137
+ * may depend on) is preserved.
138
+ */
139
+ export function formatStreamFailureMessage(error) {
140
+ if (error instanceof StreamFailureError)
141
+ return error.message;
142
+ const { info, detail } = extractStreamFailureParts(error);
143
+ if (info.kind === "unknown") {
144
+ return error instanceof Error ? error.message : JSON.stringify(error);
145
+ }
146
+ return streamFailureMessage(info, detail);
147
+ }
148
+ const log = getLogger("ai.provider");
149
+ /**
150
+ * Record a terminal stream failure on the message (structured diagnostic that
151
+ * persists to session JSONL) and emit one structured log line. Call from the
152
+ * provider's terminal catch after stopReason/errorMessage are set; no-op for
153
+ * user-initiated aborts.
154
+ */
155
+ export function recordStreamFailure(model, output, error) {
156
+ if (output.stopReason !== "error")
157
+ return;
158
+ const info = extractStreamFailureInfo(error);
159
+ appendAssistantMessageDiagnostic(output, {
160
+ type: "provider_stream_failure",
161
+ timestamp: Date.now(),
162
+ error: extractDiagnosticError(error),
163
+ details: { ...info },
164
+ });
165
+ const rawMessage = error instanceof Error ? error.message : String(error);
166
+ log.error("provider stream failure", {
167
+ provider: model.provider,
168
+ model: model.id,
169
+ api: model.api,
170
+ kind: info.kind,
171
+ providerErrorType: info.providerErrorType,
172
+ status: info.status,
173
+ requestId: info.requestId,
174
+ message: output.errorMessage,
175
+ // errorMessage is user-facing and concise; keep the raw cause for debugging.
176
+ cause: rawMessage === output.errorMessage ? undefined : truncateRawPayload(rawMessage),
177
+ });
178
+ }
179
+ //# sourceMappingURL=stream-failure.js.map