@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,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How long a device-code sign-in stays valid.
|
|
3
|
+
*
|
|
4
|
+
* Shared because two apps issue these codes — codebuff.com serves the CLI and anvil.com serves
|
|
5
|
+
* Anvil Desktop — and a drift between them would show up only as one surface timing out sooner
|
|
6
|
+
* than the other, with nothing to point at.
|
|
7
|
+
*
|
|
8
|
+
* The `/api/auth/cli/code` response sends this as `expiresInMs` **alongside** the absolute
|
|
9
|
+
* `expiresAt`, and they are not redundant:
|
|
10
|
+
*
|
|
11
|
+
* - `expiresAt` is an instant on the SERVER's clock. It has to stay: it is the HMAC input the
|
|
12
|
+
* status endpoint verifies, so clients must echo it back byte-for-byte.
|
|
13
|
+
* - `expiresInMs` is a duration, which means nothing about it depends on the client's clock. A
|
|
14
|
+
* client that subtracts `expiresAt` from its own `Date.now()` is silently asking every user's
|
|
15
|
+
* system clock to be right — and a machine running an hour fast rejects every code it is ever
|
|
16
|
+
* issued, permanently, with a 200 in our logs. That was a real, unfixable-by-reinstalling bug.
|
|
17
|
+
*/
|
|
18
|
+
export const CLI_AUTH_CODE_LIFETIME_MS = 60 * 60 * 1000
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cloudflare Worker egress detection.
|
|
3
|
+
*
|
|
4
|
+
* What this catches
|
|
5
|
+
* -----------------
|
|
6
|
+
* `pingmike2/anvil2api-wokers` and its relatives run as a Cloudflare Worker
|
|
7
|
+
* that pools harvested account tokens and resells free mode as an
|
|
8
|
+
* OpenAI/Anthropic-compatible endpoint. Cloudflare stamps `CF-Worker` onto every
|
|
9
|
+
* outbound subrequest a Worker makes, naming the Worker's zone, and Worker code
|
|
10
|
+
* cannot remove it — the edge adds it after `fetch()` returns to the runtime.
|
|
11
|
+
* The published source never references the header at all.
|
|
12
|
+
*
|
|
13
|
+
* Why this signal is different from the ones that went wrong before
|
|
14
|
+
* ----------------------------------------------------------------
|
|
15
|
+
* Every previous third-party-client detector keyed on something the CLIENT
|
|
16
|
+
* chooses: `fingerprint_id` (self-reported), the tool list (they inject
|
|
17
|
+
* `end_turn` to pass), the system prompt (they now send our canonical opening
|
|
18
|
+
* byte-for-byte). Each was defeated within days, and one produced a
|
|
19
|
+
* 659-account false-positive ban that had to be fully reversed.
|
|
20
|
+
*
|
|
21
|
+
* This one is stamped by OUR edge, on the request, describing the infrastructure
|
|
22
|
+
* that sent it. A caller cannot remove it, and adding it falsely only implicates
|
|
23
|
+
* themselves. That is what makes it safe enough to action automatically — and
|
|
24
|
+
* the reason every other signal here is still only a lead.
|
|
25
|
+
*
|
|
26
|
+
* The three ways it could still be wrong, and what each costs
|
|
27
|
+
* ----------------------------------------------------------
|
|
28
|
+
* 1. **Our own infrastructure calls our own API from a Worker.** Real: the
|
|
29
|
+
* app-preview-proxy is a Cloudflare Worker. Handled by never actioning
|
|
30
|
+
* unmetered service accounts, and by the zone allowlist.
|
|
31
|
+
* 2. **Cloudflare does not strip an inbound `cf-worker` from an ordinary
|
|
32
|
+
* client.** Then anyone can send it — but only on their OWN authenticated
|
|
33
|
+
* request, so they can only get themselves actioned. It is not a vector for
|
|
34
|
+
* getting someone else banned.
|
|
35
|
+
* 3. **A user sits behind a corporate proxy implemented on Workers.** Rare but
|
|
36
|
+
* not impossible, and the reason `ban` is not the default mode.
|
|
37
|
+
*
|
|
38
|
+
* Nothing here fires on paid or BYOK traffic. The concern is subsidised
|
|
39
|
+
* free-mode capacity being resold, and a paying customer routing through their
|
|
40
|
+
* own Worker is doing nothing wrong.
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
/** Header Cloudflare stamps on subrequests originating from a Worker. */
|
|
44
|
+
export const CF_WORKER_HEADER = 'cf-worker'
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Corroborates that the request actually traversed our edge. Without it we are
|
|
48
|
+
* reading a header an arbitrary caller wrote on a request that reached the
|
|
49
|
+
* origin some other way, which is not the same claim at all.
|
|
50
|
+
*/
|
|
51
|
+
export const CF_RAY_HEADER = 'cf-ray'
|
|
52
|
+
|
|
53
|
+
export type CfWorkerMode = 'off' | 'observe' | 'block' | 'ban'
|
|
54
|
+
|
|
55
|
+
export type CfWorkerVerdict =
|
|
56
|
+
| { detected: false; reason: 'no_header' | 'not_edge_verified' | 'allowlisted' }
|
|
57
|
+
| { detected: true; zone: string }
|
|
58
|
+
|
|
59
|
+
export type CfWorkerDetectInput = {
|
|
60
|
+
/** Raw `cf-worker` value, or null/undefined when absent. */
|
|
61
|
+
cfWorkerHeader: string | null | undefined
|
|
62
|
+
/** Raw `cf-ray` value. Absent means the request did not come through our
|
|
63
|
+
* Cloudflare edge, so the `cf-worker` value is not ours to trust. */
|
|
64
|
+
cfRayHeader: string | null | undefined
|
|
65
|
+
/** Worker zones operated by us (app-preview-proxy and friends). */
|
|
66
|
+
allowedZones: ReadonlySet<string>
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Pure detection. No I/O, so the exact decision can be replayed offline when
|
|
71
|
+
* auditing a ban — a verdict that cannot be reproduced cannot be defended.
|
|
72
|
+
*/
|
|
73
|
+
export function detectCfWorker(input: CfWorkerDetectInput): CfWorkerVerdict {
|
|
74
|
+
const raw = input.cfWorkerHeader?.trim()
|
|
75
|
+
if (!raw) return { detected: false, reason: 'no_header' }
|
|
76
|
+
|
|
77
|
+
// Require edge corroboration. A `cf-worker` on a request that never touched
|
|
78
|
+
// Cloudflare is caller-authored noise, and treating it as proof would make
|
|
79
|
+
// this detector exactly as forgeable as the ones it replaces.
|
|
80
|
+
if (!input.cfRayHeader?.trim()) {
|
|
81
|
+
return { detected: false, reason: 'not_edge_verified' }
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const zone = raw.toLowerCase()
|
|
85
|
+
if (input.allowedZones.has(zone)) {
|
|
86
|
+
return { detected: false, reason: 'allowlisted' }
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return { detected: true, zone }
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Evidence recorded with every automated action, and shown verbatim in the ban
|
|
94
|
+
* review dashboard.
|
|
95
|
+
*
|
|
96
|
+
* Deliberately everything needed to re-derive the verdict without the original
|
|
97
|
+
* request: the zone, the edge ray id (so the request can be found in Cloudflare
|
|
98
|
+
* logs), and the calling client's self-description. A reviewer who cannot see
|
|
99
|
+
* why an account was banned cannot tell a true positive from a false one, which
|
|
100
|
+
* is how the 2026-08-03 sweep ended up reversed in full.
|
|
101
|
+
*/
|
|
102
|
+
export type CfWorkerEvidence = {
|
|
103
|
+
zone: string
|
|
104
|
+
cfRay: string | null
|
|
105
|
+
/** `anvil_metadata.client_id`. The published proxy sends `wf-` + 8 base36
|
|
106
|
+
* characters; our own clients send a different shape entirely. */
|
|
107
|
+
clientId: string | null
|
|
108
|
+
userAgent: string | null
|
|
109
|
+
model: string | null
|
|
110
|
+
agentId: string | null
|
|
111
|
+
endpoint: string
|
|
112
|
+
detectedAt: string
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Parse the operator-configured allowlist of our own Worker zones. */
|
|
116
|
+
export function parseAllowedWorkerZones(
|
|
117
|
+
raw: string | null | undefined,
|
|
118
|
+
): ReadonlySet<string> {
|
|
119
|
+
return new Set(
|
|
120
|
+
(raw ?? '')
|
|
121
|
+
.split(',')
|
|
122
|
+
.map((zone) => zone.trim().toLowerCase())
|
|
123
|
+
.filter((zone) => zone.length > 0),
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** A caller-supplied `client_id` matching the published proxy's generator.
|
|
128
|
+
* Corroboration only — never a reason to action on its own, because it is
|
|
129
|
+
* client-controlled and therefore trivially changed the moment it is used. */
|
|
130
|
+
export function looksLikeProxyClientId(clientId: string | null | undefined): boolean {
|
|
131
|
+
return typeof clientId === 'string' && /^wf-[a-z0-9]{8}$/.test(clientId)
|
|
132
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export const COMPOSIO_API_KEY_ENV_VAR = 'COMPOSIO_API_KEY'
|
|
2
|
+
|
|
3
|
+
export const COMPOSIO_META_TOOL_NAMES = [
|
|
4
|
+
'composio_manage_connections',
|
|
5
|
+
'composio_multi_execute_tool',
|
|
6
|
+
'composio_search_tools',
|
|
7
|
+
'composio_get_tool_schemas',
|
|
8
|
+
] as const
|
|
9
|
+
|
|
10
|
+
export type ComposioMetaToolName = (typeof COMPOSIO_META_TOOL_NAMES)[number]
|
|
11
|
+
|
|
12
|
+
export const COMPOSIO_META_TOOL_NAME_TO_UPSTREAM = {
|
|
13
|
+
composio_manage_connections: 'COMPOSIO_MANAGE_CONNECTIONS',
|
|
14
|
+
composio_multi_execute_tool: 'COMPOSIO_MULTI_EXECUTE_TOOL',
|
|
15
|
+
composio_search_tools: 'COMPOSIO_SEARCH_TOOLS',
|
|
16
|
+
composio_get_tool_schemas: 'COMPOSIO_GET_TOOL_SCHEMAS',
|
|
17
|
+
} as const satisfies Record<ComposioMetaToolName, string>
|
|
18
|
+
|
|
19
|
+
export type ComposioUpstreamMetaToolName =
|
|
20
|
+
(typeof COMPOSIO_META_TOOL_NAME_TO_UPSTREAM)[ComposioMetaToolName]
|
|
21
|
+
|
|
22
|
+
const COMPOSIO_META_TOOL_NAME_SET = new Set<string>(COMPOSIO_META_TOOL_NAMES)
|
|
23
|
+
|
|
24
|
+
export function isComposioMetaToolName(
|
|
25
|
+
toolName: string,
|
|
26
|
+
): toolName is ComposioMetaToolName {
|
|
27
|
+
return COMPOSIO_META_TOOL_NAME_SET.has(toolName)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function getComposioUpstreamToolName(
|
|
31
|
+
toolName: ComposioMetaToolName,
|
|
32
|
+
): ComposioUpstreamMetaToolName {
|
|
33
|
+
return COMPOSIO_META_TOOL_NAME_TO_UPSTREAM[toolName]
|
|
34
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Direct-to-DeepSeek mode ("own backend").
|
|
3
|
+
*
|
|
4
|
+
* When `DEEPSEEK_API_KEY` is set, the SDK routes model calls straight to
|
|
5
|
+
* DeepSeek's OpenAI-compatible API instead of the Codebuff backend, and the
|
|
6
|
+
* CLI bypasses backend auth/session gates with a local identity. When unset,
|
|
7
|
+
* every path behaves exactly as before (Codebuff backend).
|
|
8
|
+
*
|
|
9
|
+
* `DEEPSEEK_MODEL` optionally overrides the model ID sent to DeepSeek. Without
|
|
10
|
+
* it, DeepSeek's API serves exactly two model names — `deepseek-v4-pro` and
|
|
11
|
+
* `deepseek-v4-flash` (a request for anything else is rejected with "The
|
|
12
|
+
* supported API model names are deepseek-v4-pro or deepseek-v4-flash"). So:
|
|
13
|
+
*
|
|
14
|
+
* - `deepseek/…` lane ids keep their model name (`deepseek/deepseek-v4-pro`
|
|
15
|
+
* → `deepseek-v4-pro`), with the `-max` context tiers normalized to their
|
|
16
|
+
* base model and the legacy fireworks lane mapped to flash.
|
|
17
|
+
* - Every other provider id (anthropic/*, openai/*, google/*, …) cannot be
|
|
18
|
+
* served by DeepSeek at all and falls back to the default DeepSeek model.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
export const DEEPSEEK_BASE_URL = 'https://api.deepseek.com'
|
|
22
|
+
|
|
23
|
+
/** Model name sent to DeepSeek for ids that are not a DeepSeek lane model. */
|
|
24
|
+
export const DEFAULT_DIRECT_DEEPSEEK_MODEL = 'deepseek-v4-pro'
|
|
25
|
+
|
|
26
|
+
/** The user's DeepSeek API key, read from the runtime environment. */
|
|
27
|
+
export const getDirectDeepSeekApiKey = (): string | undefined =>
|
|
28
|
+
process.env.DEEPSEEK_API_KEY
|
|
29
|
+
|
|
30
|
+
/** True when the CLI/SDK should talk to DeepSeek directly instead of Codebuff. */
|
|
31
|
+
export const isDirectDeepSeekMode = (): boolean =>
|
|
32
|
+
Boolean(getDirectDeepSeekApiKey())
|
|
33
|
+
|
|
34
|
+
/** Map a Codebuff model ID to the model name DeepSeek's API accepts. */
|
|
35
|
+
export function remapModelForDirectDeepSeek(model: string): string {
|
|
36
|
+
const override = process.env.DEEPSEEK_MODEL
|
|
37
|
+
if (override) return override
|
|
38
|
+
|
|
39
|
+
// DeepSeek lane ids: keep the model name, normalizing the -max context tiers
|
|
40
|
+
// and the legacy fireworks lane to ids the API actually serves.
|
|
41
|
+
if (model.startsWith('deepseek/')) {
|
|
42
|
+
const name = model.slice('deepseek/'.length)
|
|
43
|
+
if (name === 'deepseek-v4-pro' || name === 'deepseek-v4-flash') {
|
|
44
|
+
return name
|
|
45
|
+
}
|
|
46
|
+
if (name === 'deepseek-v4-pro-max') return 'deepseek-v4-pro'
|
|
47
|
+
if (name === 'deepseek-v4-flash-max') return 'deepseek-v4-flash'
|
|
48
|
+
return DEFAULT_DIRECT_DEEPSEEK_MODEL
|
|
49
|
+
}
|
|
50
|
+
if (model === 'fireworks/deepseek-v4-flash') return 'deepseek-v4-flash'
|
|
51
|
+
|
|
52
|
+
// Any other provider (anthropic, openai, google, …) has no DeepSeek
|
|
53
|
+
// equivalent — fall back so the request is never rejected for the model name.
|
|
54
|
+
return DEFAULT_DIRECT_DEEPSEEK_MODEL
|
|
55
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export const FEEDBACK_CATEGORIES = ['good_result', 'bad_result', 'app_bug', 'other'] as const
|
|
2
|
+
export type FeedbackCategory = (typeof FEEDBACK_CATEGORIES)[number]
|
|
3
|
+
|
|
4
|
+
export const FEEDBACK_SOURCES = ['cli', 'desktop', 'sdk', 'web'] as const
|
|
5
|
+
export type FeedbackSource = (typeof FEEDBACK_SOURCES)[number]
|
|
6
|
+
|
|
7
|
+
export const MESSAGE_VARIANTS = ['ai', 'user', 'agent', 'error'] as const
|
|
8
|
+
export type MessageVariant = (typeof MESSAGE_VARIANTS)[number]
|
|
9
|
+
|
|
10
|
+
export const MAX_RECENT_MESSAGES = 10
|
|
11
|
+
export const MAX_ERRORS = 50
|
|
12
|
+
export const MAX_ERROR_MESSAGE_LENGTH = 2000
|
|
13
|
+
export const MAX_ERROR_ID_LENGTH = 200
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import { toolNames } from '../tools/constants'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Where a free-mode request goes when it did not come from a anvil client.
|
|
5
|
+
*
|
|
6
|
+
* OpenRouter's `:free` variant, so a downgraded request costs nothing upstream
|
|
7
|
+
* — which is the point. A caller proxying our free endpoint into their own
|
|
8
|
+
* harness is spending our inference budget; serving them a free model spends
|
|
9
|
+
* none of it. `getChatCompletionsProvider` has no branch for this slug and
|
|
10
|
+
* falls through to `openrouter`, so nothing else needs to know about it.
|
|
11
|
+
*
|
|
12
|
+
* Verified against the OpenRouter catalog on 2026-08-08: 262k context, $0
|
|
13
|
+
* prompt and completion, and `tools` + `tool_choice` in supported_parameters —
|
|
14
|
+
* so a downgraded tool-calling request degrades rather than hard-erroring.
|
|
15
|
+
*/
|
|
16
|
+
export const ANVIL_DOWNGRADE_MODEL_ID = 'inclusionai/ling-3.0-tiny:free'
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Tool names we define that other agent harnesses also ship.
|
|
20
|
+
*
|
|
21
|
+
* Maintained as an EXCLUSION list, with the signature derived from it, because
|
|
22
|
+
* the inclusion list rotted: `researcher-web` offers exactly
|
|
23
|
+
* `['web_search', 'read_url']`, and a hand-picked signature that happened to
|
|
24
|
+
* omit both flagged 100% of its 334,042 requests from 4,821 users over 30 days.
|
|
25
|
+
* Any tool added to `toolNames` now joins the signature automatically, so the
|
|
26
|
+
* failure mode is a new *generic* name we forget to list here — which flags a
|
|
27
|
+
* third party we could already flag, rather than silently downgrading our own
|
|
28
|
+
* users.
|
|
29
|
+
*
|
|
30
|
+
* Each entry carries the third-party usage that justifies it, so this stays
|
|
31
|
+
* evidence rather than superstition.
|
|
32
|
+
*/
|
|
33
|
+
export const GENERIC_TOOL_NAMES: ReadonlySet<string> = new Set([
|
|
34
|
+
// Counts are distinct users, over 30 days, on requests carrying NO signature
|
|
35
|
+
// tool at all — i.e. unambiguously third-party harnesses. Anything without
|
|
36
|
+
// that evidence belongs in the signature: excluding a name we define costs us
|
|
37
|
+
// nothing against proxies and risks flagging whichever agent of ours uses it
|
|
38
|
+
// alone, which is exactly how researcher-web broke.
|
|
39
|
+
'write_file', // 3,372 users (Cline)
|
|
40
|
+
'web_search', // 3,273 users (opencode)
|
|
41
|
+
'glob', // 2,691 users (opencode, Claude Code ships `Glob`)
|
|
42
|
+
'skill', // 2,257 users
|
|
43
|
+
'apply_patch', // 1,137 users (Codex)
|
|
44
|
+
])
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Tools our own surfaces define outside `toolNames`, via
|
|
48
|
+
* `customToolDefinitions`. Anvil Desktop's autorun agent
|
|
49
|
+
* (anvil-desktop/src/server/services/autorun.ts) offers exactly `decide` and
|
|
50
|
+
* nothing else, so without this it had no signature tool at all and was flagged
|
|
51
|
+
* on 100% of its 2,904 requests from 41 users over 30 days.
|
|
52
|
+
*/
|
|
53
|
+
export const ANVIL_CUSTOM_TOOL_NAMES = ['decide'] as const
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Tool names that, on their own, mark a request as coming from one of our
|
|
57
|
+
* clients: everything we define that is not generic.
|
|
58
|
+
*
|
|
59
|
+
* The discriminator is the tool schema rather than the system prompt because
|
|
60
|
+
* the two are attacker-controlled in very different ways. A system prompt is
|
|
61
|
+
* free to copy — ours ships in the CLI and is recoverable from any response —
|
|
62
|
+
* so a prompt check is a speed bump. Tool schemas are not free to copy: a
|
|
63
|
+
* harness dispatches on the tool name the model returns, so sending ours means
|
|
64
|
+
* also executing ours and speaking our result format. Evading this check
|
|
65
|
+
* converges on behaving like a real client, which is the outcome we want.
|
|
66
|
+
*/
|
|
67
|
+
export const ANVIL_SIGNATURE_TOOL_NAMES: ReadonlySet<string> = new Set([
|
|
68
|
+
...(toolNames as readonly string[]).filter(
|
|
69
|
+
(name) => !GENERIC_TOOL_NAMES.has(name),
|
|
70
|
+
),
|
|
71
|
+
...ANVIL_CUSTOM_TOOL_NAMES,
|
|
72
|
+
])
|
|
73
|
+
|
|
74
|
+
export type ForeignClientSignal =
|
|
75
|
+
| 'foreign_toolset'
|
|
76
|
+
| 'root_agent_no_tools'
|
|
77
|
+
| 'sampling_params'
|
|
78
|
+
|
|
79
|
+
export type ForeignClientVerdict = {
|
|
80
|
+
/** Null when the request looks like it came from one of our clients. */
|
|
81
|
+
signal: ForeignClientSignal | null
|
|
82
|
+
toolCount: number
|
|
83
|
+
/** A few offered tool names, for the log line. Bounded so logs stay small. */
|
|
84
|
+
sampleToolNames: string[]
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
type InspectableRequest = {
|
|
88
|
+
tools?: unknown
|
|
89
|
+
temperature?: unknown
|
|
90
|
+
top_p?: unknown
|
|
91
|
+
max_tokens?: unknown
|
|
92
|
+
max_completion_tokens?: unknown
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Longest tool name kept for the log line. Names are caller-controlled, so an
|
|
96
|
+
* untruncated one is a log-flood vector; nothing legitimate is near this. */
|
|
97
|
+
const MAX_LOGGED_TOOL_NAME_LENGTH = 64
|
|
98
|
+
|
|
99
|
+
function readToolNames(tools: unknown): string[] {
|
|
100
|
+
if (!Array.isArray(tools)) return []
|
|
101
|
+
return tools
|
|
102
|
+
.map((tool) =>
|
|
103
|
+
typeof tool === 'object' && tool !== null
|
|
104
|
+
? (tool as { function?: { name?: unknown } }).function?.name
|
|
105
|
+
: undefined,
|
|
106
|
+
)
|
|
107
|
+
.filter((name): name is string => typeof name === 'string')
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Whether a free-mode request came from something other than a anvil client.
|
|
112
|
+
*
|
|
113
|
+
* Three signals, checked in a deliberate order:
|
|
114
|
+
*
|
|
115
|
+
* 1. The request offers tools and not one of them is distinctively ours.
|
|
116
|
+
* Measured over 24h of DeepSeek V4 Flash traffic: 557 users / 75,741
|
|
117
|
+
* requests.
|
|
118
|
+
* 2. The request offers NO tools and the agent is one of our roots, which are
|
|
119
|
+
* agentic by definition — a caller using a root agent id as a bare
|
|
120
|
+
* completion endpoint. Reported only; never enforced.
|
|
121
|
+
* 3. The request offers no tools and sets `temperature`, `top_p` or
|
|
122
|
+
* `max_tokens`. Our clients leave all three unset on 99.2% of requests.
|
|
123
|
+
* Reported only; never enforced.
|
|
124
|
+
*
|
|
125
|
+
* Signal 1 wins outright when it clears the request, and that ordering is the
|
|
126
|
+
* whole safety story rather than a detail: 16 users in the same window send our
|
|
127
|
+
* toolset *and* set sampling params (2,673 requests). Checking params first, or
|
|
128
|
+
* checking them independently, would downgrade those users. Anyone sending our
|
|
129
|
+
* tools is one of ours no matter what else the body says.
|
|
130
|
+
*/
|
|
131
|
+
export function detectForeignAnvilClient(
|
|
132
|
+
body: InspectableRequest,
|
|
133
|
+
/** The resolved agent id, when the caller has it. Root agents are agentic by
|
|
134
|
+
* definition, so one that offers no tools is not being driven by our client
|
|
135
|
+
* — see `root_agent_no_tools` below. */
|
|
136
|
+
isRootAgent = false,
|
|
137
|
+
): ForeignClientVerdict {
|
|
138
|
+
const offered = readToolNames(body.tools)
|
|
139
|
+
const sampleToolNames = offered
|
|
140
|
+
.slice(0, 8)
|
|
141
|
+
.map((name) => name.slice(0, MAX_LOGGED_TOOL_NAME_LENGTH))
|
|
142
|
+
|
|
143
|
+
if (offered.length > 0) {
|
|
144
|
+
const hasSignatureTool = offered.some((name) =>
|
|
145
|
+
ANVIL_SIGNATURE_TOOL_NAMES.has(name),
|
|
146
|
+
)
|
|
147
|
+
return {
|
|
148
|
+
signal: hasSignatureTool ? null : 'foreign_toolset',
|
|
149
|
+
toolCount: offered.length,
|
|
150
|
+
sampleToolNames,
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// A ROOT agent that offers no tools at all. Our roots always ship their
|
|
155
|
+
// toolset — the CI guard in foreign-client-shipped-agents.test.ts enforces
|
|
156
|
+
// that — so on its face this is a caller driving one of our root agent ids as
|
|
157
|
+
// a bare completion endpoint.
|
|
158
|
+
//
|
|
159
|
+
// An early hand-sample of 18 such users came back 18-for-18 non-coding
|
|
160
|
+
// automation — Shopee customer-service bots, Solana memecoin traders, RAG
|
|
161
|
+
// rephrasers, benchmark probes. DO NOT cite that as evidence for enforcing.
|
|
162
|
+
// It was drawn from a tail of roughly 5,000 users and the cohort analysis
|
|
163
|
+
// below shows it was not representative: 90% of the accounts the signal names
|
|
164
|
+
// also do real agentic work. An 18-for-18 result from an unrepresentative
|
|
165
|
+
// frame is what a biased sample looks like, not a strong one.
|
|
166
|
+
//
|
|
167
|
+
// REPORTED, NEVER ENFORCED, per the full 30-day backtest. Counting only root
|
|
168
|
+
// agents and excluding the paired
|
|
169
|
+
// assistant-response rows (they carry no tools by design and are half of all
|
|
170
|
+
// rows — miss that and every agent reads ~50% tool-free):
|
|
171
|
+
//
|
|
172
|
+
// base2-free-deepseek 4.46% 215,777 reqs 2,672 users
|
|
173
|
+
// base2-free-deepseek-flash 0.296% 103,726 reqs 2,281 users
|
|
174
|
+
// base2-free 0.90% 222 reqs 15 users
|
|
175
|
+
// anvil-desktop-thread-local 0.018% 2,400 reqs 11 users
|
|
176
|
+
//
|
|
177
|
+
// Only 417 of those users are 100% tool-free — actual bare-completion
|
|
178
|
+
// proxies. The other 3,729 mix tool-free requests into heavy real agentic
|
|
179
|
+
// traffic (bucket at <10% tool-free: 1,658 users over 1.25M requests), and
|
|
180
|
+
// 7,379 of their tool-free requests land INSIDE 999 sessions that also make
|
|
181
|
+
// tool-bearing root calls. Enforcing per-request would swap the model
|
|
182
|
+
// mid-session for real coding runs.
|
|
183
|
+
//
|
|
184
|
+
// Nor does run length separate the two: the longest consecutive tool-free run
|
|
185
|
+
// belongs to the MIXED cohort (11,094) and exceeds the pure proxies' longest
|
|
186
|
+
// (2,153), with 334 mixed users exceeding 50. There is no per-request
|
|
187
|
+
// threshold, so enforcement needs an account-level verdict this function
|
|
188
|
+
// cannot see. Note also that these requests all already reproduce our
|
|
189
|
+
// canonical root system prompt at position 0 — `requestHasAnvilSystemMarker`
|
|
190
|
+
// rejects root requests that do not — so the prompt is not a discriminator
|
|
191
|
+
// either.
|
|
192
|
+
if (isRootAgent) {
|
|
193
|
+
return {
|
|
194
|
+
signal: 'root_agent_no_tools',
|
|
195
|
+
toolCount: 0,
|
|
196
|
+
sampleToolNames,
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Only reached when no tools were offered at all, so this can never override
|
|
201
|
+
// the carve-out above.
|
|
202
|
+
//
|
|
203
|
+
// `!= null` deliberately, not `!== undefined`: a client that serializes its
|
|
204
|
+
// whole request sends `"temperature": null` rather than omitting the key, and
|
|
205
|
+
// that is unset, not a choice. Treating it as set downgraded every such
|
|
206
|
+
// caller. This matches how the rest of the request path already reads these
|
|
207
|
+
// fields — see `applyOpenRouterDefaultMaxTokens`, which gates on
|
|
208
|
+
// `body.max_tokens != null` for the same reason.
|
|
209
|
+
const setsSamplingParams =
|
|
210
|
+
body.temperature != null ||
|
|
211
|
+
body.top_p != null ||
|
|
212
|
+
body.max_tokens != null ||
|
|
213
|
+
body.max_completion_tokens != null
|
|
214
|
+
return {
|
|
215
|
+
signal: setsSamplingParams ? 'sampling_params' : null,
|
|
216
|
+
toolCount: 0,
|
|
217
|
+
sampleToolNames,
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export type ForeignClientDecision = ForeignClientVerdict & {
|
|
222
|
+
signal: ForeignClientSignal
|
|
223
|
+
/** The model to serve instead, or null to serve what was requested. */
|
|
224
|
+
downgradeTo: string | null
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Detect, then decide whether the signal changes what is served.
|
|
229
|
+
*
|
|
230
|
+
* `foreign_toolset` downgrades. Using a third-party client against this
|
|
231
|
+
* endpoint is a terms violation, not a grey area: Anvil funds free
|
|
232
|
+
* inference with ads that only our own clients render, so a proxied request
|
|
233
|
+
* takes the cost and returns none of the revenue.
|
|
234
|
+
*
|
|
235
|
+
* The other two are reported but never enforced, both because they fire on our
|
|
236
|
+
* own traffic:
|
|
237
|
+
*
|
|
238
|
+
* - `sampling_params` — 568 requests / 13 users on
|
|
239
|
+
* `code-reviewer-deepseek-flash` in a 24h sample, plus the CLI's own
|
|
240
|
+
* free-mode shape, which sends `max_completion_tokens` with no tools. It
|
|
241
|
+
* now only reports NON-root agents: `root_agent_no_tools` is checked first,
|
|
242
|
+
* so the 8,884 requests / 395 users this used to cite on
|
|
243
|
+
* `base2-free-deepseek-flash` classify under that signal instead. Neither
|
|
244
|
+
* enforces, so the reclassification changes measurement, not behavior.
|
|
245
|
+
* - `root_agent_no_tools` — 3,729 users who also do real agentic work, 999 of
|
|
246
|
+
* whose sessions mix it with tool-bearing root calls. See the backtest in
|
|
247
|
+
* `detectForeignAnvilClient`. Catching the 417 genuine proxies inside
|
|
248
|
+
* that population needs an account-level verdict, not a per-request one.
|
|
249
|
+
*
|
|
250
|
+
* They stay as measurements, which is what makes the account-level rule
|
|
251
|
+
* buildable later without guessing at its blast radius.
|
|
252
|
+
*/
|
|
253
|
+
export function resolveForeignClientDowngrade(params: {
|
|
254
|
+
body: InspectableRequest & { model?: unknown }
|
|
255
|
+
isRootAgent?: boolean
|
|
256
|
+
}): ForeignClientDecision | null {
|
|
257
|
+
const { body, isRootAgent = false } = params
|
|
258
|
+
const verdict = detectForeignAnvilClient(body, isRootAgent)
|
|
259
|
+
if (!verdict.signal) return null
|
|
260
|
+
|
|
261
|
+
return {
|
|
262
|
+
...verdict,
|
|
263
|
+
signal: verdict.signal,
|
|
264
|
+
// Never downgrade something already on the downgrade model: that would be
|
|
265
|
+
// a no-op write that still reads as an enforcement in the logs.
|
|
266
|
+
downgradeTo:
|
|
267
|
+
verdict.signal === 'foreign_toolset' &&
|
|
268
|
+
body.model !== ANVIL_DOWNGRADE_MODEL_ID
|
|
269
|
+
? ANVIL_DOWNGRADE_MODEL_ID
|
|
270
|
+
: null,
|
|
271
|
+
}
|
|
272
|
+
}
|