@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,774 @@
|
|
|
1
|
+
import { describe, expect, it } from 'bun:test'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
ABORT_ERROR_MESSAGE,
|
|
5
|
+
AbortError,
|
|
6
|
+
isAbortError,
|
|
7
|
+
promptAborted,
|
|
8
|
+
promptSuccess,
|
|
9
|
+
unwrapPromptResult,
|
|
10
|
+
type PromptResult,
|
|
11
|
+
} from '../error'
|
|
12
|
+
|
|
13
|
+
describe('AbortError class', () => {
|
|
14
|
+
describe('constructor', () => {
|
|
15
|
+
it('creates error without reason', () => {
|
|
16
|
+
const error = new AbortError()
|
|
17
|
+
expect(error.message).toBe(ABORT_ERROR_MESSAGE)
|
|
18
|
+
expect(error.name).toBe('AbortError')
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('creates error with reason', () => {
|
|
22
|
+
const error = new AbortError('User cancelled')
|
|
23
|
+
expect(error.message).toBe(`${ABORT_ERROR_MESSAGE}: User cancelled`)
|
|
24
|
+
expect(error.name).toBe('AbortError')
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('creates error with empty string reason', () => {
|
|
28
|
+
const error = new AbortError('')
|
|
29
|
+
// Empty string is falsy, so no reason appended
|
|
30
|
+
expect(error.message).toBe(ABORT_ERROR_MESSAGE)
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('is instanceof Error', () => {
|
|
34
|
+
const error = new AbortError()
|
|
35
|
+
expect(error instanceof Error).toBe(true)
|
|
36
|
+
expect(error instanceof AbortError).toBe(true)
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('has stack trace', () => {
|
|
40
|
+
const error = new AbortError('test')
|
|
41
|
+
expect(error.stack).toBeDefined()
|
|
42
|
+
expect(error.stack).toContain('AbortError')
|
|
43
|
+
})
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
describe('message format', () => {
|
|
47
|
+
it('reason is appended after colon and space', () => {
|
|
48
|
+
const error = new AbortError('timeout')
|
|
49
|
+
expect(error.message).toBe('Request aborted: timeout')
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('preserves special characters in reason', () => {
|
|
53
|
+
const error = new AbortError('User pressed Ctrl+C')
|
|
54
|
+
expect(error.message).toBe('Request aborted: User pressed Ctrl+C')
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('handles multi-line reason', () => {
|
|
58
|
+
const error = new AbortError('First line\nSecond line')
|
|
59
|
+
expect(error.message).toBe('Request aborted: First line\nSecond line')
|
|
60
|
+
})
|
|
61
|
+
})
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
describe('isAbortError edge cases', () => {
|
|
65
|
+
describe('message matching with startsWith', () => {
|
|
66
|
+
it('returns true for exact ABORT_ERROR_MESSAGE', () => {
|
|
67
|
+
const error = new Error(ABORT_ERROR_MESSAGE)
|
|
68
|
+
expect(isAbortError(error)).toBe(true)
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('returns true for message with suffix after ABORT_ERROR_MESSAGE (like AbortError with reason)', () => {
|
|
72
|
+
// This is the format AbortError uses: 'Request aborted: reason'
|
|
73
|
+
const error = new Error(`${ABORT_ERROR_MESSAGE}: timeout`)
|
|
74
|
+
expect(isAbortError(error)).toBe(true)
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
it('returns false for message with non-colon suffix after ABORT_ERROR_MESSAGE', () => {
|
|
78
|
+
// Only 'Request aborted' or 'Request aborted: <reason>' should match
|
|
79
|
+
// Other patterns like 'Request aborted by user' should NOT match
|
|
80
|
+
const error = new Error(`${ABORT_ERROR_MESSAGE} due to user action`)
|
|
81
|
+
expect(isAbortError(error)).toBe(false)
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it('returns false for message containing ABORT_ERROR_MESSAGE as substring (not prefix)', () => {
|
|
85
|
+
const error = new Error(`Error: ${ABORT_ERROR_MESSAGE} by system`)
|
|
86
|
+
expect(isAbortError(error)).toBe(false)
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it('returns false for message with prefix before ABORT_ERROR_MESSAGE', () => {
|
|
90
|
+
const error = new Error(`Something failed: ${ABORT_ERROR_MESSAGE}`)
|
|
91
|
+
expect(isAbortError(error)).toBe(false)
|
|
92
|
+
})
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
describe('case sensitivity', () => {
|
|
96
|
+
it('returns false for lowercase version of message', () => {
|
|
97
|
+
const error = new Error('request aborted')
|
|
98
|
+
expect(isAbortError(error)).toBe(false)
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
it('returns false for uppercase version of message', () => {
|
|
102
|
+
const error = new Error('REQUEST ABORTED')
|
|
103
|
+
expect(isAbortError(error)).toBe(false)
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
it('returns false for mixed case version of message', () => {
|
|
107
|
+
const error = new Error('Request Aborted')
|
|
108
|
+
expect(isAbortError(error)).toBe(false)
|
|
109
|
+
})
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
describe('AbortError name detection', () => {
|
|
113
|
+
it('returns true for Error with name set to AbortError', () => {
|
|
114
|
+
const error = new Error('Some other message')
|
|
115
|
+
error.name = 'AbortError'
|
|
116
|
+
expect(isAbortError(error)).toBe(true)
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
it('returns false for name containing AbortError as substring', () => {
|
|
120
|
+
const error = new Error('test')
|
|
121
|
+
error.name = 'MyAbortErrorClass'
|
|
122
|
+
expect(isAbortError(error)).toBe(false)
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
it('returns false for lowercase aborterror name', () => {
|
|
126
|
+
const error = new Error('test')
|
|
127
|
+
error.name = 'aborterror'
|
|
128
|
+
expect(isAbortError(error)).toBe(false)
|
|
129
|
+
})
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
describe('DOMException handling', () => {
|
|
133
|
+
it('returns true for DOMException with name AbortError', () => {
|
|
134
|
+
const error = new DOMException('The operation was aborted', 'AbortError')
|
|
135
|
+
expect(isAbortError(error)).toBe(true)
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
it('returns true for DOMException with signal abort message', () => {
|
|
139
|
+
const error = new DOMException(
|
|
140
|
+
'signal is aborted without reason',
|
|
141
|
+
'AbortError',
|
|
142
|
+
)
|
|
143
|
+
expect(isAbortError(error)).toBe(true)
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
it('returns false for DOMException with different name', () => {
|
|
147
|
+
const error = new DOMException('test', 'NotFoundError')
|
|
148
|
+
expect(isAbortError(error)).toBe(false)
|
|
149
|
+
})
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
describe('Error subclasses', () => {
|
|
153
|
+
it('returns true for AbortError instance', () => {
|
|
154
|
+
const error = new AbortError('test reason')
|
|
155
|
+
expect(isAbortError(error)).toBe(true)
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
it('returns true for TypeError with AbortError name', () => {
|
|
159
|
+
const error = new TypeError('test')
|
|
160
|
+
error.name = 'AbortError'
|
|
161
|
+
expect(isAbortError(error)).toBe(true)
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
it('returns false for custom error class without AbortError characteristics', () => {
|
|
165
|
+
class CustomError extends Error {
|
|
166
|
+
constructor(message: string) {
|
|
167
|
+
super(message)
|
|
168
|
+
this.name = 'CustomError'
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
// Note: Using a message that's similar but NOT exact match to ABORT_ERROR_MESSAGE
|
|
172
|
+
const error = new CustomError('Request was aborted by user')
|
|
173
|
+
expect(isAbortError(error)).toBe(false)
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
it('returns true for custom error class with AbortError name', () => {
|
|
177
|
+
class MyAbortError extends Error {
|
|
178
|
+
constructor() {
|
|
179
|
+
super('custom message')
|
|
180
|
+
this.name = 'AbortError'
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
const error = new MyAbortError()
|
|
184
|
+
expect(isAbortError(error)).toBe(true)
|
|
185
|
+
})
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
describe('non-Error types', () => {
|
|
189
|
+
it('returns false for string', () => {
|
|
190
|
+
expect(isAbortError(ABORT_ERROR_MESSAGE)).toBe(false)
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
it('returns false for object with message property', () => {
|
|
194
|
+
expect(isAbortError({ message: ABORT_ERROR_MESSAGE })).toBe(false)
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
it('returns false for object with name property', () => {
|
|
198
|
+
expect(isAbortError({ name: 'AbortError' })).toBe(false)
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
it('returns false for null', () => {
|
|
202
|
+
expect(isAbortError(null)).toBe(false)
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
it('returns false for undefined', () => {
|
|
206
|
+
expect(isAbortError(undefined)).toBe(false)
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
it('returns false for number', () => {
|
|
210
|
+
expect(isAbortError(42)).toBe(false)
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
it('returns false for array', () => {
|
|
214
|
+
expect(isAbortError([ABORT_ERROR_MESSAGE])).toBe(false)
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
it('returns false for function', () => {
|
|
218
|
+
expect(isAbortError(() => ABORT_ERROR_MESSAGE)).toBe(false)
|
|
219
|
+
})
|
|
220
|
+
})
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
describe('unwrapPromptResult with AbortError', () => {
|
|
224
|
+
describe('successful results', () => {
|
|
225
|
+
it('returns value for successful result', () => {
|
|
226
|
+
const result = promptSuccess('test value')
|
|
227
|
+
expect(unwrapPromptResult(result)).toBe('test value')
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
it('returns null for successful null result', () => {
|
|
231
|
+
const result = promptSuccess(null)
|
|
232
|
+
expect(unwrapPromptResult(result)).toBeNull()
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
it('returns undefined for successful undefined result', () => {
|
|
236
|
+
const result = promptSuccess(undefined)
|
|
237
|
+
expect(unwrapPromptResult(result)).toBeUndefined()
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
it('returns complex object for successful result', () => {
|
|
241
|
+
const value = { nested: { array: [1, 2, 3] } }
|
|
242
|
+
const result = promptSuccess(value)
|
|
243
|
+
expect(unwrapPromptResult(result)).toEqual(value)
|
|
244
|
+
})
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
describe('aborted results throw AbortError', () => {
|
|
248
|
+
it('throws AbortError instance', () => {
|
|
249
|
+
const result = promptAborted()
|
|
250
|
+
try {
|
|
251
|
+
unwrapPromptResult(result)
|
|
252
|
+
expect(true).toBe(false) // Should not reach here
|
|
253
|
+
} catch (error) {
|
|
254
|
+
expect(error instanceof AbortError).toBe(true)
|
|
255
|
+
}
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
it('thrown error has name AbortError', () => {
|
|
259
|
+
const result = promptAborted()
|
|
260
|
+
try {
|
|
261
|
+
unwrapPromptResult(result)
|
|
262
|
+
expect(true).toBe(false)
|
|
263
|
+
} catch (error) {
|
|
264
|
+
expect((error as Error).name).toBe('AbortError')
|
|
265
|
+
}
|
|
266
|
+
})
|
|
267
|
+
|
|
268
|
+
it('thrown error includes reason in message', () => {
|
|
269
|
+
const result = promptAborted('User cancelled')
|
|
270
|
+
try {
|
|
271
|
+
unwrapPromptResult(result)
|
|
272
|
+
expect(true).toBe(false)
|
|
273
|
+
} catch (error) {
|
|
274
|
+
expect((error as Error).message).toBe('Request aborted: User cancelled')
|
|
275
|
+
}
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
it('thrown error is detectable with isAbortError', () => {
|
|
279
|
+
const result = promptAborted()
|
|
280
|
+
try {
|
|
281
|
+
unwrapPromptResult(result)
|
|
282
|
+
expect(true).toBe(false)
|
|
283
|
+
} catch (error) {
|
|
284
|
+
expect(isAbortError(error)).toBe(true)
|
|
285
|
+
}
|
|
286
|
+
})
|
|
287
|
+
|
|
288
|
+
it('thrown error with reason is detectable with isAbortError', () => {
|
|
289
|
+
const result = promptAborted('timeout')
|
|
290
|
+
try {
|
|
291
|
+
unwrapPromptResult(result)
|
|
292
|
+
expect(true).toBe(false)
|
|
293
|
+
} catch (error) {
|
|
294
|
+
expect(isAbortError(error)).toBe(true)
|
|
295
|
+
}
|
|
296
|
+
})
|
|
297
|
+
})
|
|
298
|
+
})
|
|
299
|
+
|
|
300
|
+
describe('PromptResult integration patterns', () => {
|
|
301
|
+
describe('early return pattern', () => {
|
|
302
|
+
async function mockLlmCall(shouldAbort: boolean): Promise<PromptResult<string>> {
|
|
303
|
+
if (shouldAbort) {
|
|
304
|
+
return promptAborted('User cancelled')
|
|
305
|
+
}
|
|
306
|
+
return promptSuccess('LLM response')
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
async function callerWithEarlyReturn(shouldAbort: boolean): Promise<string | null> {
|
|
310
|
+
const result = await mockLlmCall(shouldAbort)
|
|
311
|
+
if (result.aborted) {
|
|
312
|
+
return null
|
|
313
|
+
}
|
|
314
|
+
return result.value.toUpperCase()
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
it('returns transformed value on success', async () => {
|
|
318
|
+
const result = await callerWithEarlyReturn(false)
|
|
319
|
+
expect(result).toBe('LLM RESPONSE')
|
|
320
|
+
})
|
|
321
|
+
|
|
322
|
+
it('returns null on abort', async () => {
|
|
323
|
+
const result = await callerWithEarlyReturn(true)
|
|
324
|
+
expect(result).toBeNull()
|
|
325
|
+
})
|
|
326
|
+
})
|
|
327
|
+
|
|
328
|
+
describe('unwrap with try/catch pattern', () => {
|
|
329
|
+
async function mockLlmCall(shouldAbort: boolean): Promise<PromptResult<string>> {
|
|
330
|
+
if (shouldAbort) {
|
|
331
|
+
return promptAborted('Signal triggered')
|
|
332
|
+
}
|
|
333
|
+
return promptSuccess('Success response')
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
async function callerWithUnwrap(shouldAbort: boolean): Promise<string> {
|
|
337
|
+
return unwrapPromptResult(await mockLlmCall(shouldAbort))
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
async function outerCaller(shouldAbort: boolean): Promise<{ result: string; wasAborted: boolean }> {
|
|
341
|
+
try {
|
|
342
|
+
const result = await callerWithUnwrap(shouldAbort)
|
|
343
|
+
return { result, wasAborted: false }
|
|
344
|
+
} catch (error) {
|
|
345
|
+
if (isAbortError(error)) {
|
|
346
|
+
return { result: '', wasAborted: true }
|
|
347
|
+
}
|
|
348
|
+
throw error // Rethrow non-abort errors
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
it('returns result on success', async () => {
|
|
353
|
+
const { result, wasAborted } = await outerCaller(false)
|
|
354
|
+
expect(result).toBe('Success response')
|
|
355
|
+
expect(wasAborted).toBe(false)
|
|
356
|
+
})
|
|
357
|
+
|
|
358
|
+
it('catches and identifies abort', async () => {
|
|
359
|
+
const { result, wasAborted } = await outerCaller(true)
|
|
360
|
+
expect(result).toBe('')
|
|
361
|
+
expect(wasAborted).toBe(true)
|
|
362
|
+
})
|
|
363
|
+
})
|
|
364
|
+
|
|
365
|
+
describe('nested function abort propagation', () => {
|
|
366
|
+
async function deepestCall(signal: { aborted: boolean }): Promise<PromptResult<number>> {
|
|
367
|
+
if (signal.aborted) {
|
|
368
|
+
return promptAborted('Aborted at deepest level')
|
|
369
|
+
}
|
|
370
|
+
return promptSuccess(42)
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
async function middleCall(signal: { aborted: boolean }): Promise<PromptResult<string>> {
|
|
374
|
+
const result = await deepestCall(signal)
|
|
375
|
+
if (result.aborted) {
|
|
376
|
+
return result // Propagate abort
|
|
377
|
+
}
|
|
378
|
+
return promptSuccess(`Value: ${result.value}`)
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
async function topCall(signal: { aborted: boolean }): Promise<PromptResult<string[]>> {
|
|
382
|
+
const result = await middleCall(signal)
|
|
383
|
+
if (result.aborted) {
|
|
384
|
+
return result // Propagate abort
|
|
385
|
+
}
|
|
386
|
+
return promptSuccess([result.value, 'additional'])
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
it('propagates success through all levels', async () => {
|
|
390
|
+
const signal = { aborted: false }
|
|
391
|
+
const result = await topCall(signal)
|
|
392
|
+
expect(result.aborted).toBe(false)
|
|
393
|
+
if (!result.aborted) {
|
|
394
|
+
expect(result.value).toEqual(['Value: 42', 'additional'])
|
|
395
|
+
}
|
|
396
|
+
})
|
|
397
|
+
|
|
398
|
+
it('propagates abort from deepest level', async () => {
|
|
399
|
+
const signal = { aborted: true }
|
|
400
|
+
const result = await topCall(signal)
|
|
401
|
+
expect(result.aborted).toBe(true)
|
|
402
|
+
if (result.aborted) {
|
|
403
|
+
expect(result.reason).toBe('Aborted at deepest level')
|
|
404
|
+
}
|
|
405
|
+
})
|
|
406
|
+
})
|
|
407
|
+
|
|
408
|
+
describe('mixed pattern with fallback', () => {
|
|
409
|
+
async function primaryProvider(signal: { aborted: boolean }): Promise<PromptResult<string>> {
|
|
410
|
+
if (signal.aborted) {
|
|
411
|
+
return promptAborted()
|
|
412
|
+
}
|
|
413
|
+
// Simulate primary provider failure
|
|
414
|
+
throw new Error('Primary provider unavailable')
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
async function fallbackProvider(signal: { aborted: boolean }): Promise<PromptResult<string>> {
|
|
418
|
+
if (signal.aborted) {
|
|
419
|
+
return promptAborted()
|
|
420
|
+
}
|
|
421
|
+
return promptSuccess('Fallback result')
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
async function callWithFallback(signal: { aborted: boolean }): Promise<PromptResult<string>> {
|
|
425
|
+
try {
|
|
426
|
+
const result = await primaryProvider(signal)
|
|
427
|
+
// If aborted, don't try fallback
|
|
428
|
+
if (result.aborted) {
|
|
429
|
+
return result
|
|
430
|
+
}
|
|
431
|
+
return result
|
|
432
|
+
} catch (error) {
|
|
433
|
+
// Don't fall back on abort errors
|
|
434
|
+
if (isAbortError(error)) {
|
|
435
|
+
throw error
|
|
436
|
+
}
|
|
437
|
+
// Try fallback for other errors
|
|
438
|
+
return fallbackProvider(signal)
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
it('uses fallback on non-abort error', async () => {
|
|
443
|
+
const signal = { aborted: false }
|
|
444
|
+
const result = await callWithFallback(signal)
|
|
445
|
+
expect(result.aborted).toBe(false)
|
|
446
|
+
if (!result.aborted) {
|
|
447
|
+
expect(result.value).toBe('Fallback result')
|
|
448
|
+
}
|
|
449
|
+
})
|
|
450
|
+
|
|
451
|
+
it('does not use fallback on abort', async () => {
|
|
452
|
+
const signal = { aborted: true }
|
|
453
|
+
const result = await callWithFallback(signal)
|
|
454
|
+
expect(result.aborted).toBe(true)
|
|
455
|
+
})
|
|
456
|
+
})
|
|
457
|
+
|
|
458
|
+
describe('abort during async iteration', () => {
|
|
459
|
+
async function* generateValues(signal: { aborted: boolean }): AsyncGenerator<PromptResult<number>> {
|
|
460
|
+
for (let i = 0; i < 5; i++) {
|
|
461
|
+
if (signal.aborted) {
|
|
462
|
+
yield promptAborted(`Aborted at iteration ${i}`)
|
|
463
|
+
return
|
|
464
|
+
}
|
|
465
|
+
yield promptSuccess(i)
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
async function collectValues(signal: { aborted: boolean }): Promise<{ values: number[]; abortedAt?: string }> {
|
|
470
|
+
const values: number[] = []
|
|
471
|
+
for await (const result of generateValues(signal)) {
|
|
472
|
+
if (result.aborted) {
|
|
473
|
+
return { values, abortedAt: result.reason }
|
|
474
|
+
}
|
|
475
|
+
values.push(result.value)
|
|
476
|
+
}
|
|
477
|
+
return { values }
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
it('collects all values when not aborted', async () => {
|
|
481
|
+
const signal = { aborted: false }
|
|
482
|
+
const { values, abortedAt } = await collectValues(signal)
|
|
483
|
+
expect(values).toEqual([0, 1, 2, 3, 4])
|
|
484
|
+
expect(abortedAt).toBeUndefined()
|
|
485
|
+
})
|
|
486
|
+
|
|
487
|
+
it('stops iteration on abort', async () => {
|
|
488
|
+
const signal = { aborted: false }
|
|
489
|
+
// Simulate abort after first value
|
|
490
|
+
const generator = generateValues(signal)
|
|
491
|
+
const results: number[] = []
|
|
492
|
+
|
|
493
|
+
for await (const result of generator) {
|
|
494
|
+
if (result.aborted) break
|
|
495
|
+
results.push(result.value)
|
|
496
|
+
if (results.length === 2) {
|
|
497
|
+
signal.aborted = true
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
expect(results).toEqual([0, 1])
|
|
502
|
+
})
|
|
503
|
+
})
|
|
504
|
+
|
|
505
|
+
describe('rethrow pattern in catch blocks', () => {
|
|
506
|
+
async function innerOperation(): Promise<PromptResult<string>> {
|
|
507
|
+
return promptAborted('Inner abort')
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
async function middleOperation(): Promise<string> {
|
|
511
|
+
const result = await innerOperation()
|
|
512
|
+
return unwrapPromptResult(result)
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
async function outerOperationBad(): Promise<string> {
|
|
516
|
+
try {
|
|
517
|
+
return await middleOperation()
|
|
518
|
+
} catch (error) {
|
|
519
|
+
// BAD: swallows abort error
|
|
520
|
+
return 'default value'
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
async function outerOperationGood(): Promise<string> {
|
|
525
|
+
try {
|
|
526
|
+
return await middleOperation()
|
|
527
|
+
} catch (error) {
|
|
528
|
+
// GOOD: rethrows abort error
|
|
529
|
+
if (isAbortError(error)) {
|
|
530
|
+
throw error
|
|
531
|
+
}
|
|
532
|
+
return 'default value'
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
it('bad pattern swallows abort', async () => {
|
|
537
|
+
const result = await outerOperationBad()
|
|
538
|
+
// This shows the anti-pattern - abort was swallowed
|
|
539
|
+
expect(result).toBe('default value')
|
|
540
|
+
})
|
|
541
|
+
|
|
542
|
+
it('good pattern propagates abort', async () => {
|
|
543
|
+
await expect(outerOperationGood()).rejects.toThrow(ABORT_ERROR_MESSAGE)
|
|
544
|
+
})
|
|
545
|
+
|
|
546
|
+
it('good pattern rethrows AbortError that can be detected', async () => {
|
|
547
|
+
try {
|
|
548
|
+
await outerOperationGood()
|
|
549
|
+
expect(true).toBe(false) // Should not reach
|
|
550
|
+
} catch (error) {
|
|
551
|
+
expect(isAbortError(error)).toBe(true)
|
|
552
|
+
}
|
|
553
|
+
})
|
|
554
|
+
})
|
|
555
|
+
})
|
|
556
|
+
|
|
557
|
+
describe('ABORT_ERROR_MESSAGE constant', () => {
|
|
558
|
+
it('has expected value', () => {
|
|
559
|
+
expect(ABORT_ERROR_MESSAGE).toBe('Request aborted')
|
|
560
|
+
})
|
|
561
|
+
|
|
562
|
+
it('is used by AbortError class', () => {
|
|
563
|
+
const error = new AbortError()
|
|
564
|
+
expect(error.message).toBe(ABORT_ERROR_MESSAGE)
|
|
565
|
+
})
|
|
566
|
+
|
|
567
|
+
it('is detected by isAbortError', () => {
|
|
568
|
+
const error = new Error(ABORT_ERROR_MESSAGE)
|
|
569
|
+
expect(isAbortError(error)).toBe(true)
|
|
570
|
+
})
|
|
571
|
+
})
|
|
572
|
+
|
|
573
|
+
describe('AbortController integration', () => {
|
|
574
|
+
describe('signal.aborted check pattern', () => {
|
|
575
|
+
async function mockLlmCallWithSignal(signal: AbortSignal): Promise<PromptResult<string>> {
|
|
576
|
+
if (signal.aborted) {
|
|
577
|
+
return promptAborted('Signal was already aborted')
|
|
578
|
+
}
|
|
579
|
+
// Simulate async work
|
|
580
|
+
await new Promise((resolve) => setTimeout(resolve, 0))
|
|
581
|
+
if (signal.aborted) {
|
|
582
|
+
return promptAborted('Signal aborted during operation')
|
|
583
|
+
}
|
|
584
|
+
return promptSuccess('Operation completed')
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
it('returns success when signal is not aborted', async () => {
|
|
588
|
+
const controller = new AbortController()
|
|
589
|
+
const result = await mockLlmCallWithSignal(controller.signal)
|
|
590
|
+
expect(result.aborted).toBe(false)
|
|
591
|
+
if (!result.aborted) {
|
|
592
|
+
expect(result.value).toBe('Operation completed')
|
|
593
|
+
}
|
|
594
|
+
})
|
|
595
|
+
|
|
596
|
+
it('returns aborted when signal is pre-aborted', async () => {
|
|
597
|
+
const controller = new AbortController()
|
|
598
|
+
controller.abort()
|
|
599
|
+
const result = await mockLlmCallWithSignal(controller.signal)
|
|
600
|
+
expect(result.aborted).toBe(true)
|
|
601
|
+
if (result.aborted) {
|
|
602
|
+
expect(result.reason).toBe('Signal was already aborted')
|
|
603
|
+
}
|
|
604
|
+
})
|
|
605
|
+
})
|
|
606
|
+
|
|
607
|
+
describe('sequential operations with abort', () => {
|
|
608
|
+
const callLog: string[] = []
|
|
609
|
+
|
|
610
|
+
async function step1(signal: AbortSignal): Promise<PromptResult<string>> {
|
|
611
|
+
callLog.push('step1')
|
|
612
|
+
if (signal.aborted) return promptAborted('step1 aborted')
|
|
613
|
+
return promptSuccess('step1 result')
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
async function step2(signal: AbortSignal): Promise<PromptResult<string>> {
|
|
617
|
+
callLog.push('step2')
|
|
618
|
+
if (signal.aborted) return promptAborted('step2 aborted')
|
|
619
|
+
return promptSuccess('step2 result')
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
async function step3(signal: AbortSignal): Promise<PromptResult<string>> {
|
|
623
|
+
callLog.push('step3')
|
|
624
|
+
if (signal.aborted) return promptAborted('step3 aborted')
|
|
625
|
+
return promptSuccess('step3 result')
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
async function runSequentialSteps(signal: AbortSignal): Promise<PromptResult<string[]>> {
|
|
629
|
+
const results: string[] = []
|
|
630
|
+
|
|
631
|
+
const r1 = await step1(signal)
|
|
632
|
+
if (r1.aborted) return r1
|
|
633
|
+
results.push(r1.value)
|
|
634
|
+
|
|
635
|
+
const r2 = await step2(signal)
|
|
636
|
+
if (r2.aborted) return r2
|
|
637
|
+
results.push(r2.value)
|
|
638
|
+
|
|
639
|
+
const r3 = await step3(signal)
|
|
640
|
+
if (r3.aborted) return r3
|
|
641
|
+
results.push(r3.value)
|
|
642
|
+
|
|
643
|
+
return promptSuccess(results)
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
it('completes all steps when not aborted', async () => {
|
|
647
|
+
callLog.length = 0
|
|
648
|
+
const controller = new AbortController()
|
|
649
|
+
const result = await runSequentialSteps(controller.signal)
|
|
650
|
+
expect(result.aborted).toBe(false)
|
|
651
|
+
if (!result.aborted) {
|
|
652
|
+
expect(result.value).toEqual(['step1 result', 'step2 result', 'step3 result'])
|
|
653
|
+
}
|
|
654
|
+
expect(callLog).toEqual(['step1', 'step2', 'step3'])
|
|
655
|
+
})
|
|
656
|
+
|
|
657
|
+
it('stops at first step when pre-aborted', async () => {
|
|
658
|
+
callLog.length = 0
|
|
659
|
+
const controller = new AbortController()
|
|
660
|
+
controller.abort()
|
|
661
|
+
const result = await runSequentialSteps(controller.signal)
|
|
662
|
+
expect(result.aborted).toBe(true)
|
|
663
|
+
// Only step1 should be called, and it should return aborted immediately
|
|
664
|
+
expect(callLog).toEqual(['step1'])
|
|
665
|
+
})
|
|
666
|
+
})
|
|
667
|
+
|
|
668
|
+
describe('fallback should NOT occur on abort (user intent)', () => {
|
|
669
|
+
let fallbackCalled = false
|
|
670
|
+
|
|
671
|
+
async function primaryModel(signal: AbortSignal): Promise<PromptResult<string>> {
|
|
672
|
+
if (signal.aborted) {
|
|
673
|
+
return promptAborted('User cancelled')
|
|
674
|
+
}
|
|
675
|
+
return promptSuccess('Primary model response')
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
async function fallbackModel(signal: AbortSignal): Promise<PromptResult<string>> {
|
|
679
|
+
fallbackCalled = true
|
|
680
|
+
if (signal.aborted) {
|
|
681
|
+
return promptAborted('User cancelled')
|
|
682
|
+
}
|
|
683
|
+
return promptSuccess('Fallback model response')
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
async function callWithFallbackOnError(
|
|
687
|
+
signal: AbortSignal,
|
|
688
|
+
primaryShouldThrowError: boolean,
|
|
689
|
+
primaryShouldAbort: boolean,
|
|
690
|
+
): Promise<PromptResult<string>> {
|
|
691
|
+
try {
|
|
692
|
+
if (primaryShouldThrowError) {
|
|
693
|
+
throw new Error('Primary provider unavailable')
|
|
694
|
+
}
|
|
695
|
+
const primaryResult = primaryShouldAbort
|
|
696
|
+
? promptAborted('User cancelled primary')
|
|
697
|
+
: await primaryModel(signal)
|
|
698
|
+
|
|
699
|
+
// Key pattern: if aborted, do NOT fall back - abort represents user intent
|
|
700
|
+
if (primaryResult.aborted) {
|
|
701
|
+
return primaryResult
|
|
702
|
+
}
|
|
703
|
+
return primaryResult
|
|
704
|
+
} catch (error) {
|
|
705
|
+
// Don't fall back on abort errors
|
|
706
|
+
if (isAbortError(error)) {
|
|
707
|
+
throw error
|
|
708
|
+
}
|
|
709
|
+
// Try fallback for other errors
|
|
710
|
+
return fallbackModel(signal)
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
it('returns primary result when not aborted', async () => {
|
|
715
|
+
fallbackCalled = false
|
|
716
|
+
const controller = new AbortController()
|
|
717
|
+
const result = await callWithFallbackOnError(controller.signal, false, false)
|
|
718
|
+
expect(result.aborted).toBe(false)
|
|
719
|
+
if (!result.aborted) {
|
|
720
|
+
expect(result.value).toBe('Primary model response')
|
|
721
|
+
}
|
|
722
|
+
expect(fallbackCalled).toBe(false)
|
|
723
|
+
})
|
|
724
|
+
|
|
725
|
+
it('propagates abort without fallback (respects user intent)', async () => {
|
|
726
|
+
fallbackCalled = false
|
|
727
|
+
const controller = new AbortController()
|
|
728
|
+
const result = await callWithFallbackOnError(controller.signal, false, true)
|
|
729
|
+
expect(result.aborted).toBe(true)
|
|
730
|
+
// Verify fallback was never called - abort means user wants to stop, not retry
|
|
731
|
+
expect(fallbackCalled).toBe(false)
|
|
732
|
+
})
|
|
733
|
+
|
|
734
|
+
it('uses fallback on non-abort error', async () => {
|
|
735
|
+
fallbackCalled = false
|
|
736
|
+
const controller = new AbortController()
|
|
737
|
+
const result = await callWithFallbackOnError(controller.signal, true, false)
|
|
738
|
+
expect(result.aborted).toBe(false)
|
|
739
|
+
if (!result.aborted) {
|
|
740
|
+
expect(result.value).toBe('Fallback model response')
|
|
741
|
+
}
|
|
742
|
+
// Verify fallback WAS called for non-abort error
|
|
743
|
+
expect(fallbackCalled).toBe(true)
|
|
744
|
+
})
|
|
745
|
+
})
|
|
746
|
+
|
|
747
|
+
describe('DOMException from AbortController', () => {
|
|
748
|
+
it('native abort reason is detected by isAbortError', () => {
|
|
749
|
+
const controller = new AbortController()
|
|
750
|
+
controller.abort()
|
|
751
|
+
// When you call controller.abort(), signal.reason becomes a DOMException
|
|
752
|
+
// with name 'AbortError'
|
|
753
|
+
const reason = controller.signal.reason
|
|
754
|
+
expect(reason).toBeInstanceOf(DOMException)
|
|
755
|
+
expect(isAbortError(reason)).toBe(true)
|
|
756
|
+
})
|
|
757
|
+
|
|
758
|
+
it('custom abort reason string is not detected as AbortError', () => {
|
|
759
|
+
const controller = new AbortController()
|
|
760
|
+
controller.abort('custom reason string')
|
|
761
|
+
// When you provide a reason, signal.reason is that value, not a DOMException
|
|
762
|
+
const reason = controller.signal.reason
|
|
763
|
+
expect(isAbortError(reason)).toBe(false) // string is not an Error
|
|
764
|
+
})
|
|
765
|
+
|
|
766
|
+
it('custom abort reason Error with AbortError name is detected', () => {
|
|
767
|
+
const controller = new AbortController()
|
|
768
|
+
const customAbortError = new AbortError('custom abort')
|
|
769
|
+
controller.abort(customAbortError)
|
|
770
|
+
const reason = controller.signal.reason
|
|
771
|
+
expect(isAbortError(reason)).toBe(true)
|
|
772
|
+
})
|
|
773
|
+
})
|
|
774
|
+
})
|