@alumbwe/anvil 1.0.20 → 1.0.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (328) hide show
  1. package/package.json +6 -2
  2. package/src/agents/bundled-agents.generated.ts +4 -4
  3. package/src/chat.tsx +1703 -1698
  4. package/src/cli-args.ts +134 -134
  5. package/src/components/status-bar.tsx +331 -313
  6. package/src/hooks/helpers/send-message.ts +663 -654
  7. package/src/hooks/use-send-message.ts +806 -773
  8. package/src/index.tsx +473 -473
  9. package/src/state/chat-store.ts +553 -544
  10. package/src/utils/create-run-config.ts +121 -121
  11. package/src/utils/format-compact-number.ts +37 -0
  12. package/vendor/@anvil/common/package.json +27 -0
  13. package/vendor/@anvil/common/src/__tests__/agent-validation.test.ts +846 -0
  14. package/vendor/@anvil/common/src/__tests__/anvil-model-availability.test.ts +101 -0
  15. package/vendor/@anvil/common/src/__tests__/anvil-models.test.ts +1408 -0
  16. package/vendor/@anvil/common/src/__tests__/anvil-public-data-use-copy.test.ts +258 -0
  17. package/vendor/@anvil/common/src/__tests__/anvil-referral-tiers.test.ts +48 -0
  18. package/vendor/@anvil/common/src/__tests__/anvil-spend-ceilings.test.ts +282 -0
  19. package/vendor/@anvil/common/src/__tests__/anvil-trust.test.ts +526 -0
  20. package/vendor/@anvil/common/src/__tests__/deepseek-direct.test.ts +68 -0
  21. package/vendor/@anvil/common/src/__tests__/disposable-email.test.ts +132 -0
  22. package/vendor/@anvil/common/src/__tests__/dynamic-agent-template-schema.test.ts +422 -0
  23. package/vendor/@anvil/common/src/__tests__/env-ci.test.ts +167 -0
  24. package/vendor/@anvil/common/src/__tests__/env-process.test.ts +145 -0
  25. package/vendor/@anvil/common/src/__tests__/foreign-client-shipped-agents.test.ts +234 -0
  26. package/vendor/@anvil/common/src/__tests__/foreign-client-signals.test.ts +379 -0
  27. package/vendor/@anvil/common/src/__tests__/free-agents.test.ts +807 -0
  28. package/vendor/@anvil/common/src/__tests__/gravity-capi.test.ts +154 -0
  29. package/vendor/@anvil/common/src/__tests__/handlesteps-parsing.test.ts +246 -0
  30. package/vendor/@anvil/common/src/__tests__/kimi-k3-god-only.test.ts +77 -0
  31. package/vendor/@anvil/common/src/__tests__/model-config.test.ts +89 -0
  32. package/vendor/@anvil/common/src/__tests__/project-file-tree.test.ts +298 -0
  33. package/vendor/@anvil/common/src/__tests__/provisioned-model-tiers.test.ts +116 -0
  34. package/vendor/@anvil/common/src/__tests__/reasoning-effort.test.ts +167 -0
  35. package/vendor/@anvil/common/src/__tests__/reddit-capi.test.ts +132 -0
  36. package/vendor/@anvil/common/src/__tests__/response-ad-positions.test.ts +72 -0
  37. package/vendor/@anvil/common/src/__tests__/user-state.test.ts +30 -0
  38. package/vendor/@anvil/common/src/actions.ts +215 -0
  39. package/vendor/@anvil/common/src/analytics-core.ts +69 -0
  40. package/vendor/@anvil/common/src/analytics.ts +93 -0
  41. package/vendor/@anvil/common/src/api-keys/constants.ts +26 -0
  42. package/vendor/@anvil/common/src/browser-actions.ts +413 -0
  43. package/vendor/@anvil/common/src/constants/__tests__/anvil-onboarding-gate.test.ts +63 -0
  44. package/vendor/@anvil/common/src/constants/__tests__/anvil-onboarding.test.ts +246 -0
  45. package/vendor/@anvil/common/src/constants/__tests__/cf-worker-signals.test.ts +106 -0
  46. package/vendor/@anvil/common/src/constants/agents.ts +99 -0
  47. package/vendor/@anvil/common/src/constants/analytics-events.ts +435 -0
  48. package/vendor/@anvil/common/src/constants/anthropic.ts +73 -0
  49. package/vendor/@anvil/common/src/constants/anvil-data-use.ts +81 -0
  50. package/vendor/@anvil/common/src/constants/anvil-errors.ts +7 -0
  51. package/vendor/@anvil/common/src/constants/anvil-gemini-thinker.ts +21 -0
  52. package/vendor/@anvil/common/src/constants/anvil-model-ids.ts +11 -0
  53. package/vendor/@anvil/common/src/constants/anvil-models.ts +2130 -0
  54. package/vendor/@anvil/common/src/constants/anvil-onboarding-gate.ts +55 -0
  55. package/vendor/@anvil/common/src/constants/anvil-onboarding.ts +307 -0
  56. package/vendor/@anvil/common/src/constants/anvil-referral-tiers.ts +87 -0
  57. package/vendor/@anvil/common/src/constants/anvil-signup-block.ts +68 -0
  58. package/vendor/@anvil/common/src/constants/anvil-spend-ceilings.ts +450 -0
  59. package/vendor/@anvil/common/src/constants/anvil-trust.ts +976 -0
  60. package/vendor/@anvil/common/src/constants/auth.ts +18 -0
  61. package/vendor/@anvil/common/src/constants/byok.ts +2 -0
  62. package/vendor/@anvil/common/src/constants/cf-worker-signals.ts +132 -0
  63. package/vendor/@anvil/common/src/constants/composio.ts +34 -0
  64. package/vendor/@anvil/common/src/constants/deepseek-direct.ts +55 -0
  65. package/vendor/@anvil/common/src/constants/feedback.ts +13 -0
  66. package/vendor/@anvil/common/src/constants/foreign-client-signals.ts +272 -0
  67. package/vendor/@anvil/common/src/constants/free-agents.ts +777 -0
  68. package/vendor/@anvil/common/src/constants/gemini.ts +15 -0
  69. package/vendor/@anvil/common/src/constants/grant-priorities.ts +13 -0
  70. package/vendor/@anvil/common/src/constants/hosts.ts +6 -0
  71. package/vendor/@anvil/common/src/constants/images.ts +51 -0
  72. package/vendor/@anvil/common/src/constants/index.ts +7 -0
  73. package/vendor/@anvil/common/src/constants/knowledge.ts +35 -0
  74. package/vendor/@anvil/common/src/constants/limits.ts +23 -0
  75. package/vendor/@anvil/common/src/constants/model-config.ts +277 -0
  76. package/vendor/@anvil/common/src/constants/openrouter-attribution.ts +8 -0
  77. package/vendor/@anvil/common/src/constants/paths.ts +69 -0
  78. package/vendor/@anvil/common/src/constants/provider-routes.ts +317 -0
  79. package/vendor/@anvil/common/src/constants/reasoning-effort.ts +79 -0
  80. package/vendor/@anvil/common/src/constants/skills.ts +60 -0
  81. package/vendor/@anvil/common/src/constants/subscription-plans.ts +49 -0
  82. package/vendor/@anvil/common/src/constants/ui.ts +25 -0
  83. package/vendor/@anvil/common/src/env-ci.ts +36 -0
  84. package/vendor/@anvil/common/src/env-process.ts +95 -0
  85. package/vendor/@anvil/common/src/env-schema.ts +95 -0
  86. package/vendor/@anvil/common/src/env.ts +24 -0
  87. package/vendor/@anvil/common/src/gravity-capi.ts +207 -0
  88. package/vendor/@anvil/common/src/mcp/client.ts +233 -0
  89. package/vendor/@anvil/common/src/old-constants.ts +10 -0
  90. package/vendor/@anvil/common/src/project-file-tree.ts +355 -0
  91. package/vendor/@anvil/common/src/reddit-capi.ts +254 -0
  92. package/vendor/@anvil/common/src/schemas/feedback.ts +52 -0
  93. package/vendor/@anvil/common/src/schemas/logs.ts +66 -0
  94. package/vendor/@anvil/common/src/templates/agent-validation.ts +392 -0
  95. package/vendor/@anvil/common/src/templates/initial-agents-dir/LICENSE +202 -0
  96. package/vendor/@anvil/common/src/templates/initial-agents-dir/README.md +294 -0
  97. package/vendor/@anvil/common/src/templates/initial-agents-dir/examples/01-basic-diff-reviewer.ts +17 -0
  98. package/vendor/@anvil/common/src/templates/initial-agents-dir/examples/02-intermediate-git-committer.ts +78 -0
  99. package/vendor/@anvil/common/src/templates/initial-agents-dir/examples/03-advanced-file-explorer.ts +73 -0
  100. package/vendor/@anvil/common/src/templates/initial-agents-dir/my-custom-agent.ts +40 -0
  101. package/vendor/@anvil/common/src/templates/initial-agents-dir/package.json +6 -0
  102. package/vendor/@anvil/common/src/templates/initial-agents-dir/skills/README.md +65 -0
  103. package/vendor/@anvil/common/src/templates/initial-agents-dir/skills/example-skill/SKILL.md +29 -0
  104. package/vendor/@anvil/common/src/templates/initial-agents-dir/types/agent-definition.ts +497 -0
  105. package/vendor/@anvil/common/src/templates/initial-agents-dir/types/tools.ts +444 -0
  106. package/vendor/@anvil/common/src/templates/initial-agents-dir/types/util-types.ts +178 -0
  107. package/vendor/@anvil/common/src/testing/TESTING_PATTERNS.md +351 -0
  108. package/vendor/@anvil/common/src/testing/anvil-offer-invariants.ts +169 -0
  109. package/vendor/@anvil/common/src/testing/errors.ts +33 -0
  110. package/vendor/@anvil/common/src/testing/fixtures/agent-runtime.ts +334 -0
  111. package/vendor/@anvil/common/src/testing/impl/agent-runtime.ts +6 -0
  112. package/vendor/@anvil/common/src/testing/index.ts +84 -0
  113. package/vendor/@anvil/common/src/testing/mock-modules.ts +53 -0
  114. package/vendor/@anvil/common/src/testing/mock-types.ts +123 -0
  115. package/vendor/@anvil/common/src/testing/mocks/analytics.ts +261 -0
  116. package/vendor/@anvil/common/src/testing/mocks/child-process.ts +93 -0
  117. package/vendor/@anvil/common/src/testing/mocks/crypto.ts +218 -0
  118. package/vendor/@anvil/common/src/testing/mocks/database.ts +337 -0
  119. package/vendor/@anvil/common/src/testing/mocks/fetch.ts +219 -0
  120. package/vendor/@anvil/common/src/testing/mocks/filesystem.ts +166 -0
  121. package/vendor/@anvil/common/src/testing/mocks/index.ts +101 -0
  122. package/vendor/@anvil/common/src/testing/mocks/logger.ts +135 -0
  123. package/vendor/@anvil/common/src/testing/mocks/stream.ts +313 -0
  124. package/vendor/@anvil/common/src/testing/mocks/timers.ts +132 -0
  125. package/vendor/@anvil/common/src/testing/mocks/tree-sitter.ts +127 -0
  126. package/vendor/@anvil/common/src/testing/setup.ts +282 -0
  127. package/vendor/@anvil/common/src/testing-env-ci.ts +15 -0
  128. package/vendor/@anvil/common/src/testing-env-process.ts +78 -0
  129. package/vendor/@anvil/common/src/tools/__tests__/compile-tool-definitions.test.ts +34 -0
  130. package/vendor/@anvil/common/src/tools/compile-tool-definitions.ts +157 -0
  131. package/vendor/@anvil/common/src/tools/constants.ts +117 -0
  132. package/vendor/@anvil/common/src/tools/list.ts +190 -0
  133. package/vendor/@anvil/common/src/tools/params/__tests__/coerce-to-array.test.ts +213 -0
  134. package/vendor/@anvil/common/src/tools/params/__tests__/read-docs.test.ts +47 -0
  135. package/vendor/@anvil/common/src/tools/params/__tests__/render-ui.test.ts +65 -0
  136. package/vendor/@anvil/common/src/tools/params/tool/__tests__/run-terminal-command-timeout.test.ts +49 -0
  137. package/vendor/@anvil/common/src/tools/params/tool/add-message.ts +39 -0
  138. package/vendor/@anvil/common/src/tools/params/tool/add-subgoal.ts +57 -0
  139. package/vendor/@anvil/common/src/tools/params/tool/apply-patch.ts +110 -0
  140. package/vendor/@anvil/common/src/tools/params/tool/ask-user.ts +181 -0
  141. package/vendor/@anvil/common/src/tools/params/tool/browser-logs.ts +85 -0
  142. package/vendor/@anvil/common/src/tools/params/tool/cloud-plan-ready.ts +94 -0
  143. package/vendor/@anvil/common/src/tools/params/tool/code-search.ts +159 -0
  144. package/vendor/@anvil/common/src/tools/params/tool/composio.ts +131 -0
  145. package/vendor/@anvil/common/src/tools/params/tool/create-plan.ts +80 -0
  146. package/vendor/@anvil/common/src/tools/params/tool/end-turn.ts +57 -0
  147. package/vendor/@anvil/common/src/tools/params/tool/find-files.ts +60 -0
  148. package/vendor/@anvil/common/src/tools/params/tool/glob.ts +80 -0
  149. package/vendor/@anvil/common/src/tools/params/tool/gravity-index.ts +93 -0
  150. package/vendor/@anvil/common/src/tools/params/tool/list-directory.ts +58 -0
  151. package/vendor/@anvil/common/src/tools/params/tool/lookup-agent-info.ts +37 -0
  152. package/vendor/@anvil/common/src/tools/params/tool/propose-str-replace.ts +103 -0
  153. package/vendor/@anvil/common/src/tools/params/tool/propose-write-file.ts +71 -0
  154. package/vendor/@anvil/common/src/tools/params/tool/read-docs.ts +90 -0
  155. package/vendor/@anvil/common/src/tools/params/tool/read-files.ts +109 -0
  156. package/vendor/@anvil/common/src/tools/params/tool/read-subtree.ts +79 -0
  157. package/vendor/@anvil/common/src/tools/params/tool/read-url.ts +81 -0
  158. package/vendor/@anvil/common/src/tools/params/tool/render-ui.ts +168 -0
  159. package/vendor/@anvil/common/src/tools/params/tool/run-file-change-hooks.ts +57 -0
  160. package/vendor/@anvil/common/src/tools/params/tool/run-terminal-command.ts +211 -0
  161. package/vendor/@anvil/common/src/tools/params/tool/screenshot.ts +97 -0
  162. package/vendor/@anvil/common/src/tools/params/tool/set-messages.ts +44 -0
  163. package/vendor/@anvil/common/src/tools/params/tool/set-output.ts +61 -0
  164. package/vendor/@anvil/common/src/tools/params/tool/skill.ts +59 -0
  165. package/vendor/@anvil/common/src/tools/params/tool/spawn-agent-inline.ts +56 -0
  166. package/vendor/@anvil/common/src/tools/params/tool/spawn-agents.ts +154 -0
  167. package/vendor/@anvil/common/src/tools/params/tool/str-replace.ts +107 -0
  168. package/vendor/@anvil/common/src/tools/params/tool/suggest-followups.ts +97 -0
  169. package/vendor/@anvil/common/src/tools/params/tool/task-completed.ts +61 -0
  170. package/vendor/@anvil/common/src/tools/params/tool/think-deeply.ts +56 -0
  171. package/vendor/@anvil/common/src/tools/params/tool/update-subgoal.ts +89 -0
  172. package/vendor/@anvil/common/src/tools/params/tool/vision-analyze.ts +82 -0
  173. package/vendor/@anvil/common/src/tools/params/tool/web-search.ts +73 -0
  174. package/vendor/@anvil/common/src/tools/params/tool/write-file.ts +71 -0
  175. package/vendor/@anvil/common/src/tools/params/tool/write-todos.ts +67 -0
  176. package/vendor/@anvil/common/src/tools/params/utils.ts +150 -0
  177. package/vendor/@anvil/common/src/tools/utils.ts +24 -0
  178. package/vendor/@anvil/common/src/types/__tests__/dynamic-agent-template.test.ts +20 -0
  179. package/vendor/@anvil/common/src/types/agent-template.ts +215 -0
  180. package/vendor/@anvil/common/src/types/anvil-session.ts +535 -0
  181. package/vendor/@anvil/common/src/types/anvil-streak.ts +6 -0
  182. package/vendor/@anvil/common/src/types/anvil-usage.ts +64 -0
  183. package/vendor/@anvil/common/src/types/api/agents/publish.ts +61 -0
  184. package/vendor/@anvil/common/src/types/bun-test.d.ts +5 -0
  185. package/vendor/@anvil/common/src/types/contracts/agent-runtime.ts +75 -0
  186. package/vendor/@anvil/common/src/types/contracts/analytics.ts +9 -0
  187. package/vendor/@anvil/common/src/types/contracts/bigquery.ts +55 -0
  188. package/vendor/@anvil/common/src/types/contracts/billing.ts +46 -0
  189. package/vendor/@anvil/common/src/types/contracts/client.ts +53 -0
  190. package/vendor/@anvil/common/src/types/contracts/database.ts +112 -0
  191. package/vendor/@anvil/common/src/types/contracts/env.ts +203 -0
  192. package/vendor/@anvil/common/src/types/contracts/llm.ts +191 -0
  193. package/vendor/@anvil/common/src/types/contracts/logger.ts +14 -0
  194. package/vendor/@anvil/common/src/types/contracts/logs.ts +34 -0
  195. package/vendor/@anvil/common/src/types/contracts/trace.ts +21 -0
  196. package/vendor/@anvil/common/src/types/dynamic-agent-template.ts +328 -0
  197. package/vendor/@anvil/common/src/types/filesystem.ts +10 -0
  198. package/vendor/@anvil/common/src/types/function-params.ts +33 -0
  199. package/vendor/@anvil/common/src/types/grant.ts +20 -0
  200. package/vendor/@anvil/common/src/types/gravity-index.ts +170 -0
  201. package/vendor/@anvil/common/src/types/json.ts +29 -0
  202. package/vendor/@anvil/common/src/types/mcp.ts +24 -0
  203. package/vendor/@anvil/common/src/types/messages/anvil-message.ts +58 -0
  204. package/vendor/@anvil/common/src/types/messages/content-part.ts +59 -0
  205. package/vendor/@anvil/common/src/types/messages/data-content.ts +14 -0
  206. package/vendor/@anvil/common/src/types/messages/provider-metadata.ts +13 -0
  207. package/vendor/@anvil/common/src/types/organization.ts +118 -0
  208. package/vendor/@anvil/common/src/types/print-mode.ts +121 -0
  209. package/vendor/@anvil/common/src/types/publisher.ts +67 -0
  210. package/vendor/@anvil/common/src/types/session-state.ts +149 -0
  211. package/vendor/@anvil/common/src/types/skill.ts +86 -0
  212. package/vendor/@anvil/common/src/types/source.ts +11 -0
  213. package/vendor/@anvil/common/src/types/spawn.ts +13 -0
  214. package/vendor/@anvil/common/src/types/subscription.ts +67 -0
  215. package/vendor/@anvil/common/src/types/usage.ts +16 -0
  216. package/vendor/@anvil/common/src/types/util.ts +3 -0
  217. package/vendor/@anvil/common/src/util/__tests__/ad-user-agent.test.ts +26 -0
  218. package/vendor/@anvil/common/src/util/__tests__/analytics-dispatcher.test.ts +122 -0
  219. package/vendor/@anvil/common/src/util/__tests__/analytics-log.test.ts +102 -0
  220. package/vendor/@anvil/common/src/util/__tests__/analytics-sampling.test.ts +143 -0
  221. package/vendor/@anvil/common/src/util/__tests__/anvil-streak.test.ts +202 -0
  222. package/vendor/@anvil/common/src/util/__tests__/anvil-usage-summary.test.ts +193 -0
  223. package/vendor/@anvil/common/src/util/__tests__/axiom-only-log.test.ts +140 -0
  224. package/vendor/@anvil/common/src/util/__tests__/client-user-agent.test.ts +148 -0
  225. package/vendor/@anvil/common/src/util/__tests__/engagement-tracker.test.ts +115 -0
  226. package/vendor/@anvil/common/src/util/__tests__/env-file-path.test.ts +57 -0
  227. package/vendor/@anvil/common/src/util/__tests__/error-abort.test.ts +774 -0
  228. package/vendor/@anvil/common/src/util/__tests__/error-api-details.test.ts +206 -0
  229. package/vendor/@anvil/common/src/util/__tests__/file-read-limits.test.ts +173 -0
  230. package/vendor/@anvil/common/src/util/__tests__/format-code-search.test.ts +60 -0
  231. package/vendor/@anvil/common/src/util/__tests__/log-mirror.test.ts +31 -0
  232. package/vendor/@anvil/common/src/util/__tests__/messages.test.ts +1252 -0
  233. package/vendor/@anvil/common/src/util/__tests__/partial-json-delta.test.ts +505 -0
  234. package/vendor/@anvil/common/src/util/__tests__/path.test.ts +22 -0
  235. package/vendor/@anvil/common/src/util/__tests__/project-ignore.test.ts +90 -0
  236. package/vendor/@anvil/common/src/util/__tests__/promise.test.ts +325 -0
  237. package/vendor/@anvil/common/src/util/__tests__/rate-limit.test.ts +28 -0
  238. package/vendor/@anvil/common/src/util/__tests__/reddit-anvil-retention.test.ts +130 -0
  239. package/vendor/@anvil/common/src/util/__tests__/saxy.test.ts +1008 -0
  240. package/vendor/@anvil/common/src/util/__tests__/split-data.test.ts +289 -0
  241. package/vendor/@anvil/common/src/util/__tests__/string.test.ts +239 -0
  242. package/vendor/@anvil/common/src/util/__tests__/thread-title.test.ts +70 -0
  243. package/vendor/@anvil/common/src/util/__tests__/tool-result-media-order.test.ts +108 -0
  244. package/vendor/@anvil/common/src/util/__tests__/ttft-histogram.test.ts +180 -0
  245. package/vendor/@anvil/common/src/util/__tests__/with-timeout.test.ts +24 -0
  246. package/vendor/@anvil/common/src/util/__tests__/zoned-time.test.ts +88 -0
  247. package/vendor/@anvil/common/src/util/ad-user-agent.ts +25 -0
  248. package/vendor/@anvil/common/src/util/agent-file-utils.ts +110 -0
  249. package/vendor/@anvil/common/src/util/agent-id-parsing.ts +136 -0
  250. package/vendor/@anvil/common/src/util/agent-name-normalization.ts +38 -0
  251. package/vendor/@anvil/common/src/util/agent-name-resolver.ts +86 -0
  252. package/vendor/@anvil/common/src/util/analytics-dispatcher.ts +82 -0
  253. package/vendor/@anvil/common/src/util/analytics-log.ts +78 -0
  254. package/vendor/@anvil/common/src/util/analytics-sampling.ts +255 -0
  255. package/vendor/@anvil/common/src/util/anvil-model-availability.ts +102 -0
  256. package/vendor/@anvil/common/src/util/anvil-privacy.ts +82 -0
  257. package/vendor/@anvil/common/src/util/anvil-streak-line.ts +103 -0
  258. package/vendor/@anvil/common/src/util/anvil-streak.ts +156 -0
  259. package/vendor/@anvil/common/src/util/anvil-usage-summary.ts +125 -0
  260. package/vendor/@anvil/common/src/util/array.ts +31 -0
  261. package/vendor/@anvil/common/src/util/axiom-only-log.ts +124 -0
  262. package/vendor/@anvil/common/src/util/cache-debug.ts +171 -0
  263. package/vendor/@anvil/common/src/util/client-user-agent.ts +114 -0
  264. package/vendor/@anvil/common/src/util/credentials.ts +26 -0
  265. package/vendor/@anvil/common/src/util/currency.ts +25 -0
  266. package/vendor/@anvil/common/src/util/dates.ts +81 -0
  267. package/vendor/@anvil/common/src/util/disposable-email.ts +277 -0
  268. package/vendor/@anvil/common/src/util/engagement-tracker.ts +129 -0
  269. package/vendor/@anvil/common/src/util/env-file-path.ts +40 -0
  270. package/vendor/@anvil/common/src/util/error.ts +562 -0
  271. package/vendor/@anvil/common/src/util/file-read-limits.ts +236 -0
  272. package/vendor/@anvil/common/src/util/file.ts +339 -0
  273. package/vendor/@anvil/common/src/util/format-code-search.ts +115 -0
  274. package/vendor/@anvil/common/src/util/lazy-response-ads.ts +92 -0
  275. package/vendor/@anvil/common/src/util/log-data.ts +57 -0
  276. package/vendor/@anvil/common/src/util/log-ingest.ts +58 -0
  277. package/vendor/@anvil/common/src/util/log-mirror.ts +34 -0
  278. package/vendor/@anvil/common/src/util/lru-cache.ts +67 -0
  279. package/vendor/@anvil/common/src/util/messages.ts +685 -0
  280. package/vendor/@anvil/common/src/util/min-heap.ts +87 -0
  281. package/vendor/@anvil/common/src/util/model-utils.ts +4 -0
  282. package/vendor/@anvil/common/src/util/object.ts +128 -0
  283. package/vendor/@anvil/common/src/util/partial-json-delta.ts +89 -0
  284. package/vendor/@anvil/common/src/util/path.ts +15 -0
  285. package/vendor/@anvil/common/src/util/project-ignore.ts +119 -0
  286. package/vendor/@anvil/common/src/util/promise.ts +73 -0
  287. package/vendor/@anvil/common/src/util/random.ts +15 -0
  288. package/vendor/@anvil/common/src/util/rate-limit.ts +56 -0
  289. package/vendor/@anvil/common/src/util/reddit-anvil-retention.ts +60 -0
  290. package/vendor/@anvil/common/src/util/reddit-capi-events.ts +36 -0
  291. package/vendor/@anvil/common/src/util/response-ad-positions.ts +54 -0
  292. package/vendor/@anvil/common/src/util/saxy.ts +741 -0
  293. package/vendor/@anvil/common/src/util/skills.test.ts +44 -0
  294. package/vendor/@anvil/common/src/util/skills.ts +36 -0
  295. package/vendor/@anvil/common/src/util/split-data.ts +259 -0
  296. package/vendor/@anvil/common/src/util/stop-sequence.ts +61 -0
  297. package/vendor/@anvil/common/src/util/string.ts +426 -0
  298. package/vendor/@anvil/common/src/util/system-info.ts +53 -0
  299. package/vendor/@anvil/common/src/util/thread-title.ts +46 -0
  300. package/vendor/@anvil/common/src/util/ttft-histogram.ts +73 -0
  301. package/vendor/@anvil/common/src/util/xml-parser.ts +27 -0
  302. package/vendor/@anvil/common/src/util/xml.ts +17 -0
  303. package/vendor/@anvil/common/src/util/zod-schema.ts +25 -0
  304. package/vendor/@anvil/common/src/util/zoned-time.ts +136 -0
  305. package/vendor/@anvil/common/src/utils/ask-user-bridge.ts +44 -0
  306. package/vendor/@anvil/sdk/dist/index.cjs +52297 -0
  307. package/vendor/@anvil/sdk/dist/index.cjs.map +440 -0
  308. package/vendor/@anvil/sdk/dist/index.d.ts +4277 -0
  309. package/vendor/@anvil/sdk/dist/index.mjs +52290 -0
  310. package/vendor/@anvil/sdk/dist/index.mjs.map +440 -0
  311. package/vendor/@anvil/sdk/dist/vendor/ai.cjs +30886 -0
  312. package/vendor/@anvil/sdk/dist/vendor/ripgrep/arm64-darwin/rg +0 -0
  313. package/vendor/@anvil/sdk/dist/vendor/ripgrep/arm64-linux/rg +0 -0
  314. package/vendor/@anvil/sdk/dist/vendor/ripgrep/x64-darwin/rg +0 -0
  315. package/vendor/@anvil/sdk/dist/vendor/ripgrep/x64-linux/rg +0 -0
  316. package/vendor/@anvil/sdk/dist/vendor/ripgrep/x64-win32/rg.exe +0 -0
  317. package/vendor/@anvil/sdk/dist/wasm/tree-sitter-c-sharp.wasm +0 -0
  318. package/vendor/@anvil/sdk/dist/wasm/tree-sitter-cpp.wasm +0 -0
  319. package/vendor/@anvil/sdk/dist/wasm/tree-sitter-go.wasm +0 -0
  320. package/vendor/@anvil/sdk/dist/wasm/tree-sitter-java.wasm +0 -0
  321. package/vendor/@anvil/sdk/dist/wasm/tree-sitter-javascript.wasm +0 -0
  322. package/vendor/@anvil/sdk/dist/wasm/tree-sitter-python.wasm +0 -0
  323. package/vendor/@anvil/sdk/dist/wasm/tree-sitter-ruby.wasm +0 -0
  324. package/vendor/@anvil/sdk/dist/wasm/tree-sitter-rust.wasm +0 -0
  325. package/vendor/@anvil/sdk/dist/wasm/tree-sitter-tsx.wasm +0 -0
  326. package/vendor/@anvil/sdk/dist/wasm/tree-sitter-typescript.wasm +0 -0
  327. package/vendor/@anvil/sdk/dist/wasm/tree-sitter.wasm +0 -0
  328. package/vendor/@anvil/sdk/package.json +21 -0
