@alumbwe/anvil 1.0.20 → 1.0.22

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 +74 -70
  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,87 @@
1
+ /**
2
+ * A generic min heap implementation that maintains elements ordered by a numeric score.
3
+ */
4
+ export class MinHeap<T> {
5
+ private heap: { item: T; score: number }[] = []
6
+
7
+ private getParentIndex(index: number): number {
8
+ return Math.floor((index - 1) / 2)
9
+ }
10
+
11
+ private getLeftChildIndex(index: number): number {
12
+ return 2 * index + 1
13
+ }
14
+
15
+ private getRightChildIndex(index: number): number {
16
+ return 2 * index + 2
17
+ }
18
+
19
+ private swap(index1: number, index2: number): void {
20
+ const temp = this.heap[index1]
21
+ this.heap[index1] = this.heap[index2]
22
+ this.heap[index2] = temp
23
+ }
24
+
25
+ private siftUp(index: number): void {
26
+ while (index > 0) {
27
+ const parentIndex = this.getParentIndex(index)
28
+ if (this.heap[parentIndex].score <= this.heap[index].score) {
29
+ break
30
+ }
31
+ this.swap(index, parentIndex)
32
+ index = parentIndex
33
+ }
34
+ }
35
+
36
+ private siftDown(index: number): void {
37
+ while (true) {
38
+ let minIndex = index
39
+ const leftChild = this.getLeftChildIndex(index)
40
+ const rightChild = this.getRightChildIndex(index)
41
+
42
+ if (
43
+ leftChild < this.heap.length &&
44
+ this.heap[leftChild].score < this.heap[minIndex].score
45
+ ) {
46
+ minIndex = leftChild
47
+ }
48
+
49
+ if (
50
+ rightChild < this.heap.length &&
51
+ this.heap[rightChild].score < this.heap[minIndex].score
52
+ ) {
53
+ minIndex = rightChild
54
+ }
55
+
56
+ if (minIndex === index) {
57
+ break
58
+ }
59
+
60
+ this.swap(index, minIndex)
61
+ index = minIndex
62
+ }
63
+ }
64
+
65
+ insert(item: T, score: number): void {
66
+ this.heap.push({ item, score })
67
+ this.siftUp(this.heap.length - 1)
68
+ }
69
+
70
+ extractMin(): T | undefined {
71
+ if (this.heap.length === 0) return undefined
72
+
73
+ const minItem = this.heap[0].item
74
+ const last = this.heap.pop()!
75
+
76
+ if (this.heap.length > 0) {
77
+ this.heap[0] = last
78
+ this.siftDown(0)
79
+ }
80
+
81
+ return minItem
82
+ }
83
+
84
+ get size(): number {
85
+ return this.heap.length
86
+ }
87
+ }
@@ -0,0 +1,4 @@
1
+ // Compatibility export for callers that imported the old utility path. The
2
+ // implementation lives beside `models`, avoiding the old model-config ->
3
+ // model-utils -> old-constants -> model-config cycle and its CommonJS require.
4
+ export { isExplicitlyDefinedModel } from '../constants/model-config'
@@ -0,0 +1,128 @@
1
+ import { isEqual, mapValues, union } from 'lodash'
2
+
3
+ type RemoveUndefined<T extends object> = {
4
+ [K in keyof T as T[K] extends undefined ? never : K]: Exclude<T[K], undefined>
5
+ }
6
+
7
+ export const removeUndefinedProps = <T extends object>(
8
+ obj: T,
9
+ ): RemoveUndefined<T> => {
10
+ const newObj: Record<string, unknown> = {}
11
+
12
+ for (const key of Object.keys(obj)) {
13
+ const value = obj[key as keyof T]
14
+ if (value !== undefined) {
15
+ newObj[key] = value
16
+ }
17
+ }
18
+
19
+ return newObj as RemoveUndefined<T>
20
+ }
21
+
22
+ export const removeNullOrUndefinedProps = <T extends object>(
23
+ obj: T,
24
+ exceptions?: string[],
25
+ ): T => {
26
+ const newObj: Record<string, unknown> = {}
27
+
28
+ for (const key of Object.keys(obj)) {
29
+ const value = obj[key as keyof T]
30
+ if (
31
+ (value !== undefined && value !== null) ||
32
+ (exceptions ?? []).includes(key)
33
+ ) {
34
+ newObj[key] = value
35
+ }
36
+ }
37
+ return newObj as T
38
+ }
39
+
40
+ export const addObjects = <T extends { [key: string]: number }>(
41
+ obj1: T,
42
+ obj2: T,
43
+ ): T => {
44
+ const keys = union(Object.keys(obj1), Object.keys(obj2))
45
+ const newObj: { [key: string]: number } = {}
46
+
47
+ for (const key of keys) {
48
+ newObj[key] = (obj1[key] ?? 0) + (obj2[key] ?? 0)
49
+ }
50
+
51
+ return newObj as T
52
+ }
53
+
54
+ export const subtractObjects = <T extends { [key: string]: number }>(
55
+ obj1: T,
56
+ obj2: T,
57
+ ): T => {
58
+ const keys = union(Object.keys(obj1), Object.keys(obj2))
59
+ const newObj: { [key: string]: number } = {}
60
+
61
+ for (const key of keys) {
62
+ newObj[key] = (obj1[key] ?? 0) - (obj2[key] ?? 0)
63
+ }
64
+
65
+ return newObj as T
66
+ }
67
+
68
+ export const hasChanges = <T extends object>(obj: T, partial: Partial<T>) => {
69
+ const currValues = mapValues(partial, (_, key: keyof T) => obj[key])
70
+ return !isEqual(currValues, partial)
71
+ }
72
+
73
+ export const hasSignificantDeepChanges = <T extends object>(
74
+ obj: T,
75
+ partial: Partial<T>,
76
+ epsilonForNumbers: number,
77
+ ): boolean => {
78
+ const compareValues = (currValue: any, partialValue: any): boolean => {
79
+ if (typeof currValue === 'number' && typeof partialValue === 'number') {
80
+ return Math.abs(currValue - partialValue) > epsilonForNumbers
81
+ }
82
+ if (typeof currValue === 'object' && typeof partialValue === 'object') {
83
+ return hasSignificantDeepChanges(
84
+ currValue,
85
+ partialValue,
86
+ epsilonForNumbers,
87
+ )
88
+ }
89
+ return !isEqual(currValue, partialValue)
90
+ }
91
+
92
+ for (const key in partial) {
93
+ if (Object.prototype.hasOwnProperty.call(partial, key)) {
94
+ if (compareValues(obj[key], partial[key])) {
95
+ return true
96
+ }
97
+ }
98
+ }
99
+
100
+ return false
101
+ }
102
+
103
+ export const filterObject = <T extends object>(
104
+ obj: T,
105
+ predicate: (value: any, key: keyof T) => boolean,
106
+ ): { [P in keyof T]: T[P] } => {
107
+ const result = {} as { [P in keyof T]: T[P] }
108
+ for (const key in obj) {
109
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
110
+ if (predicate(obj[key], key)) {
111
+ result[key] = obj[key]
112
+ }
113
+ }
114
+ }
115
+ return result
116
+ }
117
+
118
+ /**
119
+ * Asserts that a condition is true. If the condition is false, it throws an error with the provided message.
120
+ * @param condition The condition to check
121
+ * @param message The error message to display if the condition is false
122
+ * @throws {Error} If the condition is false
123
+ */
124
+ export function assert(condition: boolean, message: string): asserts condition {
125
+ if (!condition) {
126
+ throw new Error(`Assertion failed: ${message}`)
127
+ }
128
+ }
@@ -0,0 +1,89 @@
1
+ export function parsePartialJsonObjectSingle(content: string): {
2
+ lastParamComplete: boolean
3
+ params: any
4
+ } {
5
+ try {
6
+ return { lastParamComplete: true, params: JSON.parse(content) }
7
+ } catch {}
8
+
9
+ if (!content.match(/\d$/)) {
10
+ try {
11
+ return { lastParamComplete: true, params: JSON.parse(content + '}') }
12
+ } catch {}
13
+ }
14
+
15
+ try {
16
+ return { lastParamComplete: false, params: JSON.parse(content + '"}') }
17
+ } catch {}
18
+
19
+ if (content.endsWith('\\')) {
20
+ try {
21
+ return {
22
+ lastParamComplete: false,
23
+ params: JSON.parse(content.slice(0, -1) + '"}'),
24
+ }
25
+ } catch {}
26
+ }
27
+
28
+ let commaPos = content.length
29
+ while ((commaPos = content.lastIndexOf(',', commaPos - 1)) !== -1) {
30
+ try {
31
+ return {
32
+ lastParamComplete: true,
33
+ params: JSON.parse(content.slice(0, commaPos) + '}'),
34
+ }
35
+ } catch {}
36
+ }
37
+
38
+ return { lastParamComplete: true, params: {} }
39
+ }
40
+
41
+ export function getPartialJsonDelta(
42
+ content: string,
43
+ previous: string,
44
+ ): {
45
+ delta: Record<string, any>
46
+ result: Record<string, any>
47
+ lastParam: { key: string | undefined; complete: boolean }
48
+ } {
49
+ if (!content.startsWith(previous)) {
50
+ throw new Error(
51
+ `Content must be previous content plus new content. Content ${JSON.stringify(content)} does not start with previous content ${JSON.stringify(previous)}`,
52
+ )
53
+ }
54
+ const { lastParamComplete, params } = parsePartialJsonObjectSingle(content)
55
+ const lastParam = Object.keys(params).pop()
56
+
57
+ const { lastParamComplete: prevLastParamComplete, params: prevParams } =
58
+ parsePartialJsonObjectSingle(previous)
59
+ const prevLastParam = Object.keys(prevParams).pop()
60
+
61
+ const entries = Object.entries(params)
62
+
63
+ const delta: Record<string, any> = {}
64
+ for (const [key, value] of entries) {
65
+ if (prevParams[key] === value) {
66
+ if (prevLastParam === key && !prevLastParamComplete) {
67
+ delta[key] = ''
68
+ }
69
+ continue
70
+ }
71
+ if (typeof value === 'string') {
72
+ delta[key] = value.slice((prevParams[key] ?? '').length)
73
+ } else {
74
+ delta[key] = value
75
+ }
76
+ }
77
+
78
+ return {
79
+ delta,
80
+ result: params,
81
+ lastParam: {
82
+ key: lastParam,
83
+ complete:
84
+ prevLastParam === lastParam
85
+ ? lastParamComplete && !prevLastParamComplete
86
+ : lastParamComplete,
87
+ },
88
+ }
89
+ }
@@ -0,0 +1,15 @@
1
+ import path from 'path'
2
+
3
+ /**
4
+ * True when candidate is lexically root itself or one of its descendants.
5
+ * Resolve symlinks first when the caller needs filesystem-level containment.
6
+ */
7
+ export function isPathInside(root: string, candidate: string): boolean {
8
+ const relativePath = path.relative(root, candidate)
9
+ return (
10
+ relativePath === '' ||
11
+ (relativePath !== '..' &&
12
+ !relativePath.startsWith(`..${path.sep}`) &&
13
+ !path.isAbsolute(relativePath))
14
+ )
15
+ }
@@ -0,0 +1,119 @@
1
+ import * as ignore from 'ignore'
2
+
3
+ export const PROJECT_IGNORE_FILES = [
4
+ '.gitignore',
5
+ '.anvilignore',
6
+ '.manicodeignore',
7
+ // Legacy: respect ignore files created by pre-Anvil installs.
8
+ '.codebuffignore',
9
+ ] as const
10
+
11
+ export type DirIgnore = { base: string; ig: ignore.Ignore }
12
+
13
+ function normalizeRelativePath(filePath: string): string | null {
14
+ const segments: string[] = []
15
+ for (const segment of filePath.replaceAll('\\', '/').split('/')) {
16
+ if (!segment || segment === '.') continue
17
+ if (segment === '..') {
18
+ if (segments.length === 0) return null
19
+ segments.pop()
20
+ continue
21
+ }
22
+ segments.push(segment)
23
+ }
24
+ return segments.join('/') || null
25
+ }
26
+
27
+ function testChain(chain: DirIgnore[], candidate: string): boolean {
28
+ let ignored = false
29
+ for (const { base, ig } of chain) {
30
+ if (base && !candidate.startsWith(`${base}/`)) continue
31
+ const scoped = base ? candidate.slice(base.length + 1) : candidate
32
+ if (!scoped) continue
33
+ const result = ig.test(scoped)
34
+ if (result.ignored) ignored = true
35
+ else if (result.unignored) ignored = false
36
+ }
37
+ return ignored
38
+ }
39
+
40
+ export function isIgnoredByIgnoreChain(
41
+ chain: DirIgnore[],
42
+ filePath: string,
43
+ ): boolean {
44
+ const segments = filePath.split('/')
45
+ for (let i = 0; i < segments.length - 1; i++) {
46
+ if (testChain(chain, `${segments.slice(0, i + 1).join('/')}/`)) {
47
+ return true
48
+ }
49
+ }
50
+ return testChain(chain, filePath)
51
+ }
52
+
53
+ export function addIgnoreFileContents(
54
+ ig: ignore.Ignore,
55
+ contents: string,
56
+ onInvalidPattern?: (pattern: string, error: unknown) => void,
57
+ ): void {
58
+ for (let line of contents.split('\n')) {
59
+ line = line.trim()
60
+ if (!line || line.startsWith('#')) continue
61
+ try {
62
+ ig.add(line)
63
+ } catch (error: unknown) {
64
+ // Match the local file-tree behavior: one invalid pattern must not make
65
+ // the remaining valid project rules disappear.
66
+ onInvalidPattern?.(line, error)
67
+ }
68
+ }
69
+ }
70
+
71
+ /**
72
+ * Build a fail-closed-friendly ignore-file reader for filesystems whose read
73
+ * errors do not reliably distinguish a missing file from an unreadable one.
74
+ */
75
+ export function createProjectIgnoreFileReader(params: {
76
+ listDirectory: (directory: string) => Promise<readonly string[]>
77
+ readFile: (filePath: string) => Promise<string>
78
+ }): (filePath: string) => Promise<string | null> {
79
+ const directoryEntries = new Map<string, Promise<Set<string>>>()
80
+
81
+ return async (filePath) => {
82
+ const separator = filePath.lastIndexOf('/')
83
+ const directory = separator === -1 ? '' : filePath.slice(0, separator)
84
+ const fileName = filePath.slice(separator + 1)
85
+
86
+ let entries = directoryEntries.get(directory)
87
+ if (!entries) {
88
+ entries = params.listDirectory(directory).then((names) => new Set(names))
89
+ directoryEntries.set(directory, entries)
90
+ }
91
+ if (!(await entries).has(fileName)) return null
92
+
93
+ return params.readFile(filePath)
94
+ }
95
+ }
96
+
97
+ /** Evaluate project-owned ignore files without requiring a native Git binary. */
98
+ export async function isProjectFileIgnored(params: {
99
+ filePath: string
100
+ readIgnoreFile: (filePath: string) => Promise<string | null>
101
+ }): Promise<boolean> {
102
+ const filePath = normalizeRelativePath(params.filePath)
103
+ if (!filePath) return true
104
+
105
+ const parentSegments = filePath.split('/').slice(0, -1)
106
+ const chain: DirIgnore[] = []
107
+ for (let depth = 0; depth <= parentSegments.length; depth++) {
108
+ const base = parentSegments.slice(0, depth).join('/')
109
+ const ig = ignore.default()
110
+ for (const ignoreFile of PROJECT_IGNORE_FILES) {
111
+ const ignorePath = base ? `${base}/${ignoreFile}` : ignoreFile
112
+ const contents = await params.readIgnoreFile(ignorePath)
113
+ if (contents !== null) addIgnoreFileContents(ig, contents)
114
+ }
115
+ chain.push({ base, ig })
116
+ }
117
+
118
+ return isIgnoredByIgnoreChain(chain, filePath)
119
+ }
@@ -0,0 +1,73 @@
1
+ export const INITIAL_RETRY_DELAY = 1000 // 1 second
2
+
3
+ export async function withRetry<T>(
4
+ operation: () => Promise<T>,
5
+ options: {
6
+ maxRetries?: number
7
+ retryIf?: (error: any) => boolean
8
+ onRetry?: (error: any, attempt: number) => void
9
+ retryDelayMs?: number
10
+ } = {},
11
+ ): Promise<T> {
12
+ const {
13
+ maxRetries = 3,
14
+ retryIf = (error) => error?.type === 'APIConnectionError',
15
+ onRetry = () => {},
16
+ retryDelayMs = INITIAL_RETRY_DELAY,
17
+ } = options
18
+
19
+ let lastError: any = null
20
+
21
+ for (let attempt = 0; attempt < maxRetries; attempt++) {
22
+ try {
23
+ return await operation()
24
+ } catch (error) {
25
+ lastError = error
26
+
27
+ if (!retryIf(error) || attempt === maxRetries - 1) {
28
+ throw error
29
+ }
30
+
31
+ onRetry(error, attempt + 1)
32
+
33
+ // Exponential backoff with jitter (±20%) to prevent thundering herd
34
+ const baseDelayMs = retryDelayMs * Math.pow(2, attempt)
35
+ const jitter = 0.8 + Math.random() * 0.4 // Random multiplier between 0.8 and 1.2
36
+ const delayMs = Math.round(baseDelayMs * jitter)
37
+ await new Promise((resolve) => setTimeout(resolve, delayMs))
38
+ }
39
+ }
40
+
41
+ throw lastError
42
+ }
43
+
44
+ export const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms))
45
+
46
+ /**
47
+ * Wraps a promise with a timeout
48
+ * @param promise The promise to wrap
49
+ * @param timeoutMs Timeout in milliseconds
50
+ * @param timeoutMessage Optional message for the timeout error
51
+ * @returns A promise that resolves with the result of the original promise or rejects with a timeout error
52
+ */
53
+ export async function withTimeout<T>(
54
+ promise: Promise<T>,
55
+ timeoutMs: number,
56
+ timeoutMessage: string = `Operation timed out after ${timeoutMs}ms`,
57
+ ): Promise<T> {
58
+ let timeoutId: ReturnType<typeof setTimeout> | undefined
59
+
60
+ const timeoutPromise = new Promise<never>((_, reject) => {
61
+ timeoutId = setTimeout(() => {
62
+ reject(new Error(timeoutMessage))
63
+ }, timeoutMs)
64
+ })
65
+
66
+ try {
67
+ return await Promise.race([promise, timeoutPromise])
68
+ } finally {
69
+ if (timeoutId !== undefined) {
70
+ clearTimeout(timeoutId)
71
+ }
72
+ }
73
+ }
@@ -0,0 +1,15 @@
1
+ import seedrandom from 'seedrandom'
2
+
3
+ export function sampleSizeWithSeed<T>(
4
+ array: T[],
5
+ size: number,
6
+ seed: string,
7
+ ): T[] {
8
+ const rng = seedrandom(seed)
9
+ const result = array.slice()
10
+ for (let i = result.length - 1; i > 0; i--) {
11
+ const j = Math.floor(rng() * (i + 1))
12
+ ;[result[i], result[j]] = [result[j], result[i]]
13
+ }
14
+ return result.slice(0, size)
15
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Minimal in-memory fixed-window rate limiter, shared by the unauthenticated
3
+ * `/api/logs` ingest endpoints (browser + anonymous CLI). Per-instance and
4
+ * best-effort — good enough to blunt abuse/cost on a single Render instance,
5
+ * not a distributed guarantee. Kept dependency-free and `now`-injectable so the
6
+ * window logic is unit-testable.
7
+ */
8
+ export interface FixedWindowRateLimiter {
9
+ /** Returns true if `key` has exceeded the window's request budget. */
10
+ limited(key: string, now: number): boolean
11
+ }
12
+
13
+ export function createFixedWindowRateLimiter(opts: {
14
+ windowMs: number
15
+ max: number
16
+ /** Prune expired entries once the map grows past this. Defaults to 10k. */
17
+ maxKeys?: number
18
+ }): FixedWindowRateLimiter {
19
+ const { windowMs, max, maxKeys = 10_000 } = opts
20
+ const hits = new Map<string, { count: number; resetAt: number }>()
21
+ let lastPruneAt = 0
22
+
23
+ return {
24
+ limited(key: string, now: number): boolean {
25
+ const entry = hits.get(key)
26
+ if (!entry || now >= entry.resetAt) {
27
+ hits.set(key, { count: 1, resetAt: now + windowMs })
28
+ // Bound map growth: prune expired entries, but at most once per window
29
+ // so a steady stream of live keys can't trigger an O(n) scan per call.
30
+ if (hits.size > maxKeys && now - lastPruneAt >= windowMs) {
31
+ lastPruneAt = now
32
+ for (const [k, v] of hits) if (now >= v.resetAt) hits.delete(k)
33
+ }
34
+ return false
35
+ }
36
+ entry.count++
37
+ return entry.count > max
38
+ },
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Best-effort client IP for per-IP rate limiting on the unauthenticated ingest
44
+ * endpoints. Prefers the proxy-set `x-real-ip` (harder to spoof than the
45
+ * left-most `x-forwarded-for` token). Accepts any Headers-like object so it
46
+ * works with `NextRequest.headers` without a Next dependency here.
47
+ */
48
+ export function extractClientIp(headers: {
49
+ get(name: string): string | null
50
+ }): string {
51
+ return (
52
+ headers.get('x-real-ip')?.trim() ||
53
+ headers.get('x-forwarded-for')?.split(',')[0]?.trim() ||
54
+ 'unknown'
55
+ )
56
+ }
@@ -0,0 +1,60 @@
1
+ const DAY_MS = 24 * 60 * 60 * 1000
2
+
3
+ /** PostHog-style retention windows to mirror in Reddit CAPI custom events. */
4
+ export const ANVIL_REDDIT_RETENTION_MILESTONE_DAYS = [1, 7, 24] as const
5
+
6
+ export type AnvilRedditRetentionMilestoneDays =
7
+ (typeof ANVIL_REDDIT_RETENTION_MILESTONE_DAYS)[number]
8
+
9
+ export type AnvilRedditConversionPlan = {
10
+ fireFirstPrompt: boolean
11
+ retentionMilestones: AnvilRedditRetentionMilestoneDays[]
12
+ }
13
+
14
+ function daysBetween(fromDateKey: string, toDateKey: string): number {
15
+ const from = new Date(`${fromDateKey}T00:00:00.000Z`).getTime()
16
+ const to = new Date(`${toDateKey}T00:00:00.000Z`).getTime()
17
+ if (Number.isNaN(from) || Number.isNaN(to)) {
18
+ throw new Error(`Invalid date key range: ${fromDateKey} -> ${toDateKey}`)
19
+ }
20
+ return Math.round((to - from) / DAY_MS)
21
+ }
22
+
23
+ /** First successful anvil prompt = first-ever usage day recorded. */
24
+ export function isFirstAnvilPrompt(params: {
25
+ previousUsageDays: readonly string[]
26
+ newUsageDayRecorded: boolean
27
+ }): boolean {
28
+ return params.newUsageDayRecorded && params.previousUsageDays.length === 0
29
+ }
30
+
31
+ /** Exact calendar-day milestones reached by this newly recorded usage day. */
32
+ export function getAnvilRetentionMilestonesToFire(params: {
33
+ previousUsageDays: readonly string[]
34
+ todayDateKey: string
35
+ newUsageDayRecorded: boolean
36
+ }): AnvilRedditRetentionMilestoneDays[] {
37
+ if (!params.newUsageDayRecorded) {
38
+ return []
39
+ }
40
+
41
+ const firstDay = [...params.previousUsageDays, params.todayDateKey].reduce(
42
+ (min, dateKey) => (dateKey < min ? dateKey : min),
43
+ )
44
+ const daysSinceFirstToday = daysBetween(firstDay, params.todayDateKey)
45
+
46
+ return ANVIL_REDDIT_RETENTION_MILESTONE_DAYS.filter(
47
+ (milestone) => daysSinceFirstToday === milestone,
48
+ )
49
+ }
50
+
51
+ export function planAnvilRedditConversionEvents(params: {
52
+ previousUsageDays: readonly string[]
53
+ todayDateKey: string
54
+ newUsageDayRecorded: boolean
55
+ }): AnvilRedditConversionPlan {
56
+ return {
57
+ fireFirstPrompt: isFirstAnvilPrompt(params),
58
+ retentionMilestones: getAnvilRetentionMilestonesToFire(params),
59
+ }
60
+ }
@@ -0,0 +1,36 @@
1
+ import type { AnvilRedditRetentionMilestoneDays } from '@anvil/common/util/reddit-anvil-retention'
2
+
3
+ export type RedditConversionSurface = 'cli' | 'web' | 'cloud' | 'chat'
4
+
5
+ export type RedditCapiEventName =
6
+ | 'FirstPrompt'
7
+ | 'Retention1d'
8
+ | 'Retention7d'
9
+ | 'Retention24d'
10
+
11
+ export const REDDIT_FIRST_PROMPT_EVENT = 'FirstPrompt' as const
12
+
13
+ export function redditRetentionCapiEventName(
14
+ milestone: AnvilRedditRetentionMilestoneDays,
15
+ ): RedditCapiEventName {
16
+ return `Retention${milestone}d`
17
+ }
18
+
19
+ /**
20
+ * The day boundary D1/D7/D24 eligibility is computed in.
21
+ *
22
+ * Anything deciding "have we already done this user's tracking today?" MUST
23
+ * use this same boundary. A memo keyed on a different day (Pacific, say) opens
24
+ * a window where the UTC day has rolled over but the memo has not, and a
25
+ * milestone claim can never be created in it — the conversion is lost, not
26
+ * delayed. Do not reimplement.
27
+ *
28
+ * It lives in this dependency-free module rather than next to its consumer in
29
+ * `packages/internal/src/reddit-conversions.ts`, because that module imports
30
+ * `./db`, which constructs the application connection pool at module scope.
31
+ * A pure date helper must not drag a database pool into everything that needs
32
+ * a day key.
33
+ */
34
+ export function utcDateKey(now: Date): string {
35
+ return now.toISOString().slice(0, 10)
36
+ }