@alumbwe/anvil 1.0.0
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/README.md +84 -0
- package/bin/anvil +2 -0
- package/bin/tree-sitter.wasm +0 -0
- package/package.json +67 -0
- package/src/__tests__/README.md +256 -0
- package/src/__tests__/bash-mode.test.ts +442 -0
- package/src/__tests__/cli-args.test.ts +181 -0
- package/src/__tests__/cli-harness.test.ts +52 -0
- package/src/__tests__/e2e/first-time-login.test.ts +173 -0
- package/src/__tests__/e2e/logout-relogin-flow.test.ts +110 -0
- package/src/__tests__/e2e/returning-user-auth.test.ts +113 -0
- package/src/__tests__/e2e-cli.test.ts +183 -0
- package/src/__tests__/helpers/launcher-disconnect-fixture.cjs +176 -0
- package/src/__tests__/helpers/mock-api-client.ts +64 -0
- package/src/__tests__/helpers/renderer-cleanup-fixture.tsx +89 -0
- package/src/__tests__/helpers/terminal-watchdog-fixture.ts +75 -0
- package/src/__tests__/home-directory-detection.test.ts +213 -0
- package/src/__tests__/integration/api-integration.test.ts +288 -0
- package/src/__tests__/integration/credentials-storage.test.ts +486 -0
- package/src/__tests__/integration/local-agents.test.ts +1187 -0
- package/src/__tests__/integration/login-flow-instrumentation.test.ts +134 -0
- package/src/__tests__/integration/login-polling-working.test.ts +285 -0
- package/src/__tests__/integration/queue-editing.test.tsx +261 -0
- package/src/__tests__/integration/usage-refresh-on-completion.test.ts +168 -0
- package/src/__tests__/integration-tmux.test.ts +188 -0
- package/src/__tests__/launcher-avx2-fallback.test.ts +630 -0
- package/src/__tests__/launcher-disconnect.test.ts +104 -0
- package/src/__tests__/mocks/hover-toggle-controller.ts +71 -0
- package/src/__tests__/open-tui-native-bundle.test.ts +378 -0
- package/src/__tests__/path-completion.test.ts +243 -0
- package/src/__tests__/project-files-chat-id.test.ts +34 -0
- package/src/__tests__/release/proxy-http-get.test.ts +667 -0
- package/src/__tests__/release/wrapper-safety.test.ts +412 -0
- package/src/__tests__/renderer-cleanup.test.ts +174 -0
- package/src/__tests__/rerender-perf.integration.test.ts +283 -0
- package/src/__tests__/terminal-reset-sequences.test.ts +36 -0
- package/src/__tests__/terminal-watchdog.test.ts +388 -0
- package/src/__tests__/test-utils.ts +271 -0
- package/src/__tests__/tmux-poc.ts +150 -0
- package/src/__tests__/unit/agent-mode-toggle.test.ts +137 -0
- package/src/__tests__/unit/copy-button.test.ts +157 -0
- package/src/__tests__/unit/create-run-config.test.ts +66 -0
- package/src/__tests__/unit/publish-confirmation.test.ts +70 -0
- package/src/__tests__/unit/segmented-control.test.ts +152 -0
- package/src/__tests__/utils/env.test.ts +169 -0
- package/src/__tests__/utils/project-picker.test.ts +39 -0
- package/src/agents/bundled-agents.generated.d.ts +14 -0
- package/src/agents/bundled-agents.generated.ts +3692 -0
- package/src/app.tsx +399 -0
- package/src/chat.tsx +1698 -0
- package/src/cli-args.ts +134 -0
- package/src/commands/__tests__/bash-command.test.ts +445 -0
- package/src/commands/__tests__/command-args.test.ts +349 -0
- package/src/commands/__tests__/copy-conversation-clipboard.test.ts +108 -0
- package/src/commands/__tests__/copy-conversation.test.ts +226 -0
- package/src/commands/__tests__/diagnostics-command.test.ts +35 -0
- package/src/commands/__tests__/freebuff-command-aliases.test.ts +56 -0
- package/src/commands/__tests__/image.test.ts +99 -0
- package/src/commands/__tests__/init.test.ts +456 -0
- package/src/commands/__tests__/new-command-rotates-chat.test.ts +91 -0
- package/src/commands/__tests__/process-diagnostics.test.ts +54 -0
- package/src/commands/__tests__/prompt-builders.test.ts +28 -0
- package/src/commands/__tests__/router-input.test.ts +298 -0
- package/src/commands/ads.ts +44 -0
- package/src/commands/command-registry.ts +726 -0
- package/src/commands/copy-conversation.ts +392 -0
- package/src/commands/help.ts +13 -0
- package/src/commands/image.ts +20 -0
- package/src/commands/init.ts +121 -0
- package/src/commands/process-diagnostics.ts +120 -0
- package/src/commands/prompt-builders.ts +100 -0
- package/src/commands/publish.ts +203 -0
- package/src/commands/router-utils.ts +80 -0
- package/src/commands/router.ts +487 -0
- package/src/commands/usage.ts +26 -0
- package/src/components/__tests__/ad-banner.test.tsx +83 -0
- package/src/components/__tests__/chat-input-bar.test.tsx +130 -0
- package/src/components/__tests__/freebuff-landing-heading-row.test.tsx +91 -0
- package/src/components/__tests__/freebuff-model-selector.test.tsx +519 -0
- package/src/components/__tests__/freebuff-offer-invariants.test.ts +54 -0
- package/src/components/__tests__/freebuff-takeover-prompt.test.tsx +212 -0
- package/src/components/__tests__/grid-layout.integration.test.tsx +304 -0
- package/src/components/__tests__/grid-layout.test.tsx +1051 -0
- package/src/components/__tests__/message-block.completion.test.tsx +104 -0
- package/src/components/__tests__/message-block.streaming.test.tsx +78 -0
- package/src/components/__tests__/message-with-agents.test.tsx +579 -0
- package/src/components/__tests__/multiline-input.test.tsx +1132 -0
- package/src/components/__tests__/queue-panel.test.tsx +398 -0
- package/src/components/__tests__/selectable-list.test.ts +232 -0
- package/src/components/__tests__/status-bar.test.tsx +44 -0
- package/src/components/__tests__/status-indicator.test.tsx +204 -0
- package/src/components/__tests__/terminal-thinking-orb.test.tsx +111 -0
- package/src/components/__tests__/user-error-banner.test.tsx +102 -0
- package/src/components/ad-banner.tsx +323 -0
- package/src/components/agent-checklist.tsx +350 -0
- package/src/components/agent-mode-toggle.tsx +241 -0
- package/src/components/anvil-mark.tsx +52 -0
- package/src/components/ask-user/__tests__/multiple-choice-form.test.ts +432 -0
- package/src/components/ask-user/__tests__/validation.test.ts +213 -0
- package/src/components/ask-user/components/accordion-question.tsx +153 -0
- package/src/components/ask-user/components/custom-answer-input.tsx +66 -0
- package/src/components/ask-user/components/options-list.tsx +133 -0
- package/src/components/ask-user/components/question-header.tsx +80 -0
- package/src/components/ask-user/components/question-option.tsx +89 -0
- package/src/components/ask-user/constants.ts +38 -0
- package/src/components/ask-user/index.tsx +631 -0
- package/src/components/ask-user/utils/validation.ts +85 -0
- package/src/components/attachment-card.tsx +65 -0
- package/src/components/blocks/agent-block-grid.tsx +58 -0
- package/src/components/blocks/agent-branch-item.tsx +311 -0
- package/src/components/blocks/agent-branch-wrapper.tsx +493 -0
- package/src/components/blocks/agent-list-branch.tsx +77 -0
- package/src/components/blocks/ask-user-branch.tsx +81 -0
- package/src/components/blocks/block-helpers.ts +11 -0
- package/src/components/blocks/blocks-renderer.tsx +289 -0
- package/src/components/blocks/content-with-markdown.tsx +33 -0
- package/src/components/blocks/image-block.tsx +128 -0
- package/src/components/blocks/implementor-row.tsx +429 -0
- package/src/components/blocks/single-block.tsx +201 -0
- package/src/components/blocks/thinking-block.tsx +67 -0
- package/src/components/blocks/tool-block-group.tsx +57 -0
- package/src/components/blocks/tool-branch.tsx +206 -0
- package/src/components/blocks/user-content-copy.tsx +157 -0
- package/src/components/bottom-banner.tsx +136 -0
- package/src/components/build-mode-buttons.tsx +110 -0
- package/src/components/button.tsx +66 -0
- package/src/components/chat-header.tsx +77 -0
- package/src/components/chat-history-screen.tsx +413 -0
- package/src/components/chat-input-bar.tsx +523 -0
- package/src/components/clickable-title-box.tsx +66 -0
- package/src/components/clickable.tsx +119 -0
- package/src/components/collapse-button.tsx +34 -0
- package/src/components/copy-button.tsx +203 -0
- package/src/components/elapsed-timer.tsx +55 -0
- package/src/components/error-boundary.tsx +55 -0
- package/src/components/feedback-container.tsx +181 -0
- package/src/components/feedback-icon-button.tsx +89 -0
- package/src/components/feedback-input-mode.tsx +344 -0
- package/src/components/file-attachment-card.tsx +98 -0
- package/src/components/freebuff-active-session-summary.tsx +63 -0
- package/src/components/freebuff-landing-screen.tsx +865 -0
- package/src/components/freebuff-model-selector.tsx +1087 -0
- package/src/components/freebuff-referral-banner.tsx +604 -0
- package/src/components/freebuff-superseded-screen.tsx +62 -0
- package/src/components/grid-layout.tsx +80 -0
- package/src/components/help-banner.tsx +127 -0
- package/src/components/highlighted-text.tsx +52 -0
- package/src/components/image-card.tsx +167 -0
- package/src/components/image-thumbnail.tsx +114 -0
- package/src/components/input-cursor.tsx +77 -0
- package/src/components/input-mode-banner.tsx +57 -0
- package/src/components/load-previous-button.tsx +48 -0
- package/src/components/login-modal.tsx +475 -0
- package/src/components/message-block.tsx +346 -0
- package/src/components/message-footer.tsx +258 -0
- package/src/components/message-with-agents.tsx +506 -0
- package/src/components/mode-divider.tsx +43 -0
- package/src/components/multiline-input.tsx +1241 -0
- package/src/components/out-of-credits-banner.tsx +160 -0
- package/src/components/pending-attachments-banner.tsx +116 -0
- package/src/components/pending-bash-message.tsx +59 -0
- package/src/components/progress-bar.tsx +81 -0
- package/src/components/project-picker-screen.tsx +485 -0
- package/src/components/publish-confirmation.tsx +493 -0
- package/src/components/publish-container.tsx +687 -0
- package/src/components/queue-panel.tsx +310 -0
- package/src/components/raised-pill.tsx +95 -0
- package/src/components/renderers/plan-box.tsx +59 -0
- package/src/components/review-screen.tsx +112 -0
- package/src/components/scroll-to-bottom-button.tsx +34 -0
- package/src/components/segmented-control.tsx +187 -0
- package/src/components/selectable-list.tsx +248 -0
- package/src/components/selected-chips.tsx +80 -0
- package/src/components/separator.tsx +40 -0
- package/src/components/session-ended-banner.tsx +226 -0
- package/src/components/shimmer-text.tsx +248 -0
- package/src/components/status-bar.tsx +313 -0
- package/src/components/subscription-limit-banner.tsx +206 -0
- package/src/components/suggested-prompts.tsx +184 -0
- package/src/components/suggestion-menu.tsx +215 -0
- package/src/components/terminal-command-display.tsx +151 -0
- package/src/components/terminal-link.tsx +97 -0
- package/src/components/terminal-thinking-orb.tsx +95 -0
- package/src/components/text-attachment-card.tsx +77 -0
- package/src/components/thinking.tsx +109 -0
- package/src/components/tools/__tests__/apply-patch.test.tsx +84 -0
- package/src/components/tools/__tests__/code-search.test.tsx +45 -0
- package/src/components/tools/__tests__/gravity-index.test.ts +76 -0
- package/src/components/tools/__tests__/render-ui.test.tsx +92 -0
- package/src/components/tools/__tests__/run-terminal-command.test.ts +233 -0
- package/src/components/tools/apply-patch.tsx +95 -0
- package/src/components/tools/code-search.tsx +45 -0
- package/src/components/tools/composio.tsx +152 -0
- package/src/components/tools/diff-viewer.tsx +76 -0
- package/src/components/tools/glob.tsx +61 -0
- package/src/components/tools/gravity-index.tsx +77 -0
- package/src/components/tools/list-directory.tsx +61 -0
- package/src/components/tools/read-docs.tsx +37 -0
- package/src/components/tools/read-files.tsx +91 -0
- package/src/components/tools/read-subtree.tsx +44 -0
- package/src/components/tools/read-url.tsx +33 -0
- package/src/components/tools/registry.ts +126 -0
- package/src/components/tools/render-ui.tsx +122 -0
- package/src/components/tools/run-terminal-command.tsx +79 -0
- package/src/components/tools/skill.tsx +29 -0
- package/src/components/tools/str-replace.tsx +77 -0
- package/src/components/tools/suggest-followups.tsx +369 -0
- package/src/components/tools/task-completed.tsx +16 -0
- package/src/components/tools/tool-call-item.tsx +273 -0
- package/src/components/tools/types.ts +55 -0
- package/src/components/tools/web-search.tsx +33 -0
- package/src/components/tools/write-file.tsx +16 -0
- package/src/components/tools/write-todos.tsx +95 -0
- package/src/components/top-banner.tsx +178 -0
- package/src/components/usage-banner.tsx +244 -0
- package/src/components/user-error-banner.tsx +56 -0
- package/src/components/validation-error-popover.tsx +192 -0
- package/src/contexts/__tests__/chat-runtime-context.test.tsx +149 -0
- package/src/contexts/chat-runtime-context.tsx +240 -0
- package/src/data/slash-commands.test.ts +24 -0
- package/src/data/slash-commands.ts +249 -0
- package/src/entry.ts +12 -0
- package/src/hooks/__tests__/holds-live-freebuff-slot.test.ts +49 -0
- package/src/hooks/__tests__/session-fetch-signal.test.ts +63 -0
- package/src/hooks/__tests__/use-activity-query.test.ts +1000 -0
- package/src/hooks/__tests__/use-ask-user-bridge.test.ts +176 -0
- package/src/hooks/__tests__/use-auth-query.test.ts +21 -0
- package/src/hooks/__tests__/use-chat-input-remount.test.tsx +66 -0
- package/src/hooks/__tests__/use-clipboard.test.tsx +73 -0
- package/src/hooks/__tests__/use-connection-status.test.ts +112 -0
- package/src/hooks/__tests__/use-directory-browser.test.ts +203 -0
- package/src/hooks/__tests__/use-gravity-ad.test.ts +73 -0
- package/src/hooks/__tests__/use-grid-layout.test.ts +349 -0
- package/src/hooks/__tests__/use-input-history.test.ts +699 -0
- package/src/hooks/__tests__/use-message-queue-editing.test.tsx +209 -0
- package/src/hooks/__tests__/use-message-queue.test.tsx +71 -0
- package/src/hooks/__tests__/use-path-tab-completion.test.ts +315 -0
- package/src/hooks/__tests__/use-queue-controls.test.ts +68 -0
- package/src/hooks/__tests__/use-queue-ui.test.tsx +83 -0
- package/src/hooks/__tests__/use-searchable-list.test.ts +359 -0
- package/src/hooks/__tests__/use-send-message-timer.test.ts +146 -0
- package/src/hooks/__tests__/use-suggestion-engine-mention.test.ts +361 -0
- package/src/hooks/__tests__/use-suggestion-engine.test.ts +485 -0
- package/src/hooks/__tests__/use-terminal-focus.test.ts +66 -0
- package/src/hooks/__tests__/use-terminal-layout.test.ts +675 -0
- package/src/hooks/__tests__/use-timeout.test.ts +330 -0
- package/src/hooks/__tests__/use-usage-query.test.ts +502 -0
- package/src/hooks/__tests__/use-user-details-query.test.ts +195 -0
- package/src/hooks/helpers/__tests__/send-message.test.ts +1918 -0
- package/src/hooks/helpers/send-message.ts +654 -0
- package/src/hooks/stream-state.ts +83 -0
- package/src/hooks/use-activity-query.ts +668 -0
- package/src/hooks/use-agent-validation.ts +74 -0
- package/src/hooks/use-ask-user-bridge.ts +92 -0
- package/src/hooks/use-auth-query.ts +253 -0
- package/src/hooks/use-auth-state.ts +152 -0
- package/src/hooks/use-chat-input.ts +105 -0
- package/src/hooks/use-chat-keyboard.ts +321 -0
- package/src/hooks/use-chat-messages.ts +254 -0
- package/src/hooks/use-chat-state.ts +165 -0
- package/src/hooks/use-chat-streaming.ts +180 -0
- package/src/hooks/use-chat-ui.ts +131 -0
- package/src/hooks/use-clipboard.ts +151 -0
- package/src/hooks/use-connection-status.ts +142 -0
- package/src/hooks/use-directory-browser.ts +88 -0
- package/src/hooks/use-elapsed-time.ts +116 -0
- package/src/hooks/use-event.ts +34 -0
- package/src/hooks/use-exit-handler.ts +66 -0
- package/src/hooks/use-fetch-login-url.ts +65 -0
- package/src/hooks/use-fingerprint.ts +61 -0
- package/src/hooks/use-freebuff-ctrl-c-exit.ts +23 -0
- package/src/hooks/use-freebuff-session-progress.ts +34 -0
- package/src/hooks/use-freebuff-session.ts +806 -0
- package/src/hooks/use-freebuff-streak-query.ts +61 -0
- package/src/hooks/use-gravity-ad.ts +656 -0
- package/src/hooks/use-grid-layout.ts +73 -0
- package/src/hooks/use-input-history.ts +173 -0
- package/src/hooks/use-login-keyboard-handlers.ts +75 -0
- package/src/hooks/use-login-polling.ts +114 -0
- package/src/hooks/use-logo.tsx +161 -0
- package/src/hooks/use-message-queue.ts +387 -0
- package/src/hooks/use-now.ts +20 -0
- package/src/hooks/use-path-tab-completion.ts +88 -0
- package/src/hooks/use-publish-mutation.ts +60 -0
- package/src/hooks/use-queue-controls.ts +68 -0
- package/src/hooks/use-queue-ui.ts +74 -0
- package/src/hooks/use-scroll-management.ts +184 -0
- package/src/hooks/use-searchable-list.ts +98 -0
- package/src/hooks/use-send-message.ts +773 -0
- package/src/hooks/use-sheen-animation.tsx +88 -0
- package/src/hooks/use-subscription-query.ts +71 -0
- package/src/hooks/use-suggestion-engine.ts +783 -0
- package/src/hooks/use-terminal-breakpoints.ts +77 -0
- package/src/hooks/use-terminal-dimensions.ts +46 -0
- package/src/hooks/use-terminal-focus.ts +29 -0
- package/src/hooks/use-terminal-layout.ts +189 -0
- package/src/hooks/use-theme.tsx +140 -0
- package/src/hooks/use-timeout.ts +85 -0
- package/src/hooks/use-update-preference.ts +66 -0
- package/src/hooks/use-usage-monitor.ts +55 -0
- package/src/hooks/use-usage-query.ts +124 -0
- package/src/hooks/use-user-details-query.ts +93 -0
- package/src/hooks/use-why-did-you-update.ts +177 -0
- package/src/index.tsx +441 -0
- package/src/init/__tests__/init-direnv.test.ts +526 -0
- package/src/init/init-app.ts +34 -0
- package/src/init/init-direnv.ts +133 -0
- package/src/login/constants.ts +60 -0
- package/src/login/login-flow.ts +204 -0
- package/src/login/plain-login.ts +108 -0
- package/src/login/utils.ts +183 -0
- package/src/native/ripgrep.ts +80 -0
- package/src/polyfills/bun-strip-ansi.ts +12 -0
- package/src/pre-init/tree-sitter-wasm.ts +96 -0
- package/src/project-files.ts +102 -0
- package/src/smoke/terminal-command-broker.ts +439 -0
- package/src/state/__tests__/chat-store-focus.test.ts +22 -0
- package/src/state/__tests__/feedback-store.test.ts +257 -0
- package/src/state/chat-history-store.ts +30 -0
- package/src/state/chat-store.ts +544 -0
- package/src/state/feedback-store.ts +160 -0
- package/src/state/freebuff-model-store.ts +42 -0
- package/src/state/freebuff-session-store.ts +53 -0
- package/src/state/login-store.ts +150 -0
- package/src/state/message-block-store.ts +149 -0
- package/src/state/publish-store.ts +146 -0
- package/src/state/queue-panel-store.ts +26 -0
- package/src/state/review-store.ts +24 -0
- package/src/testing/env.ts +46 -0
- package/src/types/chat-state.ts +15 -0
- package/src/types/chat.ts +241 -0
- package/src/types/contracts/send-message.ts +12 -0
- package/src/types/env.ts +98 -0
- package/src/types/freebuff-session.ts +17 -0
- package/src/types/function-params.ts +28 -0
- package/src/types/react19-compat.d.ts +19 -0
- package/src/types/store.ts +111 -0
- package/src/types/theme-system.ts +144 -0
- package/src/types/utils.ts +3 -0
- package/src/utils/__tests__/active-run.test.ts +140 -0
- package/src/utils/__tests__/activity-tracker.test.ts +440 -0
- package/src/utils/__tests__/agent-display.test.ts +139 -0
- package/src/utils/__tests__/analytics-client.test.ts +262 -0
- package/src/utils/__tests__/anonymous-id.test.ts +74 -0
- package/src/utils/__tests__/anvil-api.test.ts +578 -0
- package/src/utils/__tests__/arrays.test.ts +264 -0
- package/src/utils/__tests__/bash-context-processor.test.ts +38 -0
- package/src/utils/__tests__/block-processor.test.ts +810 -0
- package/src/utils/__tests__/chat-history.test.ts +203 -0
- package/src/utils/__tests__/chat-input-key-intercept.test.ts +40 -0
- package/src/utils/__tests__/chat-meta.test.ts +130 -0
- package/src/utils/__tests__/clipboard-linux.test.ts +359 -0
- package/src/utils/__tests__/clipboard.test.ts +910 -0
- package/src/utils/__tests__/code-search-summary.test.ts +84 -0
- package/src/utils/__tests__/collapse-helpers.test.ts +1109 -0
- package/src/utils/__tests__/error-handling.test.ts +658 -0
- package/src/utils/__tests__/exit-cleanly.test.ts +110 -0
- package/src/utils/__tests__/feedback-helpers.test.ts +444 -0
- package/src/utils/__tests__/feedback-submission.test.ts +26 -0
- package/src/utils/__tests__/fetch-usage.test.ts +355 -0
- package/src/utils/__tests__/fingerprint.test.ts +144 -0
- package/src/utils/__tests__/fixtures/terminal-command-broker-child.ts +7 -0
- package/src/utils/__tests__/fixtures/terminal-command-broker-entry.ts +3 -0
- package/src/utils/__tests__/fixtures/terminal-command-broker-owner.ts +33 -0
- package/src/utils/__tests__/format-elapsed-time.test.ts +103 -0
- package/src/utils/__tests__/format-timeout.test.ts +87 -0
- package/src/utils/__tests__/freebuff-instance-owner.test.ts +69 -0
- package/src/utils/__tests__/freebuff-model-navigation.test.ts +102 -0
- package/src/utils/__tests__/freebuff-premium-reset.test.ts +79 -0
- package/src/utils/__tests__/freebuff-referral-cache.test.ts +116 -0
- package/src/utils/__tests__/freebuff-session-api.test.ts +215 -0
- package/src/utils/__tests__/freebuff-session-display.test.ts +23 -0
- package/src/utils/__tests__/freebuff-streak-line.test.ts +217 -0
- package/src/utils/__tests__/image-dimensions.test.ts +249 -0
- package/src/utils/__tests__/image-processor.test.ts +209 -0
- package/src/utils/__tests__/implementor-helpers.test.ts +1458 -0
- package/src/utils/__tests__/keyboard-actions.test.ts +660 -0
- package/src/utils/__tests__/layout-helpers.test.ts +36 -0
- package/src/utils/__tests__/lazy-response-ads.test.ts +167 -0
- package/src/utils/__tests__/log-shipper.test.ts +53 -0
- package/src/utils/__tests__/markdown-renderer.test.tsx +421 -0
- package/src/utils/__tests__/message-block-helpers.test.ts +1263 -0
- package/src/utils/__tests__/message-updater.test.ts +645 -0
- package/src/utils/__tests__/osc-timeout-scenarios.test.ts +172 -0
- package/src/utils/__tests__/pending-attachments.test.ts +277 -0
- package/src/utils/__tests__/polling-backoff.test.ts +51 -0
- package/src/utils/__tests__/queue-panel-actions.test.ts +148 -0
- package/src/utils/__tests__/run-state-storage.test.ts +919 -0
- package/src/utils/__tests__/safe-json.test.ts +77 -0
- package/src/utils/__tests__/sdk-event-handlers.test.ts +685 -0
- package/src/utils/__tests__/send-message-helpers.test.ts +1898 -0
- package/src/utils/__tests__/send-message-timer.test.ts +64 -0
- package/src/utils/__tests__/settings.test.ts +99 -0
- package/src/utils/__tests__/strings.test.ts +192 -0
- package/src/utils/__tests__/terminal-color-detection.test.ts +425 -0
- package/src/utils/__tests__/terminal-command-broker.test.ts +528 -0
- package/src/utils/__tests__/terminal-enter-detection.test.ts +77 -0
- package/src/utils/__tests__/terminal-io.test.ts +137 -0
- package/src/utils/__tests__/terminal-protocol-controller.test.ts +119 -0
- package/src/utils/__tests__/text-layout.test.ts +82 -0
- package/src/utils/__tests__/theme-platform-detection.test.ts +125 -0
- package/src/utils/__tests__/think-tag-parser.test.ts +159 -0
- package/src/utils/__tests__/trace-writer.test.ts +223 -0
- package/src/utils/__tests__/trim-chat-logs.test.ts +78 -0
- package/src/utils/__tests__/usage-banner-state.test.ts +298 -0
- package/src/utils/__tests__/validation-error-formatting.test.ts +126 -0
- package/src/utils/__tests__/windows-terminal-health.test.ts +101 -0
- package/src/utils/__tests__/write-file-atomic.test.ts +127 -0
- package/src/utils/active-run.ts +131 -0
- package/src/utils/activity-tracker.ts +66 -0
- package/src/utils/agent-display.ts +87 -0
- package/src/utils/agent-helpers.ts +27 -0
- package/src/utils/agent-id-utils.ts +14 -0
- package/src/utils/ai-message-id.ts +12 -0
- package/src/utils/analytics.ts +361 -0
- package/src/utils/anonymous-id.ts +87 -0
- package/src/utils/anvil-api.ts +612 -0
- package/src/utils/anvil-client.ts +217 -0
- package/src/utils/arrays.ts +117 -0
- package/src/utils/auth.ts +258 -0
- package/src/utils/bash-context-processor.ts +47 -0
- package/src/utils/bash-messages.ts +108 -0
- package/src/utils/block-margins.ts +35 -0
- package/src/utils/block-operations.ts +523 -0
- package/src/utils/block-processor.ts +213 -0
- package/src/utils/chat-history.ts +253 -0
- package/src/utils/chat-input-key-intercept.ts +52 -0
- package/src/utils/chat-meta.ts +90 -0
- package/src/utils/chat-scroll-accel.ts +151 -0
- package/src/utils/clipboard-image.ts +588 -0
- package/src/utils/clipboard.ts +397 -0
- package/src/utils/code-search-summary.ts +70 -0
- package/src/utils/collapse-helpers.ts +256 -0
- package/src/utils/config-dir.ts +24 -0
- package/src/utils/constants.ts +190 -0
- package/src/utils/create-event-handler-state.ts +73 -0
- package/src/utils/create-run-config.ts +121 -0
- package/src/utils/detect-shell.ts +116 -0
- package/src/utils/directory-browser.ts +72 -0
- package/src/utils/engagement.ts +55 -0
- package/src/utils/env.ts +92 -0
- package/src/utils/error-handling.ts +255 -0
- package/src/utils/error-messages.ts +62 -0
- package/src/utils/exit-cleanly.ts +86 -0
- package/src/utils/feedback-helpers.ts +103 -0
- package/src/utils/feedback-submission.ts +22 -0
- package/src/utils/fetch-usage.ts +76 -0
- package/src/utils/fingerprint.ts +245 -0
- package/src/utils/format-elapsed-time.ts +33 -0
- package/src/utils/format-session-units.ts +6 -0
- package/src/utils/format-timeout.ts +28 -0
- package/src/utils/format-validation-errors-for-message.ts +84 -0
- package/src/utils/freebuff-agent-selection.ts +33 -0
- package/src/utils/freebuff-instance-owner.ts +66 -0
- package/src/utils/freebuff-model-navigation.ts +50 -0
- package/src/utils/freebuff-premium-reset.ts +55 -0
- package/src/utils/freebuff-referral-cache.ts +51 -0
- package/src/utils/freebuff-session-api.ts +243 -0
- package/src/utils/freebuff-session-display.ts +21 -0
- package/src/utils/freebuff-streak-line.ts +93 -0
- package/src/utils/git.ts +17 -0
- package/src/utils/helpers.ts +47 -0
- package/src/utils/image-display.ts +67 -0
- package/src/utils/image-handler.ts +325 -0
- package/src/utils/image-processor.ts +133 -0
- package/src/utils/image-thumbnail.ts +78 -0
- package/src/utils/implementor-helpers.ts +787 -0
- package/src/utils/input-modes.ts +174 -0
- package/src/utils/keyboard-actions.ts +392 -0
- package/src/utils/keypad-keys.ts +47 -0
- package/src/utils/layout-helpers.ts +33 -0
- package/src/utils/lazy-response-ads.ts +1 -0
- package/src/utils/local-agent-registry.ts +474 -0
- package/src/utils/log-shipper.ts +126 -0
- package/src/utils/logger.ts +327 -0
- package/src/utils/markdown-renderer.tsx +1090 -0
- package/src/utils/math.ts +3 -0
- package/src/utils/message-block-helpers.ts +684 -0
- package/src/utils/message-history.ts +143 -0
- package/src/utils/message-tree-utils.ts +67 -0
- package/src/utils/message-updater.ts +279 -0
- package/src/utils/open-file.ts +146 -0
- package/src/utils/open-url.ts +63 -0
- package/src/utils/path-completion.ts +111 -0
- package/src/utils/path-helpers.ts +38 -0
- package/src/utils/pending-attachments.ts +360 -0
- package/src/utils/polling-backoff.ts +59 -0
- package/src/utils/project-picker.ts +12 -0
- package/src/utils/queue-panel-actions.ts +70 -0
- package/src/utils/recent-projects.ts +156 -0
- package/src/utils/renderer-cleanup.ts +233 -0
- package/src/utils/response-ad-positions.ts +1 -0
- package/src/utils/run-state-storage.ts +603 -0
- package/src/utils/safe-json.ts +153 -0
- package/src/utils/sdk-event-handlers.ts +571 -0
- package/src/utils/send-message-helpers.ts +210 -0
- package/src/utils/send-message-timer.ts +110 -0
- package/src/utils/settings.ts +226 -0
- package/src/utils/skill-registry.ts +94 -0
- package/src/utils/spawn-agent-matcher.ts +55 -0
- package/src/utils/status-indicator-state.ts +107 -0
- package/src/utils/stream-chunk-processor.ts +57 -0
- package/src/utils/strings.ts +271 -0
- package/src/utils/subscription.ts +31 -0
- package/src/utils/syntax-highlighter.tsx +19 -0
- package/src/utils/terminal-color-detection.ts +474 -0
- package/src/utils/terminal-command-broker.ts +467 -0
- package/src/utils/terminal-enter-detection.ts +63 -0
- package/src/utils/terminal-images.ts +229 -0
- package/src/utils/terminal-io.ts +85 -0
- package/src/utils/terminal-protocol-controller.ts +173 -0
- package/src/utils/terminal-reset-sequences.ts +33 -0
- package/src/utils/terminal-title.ts +84 -0
- package/src/utils/terminal-watchdog.ts +366 -0
- package/src/utils/text-layout.ts +149 -0
- package/src/utils/theme-config.ts +141 -0
- package/src/utils/theme-system.ts +1206 -0
- package/src/utils/think-tag-parser.ts +95 -0
- package/src/utils/time-format.ts +21 -0
- package/src/utils/trace-writer.ts +162 -0
- package/src/utils/ui-constants.ts +69 -0
- package/src/utils/update-checker.ts +110 -0
- package/src/utils/usage-banner-state.ts +146 -0
- package/src/utils/validation-error-formatting.ts +86 -0
- package/src/utils/validation-error-helpers.ts +19 -0
- package/src/utils/windows-terminal-health.ts +124 -0
- package/src/utils/word-wrap-utils.ts +53 -0
- package/src/utils/write-file-atomic.ts +54 -0
- package/src/utils/yield-to-event-loop.ts +9 -0
|
@@ -0,0 +1,658 @@
|
|
|
1
|
+
import { describe, test, expect } from 'bun:test'
|
|
2
|
+
import { FREEBUFF_PROVIDER_USAGE_MESSAGE } from '@anvil/common/constants/freebuff-errors'
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
getFreebuffRateLimitErrorMessage,
|
|
6
|
+
getFreeModeUnavailableErrorMessage,
|
|
7
|
+
isOutOfCreditsError,
|
|
8
|
+
isFreeModeUnavailableError,
|
|
9
|
+
getCountryBlockFromFreeModeError,
|
|
10
|
+
OUT_OF_CREDITS_MESSAGE,
|
|
11
|
+
FREE_MODE_UNAVAILABLE_MESSAGE,
|
|
12
|
+
FREEBUFF_RATE_LIMIT_MESSAGE,
|
|
13
|
+
isFreebuffProviderUsageError,
|
|
14
|
+
createErrorMessage,
|
|
15
|
+
} from '../error-handling'
|
|
16
|
+
|
|
17
|
+
describe('error-handling', () => {
|
|
18
|
+
describe('isOutOfCreditsError', () => {
|
|
19
|
+
test('returns true for error with statusCode 402', () => {
|
|
20
|
+
const error = { statusCode: 402, message: 'Payment required' }
|
|
21
|
+
expect(isOutOfCreditsError(error)).toBe(true)
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
test('returns false for error with statusCode 401', () => {
|
|
25
|
+
const error = { statusCode: 401, message: 'Unauthorized' }
|
|
26
|
+
expect(isOutOfCreditsError(error)).toBe(false)
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
test('returns false for error with statusCode 403', () => {
|
|
30
|
+
const error = { statusCode: 403, message: 'Forbidden' }
|
|
31
|
+
expect(isOutOfCreditsError(error)).toBe(false)
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
test('returns false for error with statusCode 500', () => {
|
|
35
|
+
const error = { statusCode: 500, message: 'Server error' }
|
|
36
|
+
expect(isOutOfCreditsError(error)).toBe(false)
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
test('returns false for null error', () => {
|
|
40
|
+
expect(isOutOfCreditsError(null)).toBe(false)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
test('returns false for undefined error', () => {
|
|
44
|
+
expect(isOutOfCreditsError(undefined)).toBe(false)
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
test('returns false for string error', () => {
|
|
48
|
+
expect(isOutOfCreditsError('error string')).toBe(false)
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
test('returns false for Error object without statusCode', () => {
|
|
52
|
+
const error = new Error('Plain error')
|
|
53
|
+
expect(isOutOfCreditsError(error)).toBe(false)
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
test('returns false for error with non-402 numeric statusCode', () => {
|
|
57
|
+
const error = { statusCode: 400, message: 'Bad request' }
|
|
58
|
+
expect(isOutOfCreditsError(error)).toBe(false)
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
test('returns false for error with string statusCode', () => {
|
|
62
|
+
const error = { statusCode: '402', message: 'Payment required' }
|
|
63
|
+
expect(isOutOfCreditsError(error)).toBe(false)
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
test('returns true for 402 errors with additional properties', () => {
|
|
67
|
+
const error = {
|
|
68
|
+
statusCode: 402,
|
|
69
|
+
message: 'Payment required',
|
|
70
|
+
details: { credits: 0 },
|
|
71
|
+
timestamp: new Date().toISOString(),
|
|
72
|
+
}
|
|
73
|
+
expect(isOutOfCreditsError(error)).toBe(true)
|
|
74
|
+
})
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
describe('isFreeModeUnavailableError', () => {
|
|
78
|
+
test('returns true for error with statusCode 403 and error free_mode_unavailable', () => {
|
|
79
|
+
const error = {
|
|
80
|
+
statusCode: 403,
|
|
81
|
+
error: 'free_mode_unavailable',
|
|
82
|
+
message: 'Free mode is not available in your country.',
|
|
83
|
+
}
|
|
84
|
+
expect(isFreeModeUnavailableError(error)).toBe(true)
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
test('returns true for responseBody free_mode_unavailable errors', () => {
|
|
88
|
+
expect(
|
|
89
|
+
isFreeModeUnavailableError({
|
|
90
|
+
statusCode: 403,
|
|
91
|
+
responseBody: JSON.stringify({
|
|
92
|
+
error: 'free_mode_unavailable',
|
|
93
|
+
message: 'Freebuff cannot be used from VPN traffic.',
|
|
94
|
+
}),
|
|
95
|
+
}),
|
|
96
|
+
).toBe(true)
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
test('returns false for 403 without error field', () => {
|
|
100
|
+
const error = { statusCode: 403, message: 'Forbidden' }
|
|
101
|
+
expect(isFreeModeUnavailableError(error)).toBe(false)
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
test('returns false for 403 with different error code', () => {
|
|
105
|
+
const error = {
|
|
106
|
+
statusCode: 403,
|
|
107
|
+
error: 'account_suspended',
|
|
108
|
+
message: 'Suspended',
|
|
109
|
+
}
|
|
110
|
+
expect(isFreeModeUnavailableError(error)).toBe(false)
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
test('returns false for non-403 status with free_mode_unavailable error', () => {
|
|
114
|
+
const error = {
|
|
115
|
+
statusCode: 400,
|
|
116
|
+
error: 'free_mode_unavailable',
|
|
117
|
+
message: 'Bad request',
|
|
118
|
+
}
|
|
119
|
+
expect(isFreeModeUnavailableError(error)).toBe(false)
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
test('returns false for null', () => {
|
|
123
|
+
expect(isFreeModeUnavailableError(null)).toBe(false)
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
test('returns false for undefined', () => {
|
|
127
|
+
expect(isFreeModeUnavailableError(undefined)).toBe(false)
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
test('returns false for plain Error object', () => {
|
|
131
|
+
expect(isFreeModeUnavailableError(new Error('Forbidden'))).toBe(false)
|
|
132
|
+
})
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
describe('getFreebuffRateLimitErrorMessage', () => {
|
|
136
|
+
test('returns the generic message for untyped 429 errors', () => {
|
|
137
|
+
expect(
|
|
138
|
+
getFreebuffRateLimitErrorMessage({
|
|
139
|
+
statusCode: 429,
|
|
140
|
+
message: 'Too Many Requests',
|
|
141
|
+
}),
|
|
142
|
+
).toBe(FREEBUFF_RATE_LIMIT_MESSAGE)
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
test('returns the generic message for thrown API errors with status 429', () => {
|
|
146
|
+
expect(
|
|
147
|
+
getFreebuffRateLimitErrorMessage({
|
|
148
|
+
status: 429,
|
|
149
|
+
message: 'Too Many Requests',
|
|
150
|
+
}),
|
|
151
|
+
).toBe(FREEBUFF_RATE_LIMIT_MESSAGE)
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
test('returns the generic message for retry-wrapped untyped 429 errors', () => {
|
|
155
|
+
expect(
|
|
156
|
+
getFreebuffRateLimitErrorMessage({
|
|
157
|
+
message: 'Failed after 4 attempts. Last error: Too Many Requests',
|
|
158
|
+
lastError: {
|
|
159
|
+
statusCode: 429,
|
|
160
|
+
message: 'Too Many Requests',
|
|
161
|
+
},
|
|
162
|
+
}),
|
|
163
|
+
).toBe(FREEBUFF_RATE_LIMIT_MESSAGE)
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
test('returns null for non-429 status codes', () => {
|
|
167
|
+
expect(getFreebuffRateLimitErrorMessage({ statusCode: 402 })).toBe(null)
|
|
168
|
+
expect(getFreebuffRateLimitErrorMessage({ statusCode: 500 })).toBe(null)
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
test('returns null for string statusCode', () => {
|
|
172
|
+
expect(getFreebuffRateLimitErrorMessage({ statusCode: '429' })).toBe(
|
|
173
|
+
null,
|
|
174
|
+
)
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
test('preserves normalized free mode quota messages', () => {
|
|
178
|
+
const message =
|
|
179
|
+
'Free mode rate limit exceeded (1 minute limit). Try again in 30 seconds.'
|
|
180
|
+
|
|
181
|
+
expect(
|
|
182
|
+
getFreebuffRateLimitErrorMessage({
|
|
183
|
+
statusCode: 429,
|
|
184
|
+
error: 'free_mode_rate_limited',
|
|
185
|
+
message,
|
|
186
|
+
}),
|
|
187
|
+
).toBe(message)
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
test('preserves responseBody free mode quota messages', () => {
|
|
191
|
+
const message =
|
|
192
|
+
'Free mode rate limit exceeded (1 minute limit). Try again in 30 seconds.'
|
|
193
|
+
|
|
194
|
+
expect(
|
|
195
|
+
getFreebuffRateLimitErrorMessage({
|
|
196
|
+
statusCode: 429,
|
|
197
|
+
message: 'Too Many Requests',
|
|
198
|
+
responseBody: JSON.stringify({
|
|
199
|
+
error: 'free_mode_rate_limited',
|
|
200
|
+
message,
|
|
201
|
+
}),
|
|
202
|
+
}),
|
|
203
|
+
).toBe(message)
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
test('preserves retry-wrapped free mode quota messages', () => {
|
|
207
|
+
const message =
|
|
208
|
+
'Free mode rate limit exceeded (1 minute limit). Try again in 30 seconds.'
|
|
209
|
+
|
|
210
|
+
expect(
|
|
211
|
+
getFreebuffRateLimitErrorMessage({
|
|
212
|
+
message: 'Failed after 4 attempts. Last error: Too Many Requests',
|
|
213
|
+
lastError: {
|
|
214
|
+
statusCode: 429,
|
|
215
|
+
message: 'Too Many Requests',
|
|
216
|
+
responseBody: JSON.stringify({
|
|
217
|
+
error: 'free_mode_rate_limited',
|
|
218
|
+
message,
|
|
219
|
+
}),
|
|
220
|
+
},
|
|
221
|
+
}),
|
|
222
|
+
).toBe(message)
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
test('falls back to the generic message when typed quota errors have no message', () => {
|
|
226
|
+
expect(
|
|
227
|
+
getFreebuffRateLimitErrorMessage({
|
|
228
|
+
statusCode: 429,
|
|
229
|
+
error: 'free_mode_rate_limited',
|
|
230
|
+
}),
|
|
231
|
+
).toBe(FREEBUFF_RATE_LIMIT_MESSAGE)
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
test('appends detail from agent-run output objects for untyped 429s', () => {
|
|
235
|
+
expect(
|
|
236
|
+
getFreebuffRateLimitErrorMessage({
|
|
237
|
+
type: 'error',
|
|
238
|
+
statusCode: 429,
|
|
239
|
+
message: 'Model is at capacity. Please try again later.',
|
|
240
|
+
}),
|
|
241
|
+
).toBe(
|
|
242
|
+
`${FREEBUFF_RATE_LIMIT_MESSAGE} (Model is at capacity. Please try again later.)`,
|
|
243
|
+
)
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
test('appends detail from OpenAI-style nested provider error bodies', () => {
|
|
247
|
+
expect(
|
|
248
|
+
getFreebuffRateLimitErrorMessage({
|
|
249
|
+
statusCode: 429,
|
|
250
|
+
message: 'Too Many Requests',
|
|
251
|
+
responseBody: JSON.stringify({
|
|
252
|
+
error: {
|
|
253
|
+
message: 'Model is at capacity. Please try again later.',
|
|
254
|
+
code: null,
|
|
255
|
+
type: 'rate_limit_error',
|
|
256
|
+
},
|
|
257
|
+
}),
|
|
258
|
+
}),
|
|
259
|
+
).toBe(
|
|
260
|
+
`${FREEBUFF_RATE_LIMIT_MESSAGE} (Model is at capacity. Please try again later.)`,
|
|
261
|
+
)
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
test('does not echo bare HTTP status text from output objects', () => {
|
|
265
|
+
expect(
|
|
266
|
+
getFreebuffRateLimitErrorMessage({
|
|
267
|
+
type: 'error',
|
|
268
|
+
statusCode: 429,
|
|
269
|
+
message: 'Too Many Requests',
|
|
270
|
+
}),
|
|
271
|
+
).toBe(FREEBUFF_RATE_LIMIT_MESSAGE)
|
|
272
|
+
})
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
describe('isFreebuffProviderUsageError', () => {
|
|
276
|
+
test('recognizes Freebuff provider usage failures across upstream shapes', () => {
|
|
277
|
+
for (const error of [
|
|
278
|
+
{
|
|
279
|
+
statusCode: 402,
|
|
280
|
+
message: 'Payment required',
|
|
281
|
+
},
|
|
282
|
+
{ statusCode: 401, message: 'Not Enough Credits' },
|
|
283
|
+
{
|
|
284
|
+
type: 'error',
|
|
285
|
+
statusCode: 403,
|
|
286
|
+
message:
|
|
287
|
+
'Insufficient credits. Add more using https://openrouter.ai/settings/credits',
|
|
288
|
+
},
|
|
289
|
+
{
|
|
290
|
+
statusCode: 401,
|
|
291
|
+
responseBody: JSON.stringify({
|
|
292
|
+
error: { message: 'Not Enough Credits' },
|
|
293
|
+
}),
|
|
294
|
+
},
|
|
295
|
+
]) {
|
|
296
|
+
expect(isFreebuffProviderUsageError(error)).toBe(true)
|
|
297
|
+
}
|
|
298
|
+
})
|
|
299
|
+
|
|
300
|
+
test('does not rewrite unrelated failures', () => {
|
|
301
|
+
expect(
|
|
302
|
+
isFreebuffProviderUsageError({
|
|
303
|
+
statusCode: 500,
|
|
304
|
+
message: 'Internal server error',
|
|
305
|
+
}),
|
|
306
|
+
).toBe(false)
|
|
307
|
+
})
|
|
308
|
+
})
|
|
309
|
+
|
|
310
|
+
describe('getCountryBlockFromFreeModeError', () => {
|
|
311
|
+
test('extracts country block details from free-mode unavailable errors', () => {
|
|
312
|
+
const error = {
|
|
313
|
+
statusCode: 403,
|
|
314
|
+
error: 'free_mode_unavailable',
|
|
315
|
+
countryCode: 'US',
|
|
316
|
+
countryBlockReason: 'anonymous_network',
|
|
317
|
+
ipPrivacySignals: ['vpn', 'hosting', 123],
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
expect(getCountryBlockFromFreeModeError(error)).toEqual({
|
|
321
|
+
countryCode: 'US',
|
|
322
|
+
countryBlockReason: 'anonymous_network',
|
|
323
|
+
ipPrivacySignals: ['vpn', 'hosting'],
|
|
324
|
+
})
|
|
325
|
+
})
|
|
326
|
+
|
|
327
|
+
test('extracts country block details from responseBody errors', () => {
|
|
328
|
+
const error = {
|
|
329
|
+
statusCode: 403,
|
|
330
|
+
responseBody: JSON.stringify({
|
|
331
|
+
error: 'free_mode_unavailable',
|
|
332
|
+
countryCode: 'US',
|
|
333
|
+
countryBlockReason: 'anonymous_network',
|
|
334
|
+
ipPrivacySignals: ['proxy', 'hosting', 123],
|
|
335
|
+
}),
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
expect(getCountryBlockFromFreeModeError(error)).toEqual({
|
|
339
|
+
countryCode: 'US',
|
|
340
|
+
countryBlockReason: 'anonymous_network',
|
|
341
|
+
ipPrivacySignals: ['proxy', 'hosting'],
|
|
342
|
+
})
|
|
343
|
+
})
|
|
344
|
+
|
|
345
|
+
test('defaults missing country code to UNKNOWN', () => {
|
|
346
|
+
const error = {
|
|
347
|
+
statusCode: 403,
|
|
348
|
+
error: 'free_mode_unavailable',
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
expect(getCountryBlockFromFreeModeError(error)).toEqual({
|
|
352
|
+
countryCode: 'UNKNOWN',
|
|
353
|
+
countryBlockReason: undefined,
|
|
354
|
+
ipPrivacySignals: undefined,
|
|
355
|
+
})
|
|
356
|
+
})
|
|
357
|
+
|
|
358
|
+
test('returns null for non-free-mode errors', () => {
|
|
359
|
+
expect(
|
|
360
|
+
getCountryBlockFromFreeModeError({
|
|
361
|
+
statusCode: 403,
|
|
362
|
+
error: 'account_suspended',
|
|
363
|
+
}),
|
|
364
|
+
).toBe(null)
|
|
365
|
+
})
|
|
366
|
+
})
|
|
367
|
+
|
|
368
|
+
describe('FREE_MODE_UNAVAILABLE_MESSAGE', () => {
|
|
369
|
+
test('mentions unavailability in country', () => {
|
|
370
|
+
expect(FREE_MODE_UNAVAILABLE_MESSAGE.toLowerCase()).toContain(
|
|
371
|
+
'not available in your country',
|
|
372
|
+
)
|
|
373
|
+
})
|
|
374
|
+
})
|
|
375
|
+
|
|
376
|
+
describe('getFreeModeUnavailableErrorMessage', () => {
|
|
377
|
+
test('uses a VPN/proxy-specific message for anonymous-network blocks', () => {
|
|
378
|
+
expect(
|
|
379
|
+
getFreeModeUnavailableErrorMessage({
|
|
380
|
+
statusCode: 403,
|
|
381
|
+
error: 'free_mode_unavailable',
|
|
382
|
+
message: 'Forbidden',
|
|
383
|
+
countryBlockReason: 'anonymous_network',
|
|
384
|
+
ipPrivacySignals: ['vpn', 'hosting'],
|
|
385
|
+
}),
|
|
386
|
+
).toContain('VPN')
|
|
387
|
+
})
|
|
388
|
+
|
|
389
|
+
test('uses a VPN/proxy-specific message from responseBody details', () => {
|
|
390
|
+
expect(
|
|
391
|
+
getFreeModeUnavailableErrorMessage({
|
|
392
|
+
statusCode: 403,
|
|
393
|
+
message: 'Forbidden',
|
|
394
|
+
responseBody: JSON.stringify({
|
|
395
|
+
error: 'free_mode_unavailable',
|
|
396
|
+
countryBlockReason: 'anonymous_network',
|
|
397
|
+
ipPrivacySignals: ['tor'],
|
|
398
|
+
}),
|
|
399
|
+
}),
|
|
400
|
+
).toContain('Tor')
|
|
401
|
+
})
|
|
402
|
+
|
|
403
|
+
test('preserves server message for non-privacy free mode blocks', () => {
|
|
404
|
+
expect(
|
|
405
|
+
getFreeModeUnavailableErrorMessage({
|
|
406
|
+
statusCode: 403,
|
|
407
|
+
error: 'free_mode_unavailable',
|
|
408
|
+
message: 'Free mode is not available in your country.',
|
|
409
|
+
}),
|
|
410
|
+
).toBe('Free mode is not available in your country.')
|
|
411
|
+
})
|
|
412
|
+
})
|
|
413
|
+
|
|
414
|
+
describe('OUT_OF_CREDITS_MESSAGE', () => {
|
|
415
|
+
test('contains usage URL', () => {
|
|
416
|
+
expect(OUT_OF_CREDITS_MESSAGE).toContain('/usage')
|
|
417
|
+
})
|
|
418
|
+
|
|
419
|
+
test('contains out of credits message', () => {
|
|
420
|
+
expect(OUT_OF_CREDITS_MESSAGE.toLowerCase()).toContain('out of credits')
|
|
421
|
+
})
|
|
422
|
+
|
|
423
|
+
test('contains add credits instruction', () => {
|
|
424
|
+
expect(OUT_OF_CREDITS_MESSAGE.toLowerCase()).toContain('add credits')
|
|
425
|
+
})
|
|
426
|
+
})
|
|
427
|
+
|
|
428
|
+
describe('FREEBUFF_RATE_LIMIT_MESSAGE', () => {
|
|
429
|
+
test('encourages retry without mentioning credits or payment', () => {
|
|
430
|
+
const message = FREEBUFF_RATE_LIMIT_MESSAGE.toLowerCase()
|
|
431
|
+
expect(message).toContain('try again')
|
|
432
|
+
expect(message).not.toContain('credit')
|
|
433
|
+
expect(message).not.toContain('pay')
|
|
434
|
+
})
|
|
435
|
+
})
|
|
436
|
+
|
|
437
|
+
describe('FREEBUFF_PROVIDER_USAGE_MESSAGE', () => {
|
|
438
|
+
test('owns the refill without blaming the user account', () => {
|
|
439
|
+
const message = FREEBUFF_PROVIDER_USAGE_MESSAGE.toLowerCase()
|
|
440
|
+
expect(message).toContain('freebuff ran out of provider usage')
|
|
441
|
+
expect(message).toContain('needs a refill')
|
|
442
|
+
expect(message).toContain('not your account')
|
|
443
|
+
expect(message).not.toContain('add credits')
|
|
444
|
+
})
|
|
445
|
+
})
|
|
446
|
+
|
|
447
|
+
describe('createErrorMessage', () => {
|
|
448
|
+
test('creates message from Error object', () => {
|
|
449
|
+
const error = new Error('Something went wrong')
|
|
450
|
+
const result = createErrorMessage(error, 'msg-123')
|
|
451
|
+
|
|
452
|
+
expect(result.id).toBe('msg-123')
|
|
453
|
+
expect(result.content).toContain('Something went wrong')
|
|
454
|
+
expect(result.content).toContain('**Error:**')
|
|
455
|
+
expect(result.isComplete).toBe(true)
|
|
456
|
+
expect(result.blocks).toBeUndefined()
|
|
457
|
+
})
|
|
458
|
+
|
|
459
|
+
test('creates message from string error', () => {
|
|
460
|
+
const result = createErrorMessage('String error', 'msg-456')
|
|
461
|
+
|
|
462
|
+
expect(result.id).toBe('msg-456')
|
|
463
|
+
expect(result.content).toContain('String error')
|
|
464
|
+
})
|
|
465
|
+
|
|
466
|
+
test('creates message from object with message property', () => {
|
|
467
|
+
const error = { message: 'Object error message', code: 'ERR_001' }
|
|
468
|
+
const result = createErrorMessage(error, 'msg-789')
|
|
469
|
+
|
|
470
|
+
expect(result.content).toContain('Object error message')
|
|
471
|
+
})
|
|
472
|
+
|
|
473
|
+
test('uses fallback for unknown error types', () => {
|
|
474
|
+
const result = createErrorMessage(null, 'msg-null')
|
|
475
|
+
|
|
476
|
+
expect(result.content).toContain('Unknown error occurred')
|
|
477
|
+
})
|
|
478
|
+
|
|
479
|
+
test('includes stack trace when available', () => {
|
|
480
|
+
const error = new Error('Error with stack')
|
|
481
|
+
const result = createErrorMessage(error, 'msg-stack')
|
|
482
|
+
|
|
483
|
+
expect(result.content).toContain('Error with stack')
|
|
484
|
+
// Stack trace should be included
|
|
485
|
+
expect(result.content).toContain('at')
|
|
486
|
+
})
|
|
487
|
+
|
|
488
|
+
test('handles error without message property', () => {
|
|
489
|
+
const error = { code: 'ERR_UNKNOWN' }
|
|
490
|
+
const result = createErrorMessage(error, 'msg-no-msg')
|
|
491
|
+
|
|
492
|
+
expect(result.content).toContain('Unknown error occurred')
|
|
493
|
+
})
|
|
494
|
+
|
|
495
|
+
test('handles error with empty message', () => {
|
|
496
|
+
const error = { message: '' }
|
|
497
|
+
const result = createErrorMessage(error, 'msg-empty')
|
|
498
|
+
|
|
499
|
+
expect(result.content).toContain('Unknown error occurred')
|
|
500
|
+
})
|
|
501
|
+
|
|
502
|
+
test('handles error with numeric message', () => {
|
|
503
|
+
const error = { message: 123 }
|
|
504
|
+
const result = createErrorMessage(error, 'msg-num')
|
|
505
|
+
|
|
506
|
+
expect(result.content).toContain('Unknown error occurred')
|
|
507
|
+
})
|
|
508
|
+
|
|
509
|
+
test('handles out of credits error', () => {
|
|
510
|
+
const error = { statusCode: 402, message: 'Payment required' }
|
|
511
|
+
const result = createErrorMessage(error, 'msg-402')
|
|
512
|
+
|
|
513
|
+
expect(result.content).toContain('Payment required')
|
|
514
|
+
})
|
|
515
|
+
|
|
516
|
+
test('preserves message ID', () => {
|
|
517
|
+
const error = new Error('Test')
|
|
518
|
+
const result = createErrorMessage(error, 'unique-id-123')
|
|
519
|
+
|
|
520
|
+
expect(result.id).toBe('unique-id-123')
|
|
521
|
+
})
|
|
522
|
+
|
|
523
|
+
test('marks message as complete', () => {
|
|
524
|
+
const error = new Error('Test')
|
|
525
|
+
const result = createErrorMessage(error, 'msg-complete')
|
|
526
|
+
|
|
527
|
+
expect(result.isComplete).toBe(true)
|
|
528
|
+
})
|
|
529
|
+
|
|
530
|
+
test('clears blocks from error message', () => {
|
|
531
|
+
const error = new Error('Test')
|
|
532
|
+
const result = createErrorMessage(error, 'msg-blocks')
|
|
533
|
+
|
|
534
|
+
expect(result.blocks).toBeUndefined()
|
|
535
|
+
})
|
|
536
|
+
|
|
537
|
+
test('handles deeply nested error objects', () => {
|
|
538
|
+
const error = {
|
|
539
|
+
message: 'Outer error',
|
|
540
|
+
cause: {
|
|
541
|
+
message: 'Inner error',
|
|
542
|
+
cause: {
|
|
543
|
+
message: 'Root cause',
|
|
544
|
+
},
|
|
545
|
+
},
|
|
546
|
+
}
|
|
547
|
+
const result = createErrorMessage(error, 'msg-nested')
|
|
548
|
+
|
|
549
|
+
// Should only extract the top-level message
|
|
550
|
+
expect(result.content).toContain('Outer error')
|
|
551
|
+
})
|
|
552
|
+
|
|
553
|
+
test('handles API error responses', () => {
|
|
554
|
+
const apiError = {
|
|
555
|
+
message: 'API request failed',
|
|
556
|
+
statusCode: 500,
|
|
557
|
+
response: { error: 'Internal server error' },
|
|
558
|
+
}
|
|
559
|
+
const result = createErrorMessage(apiError, 'msg-api')
|
|
560
|
+
|
|
561
|
+
expect(result.content).toContain('API request failed')
|
|
562
|
+
})
|
|
563
|
+
|
|
564
|
+
test('handles network timeout errors', () => {
|
|
565
|
+
const timeoutError = new Error('Request timeout')
|
|
566
|
+
;(timeoutError as any).code = 'ETIMEDOUT'
|
|
567
|
+
const result = createErrorMessage(timeoutError, 'msg-timeout')
|
|
568
|
+
|
|
569
|
+
expect(result.content).toContain('Request timeout')
|
|
570
|
+
})
|
|
571
|
+
|
|
572
|
+
test('handles auth errors', () => {
|
|
573
|
+
const authError = {
|
|
574
|
+
statusCode: 401,
|
|
575
|
+
message: 'Invalid authentication token',
|
|
576
|
+
}
|
|
577
|
+
const result = createErrorMessage(authError, 'msg-auth')
|
|
578
|
+
|
|
579
|
+
expect(result.content).toContain('Invalid authentication token')
|
|
580
|
+
})
|
|
581
|
+
})
|
|
582
|
+
|
|
583
|
+
describe('error scenarios', () => {
|
|
584
|
+
test('handles rate limit error (429)', () => {
|
|
585
|
+
const rateLimitError = {
|
|
586
|
+
statusCode: 429,
|
|
587
|
+
message: 'Too many requests',
|
|
588
|
+
retryAfter: 60,
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
expect(isOutOfCreditsError(rateLimitError)).toBe(false)
|
|
592
|
+
|
|
593
|
+
const result = createErrorMessage(rateLimitError, 'msg-rate')
|
|
594
|
+
expect(result.content).toContain('Too many requests')
|
|
595
|
+
})
|
|
596
|
+
|
|
597
|
+
test('handles server error (500)', () => {
|
|
598
|
+
const serverError = {
|
|
599
|
+
statusCode: 500,
|
|
600
|
+
message: 'Internal server error',
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
expect(isOutOfCreditsError(serverError)).toBe(false)
|
|
604
|
+
|
|
605
|
+
const result = createErrorMessage(serverError, 'msg-500')
|
|
606
|
+
expect(result.content).toContain('Internal server error')
|
|
607
|
+
})
|
|
608
|
+
|
|
609
|
+
test('handles validation error (400)', () => {
|
|
610
|
+
const validationError = {
|
|
611
|
+
statusCode: 400,
|
|
612
|
+
message: 'Invalid request parameters',
|
|
613
|
+
errors: [{ field: 'prompt', message: 'Required' }],
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
expect(isOutOfCreditsError(validationError)).toBe(false)
|
|
617
|
+
|
|
618
|
+
const result = createErrorMessage(validationError, 'msg-400')
|
|
619
|
+
expect(result.content).toContain('Invalid request parameters')
|
|
620
|
+
})
|
|
621
|
+
|
|
622
|
+
test('handles forbidden error (403)', () => {
|
|
623
|
+
const forbiddenError = {
|
|
624
|
+
statusCode: 403,
|
|
625
|
+
message: 'Access denied',
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
expect(isOutOfCreditsError(forbiddenError)).toBe(false)
|
|
629
|
+
|
|
630
|
+
const result = createErrorMessage(forbiddenError, 'msg-403')
|
|
631
|
+
expect(result.content).toContain('Access denied')
|
|
632
|
+
})
|
|
633
|
+
|
|
634
|
+
test('handles not found error (404)', () => {
|
|
635
|
+
const notFoundError = {
|
|
636
|
+
statusCode: 404,
|
|
637
|
+
message: 'Resource not found',
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
expect(isOutOfCreditsError(notFoundError)).toBe(false)
|
|
641
|
+
|
|
642
|
+
const result = createErrorMessage(notFoundError, 'msg-404')
|
|
643
|
+
expect(result.content).toContain('Resource not found')
|
|
644
|
+
})
|
|
645
|
+
|
|
646
|
+
test('handles conflict error (409)', () => {
|
|
647
|
+
const conflictError = {
|
|
648
|
+
statusCode: 409,
|
|
649
|
+
message: 'Conflict detected',
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
expect(isOutOfCreditsError(conflictError)).toBe(false)
|
|
653
|
+
|
|
654
|
+
const result = createErrorMessage(conflictError, 'msg-409')
|
|
655
|
+
expect(result.content).toContain('Conflict detected')
|
|
656
|
+
})
|
|
657
|
+
})
|
|
658
|
+
})
|