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