@alumbwe/anvil 1.0.20 → 1.0.21

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 (328) hide show
  1. package/package.json +6 -2
  2. package/src/agents/bundled-agents.generated.ts +4 -4
  3. package/src/chat.tsx +1703 -1698
  4. package/src/cli-args.ts +134 -134
  5. package/src/components/status-bar.tsx +331 -313
  6. package/src/hooks/helpers/send-message.ts +663 -654
  7. package/src/hooks/use-send-message.ts +806 -773
  8. package/src/index.tsx +473 -473
  9. package/src/state/chat-store.ts +553 -544
  10. package/src/utils/create-run-config.ts +121 -121
  11. package/src/utils/format-compact-number.ts +37 -0
  12. package/vendor/@anvil/common/package.json +27 -0
  13. package/vendor/@anvil/common/src/__tests__/agent-validation.test.ts +846 -0
  14. package/vendor/@anvil/common/src/__tests__/anvil-model-availability.test.ts +101 -0
  15. package/vendor/@anvil/common/src/__tests__/anvil-models.test.ts +1408 -0
  16. package/vendor/@anvil/common/src/__tests__/anvil-public-data-use-copy.test.ts +258 -0
  17. package/vendor/@anvil/common/src/__tests__/anvil-referral-tiers.test.ts +48 -0
  18. package/vendor/@anvil/common/src/__tests__/anvil-spend-ceilings.test.ts +282 -0
  19. package/vendor/@anvil/common/src/__tests__/anvil-trust.test.ts +526 -0
  20. package/vendor/@anvil/common/src/__tests__/deepseek-direct.test.ts +68 -0
  21. package/vendor/@anvil/common/src/__tests__/disposable-email.test.ts +132 -0
  22. package/vendor/@anvil/common/src/__tests__/dynamic-agent-template-schema.test.ts +422 -0
  23. package/vendor/@anvil/common/src/__tests__/env-ci.test.ts +167 -0
  24. package/vendor/@anvil/common/src/__tests__/env-process.test.ts +145 -0
  25. package/vendor/@anvil/common/src/__tests__/foreign-client-shipped-agents.test.ts +234 -0
  26. package/vendor/@anvil/common/src/__tests__/foreign-client-signals.test.ts +379 -0
  27. package/vendor/@anvil/common/src/__tests__/free-agents.test.ts +807 -0
  28. package/vendor/@anvil/common/src/__tests__/gravity-capi.test.ts +154 -0
  29. package/vendor/@anvil/common/src/__tests__/handlesteps-parsing.test.ts +246 -0
  30. package/vendor/@anvil/common/src/__tests__/kimi-k3-god-only.test.ts +77 -0
  31. package/vendor/@anvil/common/src/__tests__/model-config.test.ts +89 -0
  32. package/vendor/@anvil/common/src/__tests__/project-file-tree.test.ts +298 -0
  33. package/vendor/@anvil/common/src/__tests__/provisioned-model-tiers.test.ts +116 -0
  34. package/vendor/@anvil/common/src/__tests__/reasoning-effort.test.ts +167 -0
  35. package/vendor/@anvil/common/src/__tests__/reddit-capi.test.ts +132 -0
  36. package/vendor/@anvil/common/src/__tests__/response-ad-positions.test.ts +72 -0
  37. package/vendor/@anvil/common/src/__tests__/user-state.test.ts +30 -0
  38. package/vendor/@anvil/common/src/actions.ts +215 -0
  39. package/vendor/@anvil/common/src/analytics-core.ts +69 -0
  40. package/vendor/@anvil/common/src/analytics.ts +93 -0
  41. package/vendor/@anvil/common/src/api-keys/constants.ts +26 -0
  42. package/vendor/@anvil/common/src/browser-actions.ts +413 -0
  43. package/vendor/@anvil/common/src/constants/__tests__/anvil-onboarding-gate.test.ts +63 -0
  44. package/vendor/@anvil/common/src/constants/__tests__/anvil-onboarding.test.ts +246 -0
  45. package/vendor/@anvil/common/src/constants/__tests__/cf-worker-signals.test.ts +106 -0
  46. package/vendor/@anvil/common/src/constants/agents.ts +99 -0
  47. package/vendor/@anvil/common/src/constants/analytics-events.ts +435 -0
  48. package/vendor/@anvil/common/src/constants/anthropic.ts +73 -0
  49. package/vendor/@anvil/common/src/constants/anvil-data-use.ts +81 -0
  50. package/vendor/@anvil/common/src/constants/anvil-errors.ts +7 -0
  51. package/vendor/@anvil/common/src/constants/anvil-gemini-thinker.ts +21 -0
  52. package/vendor/@anvil/common/src/constants/anvil-model-ids.ts +11 -0
  53. package/vendor/@anvil/common/src/constants/anvil-models.ts +2130 -0
  54. package/vendor/@anvil/common/src/constants/anvil-onboarding-gate.ts +55 -0
  55. package/vendor/@anvil/common/src/constants/anvil-onboarding.ts +307 -0
  56. package/vendor/@anvil/common/src/constants/anvil-referral-tiers.ts +87 -0
  57. package/vendor/@anvil/common/src/constants/anvil-signup-block.ts +68 -0
  58. package/vendor/@anvil/common/src/constants/anvil-spend-ceilings.ts +450 -0
  59. package/vendor/@anvil/common/src/constants/anvil-trust.ts +976 -0
  60. package/vendor/@anvil/common/src/constants/auth.ts +18 -0
  61. package/vendor/@anvil/common/src/constants/byok.ts +2 -0
  62. package/vendor/@anvil/common/src/constants/cf-worker-signals.ts +132 -0
  63. package/vendor/@anvil/common/src/constants/composio.ts +34 -0
  64. package/vendor/@anvil/common/src/constants/deepseek-direct.ts +55 -0
  65. package/vendor/@anvil/common/src/constants/feedback.ts +13 -0
  66. package/vendor/@anvil/common/src/constants/foreign-client-signals.ts +272 -0
  67. package/vendor/@anvil/common/src/constants/free-agents.ts +777 -0
  68. package/vendor/@anvil/common/src/constants/gemini.ts +15 -0
  69. package/vendor/@anvil/common/src/constants/grant-priorities.ts +13 -0
  70. package/vendor/@anvil/common/src/constants/hosts.ts +6 -0
  71. package/vendor/@anvil/common/src/constants/images.ts +51 -0
  72. package/vendor/@anvil/common/src/constants/index.ts +7 -0
  73. package/vendor/@anvil/common/src/constants/knowledge.ts +35 -0
  74. package/vendor/@anvil/common/src/constants/limits.ts +23 -0
  75. package/vendor/@anvil/common/src/constants/model-config.ts +277 -0
  76. package/vendor/@anvil/common/src/constants/openrouter-attribution.ts +8 -0
  77. package/vendor/@anvil/common/src/constants/paths.ts +69 -0
  78. package/vendor/@anvil/common/src/constants/provider-routes.ts +317 -0
  79. package/vendor/@anvil/common/src/constants/reasoning-effort.ts +79 -0
  80. package/vendor/@anvil/common/src/constants/skills.ts +60 -0
  81. package/vendor/@anvil/common/src/constants/subscription-plans.ts +49 -0
  82. package/vendor/@anvil/common/src/constants/ui.ts +25 -0
  83. package/vendor/@anvil/common/src/env-ci.ts +36 -0
  84. package/vendor/@anvil/common/src/env-process.ts +95 -0
  85. package/vendor/@anvil/common/src/env-schema.ts +95 -0
  86. package/vendor/@anvil/common/src/env.ts +24 -0
  87. package/vendor/@anvil/common/src/gravity-capi.ts +207 -0
  88. package/vendor/@anvil/common/src/mcp/client.ts +233 -0
  89. package/vendor/@anvil/common/src/old-constants.ts +10 -0
  90. package/vendor/@anvil/common/src/project-file-tree.ts +355 -0
  91. package/vendor/@anvil/common/src/reddit-capi.ts +254 -0
  92. package/vendor/@anvil/common/src/schemas/feedback.ts +52 -0
  93. package/vendor/@anvil/common/src/schemas/logs.ts +66 -0
  94. package/vendor/@anvil/common/src/templates/agent-validation.ts +392 -0
  95. package/vendor/@anvil/common/src/templates/initial-agents-dir/LICENSE +202 -0
  96. package/vendor/@anvil/common/src/templates/initial-agents-dir/README.md +294 -0
  97. package/vendor/@anvil/common/src/templates/initial-agents-dir/examples/01-basic-diff-reviewer.ts +17 -0
  98. package/vendor/@anvil/common/src/templates/initial-agents-dir/examples/02-intermediate-git-committer.ts +78 -0
  99. package/vendor/@anvil/common/src/templates/initial-agents-dir/examples/03-advanced-file-explorer.ts +73 -0
  100. package/vendor/@anvil/common/src/templates/initial-agents-dir/my-custom-agent.ts +40 -0
  101. package/vendor/@anvil/common/src/templates/initial-agents-dir/package.json +6 -0
  102. package/vendor/@anvil/common/src/templates/initial-agents-dir/skills/README.md +65 -0
  103. package/vendor/@anvil/common/src/templates/initial-agents-dir/skills/example-skill/SKILL.md +29 -0
  104. package/vendor/@anvil/common/src/templates/initial-agents-dir/types/agent-definition.ts +497 -0
  105. package/vendor/@anvil/common/src/templates/initial-agents-dir/types/tools.ts +444 -0
  106. package/vendor/@anvil/common/src/templates/initial-agents-dir/types/util-types.ts +178 -0
  107. package/vendor/@anvil/common/src/testing/TESTING_PATTERNS.md +351 -0
  108. package/vendor/@anvil/common/src/testing/anvil-offer-invariants.ts +169 -0
  109. package/vendor/@anvil/common/src/testing/errors.ts +33 -0
  110. package/vendor/@anvil/common/src/testing/fixtures/agent-runtime.ts +334 -0
  111. package/vendor/@anvil/common/src/testing/impl/agent-runtime.ts +6 -0
  112. package/vendor/@anvil/common/src/testing/index.ts +84 -0
  113. package/vendor/@anvil/common/src/testing/mock-modules.ts +53 -0
  114. package/vendor/@anvil/common/src/testing/mock-types.ts +123 -0
  115. package/vendor/@anvil/common/src/testing/mocks/analytics.ts +261 -0
  116. package/vendor/@anvil/common/src/testing/mocks/child-process.ts +93 -0
  117. package/vendor/@anvil/common/src/testing/mocks/crypto.ts +218 -0
  118. package/vendor/@anvil/common/src/testing/mocks/database.ts +337 -0
  119. package/vendor/@anvil/common/src/testing/mocks/fetch.ts +219 -0
  120. package/vendor/@anvil/common/src/testing/mocks/filesystem.ts +166 -0
  121. package/vendor/@anvil/common/src/testing/mocks/index.ts +101 -0
  122. package/vendor/@anvil/common/src/testing/mocks/logger.ts +135 -0
  123. package/vendor/@anvil/common/src/testing/mocks/stream.ts +313 -0
  124. package/vendor/@anvil/common/src/testing/mocks/timers.ts +132 -0
  125. package/vendor/@anvil/common/src/testing/mocks/tree-sitter.ts +127 -0
  126. package/vendor/@anvil/common/src/testing/setup.ts +282 -0
  127. package/vendor/@anvil/common/src/testing-env-ci.ts +15 -0
  128. package/vendor/@anvil/common/src/testing-env-process.ts +78 -0
  129. package/vendor/@anvil/common/src/tools/__tests__/compile-tool-definitions.test.ts +34 -0
  130. package/vendor/@anvil/common/src/tools/compile-tool-definitions.ts +157 -0
  131. package/vendor/@anvil/common/src/tools/constants.ts +117 -0
  132. package/vendor/@anvil/common/src/tools/list.ts +190 -0
  133. package/vendor/@anvil/common/src/tools/params/__tests__/coerce-to-array.test.ts +213 -0
  134. package/vendor/@anvil/common/src/tools/params/__tests__/read-docs.test.ts +47 -0
  135. package/vendor/@anvil/common/src/tools/params/__tests__/render-ui.test.ts +65 -0
  136. package/vendor/@anvil/common/src/tools/params/tool/__tests__/run-terminal-command-timeout.test.ts +49 -0
  137. package/vendor/@anvil/common/src/tools/params/tool/add-message.ts +39 -0
  138. package/vendor/@anvil/common/src/tools/params/tool/add-subgoal.ts +57 -0
  139. package/vendor/@anvil/common/src/tools/params/tool/apply-patch.ts +110 -0
  140. package/vendor/@anvil/common/src/tools/params/tool/ask-user.ts +181 -0
  141. package/vendor/@anvil/common/src/tools/params/tool/browser-logs.ts +85 -0
  142. package/vendor/@anvil/common/src/tools/params/tool/cloud-plan-ready.ts +94 -0
  143. package/vendor/@anvil/common/src/tools/params/tool/code-search.ts +159 -0
  144. package/vendor/@anvil/common/src/tools/params/tool/composio.ts +131 -0
  145. package/vendor/@anvil/common/src/tools/params/tool/create-plan.ts +80 -0
  146. package/vendor/@anvil/common/src/tools/params/tool/end-turn.ts +57 -0
  147. package/vendor/@anvil/common/src/tools/params/tool/find-files.ts +60 -0
  148. package/vendor/@anvil/common/src/tools/params/tool/glob.ts +80 -0
  149. package/vendor/@anvil/common/src/tools/params/tool/gravity-index.ts +93 -0
  150. package/vendor/@anvil/common/src/tools/params/tool/list-directory.ts +58 -0
  151. package/vendor/@anvil/common/src/tools/params/tool/lookup-agent-info.ts +37 -0
  152. package/vendor/@anvil/common/src/tools/params/tool/propose-str-replace.ts +103 -0
  153. package/vendor/@anvil/common/src/tools/params/tool/propose-write-file.ts +71 -0
  154. package/vendor/@anvil/common/src/tools/params/tool/read-docs.ts +90 -0
  155. package/vendor/@anvil/common/src/tools/params/tool/read-files.ts +109 -0
  156. package/vendor/@anvil/common/src/tools/params/tool/read-subtree.ts +79 -0
  157. package/vendor/@anvil/common/src/tools/params/tool/read-url.ts +81 -0
  158. package/vendor/@anvil/common/src/tools/params/tool/render-ui.ts +168 -0
  159. package/vendor/@anvil/common/src/tools/params/tool/run-file-change-hooks.ts +57 -0
  160. package/vendor/@anvil/common/src/tools/params/tool/run-terminal-command.ts +211 -0
  161. package/vendor/@anvil/common/src/tools/params/tool/screenshot.ts +97 -0
  162. package/vendor/@anvil/common/src/tools/params/tool/set-messages.ts +44 -0
  163. package/vendor/@anvil/common/src/tools/params/tool/set-output.ts +61 -0
  164. package/vendor/@anvil/common/src/tools/params/tool/skill.ts +59 -0
  165. package/vendor/@anvil/common/src/tools/params/tool/spawn-agent-inline.ts +56 -0
  166. package/vendor/@anvil/common/src/tools/params/tool/spawn-agents.ts +154 -0
  167. package/vendor/@anvil/common/src/tools/params/tool/str-replace.ts +107 -0
  168. package/vendor/@anvil/common/src/tools/params/tool/suggest-followups.ts +97 -0
  169. package/vendor/@anvil/common/src/tools/params/tool/task-completed.ts +61 -0
  170. package/vendor/@anvil/common/src/tools/params/tool/think-deeply.ts +56 -0
  171. package/vendor/@anvil/common/src/tools/params/tool/update-subgoal.ts +89 -0
  172. package/vendor/@anvil/common/src/tools/params/tool/vision-analyze.ts +82 -0
  173. package/vendor/@anvil/common/src/tools/params/tool/web-search.ts +73 -0
  174. package/vendor/@anvil/common/src/tools/params/tool/write-file.ts +71 -0
  175. package/vendor/@anvil/common/src/tools/params/tool/write-todos.ts +67 -0
  176. package/vendor/@anvil/common/src/tools/params/utils.ts +150 -0
  177. package/vendor/@anvil/common/src/tools/utils.ts +24 -0
  178. package/vendor/@anvil/common/src/types/__tests__/dynamic-agent-template.test.ts +20 -0
  179. package/vendor/@anvil/common/src/types/agent-template.ts +215 -0
  180. package/vendor/@anvil/common/src/types/anvil-session.ts +535 -0
  181. package/vendor/@anvil/common/src/types/anvil-streak.ts +6 -0
  182. package/vendor/@anvil/common/src/types/anvil-usage.ts +64 -0
  183. package/vendor/@anvil/common/src/types/api/agents/publish.ts +61 -0
  184. package/vendor/@anvil/common/src/types/bun-test.d.ts +5 -0
  185. package/vendor/@anvil/common/src/types/contracts/agent-runtime.ts +75 -0
  186. package/vendor/@anvil/common/src/types/contracts/analytics.ts +9 -0
  187. package/vendor/@anvil/common/src/types/contracts/bigquery.ts +55 -0
  188. package/vendor/@anvil/common/src/types/contracts/billing.ts +46 -0
  189. package/vendor/@anvil/common/src/types/contracts/client.ts +53 -0
  190. package/vendor/@anvil/common/src/types/contracts/database.ts +112 -0
  191. package/vendor/@anvil/common/src/types/contracts/env.ts +203 -0
  192. package/vendor/@anvil/common/src/types/contracts/llm.ts +191 -0
  193. package/vendor/@anvil/common/src/types/contracts/logger.ts +14 -0
  194. package/vendor/@anvil/common/src/types/contracts/logs.ts +34 -0
  195. package/vendor/@anvil/common/src/types/contracts/trace.ts +21 -0
  196. package/vendor/@anvil/common/src/types/dynamic-agent-template.ts +328 -0
  197. package/vendor/@anvil/common/src/types/filesystem.ts +10 -0
  198. package/vendor/@anvil/common/src/types/function-params.ts +33 -0
  199. package/vendor/@anvil/common/src/types/grant.ts +20 -0
  200. package/vendor/@anvil/common/src/types/gravity-index.ts +170 -0
  201. package/vendor/@anvil/common/src/types/json.ts +29 -0
  202. package/vendor/@anvil/common/src/types/mcp.ts +24 -0
  203. package/vendor/@anvil/common/src/types/messages/anvil-message.ts +58 -0
  204. package/vendor/@anvil/common/src/types/messages/content-part.ts +59 -0
  205. package/vendor/@anvil/common/src/types/messages/data-content.ts +14 -0
  206. package/vendor/@anvil/common/src/types/messages/provider-metadata.ts +13 -0
  207. package/vendor/@anvil/common/src/types/organization.ts +118 -0
  208. package/vendor/@anvil/common/src/types/print-mode.ts +121 -0
  209. package/vendor/@anvil/common/src/types/publisher.ts +67 -0
  210. package/vendor/@anvil/common/src/types/session-state.ts +149 -0
  211. package/vendor/@anvil/common/src/types/skill.ts +86 -0
  212. package/vendor/@anvil/common/src/types/source.ts +11 -0
  213. package/vendor/@anvil/common/src/types/spawn.ts +13 -0
  214. package/vendor/@anvil/common/src/types/subscription.ts +67 -0
  215. package/vendor/@anvil/common/src/types/usage.ts +16 -0
  216. package/vendor/@anvil/common/src/types/util.ts +3 -0
  217. package/vendor/@anvil/common/src/util/__tests__/ad-user-agent.test.ts +26 -0
  218. package/vendor/@anvil/common/src/util/__tests__/analytics-dispatcher.test.ts +122 -0
  219. package/vendor/@anvil/common/src/util/__tests__/analytics-log.test.ts +102 -0
  220. package/vendor/@anvil/common/src/util/__tests__/analytics-sampling.test.ts +143 -0
  221. package/vendor/@anvil/common/src/util/__tests__/anvil-streak.test.ts +202 -0
  222. package/vendor/@anvil/common/src/util/__tests__/anvil-usage-summary.test.ts +193 -0
  223. package/vendor/@anvil/common/src/util/__tests__/axiom-only-log.test.ts +140 -0
  224. package/vendor/@anvil/common/src/util/__tests__/client-user-agent.test.ts +148 -0
  225. package/vendor/@anvil/common/src/util/__tests__/engagement-tracker.test.ts +115 -0
  226. package/vendor/@anvil/common/src/util/__tests__/env-file-path.test.ts +57 -0
  227. package/vendor/@anvil/common/src/util/__tests__/error-abort.test.ts +774 -0
  228. package/vendor/@anvil/common/src/util/__tests__/error-api-details.test.ts +206 -0
  229. package/vendor/@anvil/common/src/util/__tests__/file-read-limits.test.ts +173 -0
  230. package/vendor/@anvil/common/src/util/__tests__/format-code-search.test.ts +60 -0
  231. package/vendor/@anvil/common/src/util/__tests__/log-mirror.test.ts +31 -0
  232. package/vendor/@anvil/common/src/util/__tests__/messages.test.ts +1252 -0
  233. package/vendor/@anvil/common/src/util/__tests__/partial-json-delta.test.ts +505 -0
  234. package/vendor/@anvil/common/src/util/__tests__/path.test.ts +22 -0
  235. package/vendor/@anvil/common/src/util/__tests__/project-ignore.test.ts +90 -0
  236. package/vendor/@anvil/common/src/util/__tests__/promise.test.ts +325 -0
  237. package/vendor/@anvil/common/src/util/__tests__/rate-limit.test.ts +28 -0
  238. package/vendor/@anvil/common/src/util/__tests__/reddit-anvil-retention.test.ts +130 -0
  239. package/vendor/@anvil/common/src/util/__tests__/saxy.test.ts +1008 -0
  240. package/vendor/@anvil/common/src/util/__tests__/split-data.test.ts +289 -0
  241. package/vendor/@anvil/common/src/util/__tests__/string.test.ts +239 -0
  242. package/vendor/@anvil/common/src/util/__tests__/thread-title.test.ts +70 -0
  243. package/vendor/@anvil/common/src/util/__tests__/tool-result-media-order.test.ts +108 -0
  244. package/vendor/@anvil/common/src/util/__tests__/ttft-histogram.test.ts +180 -0
  245. package/vendor/@anvil/common/src/util/__tests__/with-timeout.test.ts +24 -0
  246. package/vendor/@anvil/common/src/util/__tests__/zoned-time.test.ts +88 -0
  247. package/vendor/@anvil/common/src/util/ad-user-agent.ts +25 -0
  248. package/vendor/@anvil/common/src/util/agent-file-utils.ts +110 -0
  249. package/vendor/@anvil/common/src/util/agent-id-parsing.ts +136 -0
  250. package/vendor/@anvil/common/src/util/agent-name-normalization.ts +38 -0
  251. package/vendor/@anvil/common/src/util/agent-name-resolver.ts +86 -0
  252. package/vendor/@anvil/common/src/util/analytics-dispatcher.ts +82 -0
  253. package/vendor/@anvil/common/src/util/analytics-log.ts +78 -0
  254. package/vendor/@anvil/common/src/util/analytics-sampling.ts +255 -0
  255. package/vendor/@anvil/common/src/util/anvil-model-availability.ts +102 -0
  256. package/vendor/@anvil/common/src/util/anvil-privacy.ts +82 -0
  257. package/vendor/@anvil/common/src/util/anvil-streak-line.ts +103 -0
  258. package/vendor/@anvil/common/src/util/anvil-streak.ts +156 -0
  259. package/vendor/@anvil/common/src/util/anvil-usage-summary.ts +125 -0
  260. package/vendor/@anvil/common/src/util/array.ts +31 -0
  261. package/vendor/@anvil/common/src/util/axiom-only-log.ts +124 -0
  262. package/vendor/@anvil/common/src/util/cache-debug.ts +171 -0
  263. package/vendor/@anvil/common/src/util/client-user-agent.ts +114 -0
  264. package/vendor/@anvil/common/src/util/credentials.ts +26 -0
  265. package/vendor/@anvil/common/src/util/currency.ts +25 -0
  266. package/vendor/@anvil/common/src/util/dates.ts +81 -0
  267. package/vendor/@anvil/common/src/util/disposable-email.ts +277 -0
  268. package/vendor/@anvil/common/src/util/engagement-tracker.ts +129 -0
  269. package/vendor/@anvil/common/src/util/env-file-path.ts +40 -0
  270. package/vendor/@anvil/common/src/util/error.ts +562 -0
  271. package/vendor/@anvil/common/src/util/file-read-limits.ts +236 -0
  272. package/vendor/@anvil/common/src/util/file.ts +339 -0
  273. package/vendor/@anvil/common/src/util/format-code-search.ts +115 -0
  274. package/vendor/@anvil/common/src/util/lazy-response-ads.ts +92 -0
  275. package/vendor/@anvil/common/src/util/log-data.ts +57 -0
  276. package/vendor/@anvil/common/src/util/log-ingest.ts +58 -0
  277. package/vendor/@anvil/common/src/util/log-mirror.ts +34 -0
  278. package/vendor/@anvil/common/src/util/lru-cache.ts +67 -0
  279. package/vendor/@anvil/common/src/util/messages.ts +685 -0
  280. package/vendor/@anvil/common/src/util/min-heap.ts +87 -0
  281. package/vendor/@anvil/common/src/util/model-utils.ts +4 -0
  282. package/vendor/@anvil/common/src/util/object.ts +128 -0
  283. package/vendor/@anvil/common/src/util/partial-json-delta.ts +89 -0
  284. package/vendor/@anvil/common/src/util/path.ts +15 -0
  285. package/vendor/@anvil/common/src/util/project-ignore.ts +119 -0
  286. package/vendor/@anvil/common/src/util/promise.ts +73 -0
  287. package/vendor/@anvil/common/src/util/random.ts +15 -0
  288. package/vendor/@anvil/common/src/util/rate-limit.ts +56 -0
  289. package/vendor/@anvil/common/src/util/reddit-anvil-retention.ts +60 -0
  290. package/vendor/@anvil/common/src/util/reddit-capi-events.ts +36 -0
  291. package/vendor/@anvil/common/src/util/response-ad-positions.ts +54 -0
  292. package/vendor/@anvil/common/src/util/saxy.ts +741 -0
  293. package/vendor/@anvil/common/src/util/skills.test.ts +44 -0
  294. package/vendor/@anvil/common/src/util/skills.ts +36 -0
  295. package/vendor/@anvil/common/src/util/split-data.ts +259 -0
  296. package/vendor/@anvil/common/src/util/stop-sequence.ts +61 -0
  297. package/vendor/@anvil/common/src/util/string.ts +426 -0
  298. package/vendor/@anvil/common/src/util/system-info.ts +53 -0
  299. package/vendor/@anvil/common/src/util/thread-title.ts +46 -0
  300. package/vendor/@anvil/common/src/util/ttft-histogram.ts +73 -0
  301. package/vendor/@anvil/common/src/util/xml-parser.ts +27 -0
  302. package/vendor/@anvil/common/src/util/xml.ts +17 -0
  303. package/vendor/@anvil/common/src/util/zod-schema.ts +25 -0
  304. package/vendor/@anvil/common/src/util/zoned-time.ts +136 -0
  305. package/vendor/@anvil/common/src/utils/ask-user-bridge.ts +44 -0
  306. package/vendor/@anvil/sdk/dist/index.cjs +52297 -0
  307. package/vendor/@anvil/sdk/dist/index.cjs.map +440 -0
  308. package/vendor/@anvil/sdk/dist/index.d.ts +4277 -0
  309. package/vendor/@anvil/sdk/dist/index.mjs +52290 -0
  310. package/vendor/@anvil/sdk/dist/index.mjs.map +440 -0
  311. package/vendor/@anvil/sdk/dist/vendor/ai.cjs +30886 -0
  312. package/vendor/@anvil/sdk/dist/vendor/ripgrep/arm64-darwin/rg +0 -0
  313. package/vendor/@anvil/sdk/dist/vendor/ripgrep/arm64-linux/rg +0 -0
  314. package/vendor/@anvil/sdk/dist/vendor/ripgrep/x64-darwin/rg +0 -0
  315. package/vendor/@anvil/sdk/dist/vendor/ripgrep/x64-linux/rg +0 -0
  316. package/vendor/@anvil/sdk/dist/vendor/ripgrep/x64-win32/rg.exe +0 -0
  317. package/vendor/@anvil/sdk/dist/wasm/tree-sitter-c-sharp.wasm +0 -0
  318. package/vendor/@anvil/sdk/dist/wasm/tree-sitter-cpp.wasm +0 -0
  319. package/vendor/@anvil/sdk/dist/wasm/tree-sitter-go.wasm +0 -0
  320. package/vendor/@anvil/sdk/dist/wasm/tree-sitter-java.wasm +0 -0
  321. package/vendor/@anvil/sdk/dist/wasm/tree-sitter-javascript.wasm +0 -0
  322. package/vendor/@anvil/sdk/dist/wasm/tree-sitter-python.wasm +0 -0
  323. package/vendor/@anvil/sdk/dist/wasm/tree-sitter-ruby.wasm +0 -0
  324. package/vendor/@anvil/sdk/dist/wasm/tree-sitter-rust.wasm +0 -0
  325. package/vendor/@anvil/sdk/dist/wasm/tree-sitter-tsx.wasm +0 -0
  326. package/vendor/@anvil/sdk/dist/wasm/tree-sitter-typescript.wasm +0 -0
  327. package/vendor/@anvil/sdk/dist/wasm/tree-sitter.wasm +0 -0
  328. package/vendor/@anvil/sdk/package.json +21 -0
