evolcore 0.0.5 → 0.0.6

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 (322) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/README.md +4 -12
  3. package/dist/agents/baseagent.js +39 -0
  4. package/dist/agents/claude-runner.js +10 -2
  5. package/dist/agents/codex-runner.js +37 -8
  6. package/dist/agents/ecagent-runner.js +1159 -0
  7. package/dist/agents/gemini-runner.js +4 -1
  8. package/dist/agents/runner-types.js +20 -4
  9. package/dist/aun/msg/history.js +32 -9
  10. package/dist/aun/outbox.js +4 -9
  11. package/dist/channels/aun.js +32 -6
  12. package/dist/channels/daemon.js +7 -4
  13. package/dist/channels/dingtalk.js +3 -5
  14. package/dist/channels/feishu.js +16 -108
  15. package/dist/channels/wechat.js +4 -4
  16. package/dist/channels/wecom.js +4 -6
  17. package/dist/cli/agent-command.js +1 -1
  18. package/dist/cli/agent.js +42 -11
  19. package/dist/cli/aun-commands.js +50 -3
  20. package/dist/cli/daemon-commands.js +81 -6
  21. package/dist/cli/data-command.js +132 -0
  22. package/dist/cli/index.js +11 -3
  23. package/dist/cli/init.js +76 -17
  24. package/dist/cli/net-check.js +9 -20
  25. package/dist/cli/restart-monitor.js +11 -2
  26. package/dist/cli/trigger-command.js +1 -1
  27. package/dist/cli/watch-msg.js +12 -25
  28. package/dist/config/builtin-roles.js +3 -1
  29. package/dist/config/config-field-policy.js +1 -1
  30. package/dist/config/gateway-config.js +21 -4
  31. package/dist/config/resolved-config-op.js +10 -0
  32. package/dist/core/command/command-handler.js +2 -2
  33. package/dist/core/command/menu-handler.js +11 -9
  34. package/dist/core/command/role-menu.js +11 -0
  35. package/dist/core/command/slash-handler.js +18 -14
  36. package/dist/core/data-migration.js +1434 -0
  37. package/dist/core/handoff/store.js +14 -4
  38. package/dist/core/inference/text-inference.js +42 -1
  39. package/dist/core/message/pending-hints.js +1 -1
  40. package/dist/core/message/response-engine.js +406 -167
  41. package/dist/core/message/retry-scheduler.js +71 -0
  42. package/dist/core/model/model-catalog.js +7 -2
  43. package/dist/core/model/model-fallback.js +81 -0
  44. package/dist/core/protected-paths.js +41 -8
  45. package/dist/core/session/adapters/ecagent-session-file-adapter.js +112 -0
  46. package/dist/core/session/session-fs-store.js +283 -59
  47. package/dist/core/session/session-manager.js +19 -21
  48. package/dist/core/session/session-title.js +1 -0
  49. package/dist/core/session/session-turn-coordinator.js +8 -1
  50. package/dist/core/system-channels.js +1 -0
  51. package/dist/index.js +10 -9
  52. package/dist/paths.js +35 -27
  53. package/dist/trigger/feedback.js +16 -0
  54. package/dist/trigger/legacy-session-history.js +19 -0
  55. package/dist/trigger/manager.js +12 -1
  56. package/dist/trigger/parser.js +4 -2
  57. package/dist/trigger/scheduler.js +185 -15
  58. package/dist/trigger/session-lock.js +62 -0
  59. package/dist/trigger/state.js +64 -0
  60. package/dist/trigger/validation.js +32 -2
  61. package/dist/utils/aid-bind.js +1 -1
  62. package/dist/utils/error-utils.js +9 -0
  63. package/dist/utils/welcome.js +2 -3
  64. package/ecagent/LICENSE +21 -0
  65. package/ecagent/NOTICE +11 -0
  66. package/ecagent/README.md +61 -0
  67. package/ecagent/dist/agent-loop.d.ts +24 -0
  68. package/ecagent/dist/agent-loop.d.ts.map +1 -0
  69. package/ecagent/dist/agent-loop.js +547 -0
  70. package/ecagent/dist/agent-loop.js.map +1 -0
  71. package/ecagent/dist/agent.d.ts +127 -0
  72. package/ecagent/dist/agent.d.ts.map +1 -0
  73. package/ecagent/dist/agent.js +386 -0
  74. package/ecagent/dist/agent.js.map +1 -0
  75. package/ecagent/dist/harness/agent-harness.d.ts +95 -0
  76. package/ecagent/dist/harness/agent-harness.d.ts.map +1 -0
  77. package/ecagent/dist/harness/agent-harness.js +926 -0
  78. package/ecagent/dist/harness/agent-harness.js.map +1 -0
  79. package/ecagent/dist/harness/compaction/branch-summarization.d.ts +51 -0
  80. package/ecagent/dist/harness/compaction/branch-summarization.d.ts.map +1 -0
  81. package/ecagent/dist/harness/compaction/branch-summarization.js +174 -0
  82. package/ecagent/dist/harness/compaction/branch-summarization.js.map +1 -0
  83. package/ecagent/dist/harness/compaction/compaction.d.ts +95 -0
  84. package/ecagent/dist/harness/compaction/compaction.d.ts.map +1 -0
  85. package/ecagent/dist/harness/compaction/compaction.js +528 -0
  86. package/ecagent/dist/harness/compaction/compaction.js.map +1 -0
  87. package/ecagent/dist/harness/compaction/utils.d.ts +25 -0
  88. package/ecagent/dist/harness/compaction/utils.d.ts.map +1 -0
  89. package/ecagent/dist/harness/compaction/utils.js +131 -0
  90. package/ecagent/dist/harness/compaction/utils.js.map +1 -0
  91. package/ecagent/dist/harness/env/nodejs.d.ts +51 -0
  92. package/ecagent/dist/harness/env/nodejs.d.ts.map +1 -0
  93. package/ecagent/dist/harness/env/nodejs.js +513 -0
  94. package/ecagent/dist/harness/env/nodejs.js.map +1 -0
  95. package/ecagent/dist/harness/messages.d.ts +51 -0
  96. package/ecagent/dist/harness/messages.d.ts.map +1 -0
  97. package/ecagent/dist/harness/messages.js +102 -0
  98. package/ecagent/dist/harness/messages.js.map +1 -0
  99. package/ecagent/dist/harness/prompt-templates.d.ts +48 -0
  100. package/ecagent/dist/harness/prompt-templates.d.ts.map +1 -0
  101. package/ecagent/dist/harness/prompt-templates.js +230 -0
  102. package/ecagent/dist/harness/prompt-templates.js.map +1 -0
  103. package/ecagent/dist/harness/session/jsonl-repo.d.ts +26 -0
  104. package/ecagent/dist/harness/session/jsonl-repo.d.ts.map +1 -0
  105. package/ecagent/dist/harness/session/jsonl-repo.js +100 -0
  106. package/ecagent/dist/harness/session/jsonl-repo.js.map +1 -0
  107. package/ecagent/dist/harness/session/jsonl-storage.d.ts +34 -0
  108. package/ecagent/dist/harness/session/jsonl-storage.d.ts.map +1 -0
  109. package/ecagent/dist/harness/session/jsonl-storage.js +234 -0
  110. package/ecagent/dist/harness/session/jsonl-storage.js.map +1 -0
  111. package/ecagent/dist/harness/session/memory-repo.d.ts +18 -0
  112. package/ecagent/dist/harness/session/memory-repo.d.ts.map +1 -0
  113. package/ecagent/dist/harness/session/memory-repo.js +44 -0
  114. package/ecagent/dist/harness/session/memory-repo.js.map +1 -0
  115. package/ecagent/dist/harness/session/memory-storage.d.ts +25 -0
  116. package/ecagent/dist/harness/session/memory-storage.d.ts.map +1 -0
  117. package/ecagent/dist/harness/session/memory-storage.js +111 -0
  118. package/ecagent/dist/harness/session/memory-storage.js.map +1 -0
  119. package/ecagent/dist/harness/session/repo-utils.d.ts +11 -0
  120. package/ecagent/dist/harness/session/repo-utils.d.ts.map +1 -0
  121. package/ecagent/dist/harness/session/repo-utils.js +39 -0
  122. package/ecagent/dist/harness/session/repo-utils.js.map +1 -0
  123. package/ecagent/dist/harness/session/session.d.ts +47 -0
  124. package/ecagent/dist/harness/session/session.d.ts.map +1 -0
  125. package/ecagent/dist/harness/session/session.js +245 -0
  126. package/ecagent/dist/harness/session/session.js.map +1 -0
  127. package/ecagent/dist/harness/session/uuid.d.ts +2 -0
  128. package/ecagent/dist/harness/session/uuid.d.ts.map +1 -0
  129. package/ecagent/dist/harness/session/uuid.js +50 -0
  130. package/ecagent/dist/harness/session/uuid.js.map +1 -0
  131. package/ecagent/dist/harness/skills.d.ts +44 -0
  132. package/ecagent/dist/harness/skills.d.ts.map +1 -0
  133. package/ecagent/dist/harness/skills.js +311 -0
  134. package/ecagent/dist/harness/skills.js.map +1 -0
  135. package/ecagent/dist/harness/system-prompt.d.ts +3 -0
  136. package/ecagent/dist/harness/system-prompt.d.ts.map +1 -0
  137. package/ecagent/dist/harness/system-prompt.js +30 -0
  138. package/ecagent/dist/harness/system-prompt.js.map +1 -0
  139. package/ecagent/dist/harness/types.d.ts +619 -0
  140. package/ecagent/dist/harness/types.d.ts.map +1 -0
  141. package/ecagent/dist/harness/types.js +81 -0
  142. package/ecagent/dist/harness/types.js.map +1 -0
  143. package/ecagent/dist/harness/utils/shell-output.d.ts +14 -0
  144. package/ecagent/dist/harness/utils/shell-output.d.ts.map +1 -0
  145. package/ecagent/dist/harness/utils/shell-output.js +126 -0
  146. package/ecagent/dist/harness/utils/shell-output.js.map +1 -0
  147. package/ecagent/dist/harness/utils/truncate.d.ts +70 -0
  148. package/ecagent/dist/harness/utils/truncate.d.ts.map +1 -0
  149. package/ecagent/dist/harness/utils/truncate.js +290 -0
  150. package/ecagent/dist/harness/utils/truncate.js.map +1 -0
  151. package/ecagent/dist/index.d.ts +22 -0
  152. package/ecagent/dist/index.d.ts.map +1 -0
  153. package/ecagent/dist/index.js +27 -0
  154. package/ecagent/dist/index.js.map +1 -0
  155. package/ecagent/dist/node.d.ts +3 -0
  156. package/ecagent/dist/node.d.ts.map +1 -0
  157. package/ecagent/dist/node.js +3 -0
  158. package/ecagent/dist/node.js.map +1 -0
  159. package/ecagent/dist/proxy.d.ts +69 -0
  160. package/ecagent/dist/proxy.d.ts.map +1 -0
  161. package/ecagent/dist/proxy.js +278 -0
  162. package/ecagent/dist/proxy.js.map +1 -0
  163. package/ecagent/dist/runtime/api/lazy.d.ts +15 -0
  164. package/ecagent/dist/runtime/api/lazy.d.ts.map +1 -0
  165. package/ecagent/dist/runtime/api/lazy.js +59 -0
  166. package/ecagent/dist/runtime/api/lazy.js.map +1 -0
  167. package/ecagent/dist/runtime/api/pi-messages.d.ts +97 -0
  168. package/ecagent/dist/runtime/api/pi-messages.d.ts.map +1 -0
  169. package/ecagent/dist/runtime/api/pi-messages.js +307 -0
  170. package/ecagent/dist/runtime/api/pi-messages.js.map +1 -0
  171. package/ecagent/dist/runtime/api/pi-messages.lazy.d.ts +3 -0
  172. package/ecagent/dist/runtime/api/pi-messages.lazy.d.ts.map +1 -0
  173. package/ecagent/dist/runtime/api/pi-messages.lazy.js +3 -0
  174. package/ecagent/dist/runtime/api/pi-messages.lazy.js.map +1 -0
  175. package/ecagent/dist/runtime/auth/context.d.ts +7 -0
  176. package/ecagent/dist/runtime/auth/context.d.ts.map +1 -0
  177. package/ecagent/dist/runtime/auth/context.js +42 -0
  178. package/ecagent/dist/runtime/auth/context.js.map +1 -0
  179. package/ecagent/dist/runtime/auth/credential-store.d.ts +16 -0
  180. package/ecagent/dist/runtime/auth/credential-store.d.ts.map +1 -0
  181. package/ecagent/dist/runtime/auth/credential-store.js +39 -0
  182. package/ecagent/dist/runtime/auth/credential-store.js.map +1 -0
  183. package/ecagent/dist/runtime/auth/helpers.d.ts +20 -0
  184. package/ecagent/dist/runtime/auth/helpers.d.ts.map +1 -0
  185. package/ecagent/dist/runtime/auth/helpers.js +46 -0
  186. package/ecagent/dist/runtime/auth/helpers.js.map +1 -0
  187. package/ecagent/dist/runtime/auth/resolve.d.ts +26 -0
  188. package/ecagent/dist/runtime/auth/resolve.d.ts.map +1 -0
  189. package/ecagent/dist/runtime/auth/resolve.js +100 -0
  190. package/ecagent/dist/runtime/auth/resolve.js.map +1 -0
  191. package/ecagent/dist/runtime/auth/types.d.ts +180 -0
  192. package/ecagent/dist/runtime/auth/types.d.ts.map +1 -0
  193. package/ecagent/dist/runtime/auth/types.js +2 -0
  194. package/ecagent/dist/runtime/auth/types.js.map +1 -0
  195. package/ecagent/dist/runtime/compat.d.ts +28 -0
  196. package/ecagent/dist/runtime/compat.d.ts.map +1 -0
  197. package/ecagent/dist/runtime/compat.js +86 -0
  198. package/ecagent/dist/runtime/compat.js.map +1 -0
  199. package/ecagent/dist/runtime/index.d.ts +20 -0
  200. package/ecagent/dist/runtime/index.d.ts.map +1 -0
  201. package/ecagent/dist/runtime/index.js +17 -0
  202. package/ecagent/dist/runtime/index.js.map +1 -0
  203. package/ecagent/dist/runtime/models.d.ts +142 -0
  204. package/ecagent/dist/runtime/models.d.ts.map +1 -0
  205. package/ecagent/dist/runtime/models.js +245 -0
  206. package/ecagent/dist/runtime/models.js.map +1 -0
  207. package/ecagent/dist/runtime/oauth.d.ts +2 -0
  208. package/ecagent/dist/runtime/oauth.d.ts.map +1 -0
  209. package/ecagent/dist/runtime/oauth.js +2 -0
  210. package/ecagent/dist/runtime/oauth.js.map +1 -0
  211. package/ecagent/dist/runtime/providers/faux.d.ts +97 -0
  212. package/ecagent/dist/runtime/providers/faux.d.ts.map +1 -0
  213. package/ecagent/dist/runtime/providers/faux.js +395 -0
  214. package/ecagent/dist/runtime/providers/faux.js.map +1 -0
  215. package/ecagent/dist/runtime/types.d.ts +607 -0
  216. package/ecagent/dist/runtime/types.d.ts.map +1 -0
  217. package/ecagent/dist/runtime/types.js +2 -0
  218. package/ecagent/dist/runtime/types.js.map +1 -0
  219. package/ecagent/dist/runtime/utils/diagnostics.d.ts +19 -0
  220. package/ecagent/dist/runtime/utils/diagnostics.d.ts.map +1 -0
  221. package/ecagent/dist/runtime/utils/diagnostics.js +25 -0
  222. package/ecagent/dist/runtime/utils/diagnostics.js.map +1 -0
  223. package/ecagent/dist/runtime/utils/event-stream.d.ts +21 -0
  224. package/ecagent/dist/runtime/utils/event-stream.d.ts.map +1 -0
  225. package/ecagent/dist/runtime/utils/event-stream.js +77 -0
  226. package/ecagent/dist/runtime/utils/event-stream.js.map +1 -0
  227. package/ecagent/dist/runtime/utils/headers.d.ts +4 -0
  228. package/ecagent/dist/runtime/utils/headers.d.ts.map +1 -0
  229. package/ecagent/dist/runtime/utils/headers.js +18 -0
  230. package/ecagent/dist/runtime/utils/headers.js.map +1 -0
  231. package/ecagent/dist/runtime/utils/json-parse.d.ts +16 -0
  232. package/ecagent/dist/runtime/utils/json-parse.d.ts.map +1 -0
  233. package/ecagent/dist/runtime/utils/json-parse.js +113 -0
  234. package/ecagent/dist/runtime/utils/json-parse.js.map +1 -0
  235. package/ecagent/dist/runtime/utils/oauth/types.d.ts +64 -0
  236. package/ecagent/dist/runtime/utils/oauth/types.d.ts.map +1 -0
  237. package/ecagent/dist/runtime/utils/oauth/types.js +2 -0
  238. package/ecagent/dist/runtime/utils/oauth/types.js.map +1 -0
  239. package/ecagent/dist/runtime/utils/overflow.d.ts +58 -0
  240. package/ecagent/dist/runtime/utils/overflow.d.ts.map +1 -0
  241. package/ecagent/dist/runtime/utils/overflow.js +158 -0
  242. package/ecagent/dist/runtime/utils/overflow.js.map +1 -0
  243. package/ecagent/dist/runtime/utils/provider-env.d.ts +7 -0
  244. package/ecagent/dist/runtime/utils/provider-env.d.ts.map +1 -0
  245. package/ecagent/dist/runtime/utils/provider-env.js +44 -0
  246. package/ecagent/dist/runtime/utils/provider-env.js.map +1 -0
  247. package/ecagent/dist/runtime/utils/retry.d.ts +12 -0
  248. package/ecagent/dist/runtime/utils/retry.d.ts.map +1 -0
  249. package/ecagent/dist/runtime/utils/retry.js +90 -0
  250. package/ecagent/dist/runtime/utils/retry.js.map +1 -0
  251. package/ecagent/dist/runtime/utils/typebox-helpers.d.ts +17 -0
  252. package/ecagent/dist/runtime/utils/typebox-helpers.d.ts.map +1 -0
  253. package/ecagent/dist/runtime/utils/typebox-helpers.js +21 -0
  254. package/ecagent/dist/runtime/utils/typebox-helpers.js.map +1 -0
  255. package/ecagent/dist/runtime/utils/validation.d.ts +18 -0
  256. package/ecagent/dist/runtime/utils/validation.d.ts.map +1 -0
  257. package/ecagent/dist/runtime/utils/validation.js +269 -0
  258. package/ecagent/dist/runtime/utils/validation.js.map +1 -0
  259. package/ecagent/dist/types.d.ts +401 -0
  260. package/ecagent/dist/types.d.ts.map +1 -0
  261. package/ecagent/dist/types.js +2 -0
  262. package/ecagent/dist/types.js.map +1 -0
  263. package/ecagent/package.json +93 -0
  264. package/ecagent/src/agent-loop.ts +792 -0
  265. package/ecagent/src/agent.ts +575 -0
  266. package/ecagent/src/harness/agent-harness.ts +1029 -0
  267. package/ecagent/src/harness/compaction/branch-summarization.ts +261 -0
  268. package/ecagent/src/harness/compaction/compaction.ts +753 -0
  269. package/ecagent/src/harness/compaction/utils.ts +144 -0
  270. package/ecagent/src/harness/env/nodejs.ts +569 -0
  271. package/ecagent/src/harness/messages.ts +164 -0
  272. package/ecagent/src/harness/prompt-templates.ts +267 -0
  273. package/ecagent/src/harness/session/jsonl-repo.ts +179 -0
  274. package/ecagent/src/harness/session/jsonl-storage.ts +314 -0
  275. package/ecagent/src/harness/session/memory-repo.ts +50 -0
  276. package/ecagent/src/harness/session/memory-storage.ts +133 -0
  277. package/ecagent/src/harness/session/repo-utils.ts +51 -0
  278. package/ecagent/src/harness/session/session.ts +338 -0
  279. package/ecagent/src/harness/session/uuid.ts +54 -0
  280. package/ecagent/src/harness/skills.ts +375 -0
  281. package/ecagent/src/harness/system-prompt.ts +34 -0
  282. package/ecagent/src/harness/types.ts +838 -0
  283. package/ecagent/src/harness/utils/shell-output.ts +135 -0
  284. package/ecagent/src/harness/utils/truncate.ts +344 -0
  285. package/ecagent/src/index.ts +46 -0
  286. package/ecagent/src/node.ts +2 -0
  287. package/ecagent/src/proxy.ts +367 -0
  288. package/ecagent/src/runtime/api/lazy.ts +70 -0
  289. package/ecagent/src/runtime/api/pi-messages.lazy.ts +4 -0
  290. package/ecagent/src/runtime/api/pi-messages.ts +436 -0
  291. package/ecagent/src/runtime/auth/context.ts +45 -0
  292. package/ecagent/src/runtime/auth/credential-store.ts +47 -0
  293. package/ecagent/src/runtime/auth/helpers.ts +46 -0
  294. package/ecagent/src/runtime/auth/resolve.ts +141 -0
  295. package/ecagent/src/runtime/auth/types.ts +182 -0
  296. package/ecagent/src/runtime/compat.ts +158 -0
  297. package/ecagent/src/runtime/index.ts +31 -0
  298. package/ecagent/src/runtime/models.ts +452 -0
  299. package/ecagent/src/runtime/oauth.ts +1 -0
  300. package/ecagent/src/runtime/providers/faux.ts +538 -0
  301. package/ecagent/src/runtime/types.ts +718 -0
  302. package/ecagent/src/runtime/utils/diagnostics.ts +45 -0
  303. package/ecagent/src/runtime/utils/event-stream.ts +88 -0
  304. package/ecagent/src/runtime/utils/headers.ts +18 -0
  305. package/ecagent/src/runtime/utils/json-parse.ts +124 -0
  306. package/ecagent/src/runtime/utils/oauth/types.ts +79 -0
  307. package/ecagent/src/runtime/utils/overflow.ts +165 -0
  308. package/ecagent/src/runtime/utils/provider-env.ts +52 -0
  309. package/ecagent/src/runtime/utils/retry.ts +101 -0
  310. package/ecagent/src/runtime/utils/typebox-helpers.ts +24 -0
  311. package/ecagent/src/runtime/utils/validation.ts +310 -0
  312. package/ecagent/src/types.ts +430 -0
  313. package/ecagent/tsconfig.build.json +32 -0
  314. package/kits/schemas/_meta.json +7 -4
  315. package/kits/schemas/agent-config.schema.10.json +370 -0
  316. package/kits/schemas/defaults.schema.4.json +93 -0
  317. package/kits/schemas/relation-config.schema.8.json +69 -0
  318. package/kits/templates/roles/admin.json +1 -1
  319. package/package.json +22 -6
  320. package/skills/eclink/SKILL.md +70 -0
  321. package/skills/eclink/agents/openai.yaml +4 -0
  322. package/assets/.env.template +0 -4
