@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,450 @@
1
+ /**
2
+ * Per-account daily spend ceilings for free mode.
3
+ *
4
+ * These replace the flat `ANVIL_DAILY_SPEND_LIMIT_USD = 50` that every
5
+ * account shared. A ceiling is the settled provider cost, since midnight
6
+ * Pacific, at or above which no FRESH session is admitted — live sessions and
7
+ * reclaims are never interrupted, so the honest reading is "how much we will
8
+ * spend STARTING new work for this account today".
9
+ *
10
+ * ## Why a ceiling and not a ban
11
+ *
12
+ * Everything here is a spend cap, and that is deliberate. A cap is reversible,
13
+ * proportionate, self-limiting, and wrong-in-a-cheap-way: a real user who hits
14
+ * one loses the ability to start a new session today, not their account.
15
+ * `docs/anvil-abuse-detection.md` records what the other kind of error
16
+ * costs — 659 accounts wrongly banned on 2026-08-03, reversed by hand — and
17
+ * its evidence standard names region, domain, cost and volume as **weak**
18
+ * signals that must never carry a ban alone. Every cohort below is built from
19
+ * exactly those weak signals, so every cohort below gets a cap.
20
+ *
21
+ * Bans still happen. They happen through the sweeps, on the strong evidence
22
+ * those sweeps re-derive (proxy fanout, honeypot hits), and a low ceiling is
23
+ * what bounds the damage in the meantime.
24
+ *
25
+ * ## The ceilings compose by MINIMUM
26
+ *
27
+ * An account can be in several cohorts at once — a limited-region account on a
28
+ * disposable domain running a third-party client. Rather than ordering the
29
+ * rules and picking the first match, every applicable ceiling is computed and
30
+ * the lowest wins. That makes the outcome independent of rule order, which is
31
+ * the property that stops this file rotting as cohorts are added.
32
+ *
33
+ * It also means these can only ever LOWER a limit. The trust-level matrix
34
+ * (`anvil-trust.ts`) participates in the same minimum when it is enforcing,
35
+ * so shipping these ceilings cannot accidentally raise anyone's budget while
36
+ * that rollout is still in `observe`.
37
+ */
38
+
39
+ import type { AnvilAccessTier } from './anvil-models'
40
+
41
+ /**
42
+ * Region ceilings, replacing the flat $50.
43
+ *
44
+ * Sized against the measured per-user daily distribution rather than picked:
45
+ * ordinary use sits far below these, and the accounts they bind are the tail
46
+ * that made the flat cap meaningless. The limited region is lower because it
47
+ * cannot reach a premium model at all, so the same dollar figure buys far more
48
+ * requests there — an identical cap would not be an identical constraint.
49
+ */
50
+ export const ANVIL_REGION_DAILY_SPEND_USD: Record<
51
+ AnvilAccessTier,
52
+ number
53
+ > = {
54
+ full: 15,
55
+ limited: 5,
56
+ }
57
+
58
+ /**
59
+ * The restricted-cohort ceiling.
60
+ *
61
+ * Deliberately not zero. A zero ceiling is a block, and a block tells the
62
+ * operator instantly which signal caught them — at which point they rotate it
63
+ * and we lose both the account and the detection. Fifty cents a day keeps them
64
+ * visible, keeps their traffic flowing through the honeypot models and the
65
+ * fanout counters that produce ban-grade evidence, and costs about as much as
66
+ * finding out would have.
67
+ *
68
+ * Note what this does and does not bound. It gates whether a FRESH session may
69
+ * start, so an account at $0.49 can still open one and spend well past the
70
+ * ceiling inside it — live sessions are never interrupted, by design. The
71
+ * ceiling therefore caps how often restricted accounts come back, not their
72
+ * worst single day. At $0.50 that is roughly one session and then nothing more
73
+ * until the Pacific-midnight reset.
74
+ *
75
+ * This is the same reasoning `docs/anvil-honeypot-models.md` gives for
76
+ * separating detection from enforcement in time.
77
+ */
78
+ export const ANVIL_RESTRICTED_DAILY_SPEND_USD = 0.5
79
+
80
+ /**
81
+ * Countries whose free-mode accounts are held at the restricted ceiling.
82
+ *
83
+ * **This is a spend cap on a region, not a judgement about people in it, and
84
+ * it is worth being precise about what it can and cannot do.** Both of these
85
+ * are among the largest VPN and datacenter exit geographies in the world, so a
86
+ * meaningful share of traffic resolving here is not physically here — which
87
+ * cuts both ways: it catches operators routing through these countries, and it
88
+ * catches residents who are exactly who they appear to be. The second group is
89
+ * the cost, and the cap rather than a ban is what keeps that cost survivable.
90
+ *
91
+ * Kept as an env-overridable list because the right answer moves with where
92
+ * the traffic is, and a code deploy is the wrong latency for that.
93
+ */
94
+ export const ANVIL_RESTRICTED_COUNTRIES: readonly string[] = ['CN']
95
+
96
+ /**
97
+ * The middle ceiling, for countries that are heavy anonymizing-egress
98
+ * geographies AND have a large population of ordinary users.
99
+ *
100
+ * Exists because the two-value scheme above forced a false choice for
101
+ * Singapore. SG is in `FREE_MODE_ALLOWED_COUNTRIES` — we call it a full-access
102
+ * country and give it the $15 region ceiling — while simultaneously sitting on
103
+ * the restricted list at $0.50. An account was told it had full access and
104
+ * then priced at a thirtieth of the full budget.
105
+ *
106
+ * Measured over 24h on 2026-08-15, that resolved to: 341 of 1,491 active SG
107
+ * users (22.9%) refused, against 3.5% in the US and 0.2% in India. 75% of the
108
+ * refused cohort carried no abuse signal of any kind, and 71% of those were on
109
+ * gmail/qq/163/outlook/hotmail. The honeypot hit rate — the strongest
110
+ * country-attributable signal we have — was 2.5% in SG against 2.1% in the US
111
+ * and 3.2% in Indonesia, which is no support at all for a 30x lower cap.
112
+ *
113
+ * The abuse in SG was real, but it was a *domain* farm rather than a country:
114
+ * ~160 accounts on dhisy/dewaa/sendang/yotube/gusil, now priced by
115
+ * `flaggedEmailDomain` instead. Catching it there is what makes this tier
116
+ * affordable.
117
+ *
118
+ * $5 and not $15: SG remains a top VPN and datacenter exit, so the tail this
119
+ * bounds is real even with the farm named. Five dollars is the same figure a
120
+ * limited-region account gets — enough for a full day's ordinary work, still a
121
+ * bound — and deliberately not a number that has to be defended per-country.
122
+ */
123
+ export const ANVIL_ELEVATED_DAILY_SPEND_USD = 5
124
+
125
+ /**
126
+ * Countries held at the elevated ceiling.
127
+ *
128
+ * Env-overridable for the same reason the restricted list is: where the
129
+ * traffic is moves faster than a deploy. CN is deliberately NOT here — it is
130
+ * not in `FREE_MODE_ALLOWED_COUNTRIES`, so its accounts already resolve to the
131
+ * limited tier's $5 region ceiling, and moving it here would remove its
132
+ * restricted ceiling entirely rather than soften it. That is a separate
133
+ * decision from this one, on separate evidence.
134
+ */
135
+ export const ANVIL_ELEVATED_COUNTRIES: readonly string[] = ['SG']
136
+
137
+ /**
138
+ * The cause-blind capacity refusal.
139
+ *
140
+ * ## What it still covers
141
+ *
142
+ * Free-mode RATE limits (prompt windows, premium-model caps) and the one spend
143
+ * cohort that must stay unnamed, `third_party_client`. It was originally the
144
+ * single sentence EVERY refusal shared; two cohorts have since been split off
145
+ * it — `ANVIL_RESTRICTED_NOTICE` on 2026-08-14 and `ANVIL_BUDGET_NOTICE`
146
+ * on 2026-08-15 — each on an explicit operator decision recorded below.
147
+ *
148
+ * ## Why it was one string
149
+ *
150
+ * A restricted account (restricted country, flagged domain, observed foreign
151
+ * toolset) hits its ceiling far sooner than anyone else, and if the message it
152
+ * saw were different it would be telling the operator which signal caught
153
+ * them — the exact leak `docs/anvil-honeypot-models.md` separates detection
154
+ * from enforcement to avoid. That argument still holds for anything that names
155
+ * a DETECTOR, which is why `third_party_client` never got its own copy. It
156
+ * does not hold for a whole-population allowance, where there is no detector
157
+ * to leak — hence the budget split.
158
+ *
159
+ * ## Why it names abuse
160
+ *
161
+ * Because it is true, and because a limit without a reason reads as a product
162
+ * that broke or a user who did something wrong. Naming the cause puts it
163
+ * somewhere other than the reader: "sustained automated abuse" is plainly
164
+ * about someone else, while still explaining why the rules changed under
165
+ * someone who did nothing differently.
166
+ *
167
+ * ## What it deliberately omits
168
+ *
169
+ * Any number. The caps stay server-side for the reason
170
+ * `docs/anvil-abuse-detection.md` gives — the abuse pattern here is
171
+ * sustained pacing just under the caps, so a published cap is a published
172
+ * pacing instruction. And the surrounding copy on each surface already
173
+ * supplies the concrete part a user actually needs: when it resets.
174
+ *
175
+ * It deliberately does not open with "Free", either: three of the four places
176
+ * it is used already begin with "Free mode…" or "Free premium-model…", and the
177
+ * repetition reads as a copy bug.
178
+ */
179
+ export const ANVIL_CAPACITY_NOTICE =
180
+ 'Capacity is now limited per account — sustained automated abuse forced us to cap how much any one account can use.'
181
+
182
+ /**
183
+ * The refusal copy for the RESTRICTED cohorts — VPN/proxy egress, restricted
184
+ * country, flagged email domain.
185
+ *
186
+ * This deliberately weakens the original cohort-blind stance, on an explicit
187
+ * operator decision (2026-08-14): real full-region developers on VPNs were
188
+ * hitting a fifty-cent wall with a message that told them nothing, and the
189
+ * support cost of that silence outweighed the signal leak. What it names is
190
+ * the SET of causes, never which one applied — an operator learns their
191
+ * account tripped one of three broad detectors, not which request or which
192
+ * signal, and the honeypot/foreign-toolset detectors stay entirely unnamed.
193
+ * The VPN clause doubles as the remedy: it is the one cause a legitimate user
194
+ * can fix in a minute, and the whole point of naming it is that they do.
195
+ */
196
+ export const ANVIL_RESTRICTED_NOTICE =
197
+ 'This account has reduced capacity: it was flagged for VPN or proxy usage, a restricted location, or an email domain commonly used by bot farms. If you are on a VPN, connecting directly restores normal limits.'
198
+
199
+ /** The reasons that show `ANVIL_RESTRICTED_NOTICE`. `third_party_client`
200
+ * stays on the cause-blind `ANVIL_CAPACITY_NOTICE` on purpose: it is a
201
+ * detector worth not naming. */
202
+ export const ANVIL_RESTRICTED_NOTICE_REASONS: ReadonlySet<string> = new Set([
203
+ 'privacy_egress',
204
+ 'restricted_country',
205
+ 'flagged_email_domain',
206
+ 'unverified_egress',
207
+ ])
208
+
209
+ /**
210
+ * The plain daily-budget refusals — no cohort, no detector, no suspicion.
211
+ *
212
+ * Split out of `ANVIL_CAPACITY_NOTICE` on 2026-08-15. Every one of these is
213
+ * a whole-population allowance, and the abuse sentence was landing on people
214
+ * it did not describe: over 24h, 17 of the 18 accounts refused by the
215
+ * limited-region $5 ceiling carried no abuse signal of any kind, and were told
216
+ * that "sustained automated abuse forced us to cap" their account. That reads
217
+ * as an accusation to someone who just did a day's work, and it is the phrasing
218
+ * support tickets come back quoting.
219
+ */
220
+ export const ANVIL_BUDGET_NOTICE_REASONS: ReadonlySet<string> = new Set([
221
+ 'region',
222
+ 'elevated_country',
223
+ 'trust_level',
224
+ ])
225
+
226
+ /**
227
+ * The refusal copy for a plain daily allowance.
228
+ *
229
+ * Deliberately says nothing about the account. It names the thing that ran out
230
+ * (today's free usage), not a property of the person, and it does not use
231
+ * "limited", "restricted" or "blocked" — those read as a verdict, and they are
232
+ * the words that generate "is my account restricted?" tickets.
233
+ *
234
+ * Carries no number, for the same reason the other two do not: a published cap
235
+ * is a published pacing instruction. It also carries no reset time, because
236
+ * every surface appends its own ("resets at midnight Pacific", "come back in
237
+ * X") and the duplication reads as a copy bug.
238
+ */
239
+ export const ANVIL_BUDGET_NOTICE =
240
+ 'You have used all of today’s free usage on this account.'
241
+
242
+ /** Pick the refusal sentence for a resolved ceiling reason. */
243
+ export function anvilSpendNoticeFor(reason: string): string {
244
+ if (ANVIL_RESTRICTED_NOTICE_REASONS.has(reason)) {
245
+ return ANVIL_RESTRICTED_NOTICE
246
+ }
247
+ if (ANVIL_BUDGET_NOTICE_REASONS.has(reason)) return ANVIL_BUDGET_NOTICE
248
+ return ANVIL_CAPACITY_NOTICE
249
+ }
250
+
251
+ /**
252
+ * How far past its ceiling an account may get before a LIVE session is cut.
253
+ *
254
+ * The ceiling above gates fresh admission only — a session already open is
255
+ * never interrupted — and for a $15 account that is the right trade: the
256
+ * overshoot is bounded by one ordinary session and interrupting real work to
257
+ * save cents is the worse error. At $0.50 the same rule is not the same rule.
258
+ * Measured over 24h on 2026-08-13, against a $0.50 ceiling:
259
+ *
260
+ * restricted_country p50 3.6x worst 12.6x ($6.32)
261
+ * privacy_egress p50 4.7x worst 23.2x ($11.59)
262
+ * flagged_email_domain p50 1.8x worst 38.2x ($19.10)
263
+ *
264
+ * A cap that is exceeded 38-fold is not a cap. This multiplier is the second,
265
+ * hard bound: crossing `ceiling x multiplier` refuses the request even mid
266
+ * session. Two, not one, deliberately — a session admitted at $0.49 must be
267
+ * able to finish the turn it started, or the gate becomes a coin flip on
268
+ * whether a legitimate reply is truncated. Two bounds the worst case at
269
+ * roughly one session's spend past the ceiling, which is what the fresh-
270
+ * admission gate was always assumed to cost.
271
+ *
272
+ * It does NOT apply to the region ceilings. Those are already large enough
273
+ * that overshoot is proportionally small (worst observed 1.3x), and cutting a
274
+ * live session at $15 buys little for a real user's interruption.
275
+ */
276
+ export const ANVIL_SPEND_CEILING_HARD_MULTIPLIER = 2
277
+
278
+ /**
279
+ * Reasons whose ceiling is small enough that the hard multiplier applies.
280
+ *
281
+ * `region`, `elevated_country` and `trust_level` are excluded: all three are
282
+ * whole-population limits where the fresh-admission gate is proportionate, and
283
+ * applying a hard cut there would interrupt ordinary paying-in-attention users
284
+ * mid-thought. `elevated_country` sits at the same $5 as a limited-region
285
+ * account precisely so it can be reasoned about as a region ceiling rather
286
+ * than as a suspicion, and cutting it live would undo that.
287
+ */
288
+ const HARD_CAPPED_REASONS: ReadonlySet<string> = new Set([
289
+ 'restricted_country',
290
+ 'privacy_egress',
291
+ 'flagged_email_domain',
292
+ 'third_party_client',
293
+ 'unverified_egress',
294
+ ])
295
+
296
+ export type AnvilSpendCeilingReason =
297
+ | 'region'
298
+ | 'elevated_country'
299
+ | 'restricted_country'
300
+ | 'privacy_egress'
301
+ | 'flagged_email_domain'
302
+ | 'third_party_client'
303
+ | 'unverified_egress'
304
+ | 'trust_level'
305
+
306
+ export interface AnvilSpendCeiling {
307
+ usd: number
308
+ /** Which rule produced the winning (lowest) ceiling. Logged so a support
309
+ * question has a one-word answer. */
310
+ reason: AnvilSpendCeilingReason
311
+ /** Every rule that applied, for the admin view. */
312
+ applied: { reason: AnvilSpendCeilingReason; usd: number }[]
313
+ }
314
+
315
+ export interface AnvilSpendCeilingInput {
316
+ accessTier: AnvilAccessTier
317
+ /** Resolved country of the request, when known. */
318
+ countryCode?: string | null
319
+ /**
320
+ * True when the request arrived over an anonymizing egress — VPN, proxy,
321
+ * Tor, or a residential-proxy exit.
322
+ *
323
+ * Callers should derive this from `isAnvilHardBlockedPrivacySignal`
324
+ * rather than by listing signals themselves. That list deliberately excludes
325
+ * `relay`: Apple iCloud Private Relay is a default consumer feature shipped
326
+ * to ordinary people, and the whole privacy pipeline treats it as a green
327
+ * flag. Pricing it as anonymizing egress would put a restricted ceiling on
328
+ * every iPhone user who left a checkbox alone.
329
+ *
330
+ * `hosting` is excluded for a softer reason: it is already the coarse signal
331
+ * that ipinfo over-reports on ISP and business networks, which is why
332
+ * `isAnvilBenignAsType` exists to walk it back.
333
+ */
334
+ privacyEgress?: boolean
335
+ /** True when the account's email domain is disposable or a privacy relay. */
336
+ flaggedEmailDomain?: boolean
337
+ /** True when the account has been observed sending a non-Anvil tool
338
+ * schema. See docs/anvil-abuse-detection.md. */
339
+ thirdPartyClient?: boolean
340
+ /**
341
+ * True when ipinfo flagged the egress and NO second opinion could be
342
+ * obtained — every configured privacy provider was disabled, exhausted, or
343
+ * erroring.
344
+ *
345
+ * This exists so a provider outage cannot buy an operator a larger budget.
346
+ * Before it, an unreachable Spur produced `spur_failed_limited`, which set a
347
+ * limited tier but no ceiling — so the cheapest way to widen a cap was to
348
+ * exhaust our own vendor quota. On 2026-08-13 Spur was dead for two days and
349
+ * 354,715 lookups in 24h returned nothing; the fail-open path was live that
350
+ * whole time.
351
+ *
352
+ * It is the restricted ceiling and not a block for the usual reason: an
353
+ * unresolved verdict is not evidence, and the accounts it catches include
354
+ * ordinary users behind a business network ipinfo called `hosting`.
355
+ */
356
+ unverifiedEgress?: boolean
357
+ /** The trust matrix's ceiling, when that rollout is enforcing. */
358
+ trustLevelCeilingUsd?: number | null
359
+ /** Overrides, all optional so a missing env var changes nothing. */
360
+ overrides?: {
361
+ regionUsd?: Partial<Record<AnvilAccessTier, number>>
362
+ restrictedUsd?: number
363
+ restrictedCountries?: readonly string[]
364
+ elevatedUsd?: number
365
+ elevatedCountries?: readonly string[]
366
+ }
367
+ }
368
+
369
+ /**
370
+ * The one place a free-mode daily spend ceiling is decided.
371
+ *
372
+ * Pure, so the policy is testable without a database and so the admin page can
373
+ * show exactly what an account would get without re-deriving it.
374
+ */
375
+ export function resolveAnvilSpendCeiling(
376
+ input: AnvilSpendCeilingInput,
377
+ ): AnvilSpendCeiling {
378
+ const restrictedUsd =
379
+ input.overrides?.restrictedUsd ?? ANVIL_RESTRICTED_DAILY_SPEND_USD
380
+ const restrictedCountries =
381
+ input.overrides?.restrictedCountries ?? ANVIL_RESTRICTED_COUNTRIES
382
+ const elevatedUsd =
383
+ input.overrides?.elevatedUsd ?? ANVIL_ELEVATED_DAILY_SPEND_USD
384
+ const elevatedCountries =
385
+ input.overrides?.elevatedCountries ?? ANVIL_ELEVATED_COUNTRIES
386
+
387
+ const applied: { reason: AnvilSpendCeilingReason; usd: number }[] = [
388
+ {
389
+ reason: 'region',
390
+ usd:
391
+ input.overrides?.regionUsd?.[input.accessTier] ??
392
+ ANVIL_REGION_DAILY_SPEND_USD[input.accessTier],
393
+ },
394
+ ]
395
+
396
+ const country = input.countryCode?.toUpperCase() ?? null
397
+ if (country && elevatedCountries.includes(country)) {
398
+ applied.push({ reason: 'elevated_country', usd: elevatedUsd })
399
+ }
400
+ if (country && restrictedCountries.includes(country)) {
401
+ applied.push({ reason: 'restricted_country', usd: restrictedUsd })
402
+ }
403
+ if (input.privacyEgress) {
404
+ applied.push({ reason: 'privacy_egress', usd: restrictedUsd })
405
+ }
406
+ if (input.flaggedEmailDomain) {
407
+ applied.push({ reason: 'flagged_email_domain', usd: restrictedUsd })
408
+ }
409
+ if (input.unverifiedEgress) {
410
+ applied.push({ reason: 'unverified_egress', usd: restrictedUsd })
411
+ }
412
+ if (input.thirdPartyClient) {
413
+ applied.push({ reason: 'third_party_client', usd: restrictedUsd })
414
+ }
415
+ if (
416
+ typeof input.trustLevelCeilingUsd === 'number' &&
417
+ Number.isFinite(input.trustLevelCeilingUsd)
418
+ ) {
419
+ applied.push({ reason: 'trust_level', usd: input.trustLevelCeilingUsd })
420
+ }
421
+
422
+ // Lowest wins, and ties resolve to the EARLIER entry — which is `region`,
423
+ // the least accusatory reason. When a restricted cohort and the region
424
+ // ceiling happen to agree, "region" is both true and the one that does not
425
+ // imply we think something about the account.
426
+ let winner = applied[0]!
427
+ for (const candidate of applied.slice(1)) {
428
+ if (candidate.usd < winner.usd) winner = candidate
429
+ }
430
+
431
+ return { usd: winner.usd, reason: winner.reason, applied }
432
+ }
433
+
434
+ /**
435
+ * The spend at which even a LIVE session is refused, or `null` when the
436
+ * winning ceiling is one the hard cap deliberately does not apply to.
437
+ *
438
+ * `null` means "fresh-admission gating only" — the behaviour every ceiling had
439
+ * before this existed. Callers must treat `null` as no hard cap rather than as
440
+ * zero; getting that backwards would cut every region-limited session the
441
+ * moment it opened.
442
+ */
443
+ export function resolveAnvilHardSpendCeiling(
444
+ ceiling: Pick<AnvilSpendCeiling, 'usd' | 'reason'>,
445
+ multiplier: number = ANVIL_SPEND_CEILING_HARD_MULTIPLIER,
446
+ ): number | null {
447
+ if (!HARD_CAPPED_REASONS.has(ceiling.reason)) return null
448
+ if (!Number.isFinite(multiplier) || multiplier <= 0) return null
449
+ return ceiling.usd * multiplier
450
+ }