@@ -0,0 +1,774 @@
1
+ import { describe, expect, it } from 'bun:test'
2
+
3
+ import {
4
+ ABORT_ERROR_MESSAGE,
5
+ AbortError,
6
+ isAbortError,
7
+ promptAborted,
8
+ promptSuccess,
9
+ unwrapPromptResult,
10
+ type PromptResult,
11
+ } from '../error'
12
+
13
+ describe('AbortError class', () => {
14
+ describe('constructor', () => {
15
+ it('creates error without reason', () => {
16
+ const error = new AbortError()
17
+ expect(error.message).toBe(ABORT_ERROR_MESSAGE)
18
+ expect(error.name).toBe('AbortError')
19
+ })
20
+
21
+ it('creates error with reason', () => {
22
+ const error = new AbortError('User cancelled')
23
+ expect(error.message).toBe(`${ABORT_ERROR_MESSAGE}: User cancelled`)
24
+ expect(error.name).toBe('AbortError')
25
+ })
26
+
27
+ it('creates error with empty string reason', () => {
28
+ const error = new AbortError('')
29
+ // Empty string is falsy, so no reason appended
30
+ expect(error.message).toBe(ABORT_ERROR_MESSAGE)
31
+ })
32
+
33
+ it('is instanceof Error', () => {
34
+ const error = new AbortError()
35
+ expect(error instanceof Error).toBe(true)
36
+ expect(error instanceof AbortError).toBe(true)
37
+ })
38
+
39
+ it('has stack trace', () => {
40
+ const error = new AbortError('test')
41
+ expect(error.stack).toBeDefined()
42
+ expect(error.stack).toContain('AbortError')
43
+ })
44
+ })
45
+
46
+ describe('message format', () => {
47
+ it('reason is appended after colon and space', () => {
48
+ const error = new AbortError('timeout')
49
+ expect(error.message).toBe('Request aborted: timeout')
50
+ })
51
+
52
+ it('preserves special characters in reason', () => {
53
+ const error = new AbortError('User pressed Ctrl+C')
54
+ expect(error.message).toBe('Request aborted: User pressed Ctrl+C')
55
+ })
56
+
57
+ it('handles multi-line reason', () => {
58
+ const error = new AbortError('First line\nSecond line')
59
+ expect(error.message).toBe('Request aborted: First line\nSecond line')
60
+ })
61
+ })
62
+ })
63
+
64
+ describe('isAbortError edge cases', () => {
65
+ describe('message matching with startsWith', () => {
66
+ it('returns true for exact ABORT_ERROR_MESSAGE', () => {
67
+ const error = new Error(ABORT_ERROR_MESSAGE)
68
+ expect(isAbortError(error)).toBe(true)
69
+ })
70
+
71
+ it('returns true for message with suffix after ABORT_ERROR_MESSAGE (like AbortError with reason)', () => {
72
+ // This is the format AbortError uses: 'Request aborted: reason'
73
+ const error = new Error(`${ABORT_ERROR_MESSAGE}: timeout`)
74
+ expect(isAbortError(error)).toBe(true)
75
+ })
76
+
77
+ it('returns false for message with non-colon suffix after ABORT_ERROR_MESSAGE', () => {
78
+ // Only 'Request aborted' or 'Request aborted: <reason>' should match
79
+ // Other patterns like 'Request aborted by user' should NOT match
80
+ const error = new Error(`${ABORT_ERROR_MESSAGE} due to user action`)
81
+ expect(isAbortError(error)).toBe(false)
82
+ })
83
+
84
+ it('returns false for message containing ABORT_ERROR_MESSAGE as substring (not prefix)', () => {
85
+ const error = new Error(`Error: ${ABORT_ERROR_MESSAGE} by system`)
86
+ expect(isAbortError(error)).toBe(false)
87
+ })
88
+
89
+ it('returns false for message with prefix before ABORT_ERROR_MESSAGE', () => {
90
+ const error = new Error(`Something failed: ${ABORT_ERROR_MESSAGE}`)
91
+ expect(isAbortError(error)).toBe(false)
92
+ })
93
+ })
94
+
95
+ describe('case sensitivity', () => {
96
+ it('returns false for lowercase version of message', () => {
97
+ const error = new Error('request aborted')
98
+ expect(isAbortError(error)).toBe(false)
99
+ })
100
+
101
+ it('returns false for uppercase version of message', () => {
102
+ const error = new Error('REQUEST ABORTED')
103
+ expect(isAbortError(error)).toBe(false)
104
+ })
105
+
106
+ it('returns false for mixed case version of message', () => {
107
+ const error = new Error('Request Aborted')
108
+ expect(isAbortError(error)).toBe(false)
109
+ })
110
+ })
111
+
112
+ describe('AbortError name detection', () => {
113
+ it('returns true for Error with name set to AbortError', () => {
114
+ const error = new Error('Some other message')
115
+ error.name = 'AbortError'
116
+ expect(isAbortError(error)).toBe(true)
117
+ })
118
+
119
+ it('returns false for name containing AbortError as substring', () => {
120
+ const error = new Error('test')
121
+ error.name = 'MyAbortErrorClass'
122
+ expect(isAbortError(error)).toBe(false)
123
+ })
124
+
125
+ it('returns false for lowercase aborterror name', () => {
126
+ const error = new Error('test')
127
+ error.name = 'aborterror'
128
+ expect(isAbortError(error)).toBe(false)
129
+ })
130
+ })
131
+
132
+ describe('DOMException handling', () => {
133
+ it('returns true for DOMException with name AbortError', () => {
134
+ const error = new DOMException('The operation was aborted', 'AbortError')
135
+ expect(isAbortError(error)).toBe(true)
136
+ })
137
+
138
+ it('returns true for DOMException with signal abort message', () => {
139
+ const error = new DOMException(
140
+ 'signal is aborted without reason',
141
+ 'AbortError',
142
+ )
143
+ expect(isAbortError(error)).toBe(true)
144
+ })
145
+
146
+ it('returns false for DOMException with different name', () => {
147
+ const error = new DOMException('test', 'NotFoundError')
148
+ expect(isAbortError(error)).toBe(false)
149
+ })
150
+ })
151
+
152
+ describe('Error subclasses', () => {
153
+ it('returns true for AbortError instance', () => {
154
+ const error = new AbortError('test reason')
155
+ expect(isAbortError(error)).toBe(true)
156
+ })
157
+
158
+ it('returns true for TypeError with AbortError name', () => {
159
+ const error = new TypeError('test')
160
+ error.name = 'AbortError'
161
+ expect(isAbortError(error)).toBe(true)
162
+ })
163
+
164
+ it('returns false for custom error class without AbortError characteristics', () => {
165
+ class CustomError extends Error {
166
+ constructor(message: string) {
167
+ super(message)
168
+ this.name = 'CustomError'
169
+ }
170
+ }
171
+ // Note: Using a message that's similar but NOT exact match to ABORT_ERROR_MESSAGE
172
+ const error = new CustomError('Request was aborted by user')
173
+ expect(isAbortError(error)).toBe(false)
174
+ })
175
+
176
+ it('returns true for custom error class with AbortError name', () => {
177
+ class MyAbortError extends Error {
178
+ constructor() {
179
+ super('custom message')
180
+ this.name = 'AbortError'
181
+ }
182
+ }
183
+ const error = new MyAbortError()
184
+ expect(isAbortError(error)).toBe(true)
185
+ })
186
+ })
187
+
188
+ describe('non-Error types', () => {
189
+ it('returns false for string', () => {
190
+ expect(isAbortError(ABORT_ERROR_MESSAGE)).toBe(false)
191
+ })
192
+
193
+ it('returns false for object with message property', () => {
194
+ expect(isAbortError({ message: ABORT_ERROR_MESSAGE })).toBe(false)
195
+ })
196
+
197
+ it('returns false for object with name property', () => {
198
+ expect(isAbortError({ name: 'AbortError' })).toBe(false)
199
+ })
200
+
201
+ it('returns false for null', () => {
202
+ expect(isAbortError(null)).toBe(false)
203
+ })
204
+
205
+ it('returns false for undefined', () => {
206
+ expect(isAbortError(undefined)).toBe(false)
207
+ })
208
+
209
+ it('returns false for number', () => {
210
+ expect(isAbortError(42)).toBe(false)
211
+ })
212
+
213
+ it('returns false for array', () => {
214
+ expect(isAbortError([ABORT_ERROR_MESSAGE])).toBe(false)
215
+ })
216
+
217
+ it('returns false for function', () => {
218
+ expect(isAbortError(() => ABORT_ERROR_MESSAGE)).toBe(false)
219
+ })
220
+ })
221
+ })
222
+
223
+ describe('unwrapPromptResult with AbortError', () => {
224
+ describe('successful results', () => {
225
+ it('returns value for successful result', () => {
226
+ const result = promptSuccess('test value')
227
+ expect(unwrapPromptResult(result)).toBe('test value')
228
+ })
229
+
230
+ it('returns null for successful null result', () => {
231
+ const result = promptSuccess(null)
232
+ expect(unwrapPromptResult(result)).toBeNull()
233
+ })
234
+
235
+ it('returns undefined for successful undefined result', () => {
236
+ const result = promptSuccess(undefined)
237
+ expect(unwrapPromptResult(result)).toBeUndefined()
238
+ })
239
+
240
+ it('returns complex object for successful result', () => {
241
+ const value = { nested: { array: [1, 2, 3] } }
242
+ const result = promptSuccess(value)
243
+ expect(unwrapPromptResult(result)).toEqual(value)
244
+ })
245
+ })
246
+
247
+ describe('aborted results throw AbortError', () => {
248
+ it('throws AbortError instance', () => {
249
+ const result = promptAborted()
250
+ try {
251
+ unwrapPromptResult(result)
252
+ expect(true).toBe(false) // Should not reach here
253
+ } catch (error) {
254
+ expect(error instanceof AbortError).toBe(true)
255
+ }
256
+ })
257
+
258
+ it('thrown error has name AbortError', () => {
259
+ const result = promptAborted()
260
+ try {
261
+ unwrapPromptResult(result)
262
+ expect(true).toBe(false)
263
+ } catch (error) {
264
+ expect((error as Error).name).toBe('AbortError')
265
+ }
266
+ })
267
+
268
+ it('thrown error includes reason in message', () => {
269
+ const result = promptAborted('User cancelled')
270
+ try {
271
+ unwrapPromptResult(result)
272
+ expect(true).toBe(false)
273
+ } catch (error) {
274
+ expect((error as Error).message).toBe('Request aborted: User cancelled')
275
+ }
276
+ })
277
+
278
+ it('thrown error is detectable with isAbortError', () => {
279
+ const result = promptAborted()
280
+ try {
281
+ unwrapPromptResult(result)
282
+ expect(true).toBe(false)
283
+ } catch (error) {
284
+ expect(isAbortError(error)).toBe(true)
285
+ }
286
+ })
287
+
288
+ it('thrown error with reason is detectable with isAbortError', () => {
289
+ const result = promptAborted('timeout')
290
+ try {
291
+ unwrapPromptResult(result)
292
+ expect(true).toBe(false)
293
+ } catch (error) {
294
+ expect(isAbortError(error)).toBe(true)
295
+ }
296
+ })
297
+ })
298
+ })
299
+
300
+ describe('PromptResult integration patterns', () => {
301
+ describe('early return pattern', () => {
302
+ async function mockLlmCall(shouldAbort: boolean): Promise<PromptResult<string>> {
303
+ if (shouldAbort) {
304
+ return promptAborted('User cancelled')
305
+ }
306
+ return promptSuccess('LLM response')
307
+ }
308
+
309
+ async function callerWithEarlyReturn(shouldAbort: boolean): Promise<string | null> {
310
+ const result = await mockLlmCall(shouldAbort)
311
+ if (result.aborted) {
312
+ return null
313
+ }
314
+ return result.value.toUpperCase()
315
+ }
316
+
317
+ it('returns transformed value on success', async () => {
318
+ const result = await callerWithEarlyReturn(false)
319
+ expect(result).toBe('LLM RESPONSE')
320
+ })
321
+
322
+ it('returns null on abort', async () => {
323
+ const result = await callerWithEarlyReturn(true)
324
+ expect(result).toBeNull()
325
+ })
326
+ })
327
+
328
+ describe('unwrap with try/catch pattern', () => {
329
+ async function mockLlmCall(shouldAbort: boolean): Promise<PromptResult<string>> {
330
+ if (shouldAbort) {
331
+ return promptAborted('Signal triggered')
332
+ }
333
+ return promptSuccess('Success response')
334
+ }
335
+
336
+ async function callerWithUnwrap(shouldAbort: boolean): Promise<string> {
337
+ return unwrapPromptResult(await mockLlmCall(shouldAbort))
338
+ }
339
+
340
+ async function outerCaller(shouldAbort: boolean): Promise<{ result: string; wasAborted: boolean }> {
341
+ try {
342
+ const result = await callerWithUnwrap(shouldAbort)
343
+ return { result, wasAborted: false }
344
+ } catch (error) {
345
+ if (isAbortError(error)) {
346
+ return { result: '', wasAborted: true }
347
+ }
348
+ throw error // Rethrow non-abort errors
349
+ }
350
+ }
351
+
352
+ it('returns result on success', async () => {
353
+ const { result, wasAborted } = await outerCaller(false)
354
+ expect(result).toBe('Success response')
355
+ expect(wasAborted).toBe(false)
356
+ })
357
+
358
+ it('catches and identifies abort', async () => {
359
+ const { result, wasAborted } = await outerCaller(true)
360
+ expect(result).toBe('')
361
+ expect(wasAborted).toBe(true)
362
+ })
363
+ })
364
+
365
+ describe('nested function abort propagation', () => {
366
+ async function deepestCall(signal: { aborted: boolean }): Promise<PromptResult<number>> {
367
+ if (signal.aborted) {
368
+ return promptAborted('Aborted at deepest level')
369
+ }
370
+ return promptSuccess(42)
371
+ }
372
+
373
+ async function middleCall(signal: { aborted: boolean }): Promise<PromptResult<string>> {
374
+ const result = await deepestCall(signal)
375
+ if (result.aborted) {
376
+ return result // Propagate abort
377
+ }
378
+ return promptSuccess(`Value: ${result.value}`)
379
+ }
380
+
381
+ async function topCall(signal: { aborted: boolean }): Promise<PromptResult<string[]>> {
382
+ const result = await middleCall(signal)
383
+ if (result.aborted) {
384
+ return result // Propagate abort
385
+ }
386
+ return promptSuccess([result.value, 'additional'])
387
+ }
388
+
389
+ it('propagates success through all levels', async () => {
390
+ const signal = { aborted: false }
391
+ const result = await topCall(signal)
392
+ expect(result.aborted).toBe(false)
393
+ if (!result.aborted) {
394
+ expect(result.value).toEqual(['Value: 42', 'additional'])
395
+ }
396
+ })
397
+
398
+ it('propagates abort from deepest level', async () => {
399
+ const signal = { aborted: true }
400
+ const result = await topCall(signal)
401
+ expect(result.aborted).toBe(true)
402
+ if (result.aborted) {
403
+ expect(result.reason).toBe('Aborted at deepest level')
404
+ }
405
+ })
406
+ })
407
+
408
+ describe('mixed pattern with fallback', () => {
409
+ async function primaryProvider(signal: { aborted: boolean }): Promise<PromptResult<string>> {
410
+ if (signal.aborted) {
411
+ return promptAborted()
412
+ }
413
+ // Simulate primary provider failure
414
+ throw new Error('Primary provider unavailable')
415
+ }
416
+
417
+ async function fallbackProvider(signal: { aborted: boolean }): Promise<PromptResult<string>> {
418
+ if (signal.aborted) {
419
+ return promptAborted()
420
+ }
421
+ return promptSuccess('Fallback result')
422
+ }
423
+
424
+ async function callWithFallback(signal: { aborted: boolean }): Promise<PromptResult<string>> {
425
+ try {
426
+ const result = await primaryProvider(signal)
427
+ // If aborted, don't try fallback
428
+ if (result.aborted) {
429
+ return result
430
+ }
431
+ return result
432
+ } catch (error) {
433
+ // Don't fall back on abort errors
434
+ if (isAbortError(error)) {
435
+ throw error
436
+ }
437
+ // Try fallback for other errors
438
+ return fallbackProvider(signal)
439
+ }
440
+ }
441
+
442
+ it('uses fallback on non-abort error', async () => {
443
+ const signal = { aborted: false }
444
+ const result = await callWithFallback(signal)
445
+ expect(result.aborted).toBe(false)
446
+ if (!result.aborted) {
447
+ expect(result.value).toBe('Fallback result')
448
+ }
449
+ })
450
+
451
+ it('does not use fallback on abort', async () => {
452
+ const signal = { aborted: true }
453
+ const result = await callWithFallback(signal)
454
+ expect(result.aborted).toBe(true)
455
+ })
456
+ })
457
+
458
+ describe('abort during async iteration', () => {
459
+ async function* generateValues(signal: { aborted: boolean }): AsyncGenerator<PromptResult<number>> {
460
+ for (let i = 0; i < 5; i++) {
461
+ if (signal.aborted) {
462
+ yield promptAborted(`Aborted at iteration ${i}`)
463
+ return
464
+ }
465
+ yield promptSuccess(i)
466
+ }
467
+ }
468
+
469
+ async function collectValues(signal: { aborted: boolean }): Promise<{ values: number[]; abortedAt?: string }> {
470
+ const values: number[] = []
471
+ for await (const result of generateValues(signal)) {
472
+ if (result.aborted) {
473
+ return { values, abortedAt: result.reason }
474
+ }
475
+ values.push(result.value)
476
+ }
477
+ return { values }
478
+ }
479
+
480
+ it('collects all values when not aborted', async () => {
481
+ const signal = { aborted: false }
482
+ const { values, abortedAt } = await collectValues(signal)
483
+ expect(values).toEqual([0, 1, 2, 3, 4])
484
+ expect(abortedAt).toBeUndefined()
485
+ })
486
+
487
+ it('stops iteration on abort', async () => {
488
+ const signal = { aborted: false }
489
+ // Simulate abort after first value
490
+ const generator = generateValues(signal)
491
+ const results: number[] = []
492
+
493
+ for await (const result of generator) {
494
+ if (result.aborted) break
495
+ results.push(result.value)
496
+ if (results.length === 2) {
497
+ signal.aborted = true
498
+ }
499
+ }
500
+
501
+ expect(results).toEqual([0, 1])
502
+ })
503
+ })
504
+
505
+ describe('rethrow pattern in catch blocks', () => {
506
+ async function innerOperation(): Promise<PromptResult<string>> {
507
+ return promptAborted('Inner abort')
508
+ }
509
+
510
+ async function middleOperation(): Promise<string> {
511
+ const result = await innerOperation()
512
+ return unwrapPromptResult(result)
513
+ }
514
+
515
+ async function outerOperationBad(): Promise<string> {
516
+ try {
517
+ return await middleOperation()
518
+ } catch (error) {
519
+ // BAD: swallows abort error
520
+ return 'default value'
521
+ }
522
+ }
523
+
524
+ async function outerOperationGood(): Promise<string> {
525
+ try {
526
+ return await middleOperation()
527
+ } catch (error) {
528
+ // GOOD: rethrows abort error
529
+ if (isAbortError(error)) {
530
+ throw error
531
+ }
532
+ return 'default value'
533
+ }
534
+ }
535
+
536
+ it('bad pattern swallows abort', async () => {
537
+ const result = await outerOperationBad()
538
+ // This shows the anti-pattern - abort was swallowed
539
+ expect(result).toBe('default value')
540
+ })
541
+
542
+ it('good pattern propagates abort', async () => {
543
+ await expect(outerOperationGood()).rejects.toThrow(ABORT_ERROR_MESSAGE)
544
+ })
545
+
546
+ it('good pattern rethrows AbortError that can be detected', async () => {
547
+ try {
548
+ await outerOperationGood()
549
+ expect(true).toBe(false) // Should not reach
550
+ } catch (error) {
551
+ expect(isAbortError(error)).toBe(true)
552
+ }
553
+ })
554
+ })
555
+ })
556
+
557
+ describe('ABORT_ERROR_MESSAGE constant', () => {
558
+ it('has expected value', () => {
559
+ expect(ABORT_ERROR_MESSAGE).toBe('Request aborted')
560
+ })
561
+
562
+ it('is used by AbortError class', () => {
563
+ const error = new AbortError()
564
+ expect(error.message).toBe(ABORT_ERROR_MESSAGE)
565
+ })
566
+
567
+ it('is detected by isAbortError', () => {
568
+ const error = new Error(ABORT_ERROR_MESSAGE)
569
+ expect(isAbortError(error)).toBe(true)
570
+ })
571
+ })
572
+
573
+ describe('AbortController integration', () => {
574
+ describe('signal.aborted check pattern', () => {
575
+ async function mockLlmCallWithSignal(signal: AbortSignal): Promise<PromptResult<string>> {
576
+ if (signal.aborted) {
577
+ return promptAborted('Signal was already aborted')
578
+ }
579
+ // Simulate async work
580
+ await new Promise((resolve) => setTimeout(resolve, 0))
581
+ if (signal.aborted) {
582
+ return promptAborted('Signal aborted during operation')
583
+ }
584
+ return promptSuccess('Operation completed')
585
+ }
586
+
587
+ it('returns success when signal is not aborted', async () => {
588
+ const controller = new AbortController()
589
+ const result = await mockLlmCallWithSignal(controller.signal)
590
+ expect(result.aborted).toBe(false)
591
+ if (!result.aborted) {
592
+ expect(result.value).toBe('Operation completed')
593
+ }
594
+ })
595
+
596
+ it('returns aborted when signal is pre-aborted', async () => {
597
+ const controller = new AbortController()
598
+ controller.abort()
599
+ const result = await mockLlmCallWithSignal(controller.signal)
600
+ expect(result.aborted).toBe(true)
601
+ if (result.aborted) {
602
+ expect(result.reason).toBe('Signal was already aborted')
603
+ }
604
+ })
605
+ })
606
+
607
+ describe('sequential operations with abort', () => {
608
+ const callLog: string[] = []
609
+
610
+ async function step1(signal: AbortSignal): Promise<PromptResult<string>> {
611
+ callLog.push('step1')
612
+ if (signal.aborted) return promptAborted('step1 aborted')
613
+ return promptSuccess('step1 result')
614
+ }
615
+
616
+ async function step2(signal: AbortSignal): Promise<PromptResult<string>> {
617
+ callLog.push('step2')
618
+ if (signal.aborted) return promptAborted('step2 aborted')
619
+ return promptSuccess('step2 result')
620
+ }
621
+
622
+ async function step3(signal: AbortSignal): Promise<PromptResult<string>> {
623
+ callLog.push('step3')
624
+ if (signal.aborted) return promptAborted('step3 aborted')
625
+ return promptSuccess('step3 result')
626
+ }
627
+
628
+ async function runSequentialSteps(signal: AbortSignal): Promise<PromptResult<string[]>> {
629
+ const results: string[] = []
630
+
631
+ const r1 = await step1(signal)
632
+ if (r1.aborted) return r1
633
+ results.push(r1.value)
634
+
635
+ const r2 = await step2(signal)
636
+ if (r2.aborted) return r2
637
+ results.push(r2.value)
638
+
639
+ const r3 = await step3(signal)
640
+ if (r3.aborted) return r3
641
+ results.push(r3.value)
642
+
643
+ return promptSuccess(results)
644
+ }
645
+
646
+ it('completes all steps when not aborted', async () => {
647
+ callLog.length = 0
648
+ const controller = new AbortController()
649
+ const result = await runSequentialSteps(controller.signal)
650
+ expect(result.aborted).toBe(false)
651
+ if (!result.aborted) {
652
+ expect(result.value).toEqual(['step1 result', 'step2 result', 'step3 result'])
653
+ }
654
+ expect(callLog).toEqual(['step1', 'step2', 'step3'])
655
+ })
656
+
657
+ it('stops at first step when pre-aborted', async () => {
658
+ callLog.length = 0
659
+ const controller = new AbortController()
660
+ controller.abort()
661
+ const result = await runSequentialSteps(controller.signal)
662
+ expect(result.aborted).toBe(true)
663
+ // Only step1 should be called, and it should return aborted immediately
664
+ expect(callLog).toEqual(['step1'])
665
+ })
666
+ })
667
+
668
+ describe('fallback should NOT occur on abort (user intent)', () => {
669
+ let fallbackCalled = false
670
+
671
+ async function primaryModel(signal: AbortSignal): Promise<PromptResult<string>> {
672
+ if (signal.aborted) {
673
+ return promptAborted('User cancelled')
674
+ }
675
+ return promptSuccess('Primary model response')
676
+ }
677
+
678
+ async function fallbackModel(signal: AbortSignal): Promise<PromptResult<string>> {
679
+ fallbackCalled = true
680
+ if (signal.aborted) {
681
+ return promptAborted('User cancelled')
682
+ }
683
+ return promptSuccess('Fallback model response')
684
+ }
685
+
686
+ async function callWithFallbackOnError(
687
+ signal: AbortSignal,
688
+ primaryShouldThrowError: boolean,
689
+ primaryShouldAbort: boolean,
690
+ ): Promise<PromptResult<string>> {
691
+ try {
692
+ if (primaryShouldThrowError) {
693
+ throw new Error('Primary provider unavailable')
694
+ }
695
+ const primaryResult = primaryShouldAbort
696
+ ? promptAborted('User cancelled primary')
697
+ : await primaryModel(signal)
698
+
699
+ // Key pattern: if aborted, do NOT fall back - abort represents user intent
700
+ if (primaryResult.aborted) {
701
+ return primaryResult
702
+ }
703
+ return primaryResult
704
+ } catch (error) {
705
+ // Don't fall back on abort errors
706
+ if (isAbortError(error)) {
707
+ throw error
708
+ }
709
+ // Try fallback for other errors
710
+ return fallbackModel(signal)
711
+ }
712
+ }
713
+
714
+ it('returns primary result when not aborted', async () => {
715
+ fallbackCalled = false
716
+ const controller = new AbortController()
717
+ const result = await callWithFallbackOnError(controller.signal, false, false)
718
+ expect(result.aborted).toBe(false)
719
+ if (!result.aborted) {
720
+ expect(result.value).toBe('Primary model response')
721
+ }
722
+ expect(fallbackCalled).toBe(false)
723
+ })
724
+
725
+ it('propagates abort without fallback (respects user intent)', async () => {
726
+ fallbackCalled = false
727
+ const controller = new AbortController()
728
+ const result = await callWithFallbackOnError(controller.signal, false, true)
729
+ expect(result.aborted).toBe(true)
730
+ // Verify fallback was never called - abort means user wants to stop, not retry
731
+ expect(fallbackCalled).toBe(false)
732
+ })
733
+
734
+ it('uses fallback on non-abort error', async () => {
735
+ fallbackCalled = false
736
+ const controller = new AbortController()
737
+ const result = await callWithFallbackOnError(controller.signal, true, false)
738
+ expect(result.aborted).toBe(false)
739
+ if (!result.aborted) {
740
+ expect(result.value).toBe('Fallback model response')
741
+ }
742
+ // Verify fallback WAS called for non-abort error
743
+ expect(fallbackCalled).toBe(true)
744
+ })
745
+ })
746
+
747
+ describe('DOMException from AbortController', () => {
748
+ it('native abort reason is detected by isAbortError', () => {
749
+ const controller = new AbortController()
750
+ controller.abort()
751
+ // When you call controller.abort(), signal.reason becomes a DOMException
752
+ // with name 'AbortError'
753
+ const reason = controller.signal.reason
754
+ expect(reason).toBeInstanceOf(DOMException)
755
+ expect(isAbortError(reason)).toBe(true)
756
+ })
757
+
758
+ it('custom abort reason string is not detected as AbortError', () => {
759
+ const controller = new AbortController()
760
+ controller.abort('custom reason string')
761
+ // When you provide a reason, signal.reason is that value, not a DOMException
762
+ const reason = controller.signal.reason
763
+ expect(isAbortError(reason)).toBe(false) // string is not an Error
764
+ })
765
+
766
+ it('custom abort reason Error with AbortError name is detected', () => {
767
+ const controller = new AbortController()
768
+ const customAbortError = new AbortError('custom abort')
769
+ controller.abort(customAbortError)
770
+ const reason = controller.signal.reason
771
+ expect(isAbortError(reason)).toBe(true)
772
+ })
773
+ })
774
+ })