@@ -0,0 +1,45 @@
1
+ export interface DiagnosticErrorInfo {
2
+ name?: string;
3
+ message: string;
4
+ stack?: string;
5
+ code?: string | number;
6
+ }
7
+
8
+ export interface AssistantMessageDiagnostic {
9
+ type: string;
10
+ timestamp: number;
11
+ error?: DiagnosticErrorInfo;
12
+ details?: Record<string, unknown>;
13
+ }
14
+
15
+ export function formatThrownValue(value: unknown): string {
16
+ if (value instanceof Error) return value.message || value.name;
17
+ if (typeof value === "string") return value;
18
+ return String(value);
19
+ }
20
+
21
+ export function extractDiagnosticError(error: unknown): DiagnosticErrorInfo {
22
+ if (!(error instanceof Error)) return { name: "ThrownValue", message: formatThrownValue(error) };
23
+ const code = (error as Error & { code?: unknown }).code;
24
+ return {
25
+ name: error.name || undefined,
26
+ message: error.message || error.name,
27
+ stack: error.stack,
28
+ code: typeof code === "string" || typeof code === "number" ? code : undefined,
29
+ };
30
+ }
31
+
32
+ export function createAssistantMessageDiagnostic(
33
+ type: string,
34
+ error: unknown,
35
+ details?: Record<string, unknown>,
36
+ ): AssistantMessageDiagnostic {
37
+ return { type, timestamp: Date.now(), error: extractDiagnosticError(error), details };
38
+ }
39
+
40
+ export function appendAssistantMessageDiagnostic<T extends { diagnostics?: AssistantMessageDiagnostic[] }>(
41
+ message: T,
42
+ diagnostic: AssistantMessageDiagnostic,
43
+ ): void {
44
+ message.diagnostics = [...(message.diagnostics ?? []), diagnostic];
45
+ }
@@ -0,0 +1,88 @@
1
+ import type { AssistantMessage, AssistantMessageEvent } from "../types.ts";
2
+
3
+ // Generic event stream class for async iteration
4
+ export class EventStream<T, R = T> implements AsyncIterable<T> {
5
+ private queue: T[] = [];
6
+ private waiting: ((value: IteratorResult<T>) => void)[] = [];
7
+ private done = false;
8
+ private finalResultPromise: Promise<R>;
9
+ private resolveFinalResult!: (result: R) => void;
10
+ private isComplete: (event: T) => boolean;
11
+ private extractResult: (event: T) => R;
12
+
13
+ constructor(isComplete: (event: T) => boolean, extractResult: (event: T) => R) {
14
+ this.isComplete = isComplete;
15
+ this.extractResult = extractResult;
16
+ this.finalResultPromise = new Promise((resolve) => {
17
+ this.resolveFinalResult = resolve;
18
+ });
19
+ }
20
+
21
+ push(event: T): void {
22
+ if (this.done) return;
23
+
24
+ if (this.isComplete(event)) {
25
+ this.done = true;
26
+ this.resolveFinalResult(this.extractResult(event));
27
+ }
28
+
29
+ // Deliver to waiting consumer or queue it
30
+ const waiter = this.waiting.shift();
31
+ if (waiter) {
32
+ waiter({ value: event, done: false });
33
+ } else {
34
+ this.queue.push(event);
35
+ }
36
+ }
37
+
38
+ end(result?: R): void {
39
+ this.done = true;
40
+ if (result !== undefined) {
41
+ this.resolveFinalResult(result);
42
+ }
43
+ // Notify all waiting consumers that we're done
44
+ while (this.waiting.length > 0) {
45
+ const waiter = this.waiting.shift()!;
46
+ waiter({ value: undefined as any, done: true });
47
+ }
48
+ }
49
+
50
+ async *[Symbol.asyncIterator](): AsyncIterator<T> {
51
+ while (true) {
52
+ if (this.queue.length > 0) {
53
+ yield this.queue.shift()!;
54
+ } else if (this.done) {
55
+ return;
56
+ } else {
57
+ const result = await new Promise<IteratorResult<T>>((resolve) => this.waiting.push(resolve));
58
+ if (result.done) return;
59
+ yield result.value;
60
+ }
61
+ }
62
+ }
63
+
64
+ result(): Promise<R> {
65
+ return this.finalResultPromise;
66
+ }
67
+ }
68
+
69
+ export class AssistantMessageEventStream extends EventStream<AssistantMessageEvent, AssistantMessage> {
70
+ constructor() {
71
+ super(
72
+ (event) => event.type === "done" || event.type === "error",
73
+ (event) => {
74
+ if (event.type === "done") {
75
+ return event.message;
76
+ } else if (event.type === "error") {
77
+ return event.error;
78
+ }
79
+ throw new Error("Unexpected event type for final result");
80
+ },
81
+ );
82
+ }
83
+ }
84
+
85
+ /** Factory function for AssistantMessageEventStream (for use in extensions) */
86
+ export function createAssistantMessageEventStream(): AssistantMessageEventStream {
87
+ return new AssistantMessageEventStream();
88
+ }
@@ -0,0 +1,18 @@
1
+ import type { ProviderHeaders } from "../types.ts";
2
+
3
+ export function headersToRecord(headers: Headers): Record<string, string> {
4
+ const result: Record<string, string> = {};
5
+ for (const [key, value] of headers.entries()) {
6
+ result[key] = value;
7
+ }
8
+ return result;
9
+ }
10
+
11
+ export function providerHeadersToRecord(headers: ProviderHeaders | undefined): Record<string, string> | undefined {
12
+ if (!headers) return undefined;
13
+ const result: Record<string, string> = {};
14
+ for (const [key, value] of Object.entries(headers)) {
15
+ if (value !== null) result[key] = value;
16
+ }
17
+ return Object.keys(result).length > 0 ? result : undefined;
18
+ }
@@ -0,0 +1,124 @@
1
+ import { parse as partialParse } from "partial-json";
2
+
3
+ const VALID_JSON_ESCAPES = new Set(['"', "\\", "/", "b", "f", "n", "r", "t", "u"]);
4
+
5
+ function isControlCharacter(char: string): boolean {
6
+ const codePoint = char.codePointAt(0);
7
+ return codePoint !== undefined && codePoint >= 0x00 && codePoint <= 0x1f;
8
+ }
9
+
10
+ function escapeControlCharacter(char: string): string {
11
+ switch (char) {
12
+ case "\b":
13
+ return "\\b";
14
+ case "\f":
15
+ return "\\f";
16
+ case "\n":
17
+ return "\\n";
18
+ case "\r":
19
+ return "\\r";
20
+ case "\t":
21
+ return "\\t";
22
+ default:
23
+ return `\\u${char.codePointAt(0)?.toString(16).padStart(4, "0") ?? "0000"}`;
24
+ }
25
+ }
26
+
27
+ /**
28
+ * Repairs malformed JSON string literals by:
29
+ * - escaping raw control characters inside strings
30
+ * - doubling backslashes before invalid escape characters
31
+ */
32
+ export function repairJson(json: string): string {
33
+ let repaired = "";
34
+ let inString = false;
35
+
36
+ for (let index = 0; index < json.length; index++) {
37
+ const char = json[index];
38
+
39
+ if (!inString) {
40
+ repaired += char;
41
+ if (char === '"') {
42
+ inString = true;
43
+ }
44
+ continue;
45
+ }
46
+
47
+ if (char === '"') {
48
+ repaired += char;
49
+ inString = false;
50
+ continue;
51
+ }
52
+
53
+ if (char === "\\") {
54
+ const nextChar = json[index + 1];
55
+ if (nextChar === undefined) {
56
+ repaired += "\\\\";
57
+ continue;
58
+ }
59
+
60
+ if (nextChar === "u") {
61
+ const unicodeDigits = json.slice(index + 2, index + 6);
62
+ if (/^[0-9a-fA-F]{4}$/.test(unicodeDigits)) {
63
+ repaired += `\\u${unicodeDigits}`;
64
+ index += 5;
65
+ continue;
66
+ }
67
+ }
68
+
69
+ if (VALID_JSON_ESCAPES.has(nextChar)) {
70
+ repaired += `\\${nextChar}`;
71
+ index += 1;
72
+ continue;
73
+ }
74
+
75
+ repaired += "\\\\";
76
+ continue;
77
+ }
78
+
79
+ repaired += isControlCharacter(char) ? escapeControlCharacter(char) : char;
80
+ }
81
+
82
+ return repaired;
83
+ }
84
+
85
+ export function parseJsonWithRepair<T>(json: string): T {
86
+ try {
87
+ return JSON.parse(json) as T;
88
+ } catch (error) {
89
+ const repairedJson = repairJson(json);
90
+ if (repairedJson !== json) {
91
+ return JSON.parse(repairedJson) as T;
92
+ }
93
+ throw error;
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Attempts to parse potentially incomplete JSON during streaming.
99
+ * Always returns a valid object, even if the JSON is incomplete.
100
+ *
101
+ * @param partialJson The partial JSON string from streaming
102
+ * @returns Parsed object or empty object if parsing fails
103
+ */
104
+ export function parseStreamingJson<T = Record<string, unknown>>(partialJson: string | undefined): T {
105
+ if (!partialJson || partialJson.trim() === "") {
106
+ return {} as T;
107
+ }
108
+
109
+ try {
110
+ return parseJsonWithRepair<T>(partialJson);
111
+ } catch {
112
+ try {
113
+ const result = partialParse(partialJson);
114
+ return (result ?? {}) as T;
115
+ } catch {
116
+ try {
117
+ const result = partialParse(repairJson(partialJson));
118
+ return (result ?? {}) as T;
119
+ } catch {
120
+ return {} as T;
121
+ }
122
+ }
123
+ }
124
+ }
@@ -0,0 +1,79 @@
1
+ import type { Api, Model } from "../../types.ts";
2
+
3
+ export type OAuthCredentials = {
4
+ refresh: string;
5
+ access: string;
6
+ expires: number;
7
+ [key: string]: unknown;
8
+ };
9
+
10
+ export type OAuthProviderId = string;
11
+
12
+ /** @deprecated Use OAuthProviderId instead */
13
+ export type OAuthProvider = OAuthProviderId;
14
+
15
+ export type OAuthPrompt = {
16
+ message: string;
17
+ placeholder?: string;
18
+ allowEmpty?: boolean;
19
+ };
20
+
21
+ export type OAuthAuthInfo = {
22
+ url: string;
23
+ instructions?: string;
24
+ };
25
+
26
+ export type OAuthDeviceCodeInfo = {
27
+ userCode: string;
28
+ verificationUri: string;
29
+ intervalSeconds?: number;
30
+ expiresInSeconds?: number;
31
+ };
32
+
33
+ export type OAuthSelectOption = {
34
+ id: string;
35
+ label: string;
36
+ };
37
+
38
+ export type OAuthSelectPrompt = {
39
+ message: string;
40
+ options: OAuthSelectOption[];
41
+ };
42
+
43
+ export interface OAuthLoginCallbacks {
44
+ onAuth: (info: OAuthAuthInfo) => void;
45
+ onDeviceCode: (info: OAuthDeviceCodeInfo) => void;
46
+ onPrompt: (prompt: OAuthPrompt) => Promise<string>;
47
+ onProgress?: (message: string) => void;
48
+ onManualCodeInput?: () => Promise<string>;
49
+ /** Show an interactive selector and return the selected option id, or undefined on cancel. */
50
+ onSelect: (prompt: OAuthSelectPrompt) => Promise<string | undefined>;
51
+ signal?: AbortSignal;
52
+ }
53
+
54
+ export interface OAuthProviderInterface {
55
+ readonly id: OAuthProviderId;
56
+ readonly name: string;
57
+
58
+ /** Run the login flow, return credentials to persist */
59
+ login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials>;
60
+
61
+ /** Whether login uses a local callback server and supports manual code input. */
62
+ usesCallbackServer?: boolean;
63
+
64
+ /** Refresh expired credentials, return updated credentials to persist */
65
+ refreshToken(credentials: OAuthCredentials): Promise<OAuthCredentials>;
66
+
67
+ /** Convert credentials to API key string for the provider */
68
+ getApiKey(credentials: OAuthCredentials): string;
69
+
70
+ /** Optional: modify models for this provider (e.g., update baseUrl) */
71
+ modifyModels?(models: Model<Api>[], credentials: OAuthCredentials): Model<Api>[];
72
+ }
73
+
74
+ /** @deprecated Use OAuthProviderInterface instead */
75
+ export interface OAuthProviderInfo {
76
+ id: OAuthProviderId;
77
+ name: string;
78
+ available: boolean;
79
+ }
@@ -0,0 +1,165 @@
1
+ import type { AssistantMessage } from "../types.ts";
2
+
3
+ /**
4
+ * Regex patterns to detect context overflow errors from different providers.
5
+ *
6
+ * These patterns match error messages returned when the input exceeds
7
+ * the model's context window.
8
+ *
9
+ * Provider-specific patterns (with example error messages):
10
+ *
11
+ * - Anthropic: "prompt is too long: 213462 tokens > 200000 maximum"
12
+ * - Anthropic: "413 {\"error\":{\"type\":\"request_too_large\",\"message\":\"Request exceeds the maximum size\"}}"
13
+ * - OpenAI: "Your input exceeds the context window of this model"
14
+ * - OpenAI/LiteLLM: "Requested token count exceeds the model's maximum context length of 131072 tokens"
15
+ * - OpenAI-compatible: "Input length (265330) exceeds model's maximum context length (262144)."
16
+ * - Google: "The input token count (1196265) exceeds the maximum number of tokens allowed (1048575)"
17
+ * - xAI: "This model's maximum prompt length is 131072 but the request contains 537812 tokens"
18
+ * - Groq: "Please reduce the length of the messages or completion"
19
+ * - OpenRouter: "This endpoint's maximum context length is X tokens. However, you requested about Y tokens"
20
+ * - OpenRouter/Poolside: "Input length X exceeds the maximum allowed input length of Y tokens."
21
+ * - Together AI: "The input (X tokens) is longer than the model's context length (Y tokens)."
22
+ * - llama.cpp: "the request exceeds the available context size, try increasing it"
23
+ * - LM Studio: "tokens to keep from the initial prompt is greater than the context length"
24
+ * - GitHub Copilot: "prompt token count of X exceeds the limit of Y"
25
+ * - MiniMax: "invalid params, context window exceeds limit"
26
+ * - Kimi For Coding: "Your request exceeded model token limit: X (requested: Y)"
27
+ * - DS4: "Prompt has X tokens, but the configured context size is Y tokens"
28
+ * - Cerebras: "400/413 status code (no body)"
29
+ * - Mistral: "Prompt contains X tokens ... too large for model with Y maximum context length"
30
+ * - z.ai: Does NOT error, accepts overflow silently - handled via usage.input > contextWindow
31
+ * - Xiaomi MiMo: Truncates input to fill contextWindow exactly, then returns finish_reason "length"
32
+ * with output=0 (no room left to generate). Detected via stopReason "length" + zero output +
33
+ * input filling the context window.
34
+ * - Ollama: Some deployments truncate silently, others return errors like "prompt too long; exceeded max context length by X tokens"
35
+ */
36
+ const OVERFLOW_PATTERNS = [
37
+ /prompt is too long/i, // Anthropic token overflow
38
+ /request_too_large/i, // Anthropic request byte-size overflow (HTTP 413)
39
+ /input is too long for requested model/i, // Amazon Bedrock
40
+ /exceeds the context window/i, // OpenAI (Completions & Responses API)
41
+ /exceeds (?:the )?(?:model'?s )?maximum context length(?: of [\d,]+ tokens?|\s*\([\d,]+\))/i, // OpenAI-compatible proxies (LiteLLM)
42
+ /input token count.*exceeds the maximum/i, // Google (Gemini)
43
+ /maximum prompt length is \d+/i, // xAI (Grok)
44
+ /reduce the length of the messages/i, // Groq
45
+ /maximum context length is \d+ tokens/i, // OpenRouter (most backends)
46
+ /exceeds (?:the )?maximum allowed input length of [\d,]+ tokens?/i, // OpenRouter/Poolside
47
+ /input \(\d+ tokens\) is longer than the model'?s context length \(\d+ tokens\)/i, // Together AI
48
+ /exceeds the limit of \d+/i, // GitHub Copilot
49
+ /exceeds the available context size/i, // llama.cpp server
50
+ /greater than the context length/i, // LM Studio
51
+ /context window exceeds limit/i, // MiniMax
52
+ /exceeded model token limit/i, // Kimi For Coding
53
+ /too large for model with \d+ maximum context length/i, // Mistral
54
+ /prompt has [\d,]+ tokens?, but the configured context size is [\d,]+ tokens?/i, // DS4 server
55
+ /model_context_window_exceeded/i, // z.ai non-standard finish_reason surfaced as error text
56
+ /prompt too long; exceeded (?:max )?context length/i, // Ollama explicit overflow error
57
+ /context[_ ]length[_ ]exceeded/i, // Generic fallback
58
+ /too many tokens/i, // Generic fallback
59
+ /token limit exceeded/i, // Generic fallback
60
+ /^4(?:00|13)\s*(?:status code)?\s*\(no body\)/i, // Cerebras: 400/413 with no body
61
+ ];
62
+
63
+ /**
64
+ * Patterns that indicate non-overflow errors (e.g. rate limiting, server errors).
65
+ * Error messages matching any of these are excluded from overflow detection
66
+ * even if they also match an OVERFLOW_PATTERN.
67
+ *
68
+ * Example: Bedrock formats throttling errors as "ThrottlingException: Too many tokens,
69
+ * please wait before trying again." which would match the /too many tokens/i overflow
70
+ * pattern without this exclusion.
71
+ */
72
+ const NON_OVERFLOW_PATTERNS = [
73
+ /^(Throttling error|Service unavailable):/i, // AWS Bedrock non-overflow errors (human-readable prefixes from formatBedrockError)
74
+ /rate limit/i, // Generic rate limiting
75
+ /too many requests/i, // Generic HTTP 429 style
76
+ ];
77
+
78
+ /**
79
+ * Check if an assistant message represents a context overflow error.
80
+ *
81
+ * This handles two cases:
82
+ * 1. Error-based overflow: Most providers return stopReason "error" with a
83
+ * specific error message pattern.
84
+ * 2. Silent overflow: Some providers accept overflow requests and return
85
+ * successfully. For these, we check if usage.input exceeds the context window.
86
+ *
87
+ * ## Reliability by Provider
88
+ *
89
+ * **Reliable detection (returns error with detectable message):**
90
+ * - Anthropic: "prompt is too long: X tokens > Y maximum" or "request_too_large"
91
+ * - OpenAI (Completions & Responses): "exceeds the context window", "exceeds the model's maximum context length of X tokens", or "exceeds model's maximum context length (X)"
92
+ * - Google Gemini: "input token count exceeds the maximum"
93
+ * - xAI (Grok): "maximum prompt length is X but request contains Y"
94
+ * - Groq: "reduce the length of the messages"
95
+ * - Cerebras: 400/413 status code (no body)
96
+ * - Mistral: "Prompt contains X tokens ... too large for model with Y maximum context length"
97
+ * - OpenRouter (most backends): "maximum context length is X tokens"
98
+ * - OpenRouter/Poolside: "Input length X exceeds the maximum allowed input length of Y tokens."
99
+ * - Together AI: "The input (X tokens) is longer than the model's context length (Y tokens)."
100
+ * - llama.cpp: "exceeds the available context size"
101
+ * - LM Studio: "greater than the context length"
102
+ * - Kimi For Coding: "exceeded model token limit: X (requested: Y)"
103
+ * - DS4: "Prompt has X tokens, but the configured context size is Y tokens"
104
+ *
105
+ * **Unreliable detection:**
106
+ * - z.ai: Sometimes accepts overflow silently (detectable via usage.input > contextWindow),
107
+ * sometimes returns rate limit errors. Pass contextWindow param to detect silent overflow.
108
+ * - Xiaomi MiMo: Truncates input to fit contextWindow then returns stopReason "length" with
109
+ * output=0. Pass contextWindow param to detect via the "filled context + zero output" signal.
110
+ * - Ollama: May truncate input silently for some setups, but may also return explicit
111
+ * overflow errors that match the patterns above. Silent truncation still cannot be
112
+ * detected here because we do not know the expected token count.
113
+ *
114
+ * ## Custom Providers
115
+ *
116
+ * If you've added custom models via settings.json, this function may not detect
117
+ * overflow errors from those providers. To add support:
118
+ *
119
+ * 1. Send a request that exceeds the model's context window
120
+ * 2. Check the errorMessage in the response
121
+ * 3. Create a regex pattern that matches the error
122
+ * 4. The pattern should be added to OVERFLOW_PATTERNS in this file, or
123
+ * check the errorMessage yourself before calling this function
124
+ *
125
+ * @param message - The assistant message to check
126
+ * @param contextWindow - Optional context window size for detecting silent overflow (z.ai)
127
+ * @returns true if the message indicates a context overflow
128
+ */
129
+ export function isContextOverflow(message: AssistantMessage, contextWindow?: number): boolean {
130
+ // Case 1: Check error message patterns
131
+ if (message.stopReason === "error" && message.errorMessage) {
132
+ // Skip messages matching known non-overflow patterns (e.g. throttling / rate-limit)
133
+ const isNonOverflow = NON_OVERFLOW_PATTERNS.some((p) => p.test(message.errorMessage!));
134
+ if (!isNonOverflow && OVERFLOW_PATTERNS.some((p) => p.test(message.errorMessage!))) {
135
+ return true;
136
+ }
137
+ }
138
+
139
+ // Case 2: Silent overflow (z.ai style) - successful but usage exceeds context
140
+ if (contextWindow && message.stopReason === "stop") {
141
+ const inputTokens = message.usage.input + message.usage.cacheRead;
142
+ if (inputTokens > contextWindow) {
143
+ return true;
144
+ }
145
+ }
146
+
147
+ // Case 3: Length-stop overflow (Xiaomi MiMo style) - server truncates oversized input
148
+ // to fit the context window, leaving no room for output. Returns stopReason "length"
149
+ // with output=0 and input+cacheRead filling the context window.
150
+ if (contextWindow && message.stopReason === "length" && message.usage.output === 0) {
151
+ const inputTokens = message.usage.input + message.usage.cacheRead;
152
+ if (inputTokens >= contextWindow * 0.99) {
153
+ return true;
154
+ }
155
+ }
156
+
157
+ return false;
158
+ }
159
+
160
+ /**
161
+ * Get the overflow patterns for testing purposes.
162
+ */
163
+ export function getOverflowPatterns(): RegExp[] {
164
+ return [...OVERFLOW_PATTERNS];
165
+ }
@@ -0,0 +1,52 @@
1
+ import type { ProviderEnv } from "../types.ts";
2
+
3
+ let procEnvCache: Map<string, string> | null = null;
4
+
5
+ /**
6
+ * Fallback for https://github.com/oven-sh/bun/issues/27802.
7
+ * Bun compiled binaries can expose an empty process.env inside Linux sandboxes
8
+ * even though /proc/self/environ contains the environment.
9
+ *
10
+ * This intentionally duplicates restoreSandboxEnv() in
11
+ * packages/coding-agent/src/bun/restore-sandbox-env.ts. The ai package can be
12
+ * used directly, without going through that entrypoint, so provider env lookup
13
+ * must not depend on process.env having been patched.
14
+ */
15
+ function getBunSandboxEnvValue(name: string): string | undefined {
16
+ if (typeof process === "undefined" || !process.versions?.bun || Object.keys(process.env).length > 0) {
17
+ return undefined;
18
+ }
19
+
20
+ if (procEnvCache === null) {
21
+ procEnvCache = new Map();
22
+ try {
23
+ const { readFileSync } = require("node:fs") as {
24
+ readFileSync(path: string, encoding: BufferEncoding): string;
25
+ };
26
+ const data = readFileSync("/proc/self/environ", "utf-8");
27
+ for (const entry of data.split("\0")) {
28
+ const idx = entry.indexOf("=");
29
+ if (idx > 0) {
30
+ procEnvCache.set(entry.slice(0, idx), entry.slice(idx + 1));
31
+ }
32
+ }
33
+ } catch {
34
+ // /proc/self/environ may not exist or may not be readable.
35
+ }
36
+ }
37
+
38
+ return procEnvCache.get(name);
39
+ }
40
+
41
+ /**
42
+ * Resolve a provider env value from scoped overrides, normal process.env, then
43
+ * the duplicated Bun sandbox fallback for direct pi-ai consumers.
44
+ */
45
+ export function getProviderEnvValue(name: string, env?: ProviderEnv): string | undefined {
46
+ return (
47
+ env?.[name] ||
48
+ (typeof process !== "undefined" ? process.env[name] : undefined) ||
49
+ getBunSandboxEnvValue(name) ||
50
+ undefined
51
+ );
52
+ }
@@ -0,0 +1,101 @@
1
+ import type { AssistantMessage } from "../types.ts";
2
+
3
+ function buildProviderErrorPattern(patterns: readonly string[]): RegExp {
4
+ return new RegExp(patterns.join("|"), "i");
5
+ }
6
+
7
+ const NON_RETRYABLE_PROVIDER_LIMIT_ERROR_PATTERN = buildProviderErrorPattern([
8
+ // OpenCode Go/free-tier limits returned as 429 JSON error types by OpenCode's
9
+ // Zen API. These are subscription/account limits, not transient throttles.
10
+ "GoUsageLimitError",
11
+ "FreeUsageLimitError",
12
+
13
+ // OpenCode Go subscription-limit text asks users to enable available-balance
14
+ // usage after rolling/weekly/monthly limits are reached.
15
+ "Monthly usage limit reached",
16
+ "available balance",
17
+
18
+ // Generic quota/budget/billing exhaustion. `insufficient_quota` is OpenAI's
19
+ // quota/billing error code; the other strings cover common gateway wording.
20
+ "insufficient_quota",
21
+ "out of budget",
22
+ "quota exceeded",
23
+ "billing",
24
+ ]);
25
+
26
+ const RETRYABLE_PROVIDER_ERROR_PATTERN = buildProviderErrorPattern([
27
+ // Generic provider load, HTTP status, and server-side transient failures.
28
+ "overloaded",
29
+ "rate.?limit",
30
+ "too many requests",
31
+ "429",
32
+ "500",
33
+ "502",
34
+ "503",
35
+ "504",
36
+ "524",
37
+ "service.?unavailable",
38
+ "server.?error",
39
+ "internal.?error",
40
+
41
+ // Wrapper/provider text for transient upstream failures, including OpenRouter
42
+ // "Provider returned error" responses (#2264).
43
+ "provider.?returned.?error",
44
+
45
+ // Network, proxy, and fetch transport failures. This includes OpenAI Codex
46
+ // raw-fetch failures such as "upstream connect", "connection refused", and
47
+ // "reset before headers" (#733), plus OpenRouter connection drops (#3317).
48
+ "network.?error",
49
+ "connection.?error",
50
+ "connection.?refused",
51
+ "connection.?lost",
52
+ "other side closed",
53
+ "fetch failed",
54
+ "upstream.?connect",
55
+ "reset before headers",
56
+ "socket hang up",
57
+ "socket connection was closed",
58
+ "timed? out",
59
+ "timeout",
60
+ "terminated",
61
+
62
+ // WebSocket transports can report close/error text instead of HTTP/fetch text.
63
+ "websocket.?closed",
64
+ "websocket.?error",
65
+
66
+ // Premature stream endings from SDKs and transports. Anthropic can throw
67
+ // "stream ended without ..." and "Anthropic stream ended before message_stop"
68
+ // (#4433); Bedrock/Smithy can throw an HTTP/2 no-response error (#3594).
69
+ "ended without",
70
+ "stream ended before message_stop",
71
+ "http2 request did not get a response",
72
+
73
+ // Provider-requested retry delay cap failures should flow through the outer
74
+ // retry policy so callers can surface/abort the backoff (#1123).
75
+ "retry delay",
76
+
77
+ // Explicit retry guidance emitted mid-stream by OpenAI Responses and Bedrock
78
+ // stream exceptions (#6019).
79
+ "you can retry your request",
80
+ "try your request again",
81
+ "please retry your request",
82
+
83
+ // gRPC based providers (e.g. NVIDIA NIM)
84
+ "ResourceExhausted",
85
+ ]);
86
+
87
+ /**
88
+ * Classifies whether a failed assistant message looks like a transient provider
89
+ * or transport error, so callers can decide if the last assistant turn should be
90
+ * restarted.
91
+ *
92
+ * This does not implement retry policy. Callers should first handle context
93
+ * overflow separately, then apply their own retry budget, backoff, and reporting
94
+ * before restarting the assistant turn.
95
+ */
96
+ export function isRetryableAssistantError(message: AssistantMessage): boolean {
97
+ if (message.stopReason !== "error" || !message.errorMessage) return false;
98
+ const errorMessage = message.errorMessage;
99
+ if (NON_RETRYABLE_PROVIDER_LIMIT_ERROR_PATTERN.test(errorMessage)) return false;
100
+ return RETRYABLE_PROVIDER_ERROR_PATTERN.test(errorMessage);
101
+ }