@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,426 @@
1
+ import { sumBy } from 'lodash'
2
+
3
+ export const truncateString = (str: string, maxLength: number) => {
4
+ if (str.length <= maxLength) {
5
+ return str
6
+ }
7
+ return str.slice(0, maxLength) + '...'
8
+ }
9
+
10
+ export const truncateStringWithMessage = ({
11
+ str,
12
+ maxLength,
13
+ message = 'TRUNCATED DUE TO LENGTH',
14
+ remove = 'END',
15
+ }: {
16
+ str: string
17
+ maxLength: number
18
+ message?: string
19
+ remove?: 'END' | 'START' | 'MIDDLE'
20
+ }) => {
21
+ if (str.length <= maxLength) {
22
+ return str
23
+ }
24
+
25
+ if (remove === 'END') {
26
+ const suffix = `\n[${message}...]`
27
+ return str.slice(0, maxLength - suffix.length) + suffix
28
+ }
29
+ if (remove === 'START') {
30
+ const prefix = `[...${message}]\n`
31
+ return prefix + str.slice(str.length - maxLength + prefix.length)
32
+ }
33
+
34
+ const middle = `\n[...${message}...]\n`
35
+ const length = Math.floor((maxLength - middle.length) / 2)
36
+ return str.slice(0, length) + middle + str.slice(-length)
37
+ }
38
+
39
+ /**
40
+ * Check if a character is a whitespace character according
41
+ * to the XML spec (space, carriage return, line feed or tab)
42
+ *
43
+ * @param character Character to check
44
+ * @return Whether the character is whitespace or not
45
+ */
46
+ export const isWhitespace = (character: string) => /\s/.test(character)
47
+
48
+ export const randBoolFromStr = (str: string) => {
49
+ return sumBy(str.split(''), (char) => char.charCodeAt(0)) % 2 === 0
50
+ }
51
+
52
+ // Irregular plurals that cannot be derived from rules
53
+ // Note: -sis → -ses words are handled by rule, not listed here
54
+ const IRREGULAR_PLURALS: Record<string, string> = {
55
+ // Common English irregulars
56
+ person: 'people',
57
+ child: 'children',
58
+ man: 'men',
59
+ woman: 'women',
60
+ mouse: 'mice', // computer mice
61
+ // Tech terms with -ex/-ix → -ices (no reliable rule)
62
+ index: 'indices',
63
+ vertex: 'vertices',
64
+ matrix: 'matrices',
65
+ appendix: 'appendices',
66
+ // Latin -um → -a (too many exceptions like forum→forums for a rule)
67
+ datum: 'data',
68
+ medium: 'media',
69
+ criterion: 'criteria',
70
+ phenomenon: 'phenomena',
71
+ // Latin -us → -i (too many exceptions like bus→buses for a rule)
72
+ stimulus: 'stimuli',
73
+ radius: 'radii',
74
+ focus: 'foci',
75
+ cactus: 'cacti',
76
+ nucleus: 'nuclei',
77
+ syllabus: 'syllabi',
78
+ }
79
+
80
+ // Words that stay the same in plural (only those not derivable from rules)
81
+ // Note: -ware words and -ics words are handled by rules, not listed here
82
+ const UNCHANGING_PLURALS = new Set([
83
+ // Uncountable/mass nouns in tech
84
+ 'data', // already plural of datum, but often used as uncountable
85
+ 'metadata',
86
+ 'info',
87
+ 'feedback',
88
+ 'news',
89
+ // Words ending in -s that don't change
90
+ 'series',
91
+ 'species',
92
+ 'chassis',
93
+ 'corps',
94
+ 'means',
95
+ ])
96
+
97
+ // Words ending in -f that just add -s (exceptions to -f → -ves rule)
98
+ const F_EXCEPTIONS = new Set([
99
+ // Tech/common terms
100
+ 'roof',
101
+ 'proof', // proofs (mathematical proofs, design proofs)
102
+ 'chief', // tech leads
103
+ 'brief', // design briefs
104
+ 'belief',
105
+ 'cliff',
106
+ 'staff',
107
+ 'bluff',
108
+ 'spoof',
109
+ 'motif', // design motifs
110
+ 'serif', // fonts
111
+ 'tariff',
112
+ 'plaintiff',
113
+ 'sheriff',
114
+ 'reef',
115
+ 'chef',
116
+ 'ref',
117
+ 'gif', // GIFs
118
+ 'pdf', // PDFs
119
+ ])
120
+
121
+ // Words ending in -o that just add -s (exceptions to -o → -oes rule)
122
+ const O_EXCEPTIONS = new Set([
123
+ // Tech terms
124
+ 'photo',
125
+ 'video',
126
+ 'audio',
127
+ 'studio',
128
+ 'logo',
129
+ 'demo',
130
+ 'memo',
131
+ 'repo', // repositories
132
+ 'info',
133
+ 'typo',
134
+ 'intro',
135
+ 'outro',
136
+ 'combo',
137
+ 'promo',
138
+ 'proto', // prototypes
139
+ 'retro',
140
+ 'macro',
141
+ 'micro',
142
+ 'nano',
143
+ 'auto',
144
+ 'euro',
145
+ 'pro',
146
+ 'disco',
147
+ 'duo',
148
+ 'trio',
149
+ 'solo',
150
+ 'piano',
151
+ 'casino',
152
+ 'ratio',
153
+ 'portfolio',
154
+ 'scenario',
155
+ 'studio',
156
+ 'folio',
157
+ 'manifesto',
158
+ 'motto',
159
+ 'dynamo',
160
+ 'limo',
161
+ 'albino',
162
+ 'espresso',
163
+ 'fiasco',
164
+ 'ghetto',
165
+ 'grotto',
166
+ 'inferno',
167
+ 'jumbo',
168
+ 'kimono',
169
+ 'libido',
170
+ 'lingo',
171
+ 'memento',
172
+ 'neutrino',
173
+ 'placebo',
174
+ 'silo', // data silos
175
+ 'stiletto',
176
+ 'tempo',
177
+ 'torso',
178
+ 'virtuoso',
179
+ 'volcano',
180
+ 'zero',
181
+ ])
182
+
183
+ export const pluralize = (
184
+ count: number,
185
+ word: string,
186
+ { includeCount = true }: { includeCount?: boolean } = {},
187
+ ) => {
188
+ let pluralWord: string
189
+ const lowerWord = word.toLowerCase()
190
+
191
+ if (count === 1) {
192
+ pluralWord = word
193
+ } else if (word === word.toUpperCase() && word.length > 1) {
194
+ // Acronyms (API, SDK, URL, etc.) - just add 's'
195
+ pluralWord = word + 's'
196
+ } else if (IRREGULAR_PLURALS[lowerWord]) {
197
+ // Check irregular plurals first (truly irregular words)
198
+ pluralWord = IRREGULAR_PLURALS[lowerWord]
199
+ } else if (UNCHANGING_PLURALS.has(lowerWord)) {
200
+ // Specific words that don't change
201
+ pluralWord = word
202
+ } else if (lowerWord.endsWith('ware')) {
203
+ // Rule: -ware words stay unchanged (software, hardware, firmware, malware, etc.)
204
+ pluralWord = word
205
+ } else if (lowerWord.endsWith('ics')) {
206
+ // Rule: -ics words stay unchanged (analytics, graphics, physics, statistics, etc.)
207
+ pluralWord = word
208
+ } else if (lowerWord.endsWith('sis')) {
209
+ // Rule: -sis → -ses (analysis→analyses, basis→bases, crisis→crises, etc.)
210
+ pluralWord = word.slice(0, -2) + 'es'
211
+ } else if (lowerWord.endsWith('xis')) {
212
+ // Rule: -xis → -xes (axis→axes)
213
+ pluralWord = word.slice(0, -2) + 'es'
214
+ } else if (F_EXCEPTIONS.has(lowerWord)) {
215
+ // -f words that just add -s
216
+ pluralWord = word + 's'
217
+ } else if (word.endsWith('f')) {
218
+ // Handle words ending in -f → -ves
219
+ pluralWord = word.slice(0, -1) + 'ves'
220
+ } else if (word.endsWith('fe')) {
221
+ // Handle words ending in -fe → -ves
222
+ pluralWord = word.slice(0, -2) + 'ves'
223
+ } else if (word.endsWith('y') && !word.match(/[aeiou]y$/)) {
224
+ // Handle words ending in 'y' (unless preceded by a vowel)
225
+ pluralWord = word.slice(0, -1) + 'ies'
226
+ } else if (O_EXCEPTIONS.has(lowerWord)) {
227
+ // -o words that just add -s
228
+ pluralWord = word + 's'
229
+ } else if (word.match(/[cs]h$/) || word.match(/o$/)) {
230
+ // Handle words ending in sh, ch, o → add -es
231
+ pluralWord = word + 'es'
232
+ } else if (word.match(/[^z]z$/)) {
233
+ // Single z at end (not zz) → double the z and add -es (quiz → quizzes)
234
+ pluralWord = word + 'zes'
235
+ } else if (word.match(/[sxz]$/)) {
236
+ // Handle words ending in s, x, zz → add -es
237
+ pluralWord = word + 'es'
238
+ } else {
239
+ pluralWord = word + 's'
240
+ }
241
+
242
+ return includeCount ? `${count} ${pluralWord}` : pluralWord
243
+ }
244
+
245
+ /**
246
+ * Safely replaces all occurrences of a search string with a replacement string,
247
+ * escaping special replacement patterns (like $) in the replacement string.
248
+ */
249
+ export const capitalize = (str: string): string => {
250
+ if (!str) return str
251
+ return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase()
252
+ }
253
+
254
+ /**
255
+ * Converts a snake_case string to Title Case
256
+ * Example: "add_subgoal" -> "Add Subgoal"
257
+ */
258
+ export const snakeToTitleCase = (str: string): string => {
259
+ return str
260
+ .split('_')
261
+ .map((word) => capitalize(word))
262
+ .join(' ')
263
+ }
264
+
265
+ /**
266
+ * Ensures a URL has the appropriate protocol (http:// or https://)
267
+ * Uses http:// for localhost and local IPs, https:// for all other domains
268
+ */
269
+ export const ensureUrlProtocol = (url: string): string => {
270
+ if (
271
+ url.startsWith('http://') ||
272
+ url.startsWith('https://') ||
273
+ url.startsWith('file://')
274
+ ) {
275
+ return url
276
+ }
277
+
278
+ if (url.startsWith('localhost') || url.match(/^127\.\d+\.\d+\.\d+/)) {
279
+ return `http://${url}`
280
+ }
281
+
282
+ if (url.startsWith('/')) {
283
+ return `file://${url}`
284
+ }
285
+
286
+ return `https://${url}`
287
+ }
288
+
289
+ export const safeReplace = (
290
+ content: string,
291
+ searchStr: string,
292
+ replaceStr: string,
293
+ ): string => {
294
+ const escapedReplaceStr = replaceStr.replace(/\$/g, '$$$$')
295
+ return content.replace(searchStr, escapedReplaceStr)
296
+ }
297
+
298
+ /**
299
+ * Extracts a JSON field from a string, transforms it, and puts it back.
300
+ * Handles both array and object JSON values.
301
+ * @param content The string containing JSON-like content
302
+ * @param field The field name to find and transform
303
+ * @param transform Function to transform the parsed JSON value
304
+ * @param fallback String to use if parsing fails
305
+ * @returns The content string with the transformed JSON field
306
+ */
307
+ export function transformJsonInString<T = unknown>(
308
+ content: string,
309
+ field: string,
310
+ transform: (json: T) => unknown,
311
+ fallback: string,
312
+ ): string {
313
+ // Use a non-greedy match for objects/arrays to prevent over-matching
314
+ const pattern = new RegExp(`"${field}"\\s*:\\s*(\\{[^}]*?\\}|\\[[^\\]]*?\\])`)
315
+ const match = content.match(pattern)
316
+
317
+ if (!match) {
318
+ return content
319
+ }
320
+
321
+ try {
322
+ const json = JSON.parse(match[1])
323
+ const transformed = transform(json)
324
+
325
+ // Important: Only replace the exact matched portion to prevent duplicates
326
+ return content.replace(
327
+ match[0],
328
+ `"${field}":${JSON.stringify(transformed)}`,
329
+ )
330
+ } catch (error) {
331
+ // Only replace the exact matched portion even in error case
332
+ return content.replace(match[0], `"${field}":${fallback}`)
333
+ }
334
+ }
335
+
336
+ /**
337
+ * Generates a compact unique identifier by combining timestamp bits with random bits.
338
+ * Uses 40 bits of timestamp (enough for ~34 years) and 24 random bits for exactly 64 total bits.
339
+ * Encodes in base64url for compact, URL-safe strings (~11 chars).
340
+ * @param prefix Optional prefix to add to the ID
341
+ * @returns A unique string ID
342
+ * @example
343
+ * generateCompactId() // => "1a2b3c4d5e6"
344
+ * generateCompactId('msg-') // => "msg-1a2b3c4d5e6"
345
+ */
346
+ export const generateCompactId = (prefix?: string): string => {
347
+ // Get the last 32 bits of the timestamp
348
+ const timestamp = (Date.now() & 0xffffffff) >>> 0
349
+ // Generate a 32-bit random number
350
+ const random = Math.floor(Math.random() * 0x100000000) >>> 0
351
+
352
+ // Combine them into a 64-bit representation as two 32-bit numbers
353
+ const high = timestamp
354
+ const low = random
355
+
356
+ // Convert to a hex string, pad if necessary, and combine
357
+ const highHex = high.toString(16).padStart(8, '0')
358
+ const lowHex = low.toString(16).padStart(8, '0')
359
+
360
+ const combinedHex = highHex + lowHex
361
+
362
+ // Convert hex to a Buffer and then to base64url
363
+ const bytes = Buffer.from(combinedHex, 'hex')
364
+ const str = bytes.toString('base64url').replace(/=/g, '')
365
+
366
+ return prefix ? `${prefix}${str}` : str
367
+ }
368
+
369
+ /**
370
+ * Removes null characters from a string
371
+ */
372
+ export const stripNullChars = (str: string): string => {
373
+ return str.replace(/\u0000/g, '')
374
+ }
375
+
376
+ const ansiColorsRegex = /\x1B\[[0-9;]*m/g
377
+ export function stripColors(str: string): string {
378
+ return str.replace(ansiColorsRegex, '')
379
+ }
380
+
381
+ const ansiRegex = /\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~]|\][^\x1B]*\x1B\\?)/g
382
+ export function stripAnsi(str: string): string {
383
+ return str.replace(ansiRegex, '')
384
+ }
385
+
386
+ export function includesMatch(
387
+ array: (string | RegExp)[],
388
+ value: string,
389
+ ): boolean {
390
+ return array.some((p) => {
391
+ if (typeof p === 'string') {
392
+ return p === value
393
+ }
394
+ return p.test(value)
395
+ })
396
+ }
397
+
398
+ /**
399
+ * Finds the longest substring that is **both** a suffix of `source`
400
+ * **and** a prefix of `next`.
401
+ * Useful when concatenating strings while avoiding duplicate overlap.
402
+ *
403
+ * @example
404
+ * ```ts
405
+ * suffixPrefixOverlap('foobar', 'barbaz'); // ➜ 'bar'
406
+ * suffixPrefixOverlap('abc', 'def'); // ➜ ''
407
+ * ```
408
+ *
409
+ * @param source The string whose **suffix** is inspected.
410
+ * @param next The string whose **prefix** is inspected.
411
+ * @returns The longest overlapping edge, or an empty string if none exists.
412
+ */
413
+ export function suffixPrefixOverlap(source: string, next: string): string {
414
+ for (let len = next.length; len >= 0; len--) {
415
+ const prefix = next.slice(0, len)
416
+ if (source.endsWith(prefix)) {
417
+ return prefix
418
+ }
419
+ }
420
+
421
+ return ''
422
+ }
423
+
424
+ export const escapeString = (str: string) => {
425
+ return JSON.stringify(str).slice(1, -1)
426
+ }
@@ -0,0 +1,53 @@
1
+ import fs from 'fs'
2
+ import os from 'os'
3
+ import path from 'path'
4
+ import { platform } from 'process'
5
+
6
+ import { getProcessEnv } from '../env-process'
7
+
8
+ import type { ProcessEnv } from '../types/contracts/env'
9
+
10
+ const CHROME_PATHS: Record<string, string[]> = {
11
+ darwin: [
12
+ '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
13
+ '/Applications/Google Chrome Beta.app/Contents/MacOS/Google Chrome Beta',
14
+ '/Applications/Google Chrome Dev.app/Contents/MacOS/Google Chrome Dev',
15
+ '/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary',
16
+ '/Applications/Chromium.app/Contents/MacOS/Chromium',
17
+ ],
18
+ linux: [
19
+ '/usr/bin/google-chrome',
20
+ '/usr/bin/google-chrome-stable',
21
+ '/usr/bin/google-chrome-beta',
22
+ '/usr/bin/google-chrome-unstable',
23
+ '/usr/bin/chromium',
24
+ '/usr/bin/chromium-browser',
25
+ '/snap/bin/chromium',
26
+ ],
27
+ win32: [
28
+ 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
29
+ 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
30
+ `${process.env.LOCALAPPDATA ?? ''}\\Google\\Chrome\\Application\\chrome.exe`,
31
+ ],
32
+ }
33
+
34
+ export const findChromeExecutable = (): string | null => {
35
+ const paths = CHROME_PATHS[platform] ?? []
36
+ for (const p of paths) {
37
+ if (p && fs.existsSync(p)) return p
38
+ }
39
+ return null
40
+ }
41
+
42
+ export const getSystemInfo = (processEnv: ProcessEnv = getProcessEnv()) => {
43
+
44
+ return {
45
+ platform,
46
+ shell: 'bash',
47
+ nodeVersion: process.version,
48
+ arch: process.arch,
49
+ homedir: os.homedir(),
50
+ cpus: os.cpus().length,
51
+ chromeAvailable: findChromeExecutable() !== null,
52
+ }
53
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Shared pieces of "summarize the user's first message into a tab/thread
3
+ * title": the prompts every surface sends, and the sanitizer that turns raw
4
+ * model output into something safe to draw in a one-line label.
5
+ *
6
+ * Kept dependency-free so the desktop server, the desktop renderer and the web
7
+ * API can all import it.
8
+ */
9
+
10
+ /** The model only needs the gist; truncate long first messages so the title
11
+ * call stays cheap and fast. */
12
+ export const THREAD_TITLE_INPUT_MAX_CHARS = 2000
13
+
14
+ /** Titles are drawn in a tab label — anything longer is cut off anyway. */
15
+ export const THREAD_TITLE_MAX_CHARS = 60
16
+
17
+ export const THREAD_TITLE_SYSTEM_PROMPT =
18
+ 'You generate short, clean titles for chat conversations. You output only the title.'
19
+
20
+ export const THREAD_TITLE_INSTRUCTIONS_PROMPT = `Write a concise title (3-6 words) summarizing the topic of the user's message.
21
+
22
+ Rules:
23
+ - Plain text only: no surrounding quotes, no trailing punctuation, no preamble or explanation.
24
+ - Write the title in the same language as the user's message.
25
+ - Capitalize it like a headline (when the language uses capitalization).
26
+ - Describe what the message is *about*, in general terms. Never copy sensitive personal data into the title — names, emails, phone numbers, addresses, API keys, passwords, or other secrets. Summarize the topic, not the private details.
27
+ - If the message is too vague to summarize, use its wording as the title instead of a generic placeholder.
28
+
29
+ Output only the title.`
30
+
31
+ /**
32
+ * Cleans up the model's raw output into a single-line, quote-free, length-
33
+ * bounded title. Returns null when nothing usable is left, so the caller keeps
34
+ * whatever placeholder it already showed.
35
+ */
36
+ export function sanitizeThreadTitle(raw: string): string | null {
37
+ let title = raw.replace(/\s+/g, ' ').trim()
38
+ // a "Title:" label the model sometimes prepends despite instructions
39
+ title = title.replace(/^title:\s*/i, '').trim()
40
+ // surrounding quotes, straight or smart
41
+ title = title.replace(/^["'“”‘’]+|["'“”‘’]+$/g, '').trim()
42
+ // trailing sentence punctuation
43
+ title = title.replace(/[.!?,;:]+$/g, '').trim()
44
+ if (!title) return null
45
+ return title.slice(0, THREAD_TITLE_MAX_CHARS)
46
+ }
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Log-scale bucketing for time-to-first-token samples.
3
+ *
4
+ * The /latency dashboard reports p50/p95 TTFT over a 24h window, but percentiles
5
+ * are not additive: you cannot combine hourly p50s into a window p50. Storing a
6
+ * histogram per (hour, model) is what makes the rollup summable — counts add, so
7
+ * percentiles over any set of hours (and over any set of models) come from the
8
+ * merged distribution rather than from the raw `message` table.
9
+ *
10
+ * Buckets are geometric, so the error is a fixed *relative* one at every
11
+ * latency: a bucket spans BASE**i .. BASE**(i+1), and we report its geometric
12
+ * midpoint, so a reported value is within ±1.5% of the true sample. That matters
13
+ * more than absolute precision here — the page renders 1.9s and 3.0s, and models
14
+ * are ranked against each other, so a constant relative error preserves both the
15
+ * displayed figure and the ordering.
16
+ *
17
+ * These constants are baked into stored rows: `message_latency_bucket.bucket` is
18
+ * an index in this scheme. Changing BASE or BUCKET_COUNT silently reinterprets
19
+ * every existing row, so a change means re-rolling the retained window.
20
+ */
21
+
22
+ /** Ratio between consecutive bucket edges. 1.03 => ±1.5% reported error. */
23
+ export const TTFT_HISTOGRAM_BASE = 1.03
24
+
25
+ /**
26
+ * Number of buckets. 1.03**512 is ~3.7e6 ms (~62 min), well past any TTFT a
27
+ * request can produce, so the top bucket only ever catches pathological rows.
28
+ */
29
+ export const TTFT_HISTOGRAM_BUCKET_COUNT = 512
30
+
31
+ const LN_BASE = Math.log(TTFT_HISTOGRAM_BASE)
32
+
33
+ /**
34
+ * Bucket index for a TTFT sample in milliseconds.
35
+ *
36
+ * Mirrors the SQL in scripts/rollup-anvil-latency.ts — keep the two in step.
37
+ * Sub-millisecond and zero samples land in bucket 0 rather than at -Infinity.
38
+ */
39
+ export function ttftBucketIndex(ttftMs: number): number {
40
+ const index = Math.floor(Math.log(Math.max(ttftMs, 1)) / LN_BASE)
41
+ return Math.min(TTFT_HISTOGRAM_BUCKET_COUNT - 1, Math.max(0, index))
42
+ }
43
+
44
+ /**
45
+ * Representative latency for a bucket: the geometric midpoint, which is the
46
+ * value minimizing worst-case relative error across the bucket's span.
47
+ */
48
+ export function ttftBucketMs(bucket: number): number {
49
+ return Math.round(Math.exp((bucket + 0.5) * LN_BASE))
50
+ }
51
+
52
+ /**
53
+ * Percentile over a histogram given as [bucketIndex, count] pairs.
54
+ *
55
+ * Nearest-rank: the first bucket whose cumulative count reaches `quantile` of
56
+ * the total. Returns null for an empty histogram. Pairs need not be sorted.
57
+ */
58
+ export function ttftPercentileFromHistogram(
59
+ buckets: Iterable<readonly [number, number]>,
60
+ quantile: number,
61
+ ): number | null {
62
+ const sorted = [...buckets].sort((a, b) => a[0] - b[0])
63
+ const total = sorted.reduce((sum, [, count]) => sum + count, 0)
64
+ if (total === 0) return null
65
+
66
+ const target = total * quantile
67
+ let cumulative = 0
68
+ for (const [bucket, count] of sorted) {
69
+ cumulative += count
70
+ if (cumulative >= target) return ttftBucketMs(bucket)
71
+ }
72
+ return ttftBucketMs(sorted[sorted.length - 1][0])
73
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Parses XML content for a tool call into a structured object with only string values.
3
+ * Example input:
4
+ * <type>click</type>
5
+ * <selector>#button</selector>
6
+ * <timeout>5000</timeout>
7
+ */
8
+ export function parseToolCallXml(xmlString: string): Record<string, string> {
9
+ if (!xmlString.trim()) return {}
10
+
11
+ const result: Record<string, string> = {}
12
+ const tagPattern = /<(\w+)>([\s\S]*?)<\/\1>/g
13
+ let match
14
+
15
+ while ((match = tagPattern.exec(xmlString)) !== null) {
16
+ const [, key, rawValue] = match
17
+
18
+ // Remove leading/trailing whitespace but preserve internal whitespace
19
+ const value = rawValue.replace(/^\s+|\s+$/g, '')
20
+
21
+ // Assign all values as strings
22
+ result[key] = value
23
+ }
24
+
25
+ return result
26
+ }
27
+
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Generate a closing XML tag for a single tool name
3
+ * @param toolName Single tool name to generate closing tag for
4
+ * @returns Closing XML tag string
5
+ */
6
+ export function closeXml(toolName: string): string {
7
+ return `</${toolName}>`
8
+ }
9
+
10
+ /**
11
+ * Generate stop sequences (closing XML tags) for a list of tool names
12
+ * @param toolNames Array of tool names to generate closing tags for
13
+ * @returns Array of closing XML tag strings
14
+ */
15
+ export function getStopSequences(toolNames: readonly string[]): string[] {
16
+ return toolNames.map((toolName) => `</anvil_tool_${toolName}>`)
17
+ }
@@ -0,0 +1,25 @@
1
+ import z from 'zod/v4'
2
+
3
+ /**
4
+ * Convert a Zod4 schema to JSON string representation.
5
+ */
6
+ export function schemaToJsonStr(
7
+ schema: z.ZodTypeAny | undefined | Record<string, any>,
8
+ options?: Parameters<typeof z.toJSONSchema>[1],
9
+ ): string {
10
+ if (!schema) return 'None'
11
+
12
+ try {
13
+ // Handle Zod schemas
14
+ if (schema instanceof z.ZodType) {
15
+ const jsonSchema = z.toJSONSchema(schema, options)
16
+ delete jsonSchema['$schema']
17
+ return JSON.stringify(jsonSchema, null, 2)
18
+ }
19
+
20
+ // Otherwise, pass on plain object
21
+ return JSON.stringify(schema, null, 2)
22
+ } catch (error) {
23
+ return 'None'
24
+ }
25
+ }