@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.
- package/package.json +74 -70
- package/src/agents/bundled-agents.generated.ts +4 -4
- package/src/chat.tsx +1703 -1698
- package/src/cli-args.ts +134 -134
- package/src/components/status-bar.tsx +331 -313
- package/src/hooks/helpers/send-message.ts +663 -654
- package/src/hooks/use-send-message.ts +806 -773
- package/src/index.tsx +473 -473
- package/src/state/chat-store.ts +553 -544
- package/src/utils/create-run-config.ts +121 -121
- package/src/utils/format-compact-number.ts +37 -0
- package/vendor/@anvil/common/package.json +27 -0
- package/vendor/@anvil/common/src/__tests__/agent-validation.test.ts +846 -0
- package/vendor/@anvil/common/src/__tests__/anvil-model-availability.test.ts +101 -0
- package/vendor/@anvil/common/src/__tests__/anvil-models.test.ts +1408 -0
- package/vendor/@anvil/common/src/__tests__/anvil-public-data-use-copy.test.ts +258 -0
- package/vendor/@anvil/common/src/__tests__/anvil-referral-tiers.test.ts +48 -0
- package/vendor/@anvil/common/src/__tests__/anvil-spend-ceilings.test.ts +282 -0
- package/vendor/@anvil/common/src/__tests__/anvil-trust.test.ts +526 -0
- package/vendor/@anvil/common/src/__tests__/deepseek-direct.test.ts +68 -0
- package/vendor/@anvil/common/src/__tests__/disposable-email.test.ts +132 -0
- package/vendor/@anvil/common/src/__tests__/dynamic-agent-template-schema.test.ts +422 -0
- package/vendor/@anvil/common/src/__tests__/env-ci.test.ts +167 -0
- package/vendor/@anvil/common/src/__tests__/env-process.test.ts +145 -0
- package/vendor/@anvil/common/src/__tests__/foreign-client-shipped-agents.test.ts +234 -0
- package/vendor/@anvil/common/src/__tests__/foreign-client-signals.test.ts +379 -0
- package/vendor/@anvil/common/src/__tests__/free-agents.test.ts +807 -0
- package/vendor/@anvil/common/src/__tests__/gravity-capi.test.ts +154 -0
- package/vendor/@anvil/common/src/__tests__/handlesteps-parsing.test.ts +246 -0
- package/vendor/@anvil/common/src/__tests__/kimi-k3-god-only.test.ts +77 -0
- package/vendor/@anvil/common/src/__tests__/model-config.test.ts +89 -0
- package/vendor/@anvil/common/src/__tests__/project-file-tree.test.ts +298 -0
- package/vendor/@anvil/common/src/__tests__/provisioned-model-tiers.test.ts +116 -0
- package/vendor/@anvil/common/src/__tests__/reasoning-effort.test.ts +167 -0
- package/vendor/@anvil/common/src/__tests__/reddit-capi.test.ts +132 -0
- package/vendor/@anvil/common/src/__tests__/response-ad-positions.test.ts +72 -0
- package/vendor/@anvil/common/src/__tests__/user-state.test.ts +30 -0
- package/vendor/@anvil/common/src/actions.ts +215 -0
- package/vendor/@anvil/common/src/analytics-core.ts +69 -0
- package/vendor/@anvil/common/src/analytics.ts +93 -0
- package/vendor/@anvil/common/src/api-keys/constants.ts +26 -0
- package/vendor/@anvil/common/src/browser-actions.ts +413 -0
- package/vendor/@anvil/common/src/constants/__tests__/anvil-onboarding-gate.test.ts +63 -0
- package/vendor/@anvil/common/src/constants/__tests__/anvil-onboarding.test.ts +246 -0
- package/vendor/@anvil/common/src/constants/__tests__/cf-worker-signals.test.ts +106 -0
- package/vendor/@anvil/common/src/constants/agents.ts +99 -0
- package/vendor/@anvil/common/src/constants/analytics-events.ts +435 -0
- package/vendor/@anvil/common/src/constants/anthropic.ts +73 -0
- package/vendor/@anvil/common/src/constants/anvil-data-use.ts +81 -0
- package/vendor/@anvil/common/src/constants/anvil-errors.ts +7 -0
- package/vendor/@anvil/common/src/constants/anvil-gemini-thinker.ts +21 -0
- package/vendor/@anvil/common/src/constants/anvil-model-ids.ts +11 -0
- package/vendor/@anvil/common/src/constants/anvil-models.ts +2130 -0
- package/vendor/@anvil/common/src/constants/anvil-onboarding-gate.ts +55 -0
- package/vendor/@anvil/common/src/constants/anvil-onboarding.ts +307 -0
- package/vendor/@anvil/common/src/constants/anvil-referral-tiers.ts +87 -0
- package/vendor/@anvil/common/src/constants/anvil-signup-block.ts +68 -0
- package/vendor/@anvil/common/src/constants/anvil-spend-ceilings.ts +450 -0
- package/vendor/@anvil/common/src/constants/anvil-trust.ts +976 -0
- package/vendor/@anvil/common/src/constants/auth.ts +18 -0
- package/vendor/@anvil/common/src/constants/byok.ts +2 -0
- package/vendor/@anvil/common/src/constants/cf-worker-signals.ts +132 -0
- package/vendor/@anvil/common/src/constants/composio.ts +34 -0
- package/vendor/@anvil/common/src/constants/deepseek-direct.ts +55 -0
- package/vendor/@anvil/common/src/constants/feedback.ts +13 -0
- package/vendor/@anvil/common/src/constants/foreign-client-signals.ts +272 -0
- package/vendor/@anvil/common/src/constants/free-agents.ts +777 -0
- package/vendor/@anvil/common/src/constants/gemini.ts +15 -0
- package/vendor/@anvil/common/src/constants/grant-priorities.ts +13 -0
- package/vendor/@anvil/common/src/constants/hosts.ts +6 -0
- package/vendor/@anvil/common/src/constants/images.ts +51 -0
- package/vendor/@anvil/common/src/constants/index.ts +7 -0
- package/vendor/@anvil/common/src/constants/knowledge.ts +35 -0
- package/vendor/@anvil/common/src/constants/limits.ts +23 -0
- package/vendor/@anvil/common/src/constants/model-config.ts +277 -0
- package/vendor/@anvil/common/src/constants/openrouter-attribution.ts +8 -0
- package/vendor/@anvil/common/src/constants/paths.ts +69 -0
- package/vendor/@anvil/common/src/constants/provider-routes.ts +317 -0
- package/vendor/@anvil/common/src/constants/reasoning-effort.ts +79 -0
- package/vendor/@anvil/common/src/constants/skills.ts +60 -0
- package/vendor/@anvil/common/src/constants/subscription-plans.ts +49 -0
- package/vendor/@anvil/common/src/constants/ui.ts +25 -0
- package/vendor/@anvil/common/src/env-ci.ts +36 -0
- package/vendor/@anvil/common/src/env-process.ts +95 -0
- package/vendor/@anvil/common/src/env-schema.ts +95 -0
- package/vendor/@anvil/common/src/env.ts +24 -0
- package/vendor/@anvil/common/src/gravity-capi.ts +207 -0
- package/vendor/@anvil/common/src/mcp/client.ts +233 -0
- package/vendor/@anvil/common/src/old-constants.ts +10 -0
- package/vendor/@anvil/common/src/project-file-tree.ts +355 -0
- package/vendor/@anvil/common/src/reddit-capi.ts +254 -0
- package/vendor/@anvil/common/src/schemas/feedback.ts +52 -0
- package/vendor/@anvil/common/src/schemas/logs.ts +66 -0
- package/vendor/@anvil/common/src/templates/agent-validation.ts +392 -0
- package/vendor/@anvil/common/src/templates/initial-agents-dir/LICENSE +202 -0
- package/vendor/@anvil/common/src/templates/initial-agents-dir/README.md +294 -0
- package/vendor/@anvil/common/src/templates/initial-agents-dir/examples/01-basic-diff-reviewer.ts +17 -0
- package/vendor/@anvil/common/src/templates/initial-agents-dir/examples/02-intermediate-git-committer.ts +78 -0
- package/vendor/@anvil/common/src/templates/initial-agents-dir/examples/03-advanced-file-explorer.ts +73 -0
- package/vendor/@anvil/common/src/templates/initial-agents-dir/my-custom-agent.ts +40 -0
- package/vendor/@anvil/common/src/templates/initial-agents-dir/package.json +6 -0
- package/vendor/@anvil/common/src/templates/initial-agents-dir/skills/README.md +65 -0
- package/vendor/@anvil/common/src/templates/initial-agents-dir/skills/example-skill/SKILL.md +29 -0
- package/vendor/@anvil/common/src/templates/initial-agents-dir/types/agent-definition.ts +497 -0
- package/vendor/@anvil/common/src/templates/initial-agents-dir/types/tools.ts +444 -0
- package/vendor/@anvil/common/src/templates/initial-agents-dir/types/util-types.ts +178 -0
- package/vendor/@anvil/common/src/testing/TESTING_PATTERNS.md +351 -0
- package/vendor/@anvil/common/src/testing/anvil-offer-invariants.ts +169 -0
- package/vendor/@anvil/common/src/testing/errors.ts +33 -0
- package/vendor/@anvil/common/src/testing/fixtures/agent-runtime.ts +334 -0
- package/vendor/@anvil/common/src/testing/impl/agent-runtime.ts +6 -0
- package/vendor/@anvil/common/src/testing/index.ts +84 -0
- package/vendor/@anvil/common/src/testing/mock-modules.ts +53 -0
- package/vendor/@anvil/common/src/testing/mock-types.ts +123 -0
- package/vendor/@anvil/common/src/testing/mocks/analytics.ts +261 -0
- package/vendor/@anvil/common/src/testing/mocks/child-process.ts +93 -0
- package/vendor/@anvil/common/src/testing/mocks/crypto.ts +218 -0
- package/vendor/@anvil/common/src/testing/mocks/database.ts +337 -0
- package/vendor/@anvil/common/src/testing/mocks/fetch.ts +219 -0
- package/vendor/@anvil/common/src/testing/mocks/filesystem.ts +166 -0
- package/vendor/@anvil/common/src/testing/mocks/index.ts +101 -0
- package/vendor/@anvil/common/src/testing/mocks/logger.ts +135 -0
- package/vendor/@anvil/common/src/testing/mocks/stream.ts +313 -0
- package/vendor/@anvil/common/src/testing/mocks/timers.ts +132 -0
- package/vendor/@anvil/common/src/testing/mocks/tree-sitter.ts +127 -0
- package/vendor/@anvil/common/src/testing/setup.ts +282 -0
- package/vendor/@anvil/common/src/testing-env-ci.ts +15 -0
- package/vendor/@anvil/common/src/testing-env-process.ts +78 -0
- package/vendor/@anvil/common/src/tools/__tests__/compile-tool-definitions.test.ts +34 -0
- package/vendor/@anvil/common/src/tools/compile-tool-definitions.ts +157 -0
- package/vendor/@anvil/common/src/tools/constants.ts +117 -0
- package/vendor/@anvil/common/src/tools/list.ts +190 -0
- package/vendor/@anvil/common/src/tools/params/__tests__/coerce-to-array.test.ts +213 -0
- package/vendor/@anvil/common/src/tools/params/__tests__/read-docs.test.ts +47 -0
- package/vendor/@anvil/common/src/tools/params/__tests__/render-ui.test.ts +65 -0
- package/vendor/@anvil/common/src/tools/params/tool/__tests__/run-terminal-command-timeout.test.ts +49 -0
- package/vendor/@anvil/common/src/tools/params/tool/add-message.ts +39 -0
- package/vendor/@anvil/common/src/tools/params/tool/add-subgoal.ts +57 -0
- package/vendor/@anvil/common/src/tools/params/tool/apply-patch.ts +110 -0
- package/vendor/@anvil/common/src/tools/params/tool/ask-user.ts +181 -0
- package/vendor/@anvil/common/src/tools/params/tool/browser-logs.ts +85 -0
- package/vendor/@anvil/common/src/tools/params/tool/cloud-plan-ready.ts +94 -0
- package/vendor/@anvil/common/src/tools/params/tool/code-search.ts +159 -0
- package/vendor/@anvil/common/src/tools/params/tool/composio.ts +131 -0
- package/vendor/@anvil/common/src/tools/params/tool/create-plan.ts +80 -0
- package/vendor/@anvil/common/src/tools/params/tool/end-turn.ts +57 -0
- package/vendor/@anvil/common/src/tools/params/tool/find-files.ts +60 -0
- package/vendor/@anvil/common/src/tools/params/tool/glob.ts +80 -0
- package/vendor/@anvil/common/src/tools/params/tool/gravity-index.ts +93 -0
- package/vendor/@anvil/common/src/tools/params/tool/list-directory.ts +58 -0
- package/vendor/@anvil/common/src/tools/params/tool/lookup-agent-info.ts +37 -0
- package/vendor/@anvil/common/src/tools/params/tool/propose-str-replace.ts +103 -0
- package/vendor/@anvil/common/src/tools/params/tool/propose-write-file.ts +71 -0
- package/vendor/@anvil/common/src/tools/params/tool/read-docs.ts +90 -0
- package/vendor/@anvil/common/src/tools/params/tool/read-files.ts +109 -0
- package/vendor/@anvil/common/src/tools/params/tool/read-subtree.ts +79 -0
- package/vendor/@anvil/common/src/tools/params/tool/read-url.ts +81 -0
- package/vendor/@anvil/common/src/tools/params/tool/render-ui.ts +168 -0
- package/vendor/@anvil/common/src/tools/params/tool/run-file-change-hooks.ts +57 -0
- package/vendor/@anvil/common/src/tools/params/tool/run-terminal-command.ts +211 -0
- package/vendor/@anvil/common/src/tools/params/tool/screenshot.ts +97 -0
- package/vendor/@anvil/common/src/tools/params/tool/set-messages.ts +44 -0
- package/vendor/@anvil/common/src/tools/params/tool/set-output.ts +61 -0
- package/vendor/@anvil/common/src/tools/params/tool/skill.ts +59 -0
- package/vendor/@anvil/common/src/tools/params/tool/spawn-agent-inline.ts +56 -0
- package/vendor/@anvil/common/src/tools/params/tool/spawn-agents.ts +154 -0
- package/vendor/@anvil/common/src/tools/params/tool/str-replace.ts +107 -0
- package/vendor/@anvil/common/src/tools/params/tool/suggest-followups.ts +97 -0
- package/vendor/@anvil/common/src/tools/params/tool/task-completed.ts +61 -0
- package/vendor/@anvil/common/src/tools/params/tool/think-deeply.ts +56 -0
- package/vendor/@anvil/common/src/tools/params/tool/update-subgoal.ts +89 -0
- package/vendor/@anvil/common/src/tools/params/tool/vision-analyze.ts +82 -0
- package/vendor/@anvil/common/src/tools/params/tool/web-search.ts +73 -0
- package/vendor/@anvil/common/src/tools/params/tool/write-file.ts +71 -0
- package/vendor/@anvil/common/src/tools/params/tool/write-todos.ts +67 -0
- package/vendor/@anvil/common/src/tools/params/utils.ts +150 -0
- package/vendor/@anvil/common/src/tools/utils.ts +24 -0
- package/vendor/@anvil/common/src/types/__tests__/dynamic-agent-template.test.ts +20 -0
- package/vendor/@anvil/common/src/types/agent-template.ts +215 -0
- package/vendor/@anvil/common/src/types/anvil-session.ts +535 -0
- package/vendor/@anvil/common/src/types/anvil-streak.ts +6 -0
- package/vendor/@anvil/common/src/types/anvil-usage.ts +64 -0
- package/vendor/@anvil/common/src/types/api/agents/publish.ts +61 -0
- package/vendor/@anvil/common/src/types/bun-test.d.ts +5 -0
- package/vendor/@anvil/common/src/types/contracts/agent-runtime.ts +75 -0
- package/vendor/@anvil/common/src/types/contracts/analytics.ts +9 -0
- package/vendor/@anvil/common/src/types/contracts/bigquery.ts +55 -0
- package/vendor/@anvil/common/src/types/contracts/billing.ts +46 -0
- package/vendor/@anvil/common/src/types/contracts/client.ts +53 -0
- package/vendor/@anvil/common/src/types/contracts/database.ts +112 -0
- package/vendor/@anvil/common/src/types/contracts/env.ts +203 -0
- package/vendor/@anvil/common/src/types/contracts/llm.ts +191 -0
- package/vendor/@anvil/common/src/types/contracts/logger.ts +14 -0
- package/vendor/@anvil/common/src/types/contracts/logs.ts +34 -0
- package/vendor/@anvil/common/src/types/contracts/trace.ts +21 -0
- package/vendor/@anvil/common/src/types/dynamic-agent-template.ts +328 -0
- package/vendor/@anvil/common/src/types/filesystem.ts +10 -0
- package/vendor/@anvil/common/src/types/function-params.ts +33 -0
- package/vendor/@anvil/common/src/types/grant.ts +20 -0
- package/vendor/@anvil/common/src/types/gravity-index.ts +170 -0
- package/vendor/@anvil/common/src/types/json.ts +29 -0
- package/vendor/@anvil/common/src/types/mcp.ts +24 -0
- package/vendor/@anvil/common/src/types/messages/anvil-message.ts +58 -0
- package/vendor/@anvil/common/src/types/messages/content-part.ts +59 -0
- package/vendor/@anvil/common/src/types/messages/data-content.ts +14 -0
- package/vendor/@anvil/common/src/types/messages/provider-metadata.ts +13 -0
- package/vendor/@anvil/common/src/types/organization.ts +118 -0
- package/vendor/@anvil/common/src/types/print-mode.ts +121 -0
- package/vendor/@anvil/common/src/types/publisher.ts +67 -0
- package/vendor/@anvil/common/src/types/session-state.ts +149 -0
- package/vendor/@anvil/common/src/types/skill.ts +86 -0
- package/vendor/@anvil/common/src/types/source.ts +11 -0
- package/vendor/@anvil/common/src/types/spawn.ts +13 -0
- package/vendor/@anvil/common/src/types/subscription.ts +67 -0
- package/vendor/@anvil/common/src/types/usage.ts +16 -0
- package/vendor/@anvil/common/src/types/util.ts +3 -0
- package/vendor/@anvil/common/src/util/__tests__/ad-user-agent.test.ts +26 -0
- package/vendor/@anvil/common/src/util/__tests__/analytics-dispatcher.test.ts +122 -0
- package/vendor/@anvil/common/src/util/__tests__/analytics-log.test.ts +102 -0
- package/vendor/@anvil/common/src/util/__tests__/analytics-sampling.test.ts +143 -0
- package/vendor/@anvil/common/src/util/__tests__/anvil-streak.test.ts +202 -0
- package/vendor/@anvil/common/src/util/__tests__/anvil-usage-summary.test.ts +193 -0
- package/vendor/@anvil/common/src/util/__tests__/axiom-only-log.test.ts +140 -0
- package/vendor/@anvil/common/src/util/__tests__/client-user-agent.test.ts +148 -0
- package/vendor/@anvil/common/src/util/__tests__/engagement-tracker.test.ts +115 -0
- package/vendor/@anvil/common/src/util/__tests__/env-file-path.test.ts +57 -0
- package/vendor/@anvil/common/src/util/__tests__/error-abort.test.ts +774 -0
- package/vendor/@anvil/common/src/util/__tests__/error-api-details.test.ts +206 -0
- package/vendor/@anvil/common/src/util/__tests__/file-read-limits.test.ts +173 -0
- package/vendor/@anvil/common/src/util/__tests__/format-code-search.test.ts +60 -0
- package/vendor/@anvil/common/src/util/__tests__/log-mirror.test.ts +31 -0
- package/vendor/@anvil/common/src/util/__tests__/messages.test.ts +1252 -0
- package/vendor/@anvil/common/src/util/__tests__/partial-json-delta.test.ts +505 -0
- package/vendor/@anvil/common/src/util/__tests__/path.test.ts +22 -0
- package/vendor/@anvil/common/src/util/__tests__/project-ignore.test.ts +90 -0
- package/vendor/@anvil/common/src/util/__tests__/promise.test.ts +325 -0
- package/vendor/@anvil/common/src/util/__tests__/rate-limit.test.ts +28 -0
- package/vendor/@anvil/common/src/util/__tests__/reddit-anvil-retention.test.ts +130 -0
- package/vendor/@anvil/common/src/util/__tests__/saxy.test.ts +1008 -0
- package/vendor/@anvil/common/src/util/__tests__/split-data.test.ts +289 -0
- package/vendor/@anvil/common/src/util/__tests__/string.test.ts +239 -0
- package/vendor/@anvil/common/src/util/__tests__/thread-title.test.ts +70 -0
- package/vendor/@anvil/common/src/util/__tests__/tool-result-media-order.test.ts +108 -0
- package/vendor/@anvil/common/src/util/__tests__/ttft-histogram.test.ts +180 -0
- package/vendor/@anvil/common/src/util/__tests__/with-timeout.test.ts +24 -0
- package/vendor/@anvil/common/src/util/__tests__/zoned-time.test.ts +88 -0
- package/vendor/@anvil/common/src/util/ad-user-agent.ts +25 -0
- package/vendor/@anvil/common/src/util/agent-file-utils.ts +110 -0
- package/vendor/@anvil/common/src/util/agent-id-parsing.ts +136 -0
- package/vendor/@anvil/common/src/util/agent-name-normalization.ts +38 -0
- package/vendor/@anvil/common/src/util/agent-name-resolver.ts +86 -0
- package/vendor/@anvil/common/src/util/analytics-dispatcher.ts +82 -0
- package/vendor/@anvil/common/src/util/analytics-log.ts +78 -0
- package/vendor/@anvil/common/src/util/analytics-sampling.ts +255 -0
- package/vendor/@anvil/common/src/util/anvil-model-availability.ts +102 -0
- package/vendor/@anvil/common/src/util/anvil-privacy.ts +82 -0
- package/vendor/@anvil/common/src/util/anvil-streak-line.ts +103 -0
- package/vendor/@anvil/common/src/util/anvil-streak.ts +156 -0
- package/vendor/@anvil/common/src/util/anvil-usage-summary.ts +125 -0
- package/vendor/@anvil/common/src/util/array.ts +31 -0
- package/vendor/@anvil/common/src/util/axiom-only-log.ts +124 -0
- package/vendor/@anvil/common/src/util/cache-debug.ts +171 -0
- package/vendor/@anvil/common/src/util/client-user-agent.ts +114 -0
- package/vendor/@anvil/common/src/util/credentials.ts +26 -0
- package/vendor/@anvil/common/src/util/currency.ts +25 -0
- package/vendor/@anvil/common/src/util/dates.ts +81 -0
- package/vendor/@anvil/common/src/util/disposable-email.ts +277 -0
- package/vendor/@anvil/common/src/util/engagement-tracker.ts +129 -0
- package/vendor/@anvil/common/src/util/env-file-path.ts +40 -0
- package/vendor/@anvil/common/src/util/error.ts +562 -0
- package/vendor/@anvil/common/src/util/file-read-limits.ts +236 -0
- package/vendor/@anvil/common/src/util/file.ts +339 -0
- package/vendor/@anvil/common/src/util/format-code-search.ts +115 -0
- package/vendor/@anvil/common/src/util/lazy-response-ads.ts +92 -0
- package/vendor/@anvil/common/src/util/log-data.ts +57 -0
- package/vendor/@anvil/common/src/util/log-ingest.ts +58 -0
- package/vendor/@anvil/common/src/util/log-mirror.ts +34 -0
- package/vendor/@anvil/common/src/util/lru-cache.ts +67 -0
- package/vendor/@anvil/common/src/util/messages.ts +685 -0
- package/vendor/@anvil/common/src/util/min-heap.ts +87 -0
- package/vendor/@anvil/common/src/util/model-utils.ts +4 -0
- package/vendor/@anvil/common/src/util/object.ts +128 -0
- package/vendor/@anvil/common/src/util/partial-json-delta.ts +89 -0
- package/vendor/@anvil/common/src/util/path.ts +15 -0
- package/vendor/@anvil/common/src/util/project-ignore.ts +119 -0
- package/vendor/@anvil/common/src/util/promise.ts +73 -0
- package/vendor/@anvil/common/src/util/random.ts +15 -0
- package/vendor/@anvil/common/src/util/rate-limit.ts +56 -0
- package/vendor/@anvil/common/src/util/reddit-anvil-retention.ts +60 -0
- package/vendor/@anvil/common/src/util/reddit-capi-events.ts +36 -0
- package/vendor/@anvil/common/src/util/response-ad-positions.ts +54 -0
- package/vendor/@anvil/common/src/util/saxy.ts +741 -0
- package/vendor/@anvil/common/src/util/skills.test.ts +44 -0
- package/vendor/@anvil/common/src/util/skills.ts +36 -0
- package/vendor/@anvil/common/src/util/split-data.ts +259 -0
- package/vendor/@anvil/common/src/util/stop-sequence.ts +61 -0
- package/vendor/@anvil/common/src/util/string.ts +426 -0
- package/vendor/@anvil/common/src/util/system-info.ts +53 -0
- package/vendor/@anvil/common/src/util/thread-title.ts +46 -0
- package/vendor/@anvil/common/src/util/ttft-histogram.ts +73 -0
- package/vendor/@anvil/common/src/util/xml-parser.ts +27 -0
- package/vendor/@anvil/common/src/util/xml.ts +17 -0
- package/vendor/@anvil/common/src/util/zod-schema.ts +25 -0
- package/vendor/@anvil/common/src/util/zoned-time.ts +136 -0
- package/vendor/@anvil/common/src/utils/ask-user-bridge.ts +44 -0
- package/vendor/@anvil/sdk/dist/index.cjs +52297 -0
- package/vendor/@anvil/sdk/dist/index.cjs.map +440 -0
- package/vendor/@anvil/sdk/dist/index.d.ts +4277 -0
- package/vendor/@anvil/sdk/dist/index.mjs +52290 -0
- package/vendor/@anvil/sdk/dist/index.mjs.map +440 -0
- package/vendor/@anvil/sdk/dist/vendor/ai.cjs +30886 -0
- package/vendor/@anvil/sdk/dist/vendor/ripgrep/arm64-darwin/rg +0 -0
- package/vendor/@anvil/sdk/dist/vendor/ripgrep/arm64-linux/rg +0 -0
- package/vendor/@anvil/sdk/dist/vendor/ripgrep/x64-darwin/rg +0 -0
- package/vendor/@anvil/sdk/dist/vendor/ripgrep/x64-linux/rg +0 -0
- package/vendor/@anvil/sdk/dist/vendor/ripgrep/x64-win32/rg.exe +0 -0
- package/vendor/@anvil/sdk/dist/wasm/tree-sitter-c-sharp.wasm +0 -0
- package/vendor/@anvil/sdk/dist/wasm/tree-sitter-cpp.wasm +0 -0
- package/vendor/@anvil/sdk/dist/wasm/tree-sitter-go.wasm +0 -0
- package/vendor/@anvil/sdk/dist/wasm/tree-sitter-java.wasm +0 -0
- package/vendor/@anvil/sdk/dist/wasm/tree-sitter-javascript.wasm +0 -0
- package/vendor/@anvil/sdk/dist/wasm/tree-sitter-python.wasm +0 -0
- package/vendor/@anvil/sdk/dist/wasm/tree-sitter-ruby.wasm +0 -0
- package/vendor/@anvil/sdk/dist/wasm/tree-sitter-rust.wasm +0 -0
- package/vendor/@anvil/sdk/dist/wasm/tree-sitter-tsx.wasm +0 -0
- package/vendor/@anvil/sdk/dist/wasm/tree-sitter-typescript.wasm +0 -0
- package/vendor/@anvil/sdk/dist/wasm/tree-sitter.wasm +0 -0
- package/vendor/@anvil/sdk/package.json +21 -0
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import type { JSONValue } from '../types/json'
|
|
2
|
+
|
|
3
|
+
type SerializableValue = JSONValue
|
|
4
|
+
|
|
5
|
+
type SerializableRecord = Record<string, SerializableValue>
|
|
6
|
+
|
|
7
|
+
export type CacheDebugCorrelation = {
|
|
8
|
+
projectRoot: string
|
|
9
|
+
filename: string
|
|
10
|
+
snapshotId: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function normalizeForJson(value: unknown): SerializableValue {
|
|
14
|
+
if (
|
|
15
|
+
value === null ||
|
|
16
|
+
typeof value === 'string' ||
|
|
17
|
+
typeof value === 'number' ||
|
|
18
|
+
typeof value === 'boolean'
|
|
19
|
+
) {
|
|
20
|
+
return value
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (value instanceof URL) {
|
|
24
|
+
return value.toString()
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (value instanceof Uint8Array) {
|
|
28
|
+
return {
|
|
29
|
+
type: 'Uint8Array',
|
|
30
|
+
byteLength: value.byteLength,
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (Array.isArray(value)) {
|
|
35
|
+
return value.map((item) => normalizeForJson(item))
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (typeof value === 'object') {
|
|
39
|
+
return Object.fromEntries(
|
|
40
|
+
Object.entries(value as Record<string, unknown>).map(([key, entryValue]) => [
|
|
41
|
+
key,
|
|
42
|
+
normalizeForJson(entryValue),
|
|
43
|
+
]),
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return String(value)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function summarizeDataUrl(value: string): SerializableValue {
|
|
51
|
+
const firstComma = value.indexOf(',')
|
|
52
|
+
const header = firstComma >= 0 ? value.slice(0, firstComma) : value
|
|
53
|
+
const payload = firstComma >= 0 ? value.slice(firstComma + 1) : ''
|
|
54
|
+
return {
|
|
55
|
+
type: 'data-url',
|
|
56
|
+
mediaType: header.slice(5).split(';')[0] || 'unknown',
|
|
57
|
+
payloadLength: payload.length,
|
|
58
|
+
preview: payload.slice(0, 32),
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function summarizeLargeValue(value: SerializableValue): SerializableValue {
|
|
63
|
+
if (Array.isArray(value)) {
|
|
64
|
+
return value.map((item) => summarizeLargeValue(item))
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (!value || typeof value !== 'object') {
|
|
68
|
+
if (typeof value === 'string' && value.startsWith('data:')) {
|
|
69
|
+
return summarizeDataUrl(value)
|
|
70
|
+
}
|
|
71
|
+
return value
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if ('url' in value && typeof value.url === 'string' && value.url.startsWith('data:')) {
|
|
75
|
+
return {
|
|
76
|
+
...value,
|
|
77
|
+
url: summarizeDataUrl(value.url),
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return Object.fromEntries(
|
|
82
|
+
Object.entries(value).map(([key, entryValue]) => {
|
|
83
|
+
if (key === 'file_data' && typeof entryValue === 'string' && entryValue.startsWith('data:')) {
|
|
84
|
+
return [key, summarizeDataUrl(entryValue)]
|
|
85
|
+
}
|
|
86
|
+
if (key === 'arguments' && typeof entryValue === 'string') {
|
|
87
|
+
return [key, entryValue]
|
|
88
|
+
}
|
|
89
|
+
return [
|
|
90
|
+
key,
|
|
91
|
+
entryValue === undefined ? undefined : summarizeLargeValue(entryValue),
|
|
92
|
+
]
|
|
93
|
+
}),
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function parseRequestBody(body: unknown): unknown {
|
|
98
|
+
if (typeof body !== 'string') {
|
|
99
|
+
return body
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
return JSON.parse(body)
|
|
104
|
+
} catch {
|
|
105
|
+
return body
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function serializeCacheDebugCorrelation(
|
|
110
|
+
correlation: CacheDebugCorrelation,
|
|
111
|
+
): string {
|
|
112
|
+
return JSON.stringify(correlation)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function parseCacheDebugCorrelation(
|
|
116
|
+
value: unknown,
|
|
117
|
+
): CacheDebugCorrelation | undefined {
|
|
118
|
+
if (typeof value !== 'string') {
|
|
119
|
+
return undefined
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
const parsed = JSON.parse(value) as Partial<CacheDebugCorrelation>
|
|
124
|
+
if (
|
|
125
|
+
typeof parsed.projectRoot === 'string' &&
|
|
126
|
+
typeof parsed.filename === 'string' &&
|
|
127
|
+
typeof parsed.snapshotId === 'string'
|
|
128
|
+
) {
|
|
129
|
+
return {
|
|
130
|
+
projectRoot: parsed.projectRoot,
|
|
131
|
+
filename: parsed.filename,
|
|
132
|
+
snapshotId: parsed.snapshotId,
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
} catch {
|
|
136
|
+
return undefined
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return undefined
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function normalizeProviderRequestBodyForCacheDebug(params: {
|
|
143
|
+
provider: string
|
|
144
|
+
body: unknown
|
|
145
|
+
}): SerializableValue {
|
|
146
|
+
const parsed = parseRequestBody(params.body)
|
|
147
|
+
const body = normalizeForJson(parsed)
|
|
148
|
+
|
|
149
|
+
if (!body || typeof body !== 'object' || Array.isArray(body)) {
|
|
150
|
+
return body
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const record = body as SerializableRecord
|
|
154
|
+
const normalized: SerializableRecord = {}
|
|
155
|
+
|
|
156
|
+
for (const key of ['model', 'messages', 'tools', 'tool_choice', 'response_format', 'reasoning', 'reasoning_effort', 'verbosity', 'provider']) {
|
|
157
|
+
if (key in record) {
|
|
158
|
+
normalized[key] = summarizeLargeValue(record[key])
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (params.provider === 'openrouter') {
|
|
163
|
+
for (const key of ['models', 'plugins', 'web_search_options', 'include_reasoning']) {
|
|
164
|
+
if (key in record) {
|
|
165
|
+
normalized[key] = summarizeLargeValue(record[key])
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return normalized
|
|
171
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalize an HTTP `User-Agent` into a bounded token for analytics.
|
|
3
|
+
*
|
|
4
|
+
* Which client made a request is otherwise unanswerable server-side. The only
|
|
5
|
+
* client-identifying field we store is `session.fingerprint_id`, which the
|
|
6
|
+
* client picks for itself — reading it as client identity produced a
|
|
7
|
+
* 659-account false-positive ban on 2026-08-03. The request user agent is a
|
|
8
|
+
* second, independent signal: the official CLI sends `Anvil-CLI/<version>`
|
|
9
|
+
* (`getCliAdRequestUserAgent` in cli/src/hooks/use-gravity-ad.ts), so anything
|
|
10
|
+
* else is at least worth a look.
|
|
11
|
+
*
|
|
12
|
+
* It is **not** proof either: a user agent is equally client-supplied, and
|
|
13
|
+
* published Anvil proxies already spoof ours. Treat the output as a lead.
|
|
14
|
+
*
|
|
15
|
+
* Two properties make this safe to attach to a high-volume event:
|
|
16
|
+
*
|
|
17
|
+
* - **PII.** Only the leading RFC 7231 product token is kept, so every
|
|
18
|
+
* parenthetical detail is dropped before anything is recorded:
|
|
19
|
+
* `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 …`
|
|
20
|
+
* becomes `mozilla/5.0`. Nothing after the first whitespace is read.
|
|
21
|
+
* - **Cardinality.** Values are bounded in size and charset: the product is
|
|
22
|
+
* lowercased and stripped to `[a-z0-9._-]`, and anything empty or longer than
|
|
23
|
+
* 32 chars collapses to the single constant `unrecognized`. A version must
|
|
24
|
+
* both look like a version and fit in 16 chars or it is omitted. Nothing is
|
|
25
|
+
* ever truncated into a new value.
|
|
26
|
+
*
|
|
27
|
+
* This bounds each value, not the number of distinct values: a hostile client
|
|
28
|
+
* can still vary the product within 32 legal chars. That residual risk is
|
|
29
|
+
* accepted because an allowlist would defeat the entire point — spotting a
|
|
30
|
+
* client we have not seen before. It is why this field feeds analysis only,
|
|
31
|
+
* never enforcement, and why the chat-completions call site rides the
|
|
32
|
+
* existing 1% free-mode sample. If it is ever abused, the fix is an allowlist
|
|
33
|
+
* here, not at the call sites.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
const MAX_PRODUCT_LEN = 32
|
|
37
|
+
const MAX_VERSION_LEN = 16
|
|
38
|
+
|
|
39
|
+
/** Junk, or a header we could not parse into a product token. */
|
|
40
|
+
export const UNRECOGNIZED_CLIENT = 'unrecognized'
|
|
41
|
+
|
|
42
|
+
/** A version-ish token: starts with a digit, then dotted/alphanumeric parts. */
|
|
43
|
+
const VERSION_RE = /^\d[0-9A-Za-z.+-]*$/
|
|
44
|
+
|
|
45
|
+
export type NormalizedClientUserAgent = {
|
|
46
|
+
/** Lowercased product token, e.g. `anvil-cli`, or `unrecognized`. */
|
|
47
|
+
product: string
|
|
48
|
+
/** Version token when the product declared a parseable one. */
|
|
49
|
+
version?: string
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Returns `undefined` for an absent/empty header so callers can spread the
|
|
54
|
+
* result into an event payload without emitting empty keys.
|
|
55
|
+
*/
|
|
56
|
+
export function normalizeClientUserAgent(
|
|
57
|
+
raw: string | null | undefined,
|
|
58
|
+
): NormalizedClientUserAgent | undefined {
|
|
59
|
+
if (typeof raw !== 'string') return undefined
|
|
60
|
+
const trimmed = raw.trim()
|
|
61
|
+
if (!trimmed) return undefined
|
|
62
|
+
|
|
63
|
+
// RFC 7231: `product/version (comment) product/version …`. Everything from
|
|
64
|
+
// the first whitespace on is commentary — drop it unread.
|
|
65
|
+
const [firstToken = ''] = trimmed.split(/\s+/, 1)
|
|
66
|
+
const slash = firstToken.indexOf('/')
|
|
67
|
+
const rawProduct = slash === -1 ? firstToken : firstToken.slice(0, slash)
|
|
68
|
+
const rawVersion = slash === -1 ? '' : firstToken.slice(slash + 1)
|
|
69
|
+
|
|
70
|
+
// An `@` never appears in a legitimate product token, and stripping it would
|
|
71
|
+
// turn `user@example.com/1.0` into the recorded value `userexample.com` — a
|
|
72
|
+
// mangled but still-identifying address. Reject instead of sanitizing. (A
|
|
73
|
+
// contact address in the conventional `Bot/1.0 (+me@example.com)` position is
|
|
74
|
+
// already dropped: it is after the first whitespace.)
|
|
75
|
+
if (rawProduct.includes('@')) return { product: UNRECOGNIZED_CLIENT }
|
|
76
|
+
|
|
77
|
+
const product = rawProduct.toLowerCase().replace(/[^a-z0-9._-]/g, '')
|
|
78
|
+
|
|
79
|
+
// Empty after sanitizing, or implausibly long: collapse to one constant
|
|
80
|
+
// rather than record a truncated fabrication. Real product tokens are short
|
|
81
|
+
// (`anvil-cli` 12, `go-http-client` 14, `ai-sdk` 6), so anything past the
|
|
82
|
+
// cap is junk — and collapsing it also removes the cheapest way to pump
|
|
83
|
+
// cardinality, which is a long random string per request.
|
|
84
|
+
if (!product || product.length > MAX_PRODUCT_LEN) {
|
|
85
|
+
return { product: UNRECOGNIZED_CLIENT }
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Drop an over-long version rather than truncating it. Truncation fabricates
|
|
89
|
+
// a value the client never sent — `1.0.0-beta+build.1` would be reported as
|
|
90
|
+
// `1.0.0-beta+build`, which is both wrong and collides with `…build.2`. A
|
|
91
|
+
// missing version is honest; an invented one would quietly corrupt exactly
|
|
92
|
+
// the "is this client pinned to a stale version?" question this exists for.
|
|
93
|
+
if (rawVersion.length > MAX_VERSION_LEN || !VERSION_RE.test(rawVersion)) {
|
|
94
|
+
return { product }
|
|
95
|
+
}
|
|
96
|
+
return { product, version: rawVersion }
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Event-payload shape for the normalized agent. Separate fields (rather than
|
|
101
|
+
* one joined string) keep "which clients exist" and "which versions of one
|
|
102
|
+
* client" independently groupable in APL.
|
|
103
|
+
*/
|
|
104
|
+
export function clientUserAgentFields(raw: string | null | undefined): {
|
|
105
|
+
client_ua_product?: string
|
|
106
|
+
client_ua_version?: string
|
|
107
|
+
} {
|
|
108
|
+
const normalized = normalizeClientUserAgent(raw)
|
|
109
|
+
if (!normalized) return {}
|
|
110
|
+
return {
|
|
111
|
+
client_ua_product: normalized.product,
|
|
112
|
+
...(normalized.version ? { client_ua_version: normalized.version } : {}),
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import crypto from 'node:crypto'
|
|
2
|
+
|
|
3
|
+
import { z } from 'zod/v4'
|
|
4
|
+
|
|
5
|
+
export const userSchema = z.object({
|
|
6
|
+
id: z.string(),
|
|
7
|
+
email: z.string(),
|
|
8
|
+
name: z.string().nullable(),
|
|
9
|
+
authToken: z.string(),
|
|
10
|
+
fingerprintId: z.string(),
|
|
11
|
+
fingerprintHash: z.string(),
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
export type User = z.infer<typeof userSchema>
|
|
15
|
+
|
|
16
|
+
export const genAuthCode = (
|
|
17
|
+
fingerprintId: string,
|
|
18
|
+
expiresAt: string,
|
|
19
|
+
secret: string,
|
|
20
|
+
) =>
|
|
21
|
+
crypto
|
|
22
|
+
.createHash('sha256')
|
|
23
|
+
.update(secret)
|
|
24
|
+
.update(fingerprintId)
|
|
25
|
+
.update(expiresAt)
|
|
26
|
+
.digest('hex')
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Converts a credit amount to USD cents.
|
|
3
|
+
* @param credits The number of credits to convert
|
|
4
|
+
* @param centsPerCredit The cost per credit in cents
|
|
5
|
+
* @returns The amount in USD cents
|
|
6
|
+
*/
|
|
7
|
+
export function convertCreditsToUsdCents(
|
|
8
|
+
credits: number,
|
|
9
|
+
centsPerCredit: number,
|
|
10
|
+
): number {
|
|
11
|
+
return Math.ceil(credits * centsPerCredit)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Converts a Stripe grant amount in cents to credits.
|
|
16
|
+
* @param amountInCents The amount in USD cents
|
|
17
|
+
* @param centsPerCredit The cost per credit in cents
|
|
18
|
+
* @returns The number of credits
|
|
19
|
+
*/
|
|
20
|
+
export function convertStripeGrantAmountToCredits(
|
|
21
|
+
amountInCents: number,
|
|
22
|
+
centsPerCredit: number,
|
|
23
|
+
): number {
|
|
24
|
+
return Math.floor(amountInCents / centsPerCredit)
|
|
25
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calculates the next quota reset date.
|
|
3
|
+
* If the current reset date is in the past or null, it calculates the next
|
|
4
|
+
* reset date based on the current date. Otherwise, it ensures the next
|
|
5
|
+
* reset date is in the future relative to the provided date.
|
|
6
|
+
*
|
|
7
|
+
* @param referenceDate The user's current `next_quota_reset` date, or the date the cycle ended.
|
|
8
|
+
* @returns The Date object representing the next reset time.
|
|
9
|
+
*/
|
|
10
|
+
export const getNextQuotaReset = (referenceDate: Date | null): Date => {
|
|
11
|
+
const now = new Date()
|
|
12
|
+
let nextMonth = new Date(referenceDate ?? now)
|
|
13
|
+
while (nextMonth <= now) {
|
|
14
|
+
nextMonth.setMonth(nextMonth.getMonth() + 1)
|
|
15
|
+
}
|
|
16
|
+
return nextMonth
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface FormatTimeUntilOptions {
|
|
20
|
+
/**
|
|
21
|
+
* What to return when the date is in the past or invalid.
|
|
22
|
+
* @default 'now'
|
|
23
|
+
*/
|
|
24
|
+
fallback?: string
|
|
25
|
+
/**
|
|
26
|
+
* Whether to include the smaller unit (hours in "Xd Yh", minutes in "Xh Ym").
|
|
27
|
+
* @default true
|
|
28
|
+
*/
|
|
29
|
+
includeSubUnit?: boolean
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Format the time until a future date in a human-readable string.
|
|
34
|
+
*
|
|
35
|
+
* @param date - The target date (Date object or ISO string)
|
|
36
|
+
* @param options - Formatting options
|
|
37
|
+
* @returns Human-readable string like "4d 7h", "2h 30m", or "45m"
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* // Date 2 days and 5 hours in the future
|
|
41
|
+
* formatTimeUntil(futureDate) // "2d 5h"
|
|
42
|
+
* formatTimeUntil(futureDate, { includeSubUnit: false }) // "2d"
|
|
43
|
+
*
|
|
44
|
+
* // Date 3 hours and 20 minutes in the future
|
|
45
|
+
* formatTimeUntil(futureDate) // "3h 20m"
|
|
46
|
+
*
|
|
47
|
+
* // Date in the past
|
|
48
|
+
* formatTimeUntil(pastDate) // "now"
|
|
49
|
+
* formatTimeUntil(pastDate, { fallback: '0h' }) // "0h"
|
|
50
|
+
*/
|
|
51
|
+
export const formatTimeUntil = (
|
|
52
|
+
date: Date | string | null,
|
|
53
|
+
options: FormatTimeUntilOptions = {},
|
|
54
|
+
): string => {
|
|
55
|
+
const { fallback = 'now', includeSubUnit = true } = options
|
|
56
|
+
|
|
57
|
+
if (!date) return fallback
|
|
58
|
+
|
|
59
|
+
const target = typeof date === 'string' ? new Date(date) : date
|
|
60
|
+
const diffMs = target.getTime() - Date.now()
|
|
61
|
+
|
|
62
|
+
if (isNaN(diffMs) || diffMs <= 0) return fallback
|
|
63
|
+
|
|
64
|
+
const diffMins = Math.floor(diffMs / (1000 * 60))
|
|
65
|
+
const diffHours = Math.floor(diffMins / 60)
|
|
66
|
+
const diffDays = Math.floor(diffHours / 24)
|
|
67
|
+
const remainingHours = diffHours % 24
|
|
68
|
+
const remainingMins = diffMins % 60
|
|
69
|
+
|
|
70
|
+
if (diffDays > 0) {
|
|
71
|
+
return includeSubUnit && remainingHours > 0
|
|
72
|
+
? `${diffDays}d ${remainingHours}h`
|
|
73
|
+
: `${diffDays}d`
|
|
74
|
+
}
|
|
75
|
+
if (diffHours > 0) {
|
|
76
|
+
return includeSubUnit && remainingMins > 0
|
|
77
|
+
? `${diffHours}h ${remainingMins}m`
|
|
78
|
+
: `${diffHours}h`
|
|
79
|
+
}
|
|
80
|
+
return `${diffMins}m`
|
|
81
|
+
}
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Classify email domains that referral-abuse detection treats as flags.
|
|
3
|
+
*
|
|
4
|
+
* Two categories, kept separate because they carry different weight:
|
|
5
|
+
*
|
|
6
|
+
* - `disposable` — one-time / throwaway inbox providers. A real person has no
|
|
7
|
+
* reason to sign up for a durable dev-tool account with an inbox that stops
|
|
8
|
+
* existing in an hour, so a *referred* account on one of these is a strong
|
|
9
|
+
* farm signal.
|
|
10
|
+
* - `privacy_relay` — burner-friendly privacy providers and relays
|
|
11
|
+
* (Proton, Apple private relay, Firefox Relay, SimpleLogin, …). Plenty of
|
|
12
|
+
* legitimate developers live on these, so a hit is corroborating evidence
|
|
13
|
+
* only — it must never gate a reward or trigger action on its own.
|
|
14
|
+
*
|
|
15
|
+
* Matching is by exact domain or any subdomain (disposable providers hand out
|
|
16
|
+
* wildcard subdomains). Lists are deliberately curated and small: they exist
|
|
17
|
+
* to catch the providers we actually see in referral farms, not to be a
|
|
18
|
+
* complete registry. Extend them as sweeps surface new ones (note the dated
|
|
19
|
+
* "observed in referral farms" block below).
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
export type FlaggedEmailDomainKind =
|
|
23
|
+
| 'disposable'
|
|
24
|
+
| 'privacy_relay'
|
|
25
|
+
/** A big-brand consumer privacy mailbox (Proton, Tutanota, Apple
|
|
26
|
+
* Hide-My-Email, DuckDuckGo, Firefox Relay). Classified so callers can SEE
|
|
27
|
+
* it, but the spend ceiling deliberately does not price it — see
|
|
28
|
+
* isSpendCeilingFlaggedEmailDomain. */
|
|
29
|
+
| 'mainstream_privacy'
|
|
30
|
+
|
|
31
|
+
const DISPOSABLE_EMAIL_DOMAINS = [
|
|
32
|
+
// Classic one-time inbox providers.
|
|
33
|
+
'10minutemail.com',
|
|
34
|
+
'dispostable.com',
|
|
35
|
+
'dropmail.me',
|
|
36
|
+
'emailondeck.com',
|
|
37
|
+
'fakeinbox.com',
|
|
38
|
+
'getnada.com',
|
|
39
|
+
'grr.la',
|
|
40
|
+
'guerrillamail.com',
|
|
41
|
+
'guerrillamail.net',
|
|
42
|
+
'maildrop.cc',
|
|
43
|
+
'mailinator.com',
|
|
44
|
+
'mailnesia.com',
|
|
45
|
+
'mail.tm',
|
|
46
|
+
'minuteinbox.com',
|
|
47
|
+
'mintemail.com',
|
|
48
|
+
'mohmal.com',
|
|
49
|
+
'sharklasers.com',
|
|
50
|
+
'temp-mail.org',
|
|
51
|
+
'tempinbox.com',
|
|
52
|
+
'tempmail.com',
|
|
53
|
+
'tempmail.dev',
|
|
54
|
+
'throwawaymail.com',
|
|
55
|
+
'trashmail.com',
|
|
56
|
+
'yopmail.com',
|
|
57
|
+
// Observed in Anvil referral farms, 2026-07 (scripted rings minting
|
|
58
|
+
// referred accounts on niche throwaway domains — see the 07-29 sock sweep).
|
|
59
|
+
'aifotoeditor.com',
|
|
60
|
+
'animateany.com',
|
|
61
|
+
'animatimg.com',
|
|
62
|
+
'biscoito.email',
|
|
63
|
+
'oldtranslator.com',
|
|
64
|
+
// Observed 2026-08-01: a 48-account free-mode compute ring, all minted on
|
|
65
|
+
// 12-character random local parts and admitted from one egress IP (peak 30
|
|
66
|
+
// concurrent, four admissions inside two seconds). These are catch-all
|
|
67
|
+
// domains whose subdomains are chosen to read as legitimate at a glance —
|
|
68
|
+
// gmail.l0veyou.com, edu.l0veyou.com, my.l0veyou.com, test123.l0veyou.com,
|
|
69
|
+
// gmail.pumpkinai.space — which subdomain matching above already covers.
|
|
70
|
+
'l0veyou.com',
|
|
71
|
+
'pumpkinai.space',
|
|
72
|
+
'pumpkinai.it.com',
|
|
73
|
+
// Compiled 2026-08-03 from prod, not from a public blocklist: every domain
|
|
74
|
+
// below has >=20 Anvil accounts and >=75% of them already banned. That
|
|
75
|
+
// covers 7,129 accounts of which 6,699 were banned before this list existed.
|
|
76
|
+
// The threshold is what keeps real providers out — Gmail sits at 0.8%
|
|
77
|
+
// banned, Outlook 11%, Proton 4%, duck.com 30%, and the two school domains
|
|
78
|
+
// that appeared in the raw data (dmpschool.ac.th 12%, sman1pace.sch.id, 5
|
|
79
|
+
// accounts) fail the volume or rate bar. Many are deliberate Gmail
|
|
80
|
+
// lookalikes (gmaiko, gmbel, ggmul, gkmaill, gmito, gmiliu, gmisol) and
|
|
81
|
+
// several were minted in a single day — gmito.my.id put 288 accounts on one
|
|
82
|
+
// date, which no real mailbox provider does.
|
|
83
|
+
'azahram.com',
|
|
84
|
+
'barcondi.my.id',
|
|
85
|
+
'bukitsakura.com',
|
|
86
|
+
'cilisung.com',
|
|
87
|
+
'cindohub.com',
|
|
88
|
+
'duccky.com',
|
|
89
|
+
'fomolu.com',
|
|
90
|
+
'gamlo.my.id',
|
|
91
|
+
'gamontok.com',
|
|
92
|
+
'gehil.my.id',
|
|
93
|
+
'geusil.com',
|
|
94
|
+
'geusil.my.id',
|
|
95
|
+
'ggmul.com',
|
|
96
|
+
'ghyuil.my.id',
|
|
97
|
+
'gkmaill.com',
|
|
98
|
+
'gmaiko.com',
|
|
99
|
+
'gmbel.com',
|
|
100
|
+
'gmiliu.my.id',
|
|
101
|
+
'gmisol.my.id',
|
|
102
|
+
'gmito.my.id',
|
|
103
|
+
'gmole.xyz',
|
|
104
|
+
'gmosel.com',
|
|
105
|
+
'gsuel.my.id',
|
|
106
|
+
'gumel.store',
|
|
107
|
+
'guzeil.com',
|
|
108
|
+
'gwemol.my.id',
|
|
109
|
+
'hayate.us',
|
|
110
|
+
'jokowi.store',
|
|
111
|
+
'jujusa.my.id',
|
|
112
|
+
'mikontol.online',
|
|
113
|
+
'monetsssky1.com',
|
|
114
|
+
'satukataku.com',
|
|
115
|
+
'simosel.site',
|
|
116
|
+
'wdrvk.dpdns.org',
|
|
117
|
+
'wdrvks.eu.org',
|
|
118
|
+
'xabree.com',
|
|
119
|
+
// Added 2026-08-03 on behavioural rather than statistical evidence — these
|
|
120
|
+
// are below the >=20-account bar the block above uses, so they qualify on
|
|
121
|
+
// what their accounts DO instead.
|
|
122
|
+
//
|
|
123
|
+
// proxyvpn.cn: 5 accounts named github, github-1/-2/-3 and master-github,
|
|
124
|
+
// on a proxy-service domain. All five were silent-endpoint callers (free-mode
|
|
125
|
+
// messages with zero client telemetry) running 372-1,435 messages each in
|
|
126
|
+
// 7 days; all five are now banned.
|
|
127
|
+
//
|
|
128
|
+
// The .qd.je pair: 14 accounts each, every one registered on a SINGLE day
|
|
129
|
+
// (impact 2026-07-10, fincy 2026-07-13), and 13 of the 28 surfaced in the
|
|
130
|
+
// silent-endpoint scan. A same-day mint of 14 accounts is not a mailbox
|
|
131
|
+
// provider signing up users.
|
|
132
|
+
//
|
|
133
|
+
// Deliberately NOT added: dns-proxy.com (1 account, no activity, no
|
|
134
|
+
// evidence) and proximus.lu (Belgium's largest telecom — a real ISP whose
|
|
135
|
+
// one user has 791 legitimate messages). A substring rule on "proxy" would
|
|
136
|
+
// have caught both.
|
|
137
|
+
'proxyvpn.cn',
|
|
138
|
+
'impact.qd.je',
|
|
139
|
+
'fincy.qd.je',
|
|
140
|
+
// Compiled 2026-08-15 from 14d of prod logs, while working out why 22.9% of
|
|
141
|
+
// Singapore's active users were hitting a spend ceiling. The restricted
|
|
142
|
+
// ceiling on the SG *geography* was doing two jobs at once: bounding a farm
|
|
143
|
+
// that happens to egress there, and taxing ~270 ordinary developers on
|
|
144
|
+
// gmail/qq/163/outlook. Naming the farm by domain is what lets the geography
|
|
145
|
+
// cap come off — see ANVIL_ELEVATED_COUNTRIES.
|
|
146
|
+
//
|
|
147
|
+
// Rates below are "share of the domain's accounts carrying an automated ban,
|
|
148
|
+
// honeypot hit, or foreign-toolset detection". Baselines from the same
|
|
149
|
+
// window: gmail.com 5%, proton.me 7%, outlook.com 23%.
|
|
150
|
+
//
|
|
151
|
+
// dhisy.com 28 accounts, 93% — clears the >=20 / >=75% statistical bar
|
|
152
|
+
// dewaa.id 20 accounts, 80% — clears it
|
|
153
|
+
// sendang.space 13 accounts, 92% — behavioural: too few accounts for the
|
|
154
|
+
// bar, ban rate far past it
|
|
155
|
+
// yotube.id 18 accounts, 83% — behavioural, and a YouTube typo-squat
|
|
156
|
+
// gusil.my.id 80 accounts, 68% — under 75%, but 13x the gmail baseline
|
|
157
|
+
// at real volume, and the same naming
|
|
158
|
+
// family as gsuel/gehil/gmisol.my.id
|
|
159
|
+
// already listed above
|
|
160
|
+
//
|
|
161
|
+
// Deliberately NOT added, and worth recording because both LOOK like members
|
|
162
|
+
// of the family above and are not:
|
|
163
|
+
// gmisel.com 49 accounts, 2% — indistinguishable from gmail's baseline
|
|
164
|
+
// despite sitting one letter from the
|
|
165
|
+
// listed gmisol.my.id. Name similarity is
|
|
166
|
+
// not evidence; this is the entry the
|
|
167
|
+
// >=75% bar exists to keep out.
|
|
168
|
+
// cemararaya.id 6 accounts, 33% — too few accounts, rate too low.
|
|
169
|
+
'dhisy.com',
|
|
170
|
+
'dewaa.id',
|
|
171
|
+
'sendang.space',
|
|
172
|
+
'yotube.id',
|
|
173
|
+
'gusil.my.id',
|
|
174
|
+
] as const
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Big-brand consumer privacy mailboxes, lifted OFF the spend ceiling on an
|
|
178
|
+
* operator decision (2026-08-14).
|
|
179
|
+
*
|
|
180
|
+
* These were in the relay list from day one, which put a bot-farm ceiling on
|
|
181
|
+
* every ProtonMail and Tutanota user — mainstream providers whose typical
|
|
182
|
+
* account is one person's primary mailbox, not an alias mint. It also
|
|
183
|
+
* contradicted the pipeline's own doctrine: the IP side refuses to price
|
|
184
|
+
* Apple Private Relay because it is a consumer default, while the email side
|
|
185
|
+
* was pricing Apple's Hide-My-Email. Alias services built for
|
|
186
|
+
* one-address-per-signup (SimpleLogin, AnonAddy, aleeas) stay priced: their
|
|
187
|
+
* product IS mailbox multiplication, which is the thing the flag exists to
|
|
188
|
+
* catch.
|
|
189
|
+
*
|
|
190
|
+
* Still classified — as `mainstream_privacy` — so trust scoring and the
|
|
191
|
+
* abuse scripts can keep seeing them; only the ceiling looks away.
|
|
192
|
+
*/
|
|
193
|
+
const MAINSTREAM_PRIVACY_EMAIL_DOMAINS = [
|
|
194
|
+
// Proton family (primary mailboxes; passmail.net is Proton's ALIAS product
|
|
195
|
+
// and stays in the relay list below).
|
|
196
|
+
'proton.me',
|
|
197
|
+
'protonmail.ch',
|
|
198
|
+
'protonmail.com',
|
|
199
|
+
'pm.me',
|
|
200
|
+
// Apple "Hide My Email".
|
|
201
|
+
'privaterelay.appleid.com',
|
|
202
|
+
// DuckDuckGo Email Protection.
|
|
203
|
+
'duck.com',
|
|
204
|
+
// Firefox Relay.
|
|
205
|
+
'mozmail.com',
|
|
206
|
+
// Tutanota family.
|
|
207
|
+
'tuta.com',
|
|
208
|
+
'tuta.io',
|
|
209
|
+
'tutamail.com',
|
|
210
|
+
'tutanota.com',
|
|
211
|
+
] as const
|
|
212
|
+
|
|
213
|
+
const PRIVACY_RELAY_EMAIL_DOMAINS = [
|
|
214
|
+
'passmail.net',
|
|
215
|
+
// Alias/relay services.
|
|
216
|
+
'aleeas.com',
|
|
217
|
+
'anonaddy.me',
|
|
218
|
+
'simplelogin.com',
|
|
219
|
+
'simplelogin.io',
|
|
220
|
+
] as const
|
|
221
|
+
|
|
222
|
+
const DISPOSABLE_SET: ReadonlySet<string> = new Set(DISPOSABLE_EMAIL_DOMAINS)
|
|
223
|
+
const PRIVACY_RELAY_SET: ReadonlySet<string> = new Set(
|
|
224
|
+
PRIVACY_RELAY_EMAIL_DOMAINS,
|
|
225
|
+
)
|
|
226
|
+
const MAINSTREAM_PRIVACY_SET: ReadonlySet<string> = new Set(
|
|
227
|
+
MAINSTREAM_PRIVACY_EMAIL_DOMAINS,
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
function domainOf(email: string): string | null {
|
|
231
|
+
const at = email.lastIndexOf('@')
|
|
232
|
+
if (at < 0 || at === email.length - 1) return null
|
|
233
|
+
return email
|
|
234
|
+
.slice(at + 1)
|
|
235
|
+
.trim()
|
|
236
|
+
.toLowerCase()
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function matchesSet(domain: string, set: ReadonlySet<string>): boolean {
|
|
240
|
+
if (set.has(domain)) return true
|
|
241
|
+
// Subdomain match: a.b.mailinator.com → b.mailinator.com → mailinator.com
|
|
242
|
+
let rest = domain
|
|
243
|
+
for (let dot = rest.indexOf('.'); dot >= 0; dot = rest.indexOf('.')) {
|
|
244
|
+
rest = rest.slice(dot + 1)
|
|
245
|
+
if (set.has(rest)) return true
|
|
246
|
+
}
|
|
247
|
+
return false
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/** The flag category for `email`'s domain, or null for an ordinary domain
|
|
251
|
+
* (or an unparseable email). */
|
|
252
|
+
export function classifyEmailDomain(
|
|
253
|
+
email: string | null | undefined,
|
|
254
|
+
): FlaggedEmailDomainKind | null {
|
|
255
|
+
if (!email) return null
|
|
256
|
+
const domain = domainOf(email)
|
|
257
|
+
if (!domain) return null
|
|
258
|
+
if (matchesSet(domain, DISPOSABLE_SET)) return 'disposable'
|
|
259
|
+
if (matchesSet(domain, PRIVACY_RELAY_SET)) return 'privacy_relay'
|
|
260
|
+
if (matchesSet(domain, MAINSTREAM_PRIVACY_SET)) return 'mainstream_privacy'
|
|
261
|
+
return null
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Whether `email`'s domain prices the flagged-domain SPEND CEILING.
|
|
266
|
+
*
|
|
267
|
+
* Deliberately narrower than `classifyEmailDomain !== null`: mainstream
|
|
268
|
+
* consumer privacy mailboxes are classified (so scoring and scripts can see
|
|
269
|
+
* them) but never priced — a ProtonMail address is not evidence of a bot
|
|
270
|
+
* farm, and for two days it was billed as one.
|
|
271
|
+
*/
|
|
272
|
+
export function isSpendCeilingFlaggedEmailDomain(
|
|
273
|
+
email: string | null | undefined,
|
|
274
|
+
): boolean {
|
|
275
|
+
const kind = classifyEmailDomain(email)
|
|
276
|
+
return kind !== null && kind !== 'mainstream_privacy'
|
|
277
|
+
}
|