@alumbwe/anvil 1.0.19 → 1.0.20
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 +28 -30
- package/bin/anvil +2 -0
- package/package.json +71 -75
- package/src/agents/bundled-agents.generated.d.ts +14 -0
- package/src/agents/bundled-agents.generated.ts +3760 -0
- package/src/app.tsx +399 -0
- package/src/chat.tsx +1698 -0
- package/src/cli-args.ts +134 -0
- package/src/commands/__tests__/anvil-command-aliases.test.ts +56 -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__/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__/anvil-landing-heading-row.test.tsx +91 -0
- package/src/components/__tests__/anvil-model-selector.test.tsx +519 -0
- package/src/components/__tests__/anvil-offer-invariants.test.ts +54 -0
- package/src/components/__tests__/anvil-takeover-prompt.test.tsx +212 -0
- package/src/components/__tests__/chat-input-bar.test.tsx +130 -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-active-session-summary.tsx +63 -0
- package/src/components/anvil-landing-screen.tsx +865 -0
- package/src/components/anvil-mark.tsx +52 -0
- package/src/components/anvil-model-selector.tsx +1087 -0
- package/src/components/anvil-referral-banner.tsx +604 -0
- package/src/components/anvil-superseded-screen.tsx +62 -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/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-anvil-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-anvil-ctrl-c-exit.ts +23 -0
- package/src/hooks/use-anvil-session-progress.ts +34 -0
- package/src/hooks/use-anvil-session.ts +806 -0
- package/src/hooks/use-anvil-streak-query.ts +61 -0
- package/src/hooks/use-ask-user-bridge.ts +92 -0
- package/src/hooks/use-auth-query.ts +262 -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-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 +473 -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/setup/key-setup.ts +295 -0
- package/src/setup/load-env.ts +106 -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/anvil-model-store.ts +42 -0
- package/src/state/anvil-session-store.ts +53 -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/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/types/anvil-session.ts +17 -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/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__/anvil-instance-owner.test.ts +69 -0
- package/src/utils/__tests__/anvil-model-navigation.test.ts +102 -0
- package/src/utils/__tests__/anvil-premium-reset.test.ts +79 -0
- package/src/utils/__tests__/anvil-referral-cache.test.ts +116 -0
- package/src/utils/__tests__/anvil-session-api.test.ts +215 -0
- package/src/utils/__tests__/anvil-session-display.test.ts +23 -0
- package/src/utils/__tests__/anvil-streak-line.test.ts +217 -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__/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-agent-selection.ts +33 -0
- package/src/utils/anvil-api.ts +612 -0
- package/src/utils/anvil-client.ts +217 -0
- package/src/utils/anvil-instance-owner.ts +66 -0
- package/src/utils/anvil-model-navigation.ts +50 -0
- package/src/utils/anvil-premium-reset.ts +55 -0
- package/src/utils/anvil-referral-cache.ts +51 -0
- package/src/utils/anvil-session-api.ts +243 -0
- package/src/utils/anvil-session-display.ts +21 -0
- package/src/utils/anvil-streak-line.ts +93 -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/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
- package/bin/anvil.cjs +0 -40
- package/dist/bundled.js +0 -123508
- package/dist/bundled.js.map +0 -828
- package/dist/vendor/ripgrep/arm64-darwin/rg +0 -0
- package/dist/vendor/ripgrep/arm64-linux/rg +0 -0
- package/dist/vendor/ripgrep/x64-darwin/rg +0 -0
- package/dist/vendor/ripgrep/x64-linux/rg +0 -0
- package/dist/vendor/ripgrep/x64-win32/rg.exe +0 -0
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
import { TextAttributes } from '@opentui/core'
|
|
2
|
+
import { useCallback, useEffect, useState } from 'react'
|
|
3
|
+
|
|
4
|
+
import { defineToolComponent } from './types'
|
|
5
|
+
import { useTerminalDimensions } from '../../hooks/use-terminal-dimensions'
|
|
6
|
+
import { useTheme } from '../../hooks/use-theme'
|
|
7
|
+
import { getLatestFollowupToolCallId, useChatStore } from '../../state/chat-store'
|
|
8
|
+
import { useAnvilSessionStore } from '../../state/anvil-session-store'
|
|
9
|
+
import { IS_ANVIL } from '../../utils/constants'
|
|
10
|
+
import { Button } from '../button'
|
|
11
|
+
|
|
12
|
+
import type { ToolRenderConfig } from './types'
|
|
13
|
+
import type { SuggestedFollowup } from '../../types/store'
|
|
14
|
+
|
|
15
|
+
const EMPTY_CLICKED_SET = new Set<number>()
|
|
16
|
+
const MIN_LABEL_COLUMN_WIDTH = 12
|
|
17
|
+
const MAX_LABEL_COLUMN_WIDTH = 60
|
|
18
|
+
/** Minimum terminal width to show the prompt description on hover */
|
|
19
|
+
const MIN_WIDTH_FOR_DESCRIPTION = 80
|
|
20
|
+
|
|
21
|
+
interface FollowupLineProps {
|
|
22
|
+
followup: SuggestedFollowup
|
|
23
|
+
index: number
|
|
24
|
+
isClicked: boolean
|
|
25
|
+
isHovered: boolean
|
|
26
|
+
onSendFollowup: (prompt: string, index: number) => void
|
|
27
|
+
onHover: (index: number | null) => void
|
|
28
|
+
disabled?: boolean
|
|
29
|
+
/** Width of the label column (for fixed-width alignment) */
|
|
30
|
+
labelColumnWidth: number
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const FollowupLine = ({
|
|
34
|
+
followup,
|
|
35
|
+
index,
|
|
36
|
+
isClicked,
|
|
37
|
+
isHovered,
|
|
38
|
+
onSendFollowup,
|
|
39
|
+
onHover,
|
|
40
|
+
disabled,
|
|
41
|
+
labelColumnWidth,
|
|
42
|
+
}: FollowupLineProps) => {
|
|
43
|
+
const theme = useTheme()
|
|
44
|
+
const { terminalWidth } = useTerminalDimensions()
|
|
45
|
+
|
|
46
|
+
const handleClick = useCallback(() => {
|
|
47
|
+
if (!disabled) {
|
|
48
|
+
onSendFollowup(followup.prompt, index)
|
|
49
|
+
}
|
|
50
|
+
}, [followup.prompt, index, onSendFollowup, disabled])
|
|
51
|
+
|
|
52
|
+
const handleMouseOver = useCallback(() => onHover(index), [onHover, index])
|
|
53
|
+
const handleMouseOut = useCallback(() => onHover(null), [onHover])
|
|
54
|
+
|
|
55
|
+
// Compute effective hover state declaratively
|
|
56
|
+
// Show hover effects if actually hovered AND not disabled AND not already clicked
|
|
57
|
+
const showHoverState = isHovered && !disabled && !isClicked
|
|
58
|
+
|
|
59
|
+
const hasLabel = Boolean(followup.label)
|
|
60
|
+
const displayText = hasLabel ? followup.label : followup.prompt
|
|
61
|
+
|
|
62
|
+
// Show description when hovered, has a label, and terminal is wide enough
|
|
63
|
+
const showDescription =
|
|
64
|
+
showHoverState && hasLabel && terminalWidth >= MIN_WIDTH_FOR_DESCRIPTION
|
|
65
|
+
|
|
66
|
+
// Calculate truncated prompt with ellipsis only when needed
|
|
67
|
+
const truncatedPrompt = showDescription
|
|
68
|
+
? (() => {
|
|
69
|
+
const availableWidth = Math.max(0, terminalWidth - labelColumnWidth - 4)
|
|
70
|
+
return followup.prompt.length > availableWidth
|
|
71
|
+
? followup.prompt.slice(0, availableWidth - 1) + '…'
|
|
72
|
+
: followup.prompt
|
|
73
|
+
})()
|
|
74
|
+
: ''
|
|
75
|
+
|
|
76
|
+
// Determine colors based on state
|
|
77
|
+
// When hovered, use primary color (violet) for both arrow and title
|
|
78
|
+
const iconColor = isClicked
|
|
79
|
+
? theme.success
|
|
80
|
+
: showHoverState
|
|
81
|
+
? theme.primary
|
|
82
|
+
: theme.muted
|
|
83
|
+
const labelColor = isClicked
|
|
84
|
+
? theme.muted
|
|
85
|
+
: showHoverState
|
|
86
|
+
? theme.primary
|
|
87
|
+
: theme.foreground
|
|
88
|
+
|
|
89
|
+
// Calculate padding spaces needed to align descriptions (only when showing description)
|
|
90
|
+
const labelLength = (displayText ?? '').length
|
|
91
|
+
const paddingSpaces = showDescription
|
|
92
|
+
? ' '.repeat(Math.max(0, labelColumnWidth - 2 - labelLength)) // -2 for "→ " prefix
|
|
93
|
+
: ''
|
|
94
|
+
|
|
95
|
+
return (
|
|
96
|
+
<box style={{ flexDirection: 'column' }}>
|
|
97
|
+
{/* Row layout: clickable label + non-clickable description */}
|
|
98
|
+
<box style={{ flexDirection: 'row', width: '100%' }}>
|
|
99
|
+
{/* Clickable label area - only the text itself is clickable */}
|
|
100
|
+
<Button
|
|
101
|
+
onClick={handleClick}
|
|
102
|
+
onMouseOver={handleMouseOver}
|
|
103
|
+
onMouseOut={handleMouseOut}
|
|
104
|
+
style={{
|
|
105
|
+
flexShrink: 0,
|
|
106
|
+
flexGrow: 0,
|
|
107
|
+
backgroundColor: showHoverState ? theme.surface : undefined,
|
|
108
|
+
}}
|
|
109
|
+
>
|
|
110
|
+
<text style={{ wrapMode: hasLabel ? 'none' : 'word' }}>
|
|
111
|
+
<span fg={iconColor}>{isClicked ? '✓' : '→'}</span>
|
|
112
|
+
<span
|
|
113
|
+
fg={labelColor}
|
|
114
|
+
attributes={showHoverState ? TextAttributes.BOLD : undefined}
|
|
115
|
+
>
|
|
116
|
+
{' '}
|
|
117
|
+
{displayText}
|
|
118
|
+
</span>
|
|
119
|
+
</text>
|
|
120
|
+
</Button>
|
|
121
|
+
{/* Flexible description column - NOT clickable, with padding for alignment */}
|
|
122
|
+
{showDescription && hasLabel && (
|
|
123
|
+
<box style={{ flexGrow: 1 }}>
|
|
124
|
+
<text style={{ wrapMode: 'none' }}>
|
|
125
|
+
<span fg={theme.muted} attributes={TextAttributes.ITALIC}>
|
|
126
|
+
{paddingSpaces}{truncatedPrompt}
|
|
127
|
+
</span>
|
|
128
|
+
</text>
|
|
129
|
+
</box>
|
|
130
|
+
)}
|
|
131
|
+
</box>
|
|
132
|
+
</box>
|
|
133
|
+
)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
interface SuggestFollowupsItemProps {
|
|
137
|
+
toolCallId: string
|
|
138
|
+
followups: SuggestedFollowup[]
|
|
139
|
+
onSendFollowup: (prompt: string, index: number) => void
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
interface PastFollowupItemProps {
|
|
143
|
+
followup: SuggestedFollowup
|
|
144
|
+
isClicked: boolean
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const PastFollowupItem = ({ followup, isClicked }: PastFollowupItemProps) => {
|
|
148
|
+
const theme = useTheme()
|
|
149
|
+
const displayLabel = followup.label || followup.prompt
|
|
150
|
+
const showFullPrompt = followup.label && followup.label !== followup.prompt
|
|
151
|
+
|
|
152
|
+
return (
|
|
153
|
+
<box style={{ flexDirection: 'column', marginLeft: 2 }}>
|
|
154
|
+
<text>
|
|
155
|
+
<span fg={isClicked ? theme.success : theme.muted}>
|
|
156
|
+
{isClicked ? '✓' : '→'}
|
|
157
|
+
</span>
|
|
158
|
+
<span fg={isClicked ? theme.muted : theme.foreground}>
|
|
159
|
+
{' '}
|
|
160
|
+
{displayLabel}
|
|
161
|
+
</span>
|
|
162
|
+
</text>
|
|
163
|
+
{showFullPrompt && (
|
|
164
|
+
<text style={{ marginLeft: 2 }}>
|
|
165
|
+
<span fg={theme.muted} attributes={TextAttributes.ITALIC}>
|
|
166
|
+
{followup.prompt}
|
|
167
|
+
</span>
|
|
168
|
+
</text>
|
|
169
|
+
)}
|
|
170
|
+
</box>
|
|
171
|
+
)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
interface PastFollowupsToggleProps {
|
|
175
|
+
toolCallId: string
|
|
176
|
+
followups: SuggestedFollowup[]
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const PastFollowupsToggle = ({
|
|
180
|
+
toolCallId,
|
|
181
|
+
followups,
|
|
182
|
+
}: PastFollowupsToggleProps) => {
|
|
183
|
+
const theme = useTheme()
|
|
184
|
+
const [isExpanded, setIsExpanded] = useState(false)
|
|
185
|
+
const clickedIndices = useChatStore(
|
|
186
|
+
(state) => state.clickedFollowupsMap.get(toolCallId) ?? EMPTY_CLICKED_SET,
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
const handleToggle = useCallback(() => {
|
|
190
|
+
setIsExpanded((prev) => !prev)
|
|
191
|
+
}, [])
|
|
192
|
+
|
|
193
|
+
const toggleIndicator = isExpanded ? '▾' : '▸'
|
|
194
|
+
|
|
195
|
+
return (
|
|
196
|
+
<box style={{ flexDirection: 'column' }}>
|
|
197
|
+
<Button onClick={handleToggle}>
|
|
198
|
+
<text>
|
|
199
|
+
<span fg={theme.muted}>{toggleIndicator}</span>
|
|
200
|
+
<span fg={theme.muted} attributes={TextAttributes.ITALIC}>
|
|
201
|
+
{' '}
|
|
202
|
+
Previously suggested followups
|
|
203
|
+
</span>
|
|
204
|
+
</text>
|
|
205
|
+
</Button>
|
|
206
|
+
{isExpanded && (
|
|
207
|
+
<box style={{ flexDirection: 'column', marginTop: 0 }}>
|
|
208
|
+
{followups.map((followup, index) => (
|
|
209
|
+
<PastFollowupItem
|
|
210
|
+
key={`past-followup-${index}`}
|
|
211
|
+
followup={followup}
|
|
212
|
+
isClicked={clickedIndices.has(index)}
|
|
213
|
+
/>
|
|
214
|
+
))}
|
|
215
|
+
</box>
|
|
216
|
+
)}
|
|
217
|
+
</box>
|
|
218
|
+
)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const SuggestFollowupsItem = ({
|
|
222
|
+
toolCallId,
|
|
223
|
+
followups,
|
|
224
|
+
onSendFollowup,
|
|
225
|
+
}: SuggestFollowupsItemProps) => {
|
|
226
|
+
const theme = useTheme()
|
|
227
|
+
const inputFocused = useChatStore((state) => state.inputFocused)
|
|
228
|
+
const isAnvilSessionOver = useAnvilSessionStore(
|
|
229
|
+
(state) => IS_ANVIL && state.session?.status === 'ended',
|
|
230
|
+
)
|
|
231
|
+
const setSuggestedFollowups = useChatStore(
|
|
232
|
+
(state) => state.setSuggestedFollowups,
|
|
233
|
+
)
|
|
234
|
+
const latestFollowupToolCallId = useChatStore((state) =>
|
|
235
|
+
getLatestFollowupToolCallId(state.messages),
|
|
236
|
+
)
|
|
237
|
+
const clickedIndices = useChatStore(
|
|
238
|
+
(state) => state.clickedFollowupsMap.get(toolCallId) ?? EMPTY_CLICKED_SET,
|
|
239
|
+
)
|
|
240
|
+
const currentSuggestedFollowups = useChatStore(
|
|
241
|
+
(state) => state.suggestedFollowups,
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
const isActive = latestFollowupToolCallId === toolCallId
|
|
245
|
+
|
|
246
|
+
useEffect(() => {
|
|
247
|
+
if (!isActive) return
|
|
248
|
+
|
|
249
|
+
const hasSameTool = currentSuggestedFollowups?.toolCallId === toolCallId
|
|
250
|
+
const hasSameFollowups =
|
|
251
|
+
hasSameTool &&
|
|
252
|
+
currentSuggestedFollowups?.followups.length === followups.length &&
|
|
253
|
+
currentSuggestedFollowups.followups.every((f, idx) => {
|
|
254
|
+
const next = followups[idx]
|
|
255
|
+
return f?.prompt === next?.prompt && f?.label === next?.label
|
|
256
|
+
})
|
|
257
|
+
const hasSameClicks =
|
|
258
|
+
hasSameTool &&
|
|
259
|
+
currentSuggestedFollowups?.clickedIndices.size === clickedIndices.size &&
|
|
260
|
+
Array.from(currentSuggestedFollowups.clickedIndices).every((idx) =>
|
|
261
|
+
clickedIndices.has(idx),
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
if (hasSameFollowups && hasSameClicks) return
|
|
265
|
+
|
|
266
|
+
// Track the currently active followups set so we can persist clicked state and avoid races
|
|
267
|
+
setSuggestedFollowups({
|
|
268
|
+
toolCallId,
|
|
269
|
+
followups,
|
|
270
|
+
clickedIndices: new Set(clickedIndices),
|
|
271
|
+
})
|
|
272
|
+
}, [
|
|
273
|
+
clickedIndices,
|
|
274
|
+
currentSuggestedFollowups,
|
|
275
|
+
followups,
|
|
276
|
+
isActive,
|
|
277
|
+
setSuggestedFollowups,
|
|
278
|
+
toolCallId,
|
|
279
|
+
])
|
|
280
|
+
|
|
281
|
+
// Track which item is hovered (for passing to children)
|
|
282
|
+
const [hoveredIndex, setHoveredIndex] = useState<number | null>(null)
|
|
283
|
+
|
|
284
|
+
// For past messages, show collapsed toggle view
|
|
285
|
+
if (!isActive) {
|
|
286
|
+
return <PastFollowupsToggle toolCallId={toolCallId} followups={followups} />
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Calculate label column width for alignment across all followups
|
|
290
|
+
// Width = "→ " (2 chars) + max label/prompt length + " " spacing (2 chars)
|
|
291
|
+
const maxDisplayLength = Math.max(
|
|
292
|
+
0,
|
|
293
|
+
...followups.map((f) => (f.label ?? f.prompt).length),
|
|
294
|
+
)
|
|
295
|
+
const labelColumnWidth = Math.min(
|
|
296
|
+
MAX_LABEL_COLUMN_WIDTH,
|
|
297
|
+
Math.max(MIN_LABEL_COLUMN_WIDTH, 2 + maxDisplayLength + 2),
|
|
298
|
+
) // "→ " + label/prompt + " "
|
|
299
|
+
|
|
300
|
+
return (
|
|
301
|
+
<box style={{ flexDirection: 'column' }}>
|
|
302
|
+
<text style={{ fg: theme.muted }}>Suggested followups:</text>
|
|
303
|
+
<box style={{ flexDirection: 'column' }}>
|
|
304
|
+
{followups.map((followup, index) => (
|
|
305
|
+
<FollowupLine
|
|
306
|
+
key={`followup-${index}`}
|
|
307
|
+
followup={followup}
|
|
308
|
+
index={index}
|
|
309
|
+
isClicked={clickedIndices.has(index)}
|
|
310
|
+
isHovered={hoveredIndex === index}
|
|
311
|
+
onSendFollowup={onSendFollowup}
|
|
312
|
+
onHover={setHoveredIndex}
|
|
313
|
+
disabled={!inputFocused || isAnvilSessionOver}
|
|
314
|
+
labelColumnWidth={labelColumnWidth}
|
|
315
|
+
/>
|
|
316
|
+
))}
|
|
317
|
+
</box>
|
|
318
|
+
</box>
|
|
319
|
+
)
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* UI component for suggest_followups tool.
|
|
324
|
+
* Displays clickable cards that send the followup prompt as a user message when clicked.
|
|
325
|
+
*/
|
|
326
|
+
export const SuggestFollowupsComponent = defineToolComponent({
|
|
327
|
+
toolName: 'suggest_followups',
|
|
328
|
+
|
|
329
|
+
render(toolBlock, _theme, options): ToolRenderConfig {
|
|
330
|
+
const { input, toolCallId } = toolBlock
|
|
331
|
+
|
|
332
|
+
// Extract followups from input
|
|
333
|
+
let followups: SuggestedFollowup[] = []
|
|
334
|
+
|
|
335
|
+
if (Array.isArray(input?.followups)) {
|
|
336
|
+
followups = input.followups.filter(
|
|
337
|
+
(f: unknown): f is SuggestedFollowup =>
|
|
338
|
+
typeof f === 'object' &&
|
|
339
|
+
f !== null &&
|
|
340
|
+
typeof (f as SuggestedFollowup).prompt === 'string',
|
|
341
|
+
)
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
if (followups.length === 0) {
|
|
345
|
+
return { content: null }
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// The actual click handling is done in chat.tsx via the global handler
|
|
349
|
+
// Here we just pass a placeholder that will be replaced
|
|
350
|
+
const handleSendFollowup = (prompt: string, index: number) => {
|
|
351
|
+
// This gets called from the FollowupCard component
|
|
352
|
+
// The actual logic is handled via the global followup handler
|
|
353
|
+
const event = new CustomEvent('anvil:send-followup', {
|
|
354
|
+
detail: { prompt, index, toolCallId },
|
|
355
|
+
})
|
|
356
|
+
globalThis.dispatchEvent(event)
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
return {
|
|
360
|
+
content: (
|
|
361
|
+
<SuggestFollowupsItem
|
|
362
|
+
toolCallId={toolCallId}
|
|
363
|
+
followups={followups}
|
|
364
|
+
onSendFollowup={handleSendFollowup}
|
|
365
|
+
/>
|
|
366
|
+
),
|
|
367
|
+
}
|
|
368
|
+
},
|
|
369
|
+
})
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { defineToolComponent } from './types'
|
|
2
|
+
|
|
3
|
+
import type { ToolRenderConfig } from './types'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* UI component for task_completed tool.
|
|
7
|
+
*/
|
|
8
|
+
export const TaskCompleteComponent = defineToolComponent({
|
|
9
|
+
toolName: 'task_completed',
|
|
10
|
+
|
|
11
|
+
render(): ToolRenderConfig {
|
|
12
|
+
return {
|
|
13
|
+
content: null,
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
})
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import { TextAttributes } from '@opentui/core'
|
|
2
|
+
import React, { type ReactNode } from 'react'
|
|
3
|
+
import stringWidth from 'string-width'
|
|
4
|
+
|
|
5
|
+
import { useTheme } from '../../hooks/use-theme'
|
|
6
|
+
import { Button } from '../button'
|
|
7
|
+
|
|
8
|
+
import type { ChatTheme } from '../../types/theme-system'
|
|
9
|
+
|
|
10
|
+
interface ToolCallItemProps {
|
|
11
|
+
name: string
|
|
12
|
+
content: ReactNode
|
|
13
|
+
isCollapsed: boolean
|
|
14
|
+
isStreaming: boolean
|
|
15
|
+
streamingPreview: string
|
|
16
|
+
finishedPreview: string
|
|
17
|
+
onToggle?: () => void
|
|
18
|
+
titleSuffix?: string
|
|
19
|
+
dense?: boolean
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const isTextRenderable = (value: ReactNode): boolean => {
|
|
23
|
+
if (value === null || value === undefined || typeof value === 'boolean') {
|
|
24
|
+
return false
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (typeof value === 'string' || typeof value === 'number') {
|
|
28
|
+
return true
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (Array.isArray(value)) {
|
|
32
|
+
return value.every((child) => isTextRenderable(child))
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (React.isValidElement(value)) {
|
|
36
|
+
const elProps = value.props as Record<string, unknown>
|
|
37
|
+
if (value.type === React.Fragment) {
|
|
38
|
+
return isTextRenderable(elProps.children as ReactNode)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (typeof value.type === 'string') {
|
|
42
|
+
if (
|
|
43
|
+
value.type === 'span' ||
|
|
44
|
+
value.type === 'strong' ||
|
|
45
|
+
value.type === 'em'
|
|
46
|
+
) {
|
|
47
|
+
return isTextRenderable(elProps.children as ReactNode)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return false
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return false
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const renderExpandedContent = (
|
|
58
|
+
value: ReactNode,
|
|
59
|
+
theme: ChatTheme,
|
|
60
|
+
getAttributes: (extra?: number) => number | undefined,
|
|
61
|
+
): ReactNode => {
|
|
62
|
+
if (
|
|
63
|
+
value === null ||
|
|
64
|
+
value === undefined ||
|
|
65
|
+
value === false ||
|
|
66
|
+
value === true
|
|
67
|
+
) {
|
|
68
|
+
return null
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (isTextRenderable(value)) {
|
|
72
|
+
return (
|
|
73
|
+
<text
|
|
74
|
+
fg={theme.foreground}
|
|
75
|
+
key="tool-expanded-text"
|
|
76
|
+
attributes={getAttributes()}
|
|
77
|
+
>
|
|
78
|
+
{value}
|
|
79
|
+
</text>
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (React.isValidElement(value)) {
|
|
84
|
+
if (value.key === null || value.key === undefined) {
|
|
85
|
+
return (
|
|
86
|
+
<box
|
|
87
|
+
key="tool-expanded-node"
|
|
88
|
+
style={{ flexDirection: 'column', gap: 0 }}
|
|
89
|
+
>
|
|
90
|
+
{value}
|
|
91
|
+
</box>
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
return value
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (Array.isArray(value)) {
|
|
98
|
+
return (
|
|
99
|
+
<box
|
|
100
|
+
key="tool-expanded-array"
|
|
101
|
+
style={{ flexDirection: 'column', gap: 0 }}
|
|
102
|
+
>
|
|
103
|
+
{value.map((child, idx) => (
|
|
104
|
+
<box
|
|
105
|
+
key={`tool-expanded-array-${idx}`}
|
|
106
|
+
style={{ flexDirection: 'column', gap: 0 }}
|
|
107
|
+
>
|
|
108
|
+
{child}
|
|
109
|
+
</box>
|
|
110
|
+
))}
|
|
111
|
+
</box>
|
|
112
|
+
)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
<box
|
|
117
|
+
key="tool-expanded-unknown"
|
|
118
|
+
style={{ flexDirection: 'column', gap: 0 }}
|
|
119
|
+
>
|
|
120
|
+
{value}
|
|
121
|
+
</box>
|
|
122
|
+
)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
interface SimpleToolCallItemProps {
|
|
126
|
+
name: string
|
|
127
|
+
/** Description - can be a string or ReactNode for rich formatting */
|
|
128
|
+
description: string | ReactNode
|
|
129
|
+
descriptionColor?: string
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export const SimpleToolCallItem = ({
|
|
133
|
+
name,
|
|
134
|
+
description,
|
|
135
|
+
descriptionColor,
|
|
136
|
+
}: SimpleToolCallItemProps) => {
|
|
137
|
+
const theme = useTheme()
|
|
138
|
+
const bulletChar = '• '
|
|
139
|
+
|
|
140
|
+
return (
|
|
141
|
+
<box style={{ flexDirection: 'row', alignItems: 'center', width: '100%' }}>
|
|
142
|
+
<text style={{ wrapMode: 'word' }}>
|
|
143
|
+
<span fg={theme.foreground}>{bulletChar}</span>
|
|
144
|
+
<span fg={theme.foreground} attributes={TextAttributes.BOLD}>
|
|
145
|
+
{name}
|
|
146
|
+
</span>
|
|
147
|
+
<span fg={theme.foreground}> </span>
|
|
148
|
+
{typeof description === 'string' ? (
|
|
149
|
+
<span fg={descriptionColor ?? theme.foreground}>{description}</span>
|
|
150
|
+
) : (
|
|
151
|
+
description
|
|
152
|
+
)}
|
|
153
|
+
</text>
|
|
154
|
+
</box>
|
|
155
|
+
)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export const ToolCallItem = ({
|
|
159
|
+
name,
|
|
160
|
+
content,
|
|
161
|
+
isCollapsed,
|
|
162
|
+
isStreaming,
|
|
163
|
+
streamingPreview,
|
|
164
|
+
finishedPreview,
|
|
165
|
+
onToggle,
|
|
166
|
+
titleSuffix,
|
|
167
|
+
dense = false,
|
|
168
|
+
}: ToolCallItemProps) => {
|
|
169
|
+
const theme = useTheme()
|
|
170
|
+
|
|
171
|
+
const baseTextAttributes = theme.messageTextAttributes ?? 0
|
|
172
|
+
const getAttributes = (extra: number = 0): number | undefined => {
|
|
173
|
+
const combined = baseTextAttributes | extra
|
|
174
|
+
return combined === 0 ? undefined : combined
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const isExpanded = !isCollapsed
|
|
178
|
+
const bulletChar = '• '
|
|
179
|
+
const toggleIndicator = onToggle ? (isCollapsed ? '▸ ' : '▾ ') : ''
|
|
180
|
+
const toggleLabel = onToggle ? toggleIndicator : bulletChar
|
|
181
|
+
// Width in cells of the toggle label (toggle arrow or bullet). Used to align
|
|
182
|
+
// expanded content directly under the toggle icon.
|
|
183
|
+
const toggleIndent = stringWidth(toggleLabel)
|
|
184
|
+
const collapsedPreviewText = isStreaming ? streamingPreview : finishedPreview
|
|
185
|
+
const showCollapsedPreview = collapsedPreviewText.length > 0
|
|
186
|
+
|
|
187
|
+
return (
|
|
188
|
+
<box style={{ flexDirection: 'column', gap: 0, width: '100%' }}>
|
|
189
|
+
<box
|
|
190
|
+
style={{
|
|
191
|
+
flexDirection: 'column',
|
|
192
|
+
gap: 0,
|
|
193
|
+
paddingLeft: 0,
|
|
194
|
+
paddingRight: 0,
|
|
195
|
+
paddingTop: 0,
|
|
196
|
+
paddingBottom: 0,
|
|
197
|
+
width: '100%',
|
|
198
|
+
}}
|
|
199
|
+
>
|
|
200
|
+
<Button
|
|
201
|
+
style={{
|
|
202
|
+
flexDirection: 'row',
|
|
203
|
+
alignItems: 'center',
|
|
204
|
+
paddingLeft: 0,
|
|
205
|
+
paddingRight: 0,
|
|
206
|
+
paddingTop: 0,
|
|
207
|
+
paddingBottom: 0,
|
|
208
|
+
width: '100%',
|
|
209
|
+
}}
|
|
210
|
+
onClick={onToggle}
|
|
211
|
+
>
|
|
212
|
+
<text style={{ wrapMode: 'none' }}>
|
|
213
|
+
<span
|
|
214
|
+
fg={theme.foreground}
|
|
215
|
+
attributes={isExpanded ? TextAttributes.BOLD : undefined}
|
|
216
|
+
>
|
|
217
|
+
{toggleLabel}
|
|
218
|
+
</span>
|
|
219
|
+
<span fg={theme.foreground} attributes={TextAttributes.BOLD}>
|
|
220
|
+
{name}
|
|
221
|
+
</span>
|
|
222
|
+
{titleSuffix ? (
|
|
223
|
+
<span fg={theme.foreground} attributes={TextAttributes.BOLD}>
|
|
224
|
+
{` ${titleSuffix}`}
|
|
225
|
+
</span>
|
|
226
|
+
) : null}
|
|
227
|
+
{isStreaming ? (
|
|
228
|
+
<span fg={theme.primary} attributes={TextAttributes.DIM}>
|
|
229
|
+
{' running'}
|
|
230
|
+
</span>
|
|
231
|
+
) : null}
|
|
232
|
+
</text>
|
|
233
|
+
</Button>
|
|
234
|
+
|
|
235
|
+
{isCollapsed ? (
|
|
236
|
+
showCollapsedPreview ? (
|
|
237
|
+
<box
|
|
238
|
+
style={{
|
|
239
|
+
paddingLeft: 0,
|
|
240
|
+
paddingRight: 0,
|
|
241
|
+
paddingTop: 0,
|
|
242
|
+
paddingBottom: 0,
|
|
243
|
+
width: '100%',
|
|
244
|
+
}}
|
|
245
|
+
>
|
|
246
|
+
<text
|
|
247
|
+
fg={isStreaming ? theme.foreground : theme.muted}
|
|
248
|
+
attributes={getAttributes(TextAttributes.ITALIC)}
|
|
249
|
+
style={{ wrapMode: 'word' }}
|
|
250
|
+
>
|
|
251
|
+
{collapsedPreviewText}
|
|
252
|
+
</text>
|
|
253
|
+
</box>
|
|
254
|
+
) : null
|
|
255
|
+
) : (
|
|
256
|
+
<box
|
|
257
|
+
style={{
|
|
258
|
+
flexDirection: 'column',
|
|
259
|
+
gap: 0,
|
|
260
|
+
// Indent expanded content underneath the toggle icon
|
|
261
|
+
paddingLeft: toggleIndent,
|
|
262
|
+
paddingRight: dense ? 0 : toggleIndent,
|
|
263
|
+
paddingTop: 0,
|
|
264
|
+
paddingBottom: 0,
|
|
265
|
+
}}
|
|
266
|
+
>
|
|
267
|
+
{renderExpandedContent(content, theme, getAttributes)}
|
|
268
|
+
</box>
|
|
269
|
+
)}
|
|
270
|
+
</box>
|
|
271
|
+
</box>
|
|
272
|
+
)
|
|
273
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { ContentBlock } from '../../types/chat'
|
|
2
|
+
import type { ChatTheme } from '../../types/theme-system'
|
|
3
|
+
import type { ToolName } from '@anvil/sdk'
|
|
4
|
+
import type { ReactNode } from 'react'
|
|
5
|
+
|
|
6
|
+
export type ToolBlock = Extract<ContentBlock, { type: 'tool' }>
|
|
7
|
+
|
|
8
|
+
export type ToolRenderOptions = {
|
|
9
|
+
availableWidth: number
|
|
10
|
+
indentationOffset: number
|
|
11
|
+
previewPrefix?: string
|
|
12
|
+
labelWidth: number
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export type ToolRenderConfig = {
|
|
16
|
+
/** Optional path to display in the tool header */
|
|
17
|
+
path?: string
|
|
18
|
+
/** Custom content to render in the tool body */
|
|
19
|
+
content?: ReactNode
|
|
20
|
+
/** Preview text to show when the tool is collapsed */
|
|
21
|
+
collapsedPreview?: string
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Base interface for tool-specific UI components.
|
|
26
|
+
* Implement this interface to create a custom renderer for a specific tool.
|
|
27
|
+
*/
|
|
28
|
+
export interface ToolComponent<T extends ToolName = ToolName> {
|
|
29
|
+
/** The tool name this component handles */
|
|
30
|
+
toolName: T
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Render function that returns configuration for how to display this tool.
|
|
34
|
+
*
|
|
35
|
+
* @param toolBlock - The tool block data containing input/output
|
|
36
|
+
* @param theme - The current chat theme
|
|
37
|
+
* @param options - Rendering options like width and indentation
|
|
38
|
+
* @returns Configuration for rendering the tool, or null to use default rendering
|
|
39
|
+
*/
|
|
40
|
+
render(
|
|
41
|
+
toolBlock: ToolBlock & { toolName: T },
|
|
42
|
+
theme: ChatTheme,
|
|
43
|
+
options: ToolRenderOptions,
|
|
44
|
+
): ToolRenderConfig
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Type-safe tool component definition.
|
|
49
|
+
* Use this helper to create tool components with full type inference.
|
|
50
|
+
*/
|
|
51
|
+
export function defineToolComponent<T extends ToolName>(
|
|
52
|
+
component: ToolComponent<T>,
|
|
53
|
+
): ToolComponent<T> {
|
|
54
|
+
return component
|
|
55
|
+
}
|