@@ -0,0 +1,18 @@
1
+ /**
2
+ * How long a device-code sign-in stays valid.
3
+ *
4
+ * Shared because two apps issue these codes — codebuff.com serves the CLI and anvil.com serves
5
+ * Anvil Desktop — and a drift between them would show up only as one surface timing out sooner
6
+ * than the other, with nothing to point at.
7
+ *
8
+ * The `/api/auth/cli/code` response sends this as `expiresInMs` **alongside** the absolute
9
+ * `expiresAt`, and they are not redundant:
10
+ *
11
+ * - `expiresAt` is an instant on the SERVER's clock. It has to stay: it is the HMAC input the
12
+ * status endpoint verifies, so clients must echo it back byte-for-byte.
13
+ * - `expiresInMs` is a duration, which means nothing about it depends on the client's clock. A
14
+ * client that subtracts `expiresAt` from its own `Date.now()` is silently asking every user's
15
+ * system clock to be right — and a machine running an hour fast rejects every code it is ever
16
+ * issued, permanently, with a 200 in our logs. That was a real, unfixable-by-reinstalling bug.
17
+ */
18
+ export const CLI_AUTH_CODE_LIFETIME_MS = 60 * 60 * 1000
@@ -0,0 +1,2 @@
1
+ export const BYOK_OPENROUTER_HEADER = 'x-openrouter-api-key'
2
+ export const BYOK_OPENROUTER_ENV_VAR = 'ANVIL_BYOK_OPENROUTER'
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Cloudflare Worker egress detection.
3
+ *
4
+ * What this catches
5
+ * -----------------
6
+ * `pingmike2/anvil2api-wokers` and its relatives run as a Cloudflare Worker
7
+ * that pools harvested account tokens and resells free mode as an
8
+ * OpenAI/Anthropic-compatible endpoint. Cloudflare stamps `CF-Worker` onto every
9
+ * outbound subrequest a Worker makes, naming the Worker's zone, and Worker code
10
+ * cannot remove it — the edge adds it after `fetch()` returns to the runtime.
11
+ * The published source never references the header at all.
12
+ *
13
+ * Why this signal is different from the ones that went wrong before
14
+ * ----------------------------------------------------------------
15
+ * Every previous third-party-client detector keyed on something the CLIENT
16
+ * chooses: `fingerprint_id` (self-reported), the tool list (they inject
17
+ * `end_turn` to pass), the system prompt (they now send our canonical opening
18
+ * byte-for-byte). Each was defeated within days, and one produced a
19
+ * 659-account false-positive ban that had to be fully reversed.
20
+ *
21
+ * This one is stamped by OUR edge, on the request, describing the infrastructure
22
+ * that sent it. A caller cannot remove it, and adding it falsely only implicates
23
+ * themselves. That is what makes it safe enough to action automatically — and
24
+ * the reason every other signal here is still only a lead.
25
+ *
26
+ * The three ways it could still be wrong, and what each costs
27
+ * ----------------------------------------------------------
28
+ * 1. **Our own infrastructure calls our own API from a Worker.** Real: the
29
+ * app-preview-proxy is a Cloudflare Worker. Handled by never actioning
30
+ * unmetered service accounts, and by the zone allowlist.
31
+ * 2. **Cloudflare does not strip an inbound `cf-worker` from an ordinary
32
+ * client.** Then anyone can send it — but only on their OWN authenticated
33
+ * request, so they can only get themselves actioned. It is not a vector for
34
+ * getting someone else banned.
35
+ * 3. **A user sits behind a corporate proxy implemented on Workers.** Rare but
36
+ * not impossible, and the reason `ban` is not the default mode.
37
+ *
38
+ * Nothing here fires on paid or BYOK traffic. The concern is subsidised
39
+ * free-mode capacity being resold, and a paying customer routing through their
40
+ * own Worker is doing nothing wrong.
41
+ */
42
+
43
+ /** Header Cloudflare stamps on subrequests originating from a Worker. */
44
+ export const CF_WORKER_HEADER = 'cf-worker'
45
+
46
+ /**
47
+ * Corroborates that the request actually traversed our edge. Without it we are
48
+ * reading a header an arbitrary caller wrote on a request that reached the
49
+ * origin some other way, which is not the same claim at all.
50
+ */
51
+ export const CF_RAY_HEADER = 'cf-ray'
52
+
53
+ export type CfWorkerMode = 'off' | 'observe' | 'block' | 'ban'
54
+
55
+ export type CfWorkerVerdict =
56
+ | { detected: false; reason: 'no_header' | 'not_edge_verified' | 'allowlisted' }
57
+ | { detected: true; zone: string }
58
+
59
+ export type CfWorkerDetectInput = {
60
+ /** Raw `cf-worker` value, or null/undefined when absent. */
61
+ cfWorkerHeader: string | null | undefined
62
+ /** Raw `cf-ray` value. Absent means the request did not come through our
63
+ * Cloudflare edge, so the `cf-worker` value is not ours to trust. */
64
+ cfRayHeader: string | null | undefined
65
+ /** Worker zones operated by us (app-preview-proxy and friends). */
66
+ allowedZones: ReadonlySet<string>
67
+ }
68
+
69
+ /**
70
+ * Pure detection. No I/O, so the exact decision can be replayed offline when
71
+ * auditing a ban — a verdict that cannot be reproduced cannot be defended.
72
+ */
73
+ export function detectCfWorker(input: CfWorkerDetectInput): CfWorkerVerdict {
74
+ const raw = input.cfWorkerHeader?.trim()
75
+ if (!raw) return { detected: false, reason: 'no_header' }
76
+
77
+ // Require edge corroboration. A `cf-worker` on a request that never touched
78
+ // Cloudflare is caller-authored noise, and treating it as proof would make
79
+ // this detector exactly as forgeable as the ones it replaces.
80
+ if (!input.cfRayHeader?.trim()) {
81
+ return { detected: false, reason: 'not_edge_verified' }
82
+ }
83
+
84
+ const zone = raw.toLowerCase()
85
+ if (input.allowedZones.has(zone)) {
86
+ return { detected: false, reason: 'allowlisted' }
87
+ }
88
+
89
+ return { detected: true, zone }
90
+ }
91
+
92
+ /**
93
+ * Evidence recorded with every automated action, and shown verbatim in the ban
94
+ * review dashboard.
95
+ *
96
+ * Deliberately everything needed to re-derive the verdict without the original
97
+ * request: the zone, the edge ray id (so the request can be found in Cloudflare
98
+ * logs), and the calling client's self-description. A reviewer who cannot see
99
+ * why an account was banned cannot tell a true positive from a false one, which
100
+ * is how the 2026-08-03 sweep ended up reversed in full.
101
+ */
102
+ export type CfWorkerEvidence = {
103
+ zone: string
104
+ cfRay: string | null
105
+ /** `anvil_metadata.client_id`. The published proxy sends `wf-` + 8 base36
106
+ * characters; our own clients send a different shape entirely. */
107
+ clientId: string | null
108
+ userAgent: string | null
109
+ model: string | null
110
+ agentId: string | null
111
+ endpoint: string
112
+ detectedAt: string
113
+ }
114
+
115
+ /** Parse the operator-configured allowlist of our own Worker zones. */
116
+ export function parseAllowedWorkerZones(
117
+ raw: string | null | undefined,
118
+ ): ReadonlySet<string> {
119
+ return new Set(
120
+ (raw ?? '')
121
+ .split(',')
122
+ .map((zone) => zone.trim().toLowerCase())
123
+ .filter((zone) => zone.length > 0),
124
+ )
125
+ }
126
+
127
+ /** A caller-supplied `client_id` matching the published proxy's generator.
128
+ * Corroboration only — never a reason to action on its own, because it is
129
+ * client-controlled and therefore trivially changed the moment it is used. */
130
+ export function looksLikeProxyClientId(clientId: string | null | undefined): boolean {
131
+ return typeof clientId === 'string' && /^wf-[a-z0-9]{8}$/.test(clientId)
132
+ }
@@ -0,0 +1,34 @@
1
+ export const COMPOSIO_API_KEY_ENV_VAR = 'COMPOSIO_API_KEY'
2
+
3
+ export const COMPOSIO_META_TOOL_NAMES = [
4
+ 'composio_manage_connections',
5
+ 'composio_multi_execute_tool',
6
+ 'composio_search_tools',
7
+ 'composio_get_tool_schemas',
8
+ ] as const
9
+
10
+ export type ComposioMetaToolName = (typeof COMPOSIO_META_TOOL_NAMES)[number]
11
+
12
+ export const COMPOSIO_META_TOOL_NAME_TO_UPSTREAM = {
13
+ composio_manage_connections: 'COMPOSIO_MANAGE_CONNECTIONS',
14
+ composio_multi_execute_tool: 'COMPOSIO_MULTI_EXECUTE_TOOL',
15
+ composio_search_tools: 'COMPOSIO_SEARCH_TOOLS',
16
+ composio_get_tool_schemas: 'COMPOSIO_GET_TOOL_SCHEMAS',
17
+ } as const satisfies Record<ComposioMetaToolName, string>
18
+
19
+ export type ComposioUpstreamMetaToolName =
20
+ (typeof COMPOSIO_META_TOOL_NAME_TO_UPSTREAM)[ComposioMetaToolName]
21
+
22
+ const COMPOSIO_META_TOOL_NAME_SET = new Set<string>(COMPOSIO_META_TOOL_NAMES)
23
+
24
+ export function isComposioMetaToolName(
25
+ toolName: string,
26
+ ): toolName is ComposioMetaToolName {
27
+ return COMPOSIO_META_TOOL_NAME_SET.has(toolName)
28
+ }
29
+
30
+ export function getComposioUpstreamToolName(
31
+ toolName: ComposioMetaToolName,
32
+ ): ComposioUpstreamMetaToolName {
33
+ return COMPOSIO_META_TOOL_NAME_TO_UPSTREAM[toolName]
34
+ }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Direct-to-DeepSeek mode ("own backend").
3
+ *
4
+ * When `DEEPSEEK_API_KEY` is set, the SDK routes model calls straight to
5
+ * DeepSeek's OpenAI-compatible API instead of the Codebuff backend, and the
6
+ * CLI bypasses backend auth/session gates with a local identity. When unset,
7
+ * every path behaves exactly as before (Codebuff backend).
8
+ *
9
+ * `DEEPSEEK_MODEL` optionally overrides the model ID sent to DeepSeek. Without
10
+ * it, DeepSeek's API serves exactly two model names — `deepseek-v4-pro` and
11
+ * `deepseek-v4-flash` (a request for anything else is rejected with "The
12
+ * supported API model names are deepseek-v4-pro or deepseek-v4-flash"). So:
13
+ *
14
+ * - `deepseek/…` lane ids keep their model name (`deepseek/deepseek-v4-pro`
15
+ * → `deepseek-v4-pro`), with the `-max` context tiers normalized to their
16
+ * base model and the legacy fireworks lane mapped to flash.
17
+ * - Every other provider id (anthropic/*, openai/*, google/*, …) cannot be
18
+ * served by DeepSeek at all and falls back to the default DeepSeek model.
19
+ */
20
+
21
+ export const DEEPSEEK_BASE_URL = 'https://api.deepseek.com'
22
+
23
+ /** Model name sent to DeepSeek for ids that are not a DeepSeek lane model. */
24
+ export const DEFAULT_DIRECT_DEEPSEEK_MODEL = 'deepseek-v4-pro'
25
+
26
+ /** The user's DeepSeek API key, read from the runtime environment. */
27
+ export const getDirectDeepSeekApiKey = (): string | undefined =>
28
+ process.env.DEEPSEEK_API_KEY
29
+
30
+ /** True when the CLI/SDK should talk to DeepSeek directly instead of Codebuff. */
31
+ export const isDirectDeepSeekMode = (): boolean =>
32
+ Boolean(getDirectDeepSeekApiKey())
33
+
34
+ /** Map a Codebuff model ID to the model name DeepSeek's API accepts. */
35
+ export function remapModelForDirectDeepSeek(model: string): string {
36
+ const override = process.env.DEEPSEEK_MODEL
37
+ if (override) return override
38
+
39
+ // DeepSeek lane ids: keep the model name, normalizing the -max context tiers
40
+ // and the legacy fireworks lane to ids the API actually serves.
41
+ if (model.startsWith('deepseek/')) {
42
+ const name = model.slice('deepseek/'.length)
43
+ if (name === 'deepseek-v4-pro' || name === 'deepseek-v4-flash') {
44
+ return name
45
+ }
46
+ if (name === 'deepseek-v4-pro-max') return 'deepseek-v4-pro'
47
+ if (name === 'deepseek-v4-flash-max') return 'deepseek-v4-flash'
48
+ return DEFAULT_DIRECT_DEEPSEEK_MODEL
49
+ }
50
+ if (model === 'fireworks/deepseek-v4-flash') return 'deepseek-v4-flash'
51
+
52
+ // Any other provider (anthropic, openai, google, …) has no DeepSeek
53
+ // equivalent — fall back so the request is never rejected for the model name.
54
+ return DEFAULT_DIRECT_DEEPSEEK_MODEL
55
+ }
@@ -0,0 +1,13 @@
1
+ export const FEEDBACK_CATEGORIES = ['good_result', 'bad_result', 'app_bug', 'other'] as const
2
+ export type FeedbackCategory = (typeof FEEDBACK_CATEGORIES)[number]
3
+
4
+ export const FEEDBACK_SOURCES = ['cli', 'desktop', 'sdk', 'web'] as const
5
+ export type FeedbackSource = (typeof FEEDBACK_SOURCES)[number]
6
+
7
+ export const MESSAGE_VARIANTS = ['ai', 'user', 'agent', 'error'] as const
8
+ export type MessageVariant = (typeof MESSAGE_VARIANTS)[number]
9
+
10
+ export const MAX_RECENT_MESSAGES = 10
11
+ export const MAX_ERRORS = 50
12
+ export const MAX_ERROR_MESSAGE_LENGTH = 2000
13
+ export const MAX_ERROR_ID_LENGTH = 200
@@ -0,0 +1,272 @@
1
+ import { toolNames } from '../tools/constants'
2
+
3
+ /**
4
+ * Where a free-mode request goes when it did not come from a anvil client.
5
+ *
6
+ * OpenRouter's `:free` variant, so a downgraded request costs nothing upstream
7
+ * — which is the point. A caller proxying our free endpoint into their own
8
+ * harness is spending our inference budget; serving them a free model spends
9
+ * none of it. `getChatCompletionsProvider` has no branch for this slug and
10
+ * falls through to `openrouter`, so nothing else needs to know about it.
11
+ *
12
+ * Verified against the OpenRouter catalog on 2026-08-08: 262k context, $0
13
+ * prompt and completion, and `tools` + `tool_choice` in supported_parameters —
14
+ * so a downgraded tool-calling request degrades rather than hard-erroring.
15
+ */
16
+ export const ANVIL_DOWNGRADE_MODEL_ID = 'inclusionai/ling-3.0-tiny:free'
17
+
18
+ /**
19
+ * Tool names we define that other agent harnesses also ship.
20
+ *
21
+ * Maintained as an EXCLUSION list, with the signature derived from it, because
22
+ * the inclusion list rotted: `researcher-web` offers exactly
23
+ * `['web_search', 'read_url']`, and a hand-picked signature that happened to
24
+ * omit both flagged 100% of its 334,042 requests from 4,821 users over 30 days.
25
+ * Any tool added to `toolNames` now joins the signature automatically, so the
26
+ * failure mode is a new *generic* name we forget to list here — which flags a
27
+ * third party we could already flag, rather than silently downgrading our own
28
+ * users.
29
+ *
30
+ * Each entry carries the third-party usage that justifies it, so this stays
31
+ * evidence rather than superstition.
32
+ */
33
+ export const GENERIC_TOOL_NAMES: ReadonlySet<string> = new Set([
34
+ // Counts are distinct users, over 30 days, on requests carrying NO signature
35
+ // tool at all — i.e. unambiguously third-party harnesses. Anything without
36
+ // that evidence belongs in the signature: excluding a name we define costs us
37
+ // nothing against proxies and risks flagging whichever agent of ours uses it
38
+ // alone, which is exactly how researcher-web broke.
39
+ 'write_file', // 3,372 users (Cline)
40
+ 'web_search', // 3,273 users (opencode)
41
+ 'glob', // 2,691 users (opencode, Claude Code ships `Glob`)
42
+ 'skill', // 2,257 users
43
+ 'apply_patch', // 1,137 users (Codex)
44
+ ])
45
+
46
+ /**
47
+ * Tools our own surfaces define outside `toolNames`, via
48
+ * `customToolDefinitions`. Anvil Desktop's autorun agent
49
+ * (anvil-desktop/src/server/services/autorun.ts) offers exactly `decide` and
50
+ * nothing else, so without this it had no signature tool at all and was flagged
51
+ * on 100% of its 2,904 requests from 41 users over 30 days.
52
+ */
53
+ export const ANVIL_CUSTOM_TOOL_NAMES = ['decide'] as const
54
+
55
+ /**
56
+ * Tool names that, on their own, mark a request as coming from one of our
57
+ * clients: everything we define that is not generic.
58
+ *
59
+ * The discriminator is the tool schema rather than the system prompt because
60
+ * the two are attacker-controlled in very different ways. A system prompt is
61
+ * free to copy — ours ships in the CLI and is recoverable from any response —
62
+ * so a prompt check is a speed bump. Tool schemas are not free to copy: a
63
+ * harness dispatches on the tool name the model returns, so sending ours means
64
+ * also executing ours and speaking our result format. Evading this check
65
+ * converges on behaving like a real client, which is the outcome we want.
66
+ */
67
+ export const ANVIL_SIGNATURE_TOOL_NAMES: ReadonlySet<string> = new Set([
68
+ ...(toolNames as readonly string[]).filter(
69
+ (name) => !GENERIC_TOOL_NAMES.has(name),
70
+ ),
71
+ ...ANVIL_CUSTOM_TOOL_NAMES,
72
+ ])
73
+
74
+ export type ForeignClientSignal =
75
+ | 'foreign_toolset'
76
+ | 'root_agent_no_tools'
77
+ | 'sampling_params'
78
+
79
+ export type ForeignClientVerdict = {
80
+ /** Null when the request looks like it came from one of our clients. */
81
+ signal: ForeignClientSignal | null
82
+ toolCount: number
83
+ /** A few offered tool names, for the log line. Bounded so logs stay small. */
84
+ sampleToolNames: string[]
85
+ }
86
+
87
+ type InspectableRequest = {
88
+ tools?: unknown
89
+ temperature?: unknown
90
+ top_p?: unknown
91
+ max_tokens?: unknown
92
+ max_completion_tokens?: unknown
93
+ }
94
+
95
+ /** Longest tool name kept for the log line. Names are caller-controlled, so an
96
+ * untruncated one is a log-flood vector; nothing legitimate is near this. */
97
+ const MAX_LOGGED_TOOL_NAME_LENGTH = 64
98
+
99
+ function readToolNames(tools: unknown): string[] {
100
+ if (!Array.isArray(tools)) return []
101
+ return tools
102
+ .map((tool) =>
103
+ typeof tool === 'object' && tool !== null
104
+ ? (tool as { function?: { name?: unknown } }).function?.name
105
+ : undefined,
106
+ )
107
+ .filter((name): name is string => typeof name === 'string')
108
+ }
109
+
110
+ /**
111
+ * Whether a free-mode request came from something other than a anvil client.
112
+ *
113
+ * Three signals, checked in a deliberate order:
114
+ *
115
+ * 1. The request offers tools and not one of them is distinctively ours.
116
+ * Measured over 24h of DeepSeek V4 Flash traffic: 557 users / 75,741
117
+ * requests.
118
+ * 2. The request offers NO tools and the agent is one of our roots, which are
119
+ * agentic by definition — a caller using a root agent id as a bare
120
+ * completion endpoint. Reported only; never enforced.
121
+ * 3. The request offers no tools and sets `temperature`, `top_p` or
122
+ * `max_tokens`. Our clients leave all three unset on 99.2% of requests.
123
+ * Reported only; never enforced.
124
+ *
125
+ * Signal 1 wins outright when it clears the request, and that ordering is the
126
+ * whole safety story rather than a detail: 16 users in the same window send our
127
+ * toolset *and* set sampling params (2,673 requests). Checking params first, or
128
+ * checking them independently, would downgrade those users. Anyone sending our
129
+ * tools is one of ours no matter what else the body says.
130
+ */
131
+ export function detectForeignAnvilClient(
132
+ body: InspectableRequest,
133
+ /** The resolved agent id, when the caller has it. Root agents are agentic by
134
+ * definition, so one that offers no tools is not being driven by our client
135
+ * — see `root_agent_no_tools` below. */
136
+ isRootAgent = false,
137
+ ): ForeignClientVerdict {
138
+ const offered = readToolNames(body.tools)
139
+ const sampleToolNames = offered
140
+ .slice(0, 8)
141
+ .map((name) => name.slice(0, MAX_LOGGED_TOOL_NAME_LENGTH))
142
+
143
+ if (offered.length > 0) {
144
+ const hasSignatureTool = offered.some((name) =>
145
+ ANVIL_SIGNATURE_TOOL_NAMES.has(name),
146
+ )
147
+ return {
148
+ signal: hasSignatureTool ? null : 'foreign_toolset',
149
+ toolCount: offered.length,
150
+ sampleToolNames,
151
+ }
152
+ }
153
+
154
+ // A ROOT agent that offers no tools at all. Our roots always ship their
155
+ // toolset — the CI guard in foreign-client-shipped-agents.test.ts enforces
156
+ // that — so on its face this is a caller driving one of our root agent ids as
157
+ // a bare completion endpoint.
158
+ //
159
+ // An early hand-sample of 18 such users came back 18-for-18 non-coding
160
+ // automation — Shopee customer-service bots, Solana memecoin traders, RAG
161
+ // rephrasers, benchmark probes. DO NOT cite that as evidence for enforcing.
162
+ // It was drawn from a tail of roughly 5,000 users and the cohort analysis
163
+ // below shows it was not representative: 90% of the accounts the signal names
164
+ // also do real agentic work. An 18-for-18 result from an unrepresentative
165
+ // frame is what a biased sample looks like, not a strong one.
166
+ //
167
+ // REPORTED, NEVER ENFORCED, per the full 30-day backtest. Counting only root
168
+ // agents and excluding the paired
169
+ // assistant-response rows (they carry no tools by design and are half of all
170
+ // rows — miss that and every agent reads ~50% tool-free):
171
+ //
172
+ // base2-free-deepseek 4.46% 215,777 reqs 2,672 users
173
+ // base2-free-deepseek-flash 0.296% 103,726 reqs 2,281 users
174
+ // base2-free 0.90% 222 reqs 15 users
175
+ // anvil-desktop-thread-local 0.018% 2,400 reqs 11 users
176
+ //
177
+ // Only 417 of those users are 100% tool-free — actual bare-completion
178
+ // proxies. The other 3,729 mix tool-free requests into heavy real agentic
179
+ // traffic (bucket at <10% tool-free: 1,658 users over 1.25M requests), and
180
+ // 7,379 of their tool-free requests land INSIDE 999 sessions that also make
181
+ // tool-bearing root calls. Enforcing per-request would swap the model
182
+ // mid-session for real coding runs.
183
+ //
184
+ // Nor does run length separate the two: the longest consecutive tool-free run
185
+ // belongs to the MIXED cohort (11,094) and exceeds the pure proxies' longest
186
+ // (2,153), with 334 mixed users exceeding 50. There is no per-request
187
+ // threshold, so enforcement needs an account-level verdict this function
188
+ // cannot see. Note also that these requests all already reproduce our
189
+ // canonical root system prompt at position 0 — `requestHasAnvilSystemMarker`
190
+ // rejects root requests that do not — so the prompt is not a discriminator
191
+ // either.
192
+ if (isRootAgent) {
193
+ return {
194
+ signal: 'root_agent_no_tools',
195
+ toolCount: 0,
196
+ sampleToolNames,
197
+ }
198
+ }
199
+
200
+ // Only reached when no tools were offered at all, so this can never override
201
+ // the carve-out above.
202
+ //
203
+ // `!= null` deliberately, not `!== undefined`: a client that serializes its
204
+ // whole request sends `"temperature": null` rather than omitting the key, and
205
+ // that is unset, not a choice. Treating it as set downgraded every such
206
+ // caller. This matches how the rest of the request path already reads these
207
+ // fields — see `applyOpenRouterDefaultMaxTokens`, which gates on
208
+ // `body.max_tokens != null` for the same reason.
209
+ const setsSamplingParams =
210
+ body.temperature != null ||
211
+ body.top_p != null ||
212
+ body.max_tokens != null ||
213
+ body.max_completion_tokens != null
214
+ return {
215
+ signal: setsSamplingParams ? 'sampling_params' : null,
216
+ toolCount: 0,
217
+ sampleToolNames,
218
+ }
219
+ }
220
+
221
+ export type ForeignClientDecision = ForeignClientVerdict & {
222
+ signal: ForeignClientSignal
223
+ /** The model to serve instead, or null to serve what was requested. */
224
+ downgradeTo: string | null
225
+ }
226
+
227
+ /**
228
+ * Detect, then decide whether the signal changes what is served.
229
+ *
230
+ * `foreign_toolset` downgrades. Using a third-party client against this
231
+ * endpoint is a terms violation, not a grey area: Anvil funds free
232
+ * inference with ads that only our own clients render, so a proxied request
233
+ * takes the cost and returns none of the revenue.
234
+ *
235
+ * The other two are reported but never enforced, both because they fire on our
236
+ * own traffic:
237
+ *
238
+ * - `sampling_params` — 568 requests / 13 users on
239
+ * `code-reviewer-deepseek-flash` in a 24h sample, plus the CLI's own
240
+ * free-mode shape, which sends `max_completion_tokens` with no tools. It
241
+ * now only reports NON-root agents: `root_agent_no_tools` is checked first,
242
+ * so the 8,884 requests / 395 users this used to cite on
243
+ * `base2-free-deepseek-flash` classify under that signal instead. Neither
244
+ * enforces, so the reclassification changes measurement, not behavior.
245
+ * - `root_agent_no_tools` — 3,729 users who also do real agentic work, 999 of
246
+ * whose sessions mix it with tool-bearing root calls. See the backtest in
247
+ * `detectForeignAnvilClient`. Catching the 417 genuine proxies inside
248
+ * that population needs an account-level verdict, not a per-request one.
249
+ *
250
+ * They stay as measurements, which is what makes the account-level rule
251
+ * buildable later without guessing at its blast radius.
252
+ */
253
+ export function resolveForeignClientDowngrade(params: {
254
+ body: InspectableRequest & { model?: unknown }
255
+ isRootAgent?: boolean
256
+ }): ForeignClientDecision | null {
257
+ const { body, isRootAgent = false } = params
258
+ const verdict = detectForeignAnvilClient(body, isRootAgent)
259
+ if (!verdict.signal) return null
260
+
261
+ return {
262
+ ...verdict,
263
+ signal: verdict.signal,
264
+ // Never downgrade something already on the downgrade model: that would be
265
+ // a no-op write that still reads as an enforcement in the logs.
266
+ downgradeTo:
267
+ verdict.signal === 'foreign_toolset' &&
268
+ body.model !== ANVIL_DOWNGRADE_MODEL_ID
269
+ ? ANVIL_DOWNGRADE_MODEL_ID
270
+ : null,
271
+ }
272
+ }