@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,236 @@
1
+ import { FILE_READ_STATUS } from '../constants/paths'
2
+
3
+ import type { FileReadWindow } from '../types/contracts/client'
4
+
5
+ /** Maximum source characters returned by one read_files tool call. */
6
+ export const MAX_READ_FILES_CHARS = 100_000
7
+
8
+ /** Maximum estimated tokens returned by one read_files tool call. */
9
+ export const MAX_READ_FILES_TOKENS = 20_000
10
+
11
+ /** Maximum lines returned per file in one read_files tool call. */
12
+ export const MAX_READ_FILE_LINES = 2_000
13
+
14
+ /** Maximum characters returned per file in one read_files tool call. */
15
+ export const MAX_READ_FILE_CHARS = 50_000
16
+
17
+ export function windowFileRead(
18
+ content: string,
19
+ offset?: number,
20
+ limit?: number,
21
+ ): string {
22
+ const lines = content.split('\n')
23
+ if (lines.length > 1 && lines[lines.length - 1] === '') lines.pop()
24
+ const totalLines = lines.length
25
+ const start = Math.max(1, Math.floor(offset ?? 1))
26
+ const maxLines = Math.min(
27
+ Math.max(1, Math.floor(limit ?? MAX_READ_FILE_LINES)),
28
+ MAX_READ_FILE_LINES,
29
+ )
30
+
31
+ if (start > totalLines) {
32
+ return `[read_files: ${totalLines} lines total; offset ${start} is beyond the end of the file.]`
33
+ }
34
+
35
+ let end = Math.min(totalLines, start - 1 + maxLines)
36
+ let selected = lines.slice(start - 1, end).join('\n')
37
+ let charCapped = false
38
+ if (selected.length > MAX_READ_FILE_CHARS) {
39
+ charCapped = true
40
+ let chars = 0
41
+ let count = 0
42
+ for (let i = start - 1; i < end; i++) {
43
+ const lineLength = lines[i].length + (count > 0 ? 1 : 0)
44
+ if (chars + lineLength > MAX_READ_FILE_CHARS && count > 0) break
45
+ chars += lineLength
46
+ count++
47
+ }
48
+ end = start - 1 + count
49
+ selected = lines.slice(start - 1, end).join('\n')
50
+ }
51
+
52
+ if (start === 1 && end === totalLines) {
53
+ return content
54
+ }
55
+
56
+ const charCapNote = charCapped
57
+ ? ` The window was shortened to stay under ${MAX_READ_FILE_CHARS.toLocaleString()} characters.`
58
+ : ''
59
+ const continueNote =
60
+ end < totalLines
61
+ ? ` Use code_search to locate the part you need and read a window around it, or call read_files again with offset=${end + 1} to continue.`
62
+ : ''
63
+ return `${selected}\n\n[read_files: showing lines ${start}-${end} of ${totalLines}.${charCapNote}${continueNote}]`
64
+ }
65
+
66
+ export type FileReadWindows = Record<string, FileReadWindow[]>
67
+
68
+ /**
69
+ * Read the line windows a `windowedFileReads` agent forwarded with a read_files
70
+ * call, if any. Absent for every other agent, and that absence is what keeps
71
+ * the legacy whole-file behavior.
72
+ *
73
+ * Hosted surfaces (Anvil Web/Cloud on Daytona, and the browser runtime) run
74
+ * read_files through their own override rather than the SDK's local reader, so
75
+ * each has to pick the windows out of the tool input itself.
76
+ */
77
+ export function fileReadWindowsOf(input: unknown): FileReadWindows | undefined {
78
+ const windows =
79
+ input && typeof input === 'object'
80
+ ? (input as { fileWindows?: unknown }).fileWindows
81
+ : undefined
82
+ return windows && typeof windows === 'object' && !Array.isArray(windows)
83
+ ? (windows as FileReadWindows)
84
+ : undefined
85
+ }
86
+
87
+ /**
88
+ * Apply one file's windows to its content.
89
+ *
90
+ * Call this BEFORE the read budget (`createFileReadLimiter`), the order the
91
+ * SDK's local reader uses. The other way round, an offset past the budget's
92
+ * truncation point answers "beyond the end of the file" for a file that has
93
+ * those lines.
94
+ *
95
+ * A path with no explicit window still goes through `windowFileRead`, which is
96
+ * where the per-file 2,000-line cap comes from — that cap, more than the
97
+ * explicit offsets, is what a windowed agent saves on a large file.
98
+ */
99
+ export function applyFileReadWindows(
100
+ content: string,
101
+ windows: FileReadWindow[] | undefined,
102
+ ): string {
103
+ // `Array.isArray`, not a truthiness check on `.length`: callers look these up
104
+ // by a model-supplied path in a map that lost its null prototype crossing the
105
+ // wire as JSON, so `windows` can be `Function.prototype` for a file named
106
+ // `constructor` — truthy, with a `.length` of 1 and no `.map`.
107
+ const list = Array.isArray(windows) && windows.length > 0 ? windows : [{}]
108
+ return list
109
+ .map((window) => windowFileRead(content, window?.offset, window?.limit))
110
+ .join('\n\n')
111
+ }
112
+
113
+ /** Small chunks avoid pathological BPE runtimes on repetitive Unicode. */
114
+ const TOKEN_CHUNK_CHARS = 1_024
115
+
116
+ type LimitedFileRead = {
117
+ content: string
118
+ includedChars: number
119
+ includedTokens: number
120
+ }
121
+
122
+ type FileReadLimiterOptions = {
123
+ /** A conservative local token estimator, including any desired safety factor. */
124
+ countTokens?: (text: string) => number
125
+ }
126
+
127
+ function avoidSplittingSurrogatePair(text: string, end: number): number {
128
+ if (
129
+ end > 0 &&
130
+ end < text.length &&
131
+ text.charCodeAt(end - 1) >= 0xd800 &&
132
+ text.charCodeAt(end - 1) <= 0xdbff
133
+ ) {
134
+ return end - 1
135
+ }
136
+ return end
137
+ }
138
+
139
+ function limitContentByTokens(
140
+ content: string,
141
+ tokenBudget: number,
142
+ countTokens: (text: string) => number,
143
+ ): { chars: number; tokens: number; truncated: boolean } {
144
+ let chars = 0
145
+ let tokens = 0
146
+
147
+ while (chars < content.length) {
148
+ let chunkEnd = Math.min(chars + TOKEN_CHUNK_CHARS, content.length)
149
+ chunkEnd = avoidSplittingSurrogatePair(content, chunkEnd)
150
+ if (chunkEnd === chars) chunkEnd++
151
+
152
+ const chunk = content.slice(chars, chunkEnd)
153
+ const chunkTokens = countTokens(chunk)
154
+ if (tokens + chunkTokens <= tokenBudget) {
155
+ chars = chunkEnd
156
+ tokens += chunkTokens
157
+ continue
158
+ }
159
+ // Keep only complete, independently verified chunks. This can leave less
160
+ // than one chunk of budget unused, but avoids costly exact-prefix fitting.
161
+ return { chars, tokens, truncated: true }
162
+ }
163
+
164
+ return { chars, tokens, truncated: false }
165
+ }
166
+
167
+ function limitFileReadContent(
168
+ content: string,
169
+ remainingChars: number,
170
+ remainingTokens: number,
171
+ countTokens?: (text: string) => number,
172
+ ): LimitedFileRead {
173
+ const charLimit = Math.min(content.length, Math.max(0, remainingChars))
174
+ const safeCharLimit = avoidSplittingSurrogatePair(content, charLimit)
175
+ const charLimitedContent = content.slice(0, safeCharLimit)
176
+ const tokenBudget = Math.max(0, remainingTokens)
177
+ const tokenLimit = countTokens
178
+ ? limitContentByTokens(charLimitedContent, tokenBudget, countTokens)
179
+ : {
180
+ chars: charLimitedContent.length,
181
+ tokens: 0,
182
+ truncated: false,
183
+ }
184
+ const includedChars = tokenLimit.chars
185
+ const includedTokens = tokenLimit.tokens
186
+
187
+ if (includedChars === content.length) {
188
+ return { content, includedChars, includedTokens }
189
+ }
190
+
191
+ let notice: string
192
+ if (tokenLimit.truncated) {
193
+ const hitAggregateLimit = remainingTokens < MAX_READ_FILES_TOKENS
194
+ notice = hitAggregateLimit
195
+ ? `${FILE_READ_STATUS.TOO_LARGE}: The combined read_files output exceeded the ${MAX_READ_FILES_TOKENS.toLocaleString()} estimated-token limit. This file was truncated after ${includedTokens.toLocaleString()} estimated tokens. Read it separately or use code_search for the relevant section.`
196
+ : `${FILE_READ_STATUS.TOO_LARGE}: This file exceeded the ${MAX_READ_FILES_TOKENS.toLocaleString()} estimated-token per-file limit. It was truncated after ${includedTokens.toLocaleString()} estimated tokens. Use code_search or a more targeted read for the relevant section.`
197
+ } else {
198
+ const hitAggregateLimit = remainingChars < MAX_READ_FILES_CHARS
199
+ notice = hitAggregateLimit
200
+ ? `${FILE_READ_STATUS.TOO_LARGE}: The combined read_files output exceeded the ${MAX_READ_FILES_CHARS.toLocaleString()} character hard limit. This file was truncated after ${includedChars.toLocaleString()} characters. Read it separately or use code_search for the relevant section.`
201
+ : `${FILE_READ_STATUS.TOO_LARGE}: This file is ${content.length.toLocaleString()} characters, exceeding the ${MAX_READ_FILES_CHARS.toLocaleString()} character hard limit. The content above has been truncated. Use code_search or a more targeted read for the relevant section.`
202
+ }
203
+
204
+ return {
205
+ content:
206
+ includedChars === 0
207
+ ? notice
208
+ : `${content.slice(0, includedChars)}\n\n${notice}`,
209
+ includedChars,
210
+ includedTokens,
211
+ }
212
+ }
213
+
214
+ /**
215
+ * Creates an ordered limiter for one read_files invocation. Status/error
216
+ * messages should bypass this limiter so they do not consume the content
217
+ * budget.
218
+ */
219
+ export function createFileReadLimiter(options: FileReadLimiterOptions = {}) {
220
+ let remainingChars = MAX_READ_FILES_CHARS
221
+ let remainingTokens = MAX_READ_FILES_TOKENS
222
+
223
+ return {
224
+ limit(content: string): string {
225
+ const limited = limitFileReadContent(
226
+ content,
227
+ remainingChars,
228
+ remainingTokens,
229
+ options.countTokens,
230
+ )
231
+ remainingChars -= limited.includedChars
232
+ remainingTokens -= limited.includedTokens
233
+ return limited.content
234
+ },
235
+ }
236
+ }
@@ -0,0 +1,339 @@
1
+ import * as os from 'os'
2
+ import * as path from 'path'
3
+
4
+ import { z } from 'zod/v4'
5
+
6
+ import type { AnvilFileSystem } from '../types/filesystem'
7
+ import type { SkillsMap } from '../types/skill'
8
+
9
+ export const FileTreeNodeSchema: z.ZodType<FileTreeNode> = z.object({
10
+ name: z.string(),
11
+ type: z.enum(['file', 'directory']),
12
+ children: z.lazy(() => z.array(FileTreeNodeSchema).optional()),
13
+ filePath: z.string(),
14
+ })
15
+
16
+ export interface FileTreeNode {
17
+ name: string
18
+ type: 'file' | 'directory'
19
+ filePath: string
20
+ lastReadTime?: number
21
+ children?: FileTreeNode[]
22
+ }
23
+
24
+ export interface DirectoryNode extends FileTreeNode {
25
+ type: 'directory'
26
+ children: FileTreeNode[]
27
+ }
28
+
29
+ export interface FileNode extends FileTreeNode {
30
+ type: 'file'
31
+ lastReadTime: number
32
+ }
33
+
34
+ export const FileVersionSchema = z.object({
35
+ path: z.string(),
36
+ content: z.string(),
37
+ })
38
+
39
+ export type FileVersion = z.infer<typeof FileVersionSchema>
40
+
41
+ export const customToolDefinitionsSchema = z
42
+ .record(
43
+ z.string(),
44
+ z.object({
45
+ // inputSchema can be a Zod schema (from MCP tools) or a JSON Schema object
46
+ // (from SDK custom tools that have been serialized). The agent-runtime
47
+ // converts JSON schemas to Zod using ensureZodSchema() before use.
48
+ inputSchema: z.custom<z.ZodType | Record<string, unknown>>(),
49
+ endsAgentStep: z.boolean().optional().default(false),
50
+ description: z.string().optional(),
51
+ exampleInputs: z.record(z.string(), z.any()).array().optional(),
52
+ }),
53
+ )
54
+ .default(() => ({}))
55
+ export type CustomToolDefinitions = NonNullable<
56
+ z.input<typeof customToolDefinitionsSchema>
57
+ >
58
+
59
+ export const ProjectFileContextSchema = z.object({
60
+ projectRoot: z.string(),
61
+ cwd: z.string(),
62
+ fileTree: z.array(z.custom<FileTreeNode>()),
63
+ fileTokenScores: z.record(z.string(), z.record(z.string(), z.number())),
64
+ tokenCallers: z
65
+ .record(z.string(), z.record(z.string(), z.array(z.string())))
66
+ .optional(),
67
+ knowledgeFiles: z.record(z.string(), z.string()),
68
+ userKnowledgeFiles: z.record(z.string(), z.string()).optional(),
69
+ agentTemplates: z.record(z.string(), z.any()).default(() => ({})),
70
+ customToolDefinitions: customToolDefinitionsSchema,
71
+ skills: z.record(z.string(), z.any()).optional(),
72
+ gitChanges: z.object({
73
+ status: z.string(),
74
+ diff: z.string(),
75
+ diffCached: z.string(),
76
+ lastCommitMessages: z.string(),
77
+ }),
78
+ changesSinceLastChat: z.record(z.string(), z.string()),
79
+ shellConfigFiles: z.record(z.string(), z.string()),
80
+ systemInfo: z.object({
81
+ platform: z.string(),
82
+ shell: z.string(),
83
+ nodeVersion: z.string(),
84
+ arch: z.string(),
85
+ homedir: z.string(),
86
+ cpus: z.number(),
87
+ chromeAvailable: z.boolean(),
88
+ }),
89
+ })
90
+
91
+ export type ProjectFileContext = {
92
+ projectRoot: string
93
+ cwd: string
94
+ fileTree: FileTreeNode[]
95
+ fileTokenScores: Record<string, Record<string, number>>
96
+ tokenCallers?: Record<string, Record<string, string[]>>
97
+ knowledgeFiles: Record<string, string>
98
+ userKnowledgeFiles?: Record<string, string>
99
+ agentTemplates: Record<string, any>
100
+ customToolDefinitions: CustomToolDefinitions
101
+ skills?: SkillsMap
102
+ gitChanges: {
103
+ status: string
104
+ diff: string
105
+ diffCached: string
106
+ lastCommitMessages: string
107
+ }
108
+ changesSinceLastChat: Record<string, string>
109
+ shellConfigFiles: Record<string, string>
110
+ systemInfo: {
111
+ platform: string
112
+ shell: string
113
+ nodeVersion: string
114
+ arch: string
115
+ homedir: string
116
+ cpus: number
117
+ chromeAvailable: boolean
118
+ }
119
+ }
120
+
121
+ export const fileRegex =
122
+ /<write_file>\s*<path>([^<]+)<\/path>\s*<content>([\s\S]*?)<\/content>\s*<\/write_file>/g
123
+ export const fileWithNoPathRegex = /<write_file>([\s\S]*?)<\/write_file>/g
124
+
125
+ export const parseFileBlocks = (fileBlocks: string) => {
126
+ let fileMatch
127
+ const files: Record<string, string> = {}
128
+ while ((fileMatch = fileRegex.exec(fileBlocks)) !== null) {
129
+ const [, filePath, fileContent] = fileMatch
130
+ files[filePath] = fileContent.startsWith('\n')
131
+ ? fileContent.slice(1)
132
+ : fileContent
133
+ }
134
+ return files
135
+ }
136
+
137
+ export const getStubProjectFileContext = (): ProjectFileContext => ({
138
+ projectRoot: '',
139
+ cwd: '',
140
+ fileTree: [],
141
+ fileTokenScores: {},
142
+ knowledgeFiles: {},
143
+ userKnowledgeFiles: {},
144
+ agentTemplates: {},
145
+ customToolDefinitions: {},
146
+ skills: {},
147
+ gitChanges: {
148
+ status: '',
149
+ diff: '',
150
+ diffCached: '',
151
+ lastCommitMessages: '',
152
+ },
153
+ changesSinceLastChat: {},
154
+ shellConfigFiles: {},
155
+ systemInfo: {
156
+ platform: '',
157
+ shell: '',
158
+ nodeVersion: '',
159
+ arch: '',
160
+ homedir: '',
161
+ cpus: 0,
162
+ chromeAvailable: false,
163
+ },
164
+ })
165
+
166
+ export const createMarkdownFileBlock = (filePath: string, content: string) => {
167
+ return `\`\`\`${filePath}\n${content}\n\`\`\``
168
+ }
169
+
170
+ export const parseMarkdownCodeBlock = (content: string) => {
171
+ const match = content.match(/^```(?:[a-zA-Z]+)?\n([\s\S]*)\n```$/)
172
+ if (match) {
173
+ return match[1] + '\n'
174
+ }
175
+ return content
176
+ }
177
+
178
+ export const createSearchReplaceBlock = (search: string, replace: string) => {
179
+ return `<<<<<<< SEARCH\n${search}\n=======\n${replace}\n>>>>>>> REPLACE`
180
+ }
181
+
182
+ export function printFileTree(
183
+ nodes: FileTreeNode[],
184
+ depth: number = 0,
185
+ ): string {
186
+ let result = ''
187
+ const indentation = ' '.repeat(depth)
188
+ for (const node of nodes) {
189
+ result += `${indentation}${node.name}${node.type === 'directory' ? '/' : ''}\n`
190
+ if (node.type === 'directory' && node.children) {
191
+ result += printFileTree(node.children, depth + 1)
192
+ }
193
+ }
194
+ return result
195
+ }
196
+
197
+ export function printFileTreeWithTokens(
198
+ nodes: FileTreeNode[],
199
+ fileTokenScores: Record<string, Record<string, number>>,
200
+ path: string[] = [],
201
+ ): string {
202
+ let result = ''
203
+ const depth = path.length
204
+ const indentToken = ' '
205
+ const indentation = indentToken.repeat(depth)
206
+ const indentationWithFile = indentToken.repeat(depth + 1)
207
+ for (const node of nodes) {
208
+ if (
209
+ node.type === 'directory' &&
210
+ (!node.children || node.children.length === 0)
211
+ ) {
212
+ // Skip empty directories
213
+ continue
214
+ }
215
+ result += `${indentation}${node.name}${node.type === 'directory' ? '/' : ''}`
216
+ path.push(node.name)
217
+ const filePath = path.join('/')
218
+ const tokenScores = fileTokenScores[filePath]
219
+ if (node.type === 'file' && tokenScores) {
220
+ const tokens = Object.keys(tokenScores)
221
+ if (tokens.length > 0) {
222
+ result += `\n${indentationWithFile}${tokens.join(' ')}`
223
+ }
224
+ }
225
+ result += '\n'
226
+ if (node.type === 'directory' && node.children) {
227
+ result += printFileTreeWithTokens(node.children, fileTokenScores, path)
228
+ }
229
+ path.pop()
230
+ }
231
+ return result
232
+ }
233
+
234
+ /**
235
+ * Ensures the given file contents ends with a newline character.
236
+ * @param contents - The file contents
237
+ * @returns the file contents with a newline character.
238
+ */
239
+ export const ensureEndsWithNewline = (
240
+ contents: string | null,
241
+ ): string | null => {
242
+ if (contents === null || contents === '') {
243
+ // Leave empty file as is
244
+ return contents
245
+ }
246
+ if (contents.endsWith('\n')) {
247
+ return contents
248
+ }
249
+ return contents + '\n'
250
+ }
251
+
252
+ /**
253
+ * Node-compatible file existence check.
254
+ * Uses fs.stat instead of Bun-specific fs.exists.
255
+ */
256
+ export async function fileExists(params: {
257
+ filePath: string
258
+ fs: AnvilFileSystem
259
+ }): Promise<boolean> {
260
+ const { filePath, fs } = params
261
+
262
+ try {
263
+ await fs.stat(filePath)
264
+ return true
265
+ } catch {
266
+ return false
267
+ }
268
+ }
269
+
270
+ export const ensureDirectoryExists = async (params: {
271
+ baseDir: string
272
+ fs: AnvilFileSystem
273
+ }) => {
274
+ const { baseDir, fs } = params
275
+
276
+ const baseDirExists = await fileExists({ filePath: baseDir, fs })
277
+ if (!baseDirExists) {
278
+ await fs.mkdir(baseDir, { recursive: true })
279
+ }
280
+ }
281
+
282
+ /**
283
+ * Removes markdown code block syntax if present, including any language tag
284
+ */
285
+ export const cleanMarkdownCodeBlock = (content: string): string => {
286
+ const cleanResponse = content.match(/^```(?:[a-zA-Z]+)?\n([\s\S]*)\n```$/)
287
+ ? content.replace(/^```(?:[a-zA-Z]+)?\n/, '').replace(/\n```$/, '')
288
+ : content
289
+ return cleanResponse
290
+ }
291
+
292
+ export function isValidFilePath(path: string) {
293
+ if (!path) return false
294
+
295
+ // Check for whitespace
296
+ if (/\s/.test(path)) return false
297
+
298
+ // Check for invalid characters
299
+ const invalidChars = /[<>:"|?*\x00-\x1F]/g
300
+ if (invalidChars.test(path)) return false
301
+
302
+ return true
303
+ }
304
+
305
+ export async function isDir(params: {
306
+ path: string
307
+ fs: AnvilFileSystem
308
+ }): Promise<boolean> {
309
+ const { path, fs } = params
310
+
311
+ try {
312
+ const stats = await fs.stat(path)
313
+ return stats.isDirectory()
314
+ } catch {
315
+ return false
316
+ }
317
+ }
318
+
319
+ /**
320
+ * Returns true if the `toPath` is a subdirectory of `fromPath`.
321
+ */
322
+ export function isSubdir(fromPath: string, toPath: string) {
323
+ const resolvedFrom = path.resolve(fromPath)
324
+ const resolvedTo = path.resolve(toPath)
325
+
326
+ if (process.platform === 'win32') {
327
+ const fromDrive = path.parse(resolvedFrom).root.toLowerCase()
328
+ const toDrive = path.parse(resolvedTo).root.toLowerCase()
329
+ if (fromDrive !== toDrive) {
330
+ return false
331
+ }
332
+ }
333
+
334
+ return !path.relative(resolvedFrom, resolvedTo).startsWith('..')
335
+ }
336
+
337
+ export function isValidProjectRoot(dir: string): boolean {
338
+ return !isSubdir(dir, os.homedir())
339
+ }
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Formats code search output to group matches by file.
3
+ *
4
+ * Input format: ./file.ts:line:content
5
+ * Output format:
6
+ * Found 3 matches
7
+ * ./file.ts:
8
+ * Line 1: content
9
+ * Line 2: another line content
10
+ * Line 3: yet another line content
11
+ *
12
+ * (double newline between distinct files)
13
+ *
14
+ * @param stdout The raw stdout from ripgrep
15
+ * @param options.matchCount The number of actual matches, excluding context lines
16
+ * @returns Formatted output with matches grouped by file
17
+ */
18
+ export function formatCodeSearchOutput(
19
+ stdout: string,
20
+ options: { matchCount?: number } = {},
21
+ ): string {
22
+ if (!stdout) {
23
+ return 'Found 0 matches'
24
+ }
25
+ const lines = stdout.split('\n')
26
+ const formatted: string[] = [
27
+ `Found ${options.matchCount ?? countFormattedMatches(lines)} matches`,
28
+ ]
29
+ let currentFile: string | null = null
30
+
31
+ for (const line of lines) {
32
+ if (!line.trim()) {
33
+ formatted.push(line)
34
+ continue
35
+ }
36
+
37
+ // Skip separator lines between result groups
38
+ if (line === '--') {
39
+ continue
40
+ }
41
+
42
+ // Ripgrep output format:
43
+ // - Match lines: filename:line_number:content
44
+ // - Context lines (with -A/-B/-C flags): filename-line_number-content
45
+
46
+ // Use regex to find the pattern: separator + digits + separator
47
+ // This handles filenames with hyphens/colons by matching the line number pattern
48
+ const parsedLine = parseRipgrepLine(line)
49
+
50
+ if (!parsedLine) {
51
+ formatted.push(line)
52
+ continue
53
+ }
54
+ const { filePath, lineNumber, content } = parsedLine
55
+
56
+ // Check if this is a new file (file paths don't start with whitespace)
57
+ if (filePath && !filePath.startsWith(' ') && !filePath.startsWith('\t')) {
58
+ if (filePath !== currentFile) {
59
+ // New file - add double newline before it (except for the first file)
60
+ if (currentFile !== null) {
61
+ formatted.push('')
62
+ }
63
+ currentFile = filePath
64
+ // Show file path with colon on its own line
65
+ formatted.push(filePath + ':')
66
+ formatted.push(` Line ${lineNumber}: ${content}`)
67
+ } else {
68
+ formatted.push(` Line ${lineNumber}: ${content}`)
69
+ }
70
+ } else {
71
+ // Line doesn't match expected format, keep as-is
72
+ formatted.push(line)
73
+ }
74
+ }
75
+
76
+ return formatted.join('\n')
77
+ }
78
+
79
+ function parseRipgrepLine(line: string): {
80
+ filePath: string
81
+ lineNumber: string
82
+ content: string
83
+ isContext: boolean
84
+ } | null {
85
+ // Try match line pattern: filename:digits:content
86
+ const matchLineMatch = line.match(/(.*?):(\d+):(.*)$/)
87
+ if (matchLineMatch) {
88
+ return {
89
+ filePath: matchLineMatch[1],
90
+ lineNumber: matchLineMatch[2],
91
+ content: matchLineMatch[3],
92
+ isContext: false,
93
+ }
94
+ }
95
+
96
+ // Try context line pattern: filename-digits-content
97
+ const contextLineMatch = line.match(/(.*?)-(\d+)-(.*)$/)
98
+ if (contextLineMatch) {
99
+ return {
100
+ filePath: contextLineMatch[1],
101
+ lineNumber: contextLineMatch[2],
102
+ content: contextLineMatch[3],
103
+ isContext: true,
104
+ }
105
+ }
106
+
107
+ return null
108
+ }
109
+
110
+ function countFormattedMatches(lines: string[]): number {
111
+ return lines.filter((line) => {
112
+ const parsedLine = parseRipgrepLine(line)
113
+ return parsedLine && !parsedLine.isContext
114
+ }).length
115
+ }