@butlerw/vellum 0.1.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/LICENSE +21 -0
- package/README.md +411 -0
- package/__fixtures__/responses/code-generation.json +42 -0
- package/__fixtures__/responses/error-response.json +20 -0
- package/__fixtures__/responses/hello-world.json +32 -0
- package/dist/auth-6MCXESOH.js +26 -0
- package/dist/chunk-SECXJGWA.js +597 -0
- package/dist/index.js +34023 -0
- package/package.json +67 -0
- package/src/__tests__/commands.e2e.test.ts +728 -0
- package/src/__tests__/credentials.test.ts +713 -0
- package/src/__tests__/mode-e2e.test.ts +391 -0
- package/src/__tests__/tui-integration.test.tsx +1271 -0
- package/src/agents/__tests__/task-persistence.test.ts +235 -0
- package/src/agents/commands/delegate.ts +240 -0
- package/src/agents/commands/index.ts +10 -0
- package/src/agents/commands/resume.ts +335 -0
- package/src/agents/index.ts +29 -0
- package/src/agents/task-persistence.ts +272 -0
- package/src/agents/task-resumption.ts +242 -0
- package/src/app.tsx +4737 -0
- package/src/commands/__tests__/.gitkeep +1 -0
- package/src/commands/__tests__/agents.test.ts +606 -0
- package/src/commands/__tests__/auth.test.ts +626 -0
- package/src/commands/__tests__/autocomplete.test.ts +683 -0
- package/src/commands/__tests__/batch.test.ts +287 -0
- package/src/commands/__tests__/chain-pipe-parser.test.ts +654 -0
- package/src/commands/__tests__/completion.test.ts +238 -0
- package/src/commands/__tests__/core.test.ts +363 -0
- package/src/commands/__tests__/executor.test.ts +496 -0
- package/src/commands/__tests__/exit-codes.test.ts +220 -0
- package/src/commands/__tests__/init.test.ts +243 -0
- package/src/commands/__tests__/language.test.ts +353 -0
- package/src/commands/__tests__/mode-cli.test.ts +667 -0
- package/src/commands/__tests__/model.test.ts +277 -0
- package/src/commands/__tests__/parser.test.ts +493 -0
- package/src/commands/__tests__/performance.bench.ts +380 -0
- package/src/commands/__tests__/registry.test.ts +534 -0
- package/src/commands/__tests__/resume.test.ts +449 -0
- package/src/commands/__tests__/security.test.ts +845 -0
- package/src/commands/__tests__/stream-json.test.ts +372 -0
- package/src/commands/__tests__/user-commands.test.ts +597 -0
- package/src/commands/adapters.ts +267 -0
- package/src/commands/agent.ts +395 -0
- package/src/commands/agents/generate.ts +506 -0
- package/src/commands/agents/index.ts +272 -0
- package/src/commands/agents/show.ts +271 -0
- package/src/commands/agents/validate.ts +387 -0
- package/src/commands/auth.ts +883 -0
- package/src/commands/autocomplete.ts +480 -0
- package/src/commands/batch/command.ts +388 -0
- package/src/commands/batch/executor.ts +361 -0
- package/src/commands/batch/index.ts +12 -0
- package/src/commands/commit.ts +235 -0
- package/src/commands/completion/index.ts +371 -0
- package/src/commands/condense.ts +191 -0
- package/src/commands/config.ts +344 -0
- package/src/commands/context-provider.ts +173 -0
- package/src/commands/copy.ts +329 -0
- package/src/commands/core/clear.ts +38 -0
- package/src/commands/core/exit.ts +43 -0
- package/src/commands/core/help.ts +354 -0
- package/src/commands/core/index.ts +15 -0
- package/src/commands/cost.ts +179 -0
- package/src/commands/credentials.tsx +618 -0
- package/src/commands/custom-agents/__tests__/custom-agents.test.ts +709 -0
- package/src/commands/custom-agents/create.ts +377 -0
- package/src/commands/custom-agents/export.ts +135 -0
- package/src/commands/custom-agents/import.ts +199 -0
- package/src/commands/custom-agents/index.ts +372 -0
- package/src/commands/custom-agents/info.ts +318 -0
- package/src/commands/custom-agents/list.ts +267 -0
- package/src/commands/custom-agents/validate.ts +388 -0
- package/src/commands/diff-mode.ts +241 -0
- package/src/commands/env.ts +53 -0
- package/src/commands/executor.ts +579 -0
- package/src/commands/exit-codes.ts +202 -0
- package/src/commands/index.ts +701 -0
- package/src/commands/init/index.ts +15 -0
- package/src/commands/init/prompts.ts +366 -0
- package/src/commands/init/templates/commands-readme.md +80 -0
- package/src/commands/init/templates/example-command.md +79 -0
- package/src/commands/init/templates/example-skill.md +168 -0
- package/src/commands/init/templates/example-workflow.md +101 -0
- package/src/commands/init/templates/prompts-readme.md +52 -0
- package/src/commands/init/templates/rules-readme.md +63 -0
- package/src/commands/init/templates/skills-readme.md +83 -0
- package/src/commands/init/templates/workflows-readme.md +94 -0
- package/src/commands/init.ts +391 -0
- package/src/commands/install.ts +90 -0
- package/src/commands/language.ts +191 -0
- package/src/commands/loaders/.gitkeep +1 -0
- package/src/commands/lsp.ts +199 -0
- package/src/commands/markdown-commands.ts +253 -0
- package/src/commands/mcp.ts +588 -0
- package/src/commands/memory/export.ts +341 -0
- package/src/commands/memory/index.ts +148 -0
- package/src/commands/memory/list.ts +261 -0
- package/src/commands/memory/search.ts +346 -0
- package/src/commands/memory/utils.ts +15 -0
- package/src/commands/metrics.ts +75 -0
- package/src/commands/migrate/index.ts +16 -0
- package/src/commands/migrate/prompts.ts +477 -0
- package/src/commands/mode.ts +331 -0
- package/src/commands/model.ts +298 -0
- package/src/commands/onboard.ts +205 -0
- package/src/commands/open.ts +169 -0
- package/src/commands/output/stream-json.ts +373 -0
- package/src/commands/parser/chain-parser.ts +370 -0
- package/src/commands/parser/index.ts +29 -0
- package/src/commands/parser/pipe-parser.ts +480 -0
- package/src/commands/parser.ts +588 -0
- package/src/commands/persistence.ts +355 -0
- package/src/commands/progress.ts +18 -0
- package/src/commands/prompt/index.ts +17 -0
- package/src/commands/prompt/validate.ts +621 -0
- package/src/commands/prompt-priority.ts +401 -0
- package/src/commands/registry.ts +374 -0
- package/src/commands/sandbox/index.ts +131 -0
- package/src/commands/security/index.ts +21 -0
- package/src/commands/security/input-sanitizer.ts +168 -0
- package/src/commands/security/permission-checker.ts +456 -0
- package/src/commands/security/sensitive-data.ts +350 -0
- package/src/commands/session/delete.ts +38 -0
- package/src/commands/session/export.ts +39 -0
- package/src/commands/session/index.ts +26 -0
- package/src/commands/session/list.ts +26 -0
- package/src/commands/session/resume.ts +562 -0
- package/src/commands/session/search.ts +434 -0
- package/src/commands/session/show.ts +26 -0
- package/src/commands/settings.ts +368 -0
- package/src/commands/setup.ts +23 -0
- package/src/commands/shell/index.ts +16 -0
- package/src/commands/shell/setup.ts +422 -0
- package/src/commands/shell-init.ts +50 -0
- package/src/commands/shell-integration/index.ts +194 -0
- package/src/commands/skill.ts +1220 -0
- package/src/commands/spec.ts +558 -0
- package/src/commands/status.ts +246 -0
- package/src/commands/theme.ts +211 -0
- package/src/commands/think.ts +551 -0
- package/src/commands/trust.ts +211 -0
- package/src/commands/tutorial.ts +522 -0
- package/src/commands/types.ts +512 -0
- package/src/commands/update.ts +274 -0
- package/src/commands/usage.ts +213 -0
- package/src/commands/user-commands.ts +630 -0
- package/src/commands/utils.ts +142 -0
- package/src/commands/vim.ts +152 -0
- package/src/commands/workflow.ts +257 -0
- package/src/components/header.tsx +25 -0
- package/src/components/input.tsx +25 -0
- package/src/components/message-list.tsx +32 -0
- package/src/components/status-bar.tsx +23 -0
- package/src/index.tsx +614 -0
- package/src/onboarding/__tests__/tutorial.test.ts +740 -0
- package/src/onboarding/index.ts +69 -0
- package/src/onboarding/tips/index.ts +9 -0
- package/src/onboarding/tips/tip-engine.ts +459 -0
- package/src/onboarding/tutorial/index.ts +88 -0
- package/src/onboarding/tutorial/lessons/basics.ts +151 -0
- package/src/onboarding/tutorial/lessons/index.ts +151 -0
- package/src/onboarding/tutorial/lessons/modes.ts +230 -0
- package/src/onboarding/tutorial/lessons/tools.ts +172 -0
- package/src/onboarding/tutorial/progress-tracker.ts +350 -0
- package/src/onboarding/tutorial/storage.ts +249 -0
- package/src/onboarding/tutorial/tutorial-system.ts +462 -0
- package/src/onboarding/tutorial/types.ts +310 -0
- package/src/orchestrator-singleton.ts +129 -0
- package/src/shutdown.ts +33 -0
- package/src/test/e2e/assertions.ts +267 -0
- package/src/test/e2e/fixtures.ts +204 -0
- package/src/test/e2e/harness.ts +575 -0
- package/src/test/e2e/index.ts +57 -0
- package/src/test/e2e/types.ts +228 -0
- package/src/test/fixtures/__tests__/fake-response-loader.test.ts +314 -0
- package/src/test/fixtures/fake-response-loader.ts +314 -0
- package/src/test/fixtures/index.ts +20 -0
- package/src/tui/__tests__/mcp-panel.test.tsx +82 -0
- package/src/tui/__tests__/mcp-wiring.test.tsx +78 -0
- package/src/tui/__tests__/mode-components.test.tsx +395 -0
- package/src/tui/__tests__/permission-ask-flow.test.tsx +138 -0
- package/src/tui/__tests__/sidebar-panel-data.test.tsx +148 -0
- package/src/tui/__tests__/tools-panel-hotkeys.test.tsx +41 -0
- package/src/tui/adapters/agent-adapter.ts +1008 -0
- package/src/tui/adapters/index.ts +48 -0
- package/src/tui/adapters/message-adapter.ts +315 -0
- package/src/tui/adapters/persistence-bridge.ts +331 -0
- package/src/tui/adapters/session-adapter.ts +419 -0
- package/src/tui/buffered-stdout.ts +223 -0
- package/src/tui/components/AgentProgress.tsx +424 -0
- package/src/tui/components/Banner/AsciiArt.ts +160 -0
- package/src/tui/components/Banner/Banner.tsx +355 -0
- package/src/tui/components/Banner/ShimmerContext.tsx +131 -0
- package/src/tui/components/Banner/ShimmerText.tsx +193 -0
- package/src/tui/components/Banner/TypeWriterGradient.tsx +321 -0
- package/src/tui/components/Banner/index.ts +61 -0
- package/src/tui/components/Banner/useShimmer.ts +241 -0
- package/src/tui/components/ChatView.tsx +11 -0
- package/src/tui/components/Checkpoint/CheckpointDiffView.tsx +371 -0
- package/src/tui/components/Checkpoint/SnapshotCheckpointPanel.tsx +440 -0
- package/src/tui/components/Checkpoint/index.ts +19 -0
- package/src/tui/components/CostDisplay.tsx +226 -0
- package/src/tui/components/InitErrorBanner.tsx +122 -0
- package/src/tui/components/Input/Autocomplete.tsx +603 -0
- package/src/tui/components/Input/EnhancedCommandInput.tsx +471 -0
- package/src/tui/components/Input/HighlightedText.tsx +236 -0
- package/src/tui/components/Input/MentionAutocomplete.tsx +375 -0
- package/src/tui/components/Input/TextInput.tsx +1002 -0
- package/src/tui/components/Input/__tests__/Autocomplete.test.tsx +374 -0
- package/src/tui/components/Input/__tests__/TextInput.test.tsx +241 -0
- package/src/tui/components/Input/__tests__/highlight.test.ts +219 -0
- package/src/tui/components/Input/__tests__/slash-command-utils.test.ts +104 -0
- package/src/tui/components/Input/highlight.ts +362 -0
- package/src/tui/components/Input/index.ts +36 -0
- package/src/tui/components/Input/slash-command-utils.ts +135 -0
- package/src/tui/components/Layout.tsx +432 -0
- package/src/tui/components/McpPanel.tsx +137 -0
- package/src/tui/components/MemoryPanel.tsx +448 -0
- package/src/tui/components/Messages/CodeBlock.tsx +527 -0
- package/src/tui/components/Messages/DiffView.tsx +679 -0
- package/src/tui/components/Messages/ImageReference.tsx +89 -0
- package/src/tui/components/Messages/MarkdownBlock.tsx +228 -0
- package/src/tui/components/Messages/MarkdownRenderer.tsx +498 -0
- package/src/tui/components/Messages/MessageBubble.tsx +270 -0
- package/src/tui/components/Messages/MessageList.tsx +1719 -0
- package/src/tui/components/Messages/StreamingText.tsx +216 -0
- package/src/tui/components/Messages/ThinkingBlock.tsx +408 -0
- package/src/tui/components/Messages/ToolResultPreview.tsx +243 -0
- package/src/tui/components/Messages/__tests__/CodeBlock.test.tsx +296 -0
- package/src/tui/components/Messages/__tests__/DiffView.test.tsx +239 -0
- package/src/tui/components/Messages/__tests__/MarkdownRenderer.test.tsx +303 -0
- package/src/tui/components/Messages/__tests__/MessageBubble.test.tsx +268 -0
- package/src/tui/components/Messages/__tests__/MessageList.test.tsx +324 -0
- package/src/tui/components/Messages/__tests__/StreamingText.test.tsx +215 -0
- package/src/tui/components/Messages/index.ts +25 -0
- package/src/tui/components/ModeIndicator.tsx +177 -0
- package/src/tui/components/ModeSelector.tsx +216 -0
- package/src/tui/components/ModelSelector.tsx +339 -0
- package/src/tui/components/OnboardingWizard.tsx +670 -0
- package/src/tui/components/PhaseProgressIndicator.tsx +270 -0
- package/src/tui/components/RateLimitIndicator.tsx +82 -0
- package/src/tui/components/ScreenReaderLayout.tsx +295 -0
- package/src/tui/components/SettingsPanel.tsx +643 -0
- package/src/tui/components/Sidebar/SystemStatusPanel.tsx +284 -0
- package/src/tui/components/Sidebar/index.ts +9 -0
- package/src/tui/components/Status/ModelStatusBar.tsx +270 -0
- package/src/tui/components/Status/index.ts +12 -0
- package/src/tui/components/StatusBar/AgentModeIndicator.tsx +257 -0
- package/src/tui/components/StatusBar/ContextProgress.tsx +167 -0
- package/src/tui/components/StatusBar/FileChangesIndicator.tsx +62 -0
- package/src/tui/components/StatusBar/GitIndicator.tsx +89 -0
- package/src/tui/components/StatusBar/HeaderBar.tsx +126 -0
- package/src/tui/components/StatusBar/ModelIndicator.tsx +157 -0
- package/src/tui/components/StatusBar/PersistenceStatusIndicator.tsx +210 -0
- package/src/tui/components/StatusBar/ResilienceIndicator.tsx +106 -0
- package/src/tui/components/StatusBar/SandboxIndicator.tsx +167 -0
- package/src/tui/components/StatusBar/StatusBar.tsx +368 -0
- package/src/tui/components/StatusBar/ThinkingModeIndicator.tsx +170 -0
- package/src/tui/components/StatusBar/TokenBreakdown.tsx +246 -0
- package/src/tui/components/StatusBar/TokenCounter.tsx +135 -0
- package/src/tui/components/StatusBar/TrustModeIndicator.tsx +130 -0
- package/src/tui/components/StatusBar/WorkspaceIndicator.tsx +86 -0
- package/src/tui/components/StatusBar/__tests__/AgentModeIndicator.test.tsx +193 -0
- package/src/tui/components/StatusBar/__tests__/StatusBar.test.tsx +729 -0
- package/src/tui/components/StatusBar/index.ts +60 -0
- package/src/tui/components/TipBanner.tsx +115 -0
- package/src/tui/components/TodoItem.tsx +208 -0
- package/src/tui/components/TodoPanel.tsx +455 -0
- package/src/tui/components/Tools/ApprovalQueue.tsx +407 -0
- package/src/tui/components/Tools/OptionSelector.tsx +160 -0
- package/src/tui/components/Tools/PermissionDialog.tsx +286 -0
- package/src/tui/components/Tools/ToolParams.tsx +483 -0
- package/src/tui/components/Tools/ToolsPanel.tsx +178 -0
- package/src/tui/components/Tools/__tests__/PermissionDialog.test.tsx +510 -0
- package/src/tui/components/Tools/__tests__/ToolParams.test.tsx +432 -0
- package/src/tui/components/Tools/index.ts +21 -0
- package/src/tui/components/TrustPrompt.tsx +279 -0
- package/src/tui/components/UpdateBanner.tsx +166 -0
- package/src/tui/components/VimModeIndicator.tsx +112 -0
- package/src/tui/components/backtrack/BacktrackControls.tsx +402 -0
- package/src/tui/components/backtrack/index.ts +13 -0
- package/src/tui/components/common/AutoApprovalStatus.tsx +251 -0
- package/src/tui/components/common/CostWarning.tsx +294 -0
- package/src/tui/components/common/DynamicShortcutHints.tsx +209 -0
- package/src/tui/components/common/EnhancedLoadingIndicator.tsx +305 -0
- package/src/tui/components/common/ErrorBoundary.tsx +140 -0
- package/src/tui/components/common/GradientText.tsx +224 -0
- package/src/tui/components/common/HotkeyHelpModal.tsx +193 -0
- package/src/tui/components/common/HotkeyHints.tsx +70 -0
- package/src/tui/components/common/MaxSizedBox.tsx +354 -0
- package/src/tui/components/common/NewMessagesBadge.tsx +65 -0
- package/src/tui/components/common/ProtectedFileLegend.tsx +89 -0
- package/src/tui/components/common/ScrollIndicator.tsx +160 -0
- package/src/tui/components/common/Spinner.tsx +342 -0
- package/src/tui/components/common/StreamingIndicator.tsx +316 -0
- package/src/tui/components/common/VirtualizedList/VirtualizedList.tsx +428 -0
- package/src/tui/components/common/VirtualizedList/hooks/index.ts +19 -0
- package/src/tui/components/common/VirtualizedList/hooks/useBatchedScroll.ts +64 -0
- package/src/tui/components/common/VirtualizedList/hooks/useScrollAnchor.ts +290 -0
- package/src/tui/components/common/VirtualizedList/hooks/useVirtualization.ts +340 -0
- package/src/tui/components/common/VirtualizedList/index.ts +30 -0
- package/src/tui/components/common/VirtualizedList/types.ts +107 -0
- package/src/tui/components/common/__tests__/NewMessagesBadge.test.tsx +74 -0
- package/src/tui/components/common/__tests__/ScrollIndicator.test.tsx +193 -0
- package/src/tui/components/common/index.ts +110 -0
- package/src/tui/components/index.ts +79 -0
- package/src/tui/components/session/CheckpointPanel.tsx +323 -0
- package/src/tui/components/session/RollbackDialog.tsx +169 -0
- package/src/tui/components/session/SessionItem.tsx +136 -0
- package/src/tui/components/session/SessionListPanel.tsx +252 -0
- package/src/tui/components/session/SessionPicker.tsx +449 -0
- package/src/tui/components/session/SessionPreview.tsx +240 -0
- package/src/tui/components/session/__tests__/session.test.tsx +408 -0
- package/src/tui/components/session/index.ts +28 -0
- package/src/tui/components/session/types.ts +116 -0
- package/src/tui/components/theme/__tests__/tokens.test.ts +471 -0
- package/src/tui/components/theme/index.ts +227 -0
- package/src/tui/components/theme/tokens.ts +484 -0
- package/src/tui/config/defaults.ts +134 -0
- package/src/tui/config/index.ts +17 -0
- package/src/tui/context/AnimationContext.tsx +284 -0
- package/src/tui/context/AppContext.tsx +349 -0
- package/src/tui/context/BracketedPasteContext.tsx +372 -0
- package/src/tui/context/LspContext.tsx +192 -0
- package/src/tui/context/McpContext.tsx +325 -0
- package/src/tui/context/MessagesContext.tsx +870 -0
- package/src/tui/context/OverflowContext.tsx +213 -0
- package/src/tui/context/RateLimitContext.tsx +108 -0
- package/src/tui/context/ResilienceContext.tsx +275 -0
- package/src/tui/context/RootProvider.tsx +136 -0
- package/src/tui/context/ScrollContext.tsx +331 -0
- package/src/tui/context/ToolsContext.tsx +702 -0
- package/src/tui/context/__tests__/BracketedPasteContext.test.tsx +416 -0
- package/src/tui/context/index.ts +140 -0
- package/src/tui/enterprise-integration.ts +282 -0
- package/src/tui/hooks/__tests__/useBacktrack.test.tsx +138 -0
- package/src/tui/hooks/__tests__/useBracketedPaste.test.tsx +222 -0
- package/src/tui/hooks/__tests__/useCopyMode.test.tsx +336 -0
- package/src/tui/hooks/__tests__/useHotkeys.ctrl-input.test.tsx +96 -0
- package/src/tui/hooks/__tests__/useHotkeys.test.tsx +454 -0
- package/src/tui/hooks/__tests__/useInputHistory.test.tsx +660 -0
- package/src/tui/hooks/__tests__/useLineBuffer.test.ts +295 -0
- package/src/tui/hooks/__tests__/useModeController.test.ts +137 -0
- package/src/tui/hooks/__tests__/useModeShortcuts.test.tsx +142 -0
- package/src/tui/hooks/__tests__/useScrollController.test.ts +464 -0
- package/src/tui/hooks/__tests__/useVim.test.tsx +531 -0
- package/src/tui/hooks/index.ts +252 -0
- package/src/tui/hooks/useAgentLoop.ts +712 -0
- package/src/tui/hooks/useAlternateBuffer.ts +398 -0
- package/src/tui/hooks/useAnimatedScrollbar.ts +241 -0
- package/src/tui/hooks/useBacktrack.ts +443 -0
- package/src/tui/hooks/useBracketedPaste.ts +104 -0
- package/src/tui/hooks/useCollapsible.ts +240 -0
- package/src/tui/hooks/useCopyMode.ts +382 -0
- package/src/tui/hooks/useCostSummary.ts +75 -0
- package/src/tui/hooks/useDesktopNotification.ts +414 -0
- package/src/tui/hooks/useDiffMode.ts +44 -0
- package/src/tui/hooks/useFileChangeStats.ts +110 -0
- package/src/tui/hooks/useFileSuggestions.ts +284 -0
- package/src/tui/hooks/useFlickerDetector.ts +250 -0
- package/src/tui/hooks/useGitStatus.ts +200 -0
- package/src/tui/hooks/useHotkeys.ts +579 -0
- package/src/tui/hooks/useImagePaste.ts +114 -0
- package/src/tui/hooks/useInputHighlight.ts +145 -0
- package/src/tui/hooks/useInputHistory.ts +246 -0
- package/src/tui/hooks/useKeyboardScroll.ts +209 -0
- package/src/tui/hooks/useLineBuffer.ts +356 -0
- package/src/tui/hooks/useMentionAutocomplete.ts +235 -0
- package/src/tui/hooks/useModeController.ts +167 -0
- package/src/tui/hooks/useModeShortcuts.ts +196 -0
- package/src/tui/hooks/usePermissionHandler.ts +146 -0
- package/src/tui/hooks/usePersistence.ts +480 -0
- package/src/tui/hooks/usePersistenceShortcuts.ts +225 -0
- package/src/tui/hooks/usePlaceholderRotation.ts +143 -0
- package/src/tui/hooks/useProviderStatus.ts +270 -0
- package/src/tui/hooks/useRateLimitStatus.ts +90 -0
- package/src/tui/hooks/useScreenReader.ts +315 -0
- package/src/tui/hooks/useScrollController.ts +450 -0
- package/src/tui/hooks/useScrollEventBatcher.ts +185 -0
- package/src/tui/hooks/useSidebarPanelData.ts +115 -0
- package/src/tui/hooks/useSmoothScroll.ts +202 -0
- package/src/tui/hooks/useSnapshots.ts +300 -0
- package/src/tui/hooks/useStateAndRef.ts +50 -0
- package/src/tui/hooks/useTerminalSize.ts +206 -0
- package/src/tui/hooks/useToolApprovalController.ts +91 -0
- package/src/tui/hooks/useVim.ts +334 -0
- package/src/tui/hooks/useWorkspace.ts +56 -0
- package/src/tui/i18n/__tests__/init.test.ts +278 -0
- package/src/tui/i18n/__tests__/language-config.test.ts +199 -0
- package/src/tui/i18n/__tests__/locale-detection.test.ts +250 -0
- package/src/tui/i18n/__tests__/settings-integration.test.ts +262 -0
- package/src/tui/i18n/index.ts +72 -0
- package/src/tui/i18n/init.ts +131 -0
- package/src/tui/i18n/language-config.ts +106 -0
- package/src/tui/i18n/locale-detection.ts +173 -0
- package/src/tui/i18n/settings-integration.ts +557 -0
- package/src/tui/i18n/tui-namespace.ts +538 -0
- package/src/tui/i18n/types.ts +312 -0
- package/src/tui/index.ts +43 -0
- package/src/tui/lsp-integration.ts +409 -0
- package/src/tui/metrics-integration.ts +366 -0
- package/src/tui/plugins.ts +383 -0
- package/src/tui/resilience.ts +342 -0
- package/src/tui/sandbox-integration.ts +317 -0
- package/src/tui/services/clipboard.ts +348 -0
- package/src/tui/services/fuzzy-search.ts +441 -0
- package/src/tui/services/index.ts +72 -0
- package/src/tui/services/markdown-renderer.ts +565 -0
- package/src/tui/services/open-external.ts +247 -0
- package/src/tui/services/syntax-highlighter.ts +483 -0
- package/src/tui/slash-commands.ts +12 -0
- package/src/tui/theme/index.ts +15 -0
- package/src/tui/theme/provider.tsx +206 -0
- package/src/tui/tip-integration.ts +300 -0
- package/src/tui/types/__tests__/ink-extended.test.ts +121 -0
- package/src/tui/types/ink-extended.ts +87 -0
- package/src/tui/utils/__tests__/bracketedPaste.test.ts +231 -0
- package/src/tui/utils/__tests__/heightEstimator.test.ts +157 -0
- package/src/tui/utils/__tests__/text-width.test.ts +158 -0
- package/src/tui/utils/__tests__/textSanitizer.test.ts +266 -0
- package/src/tui/utils/__tests__/ui-sizing.test.ts +169 -0
- package/src/tui/utils/bracketedPaste.ts +107 -0
- package/src/tui/utils/cursor-manager.ts +131 -0
- package/src/tui/utils/detectTerminal.ts +596 -0
- package/src/tui/utils/findLastSafeSplitPoint.ts +92 -0
- package/src/tui/utils/heightEstimator.ts +198 -0
- package/src/tui/utils/index.ts +91 -0
- package/src/tui/utils/isNarrowWidth.ts +52 -0
- package/src/tui/utils/stdoutGuard.ts +90 -0
- package/src/tui/utils/synchronized-update.ts +70 -0
- package/src/tui/utils/text-width.ts +225 -0
- package/src/tui/utils/textSanitizer.ts +225 -0
- package/src/tui/utils/textUtils.ts +114 -0
- package/src/tui/utils/ui-sizing.ts +192 -0
- package/src/tui-blessed/app.ts +160 -0
- package/src/tui-blessed/index.ts +2 -0
- package/src/tui-blessed/neo-blessed.d.ts +6 -0
- package/src/tui-blessed/test.ts +21 -0
- package/src/tui-blessed/types.ts +14 -0
- package/src/utils/icons.ts +130 -0
- package/src/utils/index.ts +33 -0
- package/src/utils/resume-hint.ts +86 -0
- package/src/version.ts +1 -0
- package/tsconfig.json +8 -0
- package/vitest.config.ts +35 -0
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin System Integration for TUI
|
|
3
|
+
*
|
|
4
|
+
* Provides initialization and integration of the plugin system with the CLI.
|
|
5
|
+
* Handles plugin loading from default search paths, command registration,
|
|
6
|
+
* and agent/hooks aggregation.
|
|
7
|
+
*
|
|
8
|
+
* @module cli/tui/plugins
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
getSearchPaths,
|
|
13
|
+
type HookContext,
|
|
14
|
+
type HookEvent,
|
|
15
|
+
type PluginAgentDefinition,
|
|
16
|
+
PluginManager,
|
|
17
|
+
} from "@vellum/plugin";
|
|
18
|
+
|
|
19
|
+
import type { CommandContext, CommandResult, SlashCommand } from "../commands/types.js";
|
|
20
|
+
|
|
21
|
+
// =============================================================================
|
|
22
|
+
// Types
|
|
23
|
+
// =============================================================================
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Plugin initialization options
|
|
27
|
+
*/
|
|
28
|
+
export interface PluginInitOptions {
|
|
29
|
+
/**
|
|
30
|
+
* Project root directory for project-local plugins.
|
|
31
|
+
* If provided, plugins from `${projectRoot}/.vellum/plugins/` will be loaded.
|
|
32
|
+
* @default process.cwd()
|
|
33
|
+
*/
|
|
34
|
+
projectRoot?: string;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Whether to automatically trust new plugins.
|
|
38
|
+
* @default false
|
|
39
|
+
*/
|
|
40
|
+
autoTrust?: boolean;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Whether to eagerly load all plugin components.
|
|
44
|
+
* If false, only manifests are loaded initially.
|
|
45
|
+
* @default false
|
|
46
|
+
*/
|
|
47
|
+
eagerLoad?: boolean;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Whether to include builtin plugins.
|
|
51
|
+
* @default true
|
|
52
|
+
*/
|
|
53
|
+
includeBuiltin?: boolean;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Whether to include user plugins from ~/.vellum/plugins/
|
|
57
|
+
* @default true
|
|
58
|
+
*/
|
|
59
|
+
includeUser?: boolean;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Whether to include global system plugins.
|
|
63
|
+
* @default true
|
|
64
|
+
*/
|
|
65
|
+
includeGlobal?: boolean;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Result of plugin initialization
|
|
70
|
+
*/
|
|
71
|
+
export interface PluginInitResult {
|
|
72
|
+
/** Initialized plugin manager instance */
|
|
73
|
+
manager: PluginManager;
|
|
74
|
+
|
|
75
|
+
/** Number of plugins loaded */
|
|
76
|
+
pluginCount: number;
|
|
77
|
+
|
|
78
|
+
/** Number of commands available */
|
|
79
|
+
commandCount: number;
|
|
80
|
+
|
|
81
|
+
/** Number of agents available */
|
|
82
|
+
agentCount: number;
|
|
83
|
+
|
|
84
|
+
/** Any errors encountered during loading */
|
|
85
|
+
errors: PluginLoadError[];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Plugin load error information
|
|
90
|
+
*/
|
|
91
|
+
export interface PluginLoadError {
|
|
92
|
+
/** Plugin name */
|
|
93
|
+
name: string;
|
|
94
|
+
|
|
95
|
+
/** Plugin path */
|
|
96
|
+
path: string;
|
|
97
|
+
|
|
98
|
+
/** Error message */
|
|
99
|
+
message: string;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// =============================================================================
|
|
103
|
+
// Plugin Manager Singleton
|
|
104
|
+
// =============================================================================
|
|
105
|
+
|
|
106
|
+
let _pluginManager: PluginManager | null = null;
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Gets the global plugin manager instance.
|
|
110
|
+
*
|
|
111
|
+
* Returns null if plugins have not been initialized yet.
|
|
112
|
+
*
|
|
113
|
+
* @returns The plugin manager or null
|
|
114
|
+
*/
|
|
115
|
+
export function getPluginManager(): PluginManager | null {
|
|
116
|
+
return _pluginManager;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// =============================================================================
|
|
120
|
+
// Plugin Initialization
|
|
121
|
+
// =============================================================================
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Initializes the plugin system.
|
|
125
|
+
*
|
|
126
|
+
* Loads plugins from:
|
|
127
|
+
* 1. Project plugins: `${projectRoot}/.vellum/plugins/`
|
|
128
|
+
* 2. User plugins: `~/.vellum/plugins/`
|
|
129
|
+
* 3. Global plugins: Platform-specific system directory
|
|
130
|
+
* 4. Builtin plugins: Shipped with the package
|
|
131
|
+
*
|
|
132
|
+
* @param options - Initialization options
|
|
133
|
+
* @returns Initialization result with manager and stats
|
|
134
|
+
*
|
|
135
|
+
* @example
|
|
136
|
+
* ```typescript
|
|
137
|
+
* const result = await initializePlugins({ projectRoot: process.cwd() });
|
|
138
|
+
* console.log(`Loaded ${result.pluginCount} plugins`);
|
|
139
|
+
*
|
|
140
|
+
* // Access plugin commands
|
|
141
|
+
* const commands = getPluginCommands(result.manager);
|
|
142
|
+
* ```
|
|
143
|
+
*/
|
|
144
|
+
export async function initializePlugins(
|
|
145
|
+
options: PluginInitOptions = {}
|
|
146
|
+
): Promise<PluginInitResult> {
|
|
147
|
+
const {
|
|
148
|
+
projectRoot = process.cwd(),
|
|
149
|
+
autoTrust = false,
|
|
150
|
+
eagerLoad = false,
|
|
151
|
+
includeBuiltin = true,
|
|
152
|
+
includeUser = true,
|
|
153
|
+
includeGlobal = true,
|
|
154
|
+
} = options;
|
|
155
|
+
|
|
156
|
+
// Get search paths based on options
|
|
157
|
+
const searchPaths = getSearchPaths({
|
|
158
|
+
projectRoot,
|
|
159
|
+
includeBuiltin,
|
|
160
|
+
includeUser,
|
|
161
|
+
includeGlobal,
|
|
162
|
+
filterNonExistent: true,
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// Create plugin manager with resolved paths
|
|
166
|
+
const manager = new PluginManager({
|
|
167
|
+
searchPaths,
|
|
168
|
+
autoTrust,
|
|
169
|
+
eagerLoad,
|
|
170
|
+
});
|
|
171
|
+
const errors: PluginLoadError[] = [];
|
|
172
|
+
|
|
173
|
+
// Initialize and load plugins
|
|
174
|
+
try {
|
|
175
|
+
await manager.initialize();
|
|
176
|
+
} catch (error) {
|
|
177
|
+
// Log initialization error but continue - manager may have partial success
|
|
178
|
+
console.warn("[plugins] Initialization warning:", error);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Collect any failed plugins
|
|
182
|
+
for (const failed of manager.getFailedPlugins()) {
|
|
183
|
+
errors.push({
|
|
184
|
+
name: failed.name,
|
|
185
|
+
path: failed.path,
|
|
186
|
+
message: failed.error.message,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Store manager globally for access by other modules
|
|
191
|
+
_pluginManager = manager;
|
|
192
|
+
|
|
193
|
+
// Return initialization result
|
|
194
|
+
const plugins = manager.getPlugins();
|
|
195
|
+
const commands = manager.getCommands();
|
|
196
|
+
const agents = manager.getAgents();
|
|
197
|
+
|
|
198
|
+
return {
|
|
199
|
+
manager,
|
|
200
|
+
pluginCount: plugins.length,
|
|
201
|
+
commandCount: commands.size,
|
|
202
|
+
agentCount: agents.size,
|
|
203
|
+
errors,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// =============================================================================
|
|
208
|
+
// Command Integration
|
|
209
|
+
// =============================================================================
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Gets plugin commands in CLI SlashCommand format.
|
|
213
|
+
*
|
|
214
|
+
* Converts plugin commands to the SlashCommand format used by the
|
|
215
|
+
* CommandRegistry. Commands are returned with `kind: 'plugin'` and
|
|
216
|
+
* include the source plugin name.
|
|
217
|
+
*
|
|
218
|
+
* @param manager - Plugin manager instance
|
|
219
|
+
* @returns Array of SlashCommand definitions
|
|
220
|
+
*
|
|
221
|
+
* @example
|
|
222
|
+
* ```typescript
|
|
223
|
+
* const commands = getPluginCommands(manager);
|
|
224
|
+
* for (const cmd of commands) {
|
|
225
|
+
* commandRegistry.register(cmd);
|
|
226
|
+
* }
|
|
227
|
+
* ```
|
|
228
|
+
*/
|
|
229
|
+
export function getPluginCommands(manager: PluginManager): SlashCommand[] {
|
|
230
|
+
const pluginCommands = manager.getCommands();
|
|
231
|
+
const commands: SlashCommand[] = [];
|
|
232
|
+
|
|
233
|
+
for (const [_name, cmd] of pluginCommands) {
|
|
234
|
+
// Convert plugin SlashCommand to CLI SlashCommand
|
|
235
|
+
// Need to wrap execute to adapt context types
|
|
236
|
+
const cliCommand: SlashCommand = {
|
|
237
|
+
name: cmd.name,
|
|
238
|
+
description: cmd.description,
|
|
239
|
+
kind: "plugin",
|
|
240
|
+
category: "tools", // Plugin commands go to tools category
|
|
241
|
+
aliases: cmd.aliases,
|
|
242
|
+
execute: async (ctx: CommandContext): Promise<CommandResult> => {
|
|
243
|
+
// Adapt CLI CommandContext to plugin CommandContext
|
|
244
|
+
const pluginCtx = {
|
|
245
|
+
rawArgs: ctx.parsedArgs.raw,
|
|
246
|
+
parsedArgs: {
|
|
247
|
+
positional: ctx.parsedArgs.positional,
|
|
248
|
+
named: ctx.parsedArgs.named,
|
|
249
|
+
},
|
|
250
|
+
allowedTools: undefined,
|
|
251
|
+
signal: ctx.signal,
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
// Execute the plugin command
|
|
255
|
+
const result = await cmd.execute(pluginCtx);
|
|
256
|
+
|
|
257
|
+
// Adapt plugin result to CLI result
|
|
258
|
+
if (result.kind === "success") {
|
|
259
|
+
return {
|
|
260
|
+
kind: "success",
|
|
261
|
+
message: result.message,
|
|
262
|
+
data: result.data,
|
|
263
|
+
};
|
|
264
|
+
} else {
|
|
265
|
+
return {
|
|
266
|
+
kind: "error",
|
|
267
|
+
code: "INTERNAL_ERROR",
|
|
268
|
+
message: result.message,
|
|
269
|
+
suggestions: result.suggestions,
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
},
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
commands.push(cliCommand);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return commands;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Gets the count of available plugin commands.
|
|
283
|
+
*
|
|
284
|
+
* Useful for status displays without loading full command data.
|
|
285
|
+
*
|
|
286
|
+
* @param manager - Plugin manager instance
|
|
287
|
+
* @returns Number of registered plugin commands
|
|
288
|
+
*/
|
|
289
|
+
export function getPluginCommandCount(manager: PluginManager): number {
|
|
290
|
+
return manager.getCommands().size;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// =============================================================================
|
|
294
|
+
// Agent Integration
|
|
295
|
+
// =============================================================================
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Gets plugin agents.
|
|
299
|
+
*
|
|
300
|
+
* Returns all agents defined by loaded plugins, keyed by their
|
|
301
|
+
* qualified slug (pluginName:agentSlug).
|
|
302
|
+
*
|
|
303
|
+
* @param manager - Plugin manager instance
|
|
304
|
+
* @returns Map of agent slug to agent definition
|
|
305
|
+
*
|
|
306
|
+
* @example
|
|
307
|
+
* ```typescript
|
|
308
|
+
* const agents = getPluginAgents(manager);
|
|
309
|
+
* for (const [slug, agent] of agents) {
|
|
310
|
+
* console.log(`Agent: ${agent.name} (${slug})`);
|
|
311
|
+
* }
|
|
312
|
+
* ```
|
|
313
|
+
*/
|
|
314
|
+
export function getPluginAgents(manager: PluginManager): Map<string, PluginAgentDefinition> {
|
|
315
|
+
return manager.getAgents();
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Gets a list of plugin agent names for display.
|
|
320
|
+
*
|
|
321
|
+
* @param manager - Plugin manager instance
|
|
322
|
+
* @returns Array of agent display names
|
|
323
|
+
*/
|
|
324
|
+
export function getPluginAgentNames(manager: PluginManager): string[] {
|
|
325
|
+
const agents = manager.getAgents();
|
|
326
|
+
const names: string[] = [];
|
|
327
|
+
|
|
328
|
+
for (const [_slug, agent] of agents) {
|
|
329
|
+
names.push(agent.name);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return names;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// =============================================================================
|
|
336
|
+
// Hook Integration
|
|
337
|
+
// =============================================================================
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Executes plugin hooks for a given event.
|
|
341
|
+
*
|
|
342
|
+
* Delegates to the PluginManager's hook execution system.
|
|
343
|
+
* Results from all plugins are returned.
|
|
344
|
+
*
|
|
345
|
+
* @param manager - Plugin manager instance
|
|
346
|
+
* @param event - Hook event type
|
|
347
|
+
* @param context - Context for hook execution
|
|
348
|
+
* @returns Array of hook results from all plugins
|
|
349
|
+
*
|
|
350
|
+
* @example
|
|
351
|
+
* ```typescript
|
|
352
|
+
* const results = await executePluginHooks(manager, "PreToolUse", {
|
|
353
|
+
* input: "user query",
|
|
354
|
+
* sessionId: "session-123",
|
|
355
|
+
* pluginName: "my-plugin",
|
|
356
|
+
* });
|
|
357
|
+
*
|
|
358
|
+
* // Check if any hook blocked the action
|
|
359
|
+
* const blocked = results.some(r => !r.allowed);
|
|
360
|
+
* ```
|
|
361
|
+
*/
|
|
362
|
+
export async function executePluginHooks(
|
|
363
|
+
manager: PluginManager,
|
|
364
|
+
event: HookEvent,
|
|
365
|
+
context: HookContext
|
|
366
|
+
): Promise<Array<{ allowed: boolean; message?: string }>> {
|
|
367
|
+
const results = await manager.executeHook(event, context);
|
|
368
|
+
return results;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// =============================================================================
|
|
372
|
+
// Cleanup
|
|
373
|
+
// =============================================================================
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Disposes the plugin system.
|
|
377
|
+
*
|
|
378
|
+
* Cleans up resources and clears the global manager instance.
|
|
379
|
+
* Should be called during application shutdown.
|
|
380
|
+
*/
|
|
381
|
+
export function disposePlugins(): void {
|
|
382
|
+
_pluginManager = null;
|
|
383
|
+
}
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resilience Integration
|
|
3
|
+
*
|
|
4
|
+
* Wires circuit breaker, rate limiter, and provider fallback chain
|
|
5
|
+
* to create resilient provider connections.
|
|
6
|
+
*
|
|
7
|
+
* @module cli/tui/resilience
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
CircuitBreaker,
|
|
12
|
+
type CircuitBreakerOptions,
|
|
13
|
+
CircuitOpenError,
|
|
14
|
+
// Rate limiter (from rate-limit module, now exported via core index)
|
|
15
|
+
createRateLimiter,
|
|
16
|
+
type FallbackConfig,
|
|
17
|
+
type FallbackProvider,
|
|
18
|
+
type FallbackResult,
|
|
19
|
+
ProviderFallbackChain,
|
|
20
|
+
RateLimiter,
|
|
21
|
+
type RateLimiterConfig,
|
|
22
|
+
} from "@vellum/core";
|
|
23
|
+
|
|
24
|
+
// =============================================================================
|
|
25
|
+
// Types
|
|
26
|
+
// =============================================================================
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Provider interface for resilience wrapping
|
|
30
|
+
*/
|
|
31
|
+
export interface Provider {
|
|
32
|
+
/** Provider identifier */
|
|
33
|
+
id: string;
|
|
34
|
+
/** Provider name for display */
|
|
35
|
+
name: string;
|
|
36
|
+
/** Priority (lower = higher priority) */
|
|
37
|
+
priority?: number;
|
|
38
|
+
/** Execute a request */
|
|
39
|
+
execute: <T>(request: () => Promise<T>) => Promise<T>;
|
|
40
|
+
/** Check if provider is healthy */
|
|
41
|
+
isHealthy?: () => boolean;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Resilient provider configuration
|
|
46
|
+
*/
|
|
47
|
+
export interface ResilientProviderConfig {
|
|
48
|
+
/** Circuit breaker options */
|
|
49
|
+
circuitBreaker?: Partial<CircuitBreakerOptions>;
|
|
50
|
+
/** Rate limiter options */
|
|
51
|
+
rateLimiter?: Partial<RateLimiterConfig>;
|
|
52
|
+
/** Fallback configuration */
|
|
53
|
+
fallback?: Partial<FallbackConfig>;
|
|
54
|
+
/** Enable metrics collection */
|
|
55
|
+
enableMetrics?: boolean;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Resilient provider wrapper
|
|
60
|
+
*/
|
|
61
|
+
export interface ResilientProvider {
|
|
62
|
+
/** Circuit breaker instance */
|
|
63
|
+
breaker: CircuitBreaker;
|
|
64
|
+
/** Rate limiter instance */
|
|
65
|
+
limiter: RateLimiter;
|
|
66
|
+
/** Execute a request with full resilience stack */
|
|
67
|
+
execute: <T>(
|
|
68
|
+
providerId: string,
|
|
69
|
+
request: () => Promise<T>
|
|
70
|
+
) => Promise<{ success: boolean; value?: T; error?: Error }>;
|
|
71
|
+
/** Get circuit breaker state for a provider */
|
|
72
|
+
getCircuitState: (providerId: string) => string;
|
|
73
|
+
/** Get rate limiter stats */
|
|
74
|
+
getRateLimiterStats: () => { allowedRequests: number; throttledRequests: number };
|
|
75
|
+
/** Reset all circuits */
|
|
76
|
+
resetAllCircuits: () => void;
|
|
77
|
+
/** Dispose resources */
|
|
78
|
+
dispose: () => void;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Metrics for resilience tracking
|
|
83
|
+
*/
|
|
84
|
+
export interface ResilienceMetrics {
|
|
85
|
+
/** Total requests */
|
|
86
|
+
totalRequests: number;
|
|
87
|
+
/** Successful requests */
|
|
88
|
+
successfulRequests: number;
|
|
89
|
+
/** Failed requests */
|
|
90
|
+
failedRequests: number;
|
|
91
|
+
/** Circuit breaker trips */
|
|
92
|
+
circuitTrips: number;
|
|
93
|
+
/** Rate limited requests */
|
|
94
|
+
rateLimitedRequests: number;
|
|
95
|
+
/** Fallback activations */
|
|
96
|
+
fallbackActivations: number;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// =============================================================================
|
|
100
|
+
// Default Configuration
|
|
101
|
+
// =============================================================================
|
|
102
|
+
|
|
103
|
+
const DEFAULT_CIRCUIT_BREAKER_OPTIONS: CircuitBreakerOptions = {
|
|
104
|
+
failureThreshold: 5,
|
|
105
|
+
resetTimeoutMs: 30000, // 30 seconds
|
|
106
|
+
halfOpenMaxAttempts: 3,
|
|
107
|
+
windowMs: 60000, // 1 minute
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const DEFAULT_RATE_LIMITER_CONFIG: Partial<RateLimiterConfig> = {
|
|
111
|
+
defaultBucket: {
|
|
112
|
+
capacity: 60,
|
|
113
|
+
refillRate: 1, // 1 token per second = 60 RPM
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
// =============================================================================
|
|
118
|
+
// Resilience Factory
|
|
119
|
+
// =============================================================================
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Create a resilient provider wrapper with circuit breaker and rate limiter.
|
|
123
|
+
*
|
|
124
|
+
* @param providers - Array of providers to wrap
|
|
125
|
+
* @param config - Resilience configuration
|
|
126
|
+
* @returns Resilient provider wrapper
|
|
127
|
+
*
|
|
128
|
+
* @example
|
|
129
|
+
* ```typescript
|
|
130
|
+
* const resilient = createResilientProvider([
|
|
131
|
+
* { id: "anthropic", name: "Anthropic Claude", execute: anthropicExecute },
|
|
132
|
+
* { id: "openai", name: "OpenAI GPT", execute: openaiExecute, priority: 1 },
|
|
133
|
+
* ]);
|
|
134
|
+
*
|
|
135
|
+
* const result = await resilient.execute("anthropic", () => client.chat(...));
|
|
136
|
+
* if (result.success) {
|
|
137
|
+
* console.log(result.value);
|
|
138
|
+
* } else {
|
|
139
|
+
* console.error("Request failed:", result.error);
|
|
140
|
+
* }
|
|
141
|
+
* ```
|
|
142
|
+
*/
|
|
143
|
+
export function createResilientProvider(
|
|
144
|
+
providers: Provider[],
|
|
145
|
+
config: ResilientProviderConfig = {}
|
|
146
|
+
): ResilientProvider {
|
|
147
|
+
// Create circuit breakers for each provider
|
|
148
|
+
const breakerOptions: CircuitBreakerOptions = {
|
|
149
|
+
...DEFAULT_CIRCUIT_BREAKER_OPTIONS,
|
|
150
|
+
...config.circuitBreaker,
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const breakers = new Map<string, CircuitBreaker>();
|
|
154
|
+
for (const provider of providers) {
|
|
155
|
+
breakers.set(provider.id, new CircuitBreaker(provider.id, breakerOptions));
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Create rate limiter
|
|
159
|
+
const limiterConfig: RateLimiterConfig = {
|
|
160
|
+
...DEFAULT_RATE_LIMITER_CONFIG,
|
|
161
|
+
...config.rateLimiter,
|
|
162
|
+
} as RateLimiterConfig;
|
|
163
|
+
const limiter = createRateLimiter(limiterConfig);
|
|
164
|
+
|
|
165
|
+
// Metrics tracking
|
|
166
|
+
const metrics: ResilienceMetrics = {
|
|
167
|
+
totalRequests: 0,
|
|
168
|
+
successfulRequests: 0,
|
|
169
|
+
failedRequests: 0,
|
|
170
|
+
circuitTrips: 0,
|
|
171
|
+
rateLimitedRequests: 0,
|
|
172
|
+
fallbackActivations: 0,
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
// Get a circuit breaker for a provider (or create default)
|
|
176
|
+
const getBreaker = (providerId: string): CircuitBreaker => {
|
|
177
|
+
let breaker = breakers.get(providerId);
|
|
178
|
+
if (!breaker) {
|
|
179
|
+
breaker = new CircuitBreaker(providerId, breakerOptions);
|
|
180
|
+
breakers.set(providerId, breaker);
|
|
181
|
+
}
|
|
182
|
+
return breaker;
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
// Get primary breaker
|
|
186
|
+
const primaryBreaker = getBreaker(providers[0]?.id ?? "default");
|
|
187
|
+
|
|
188
|
+
return {
|
|
189
|
+
breaker: primaryBreaker,
|
|
190
|
+
limiter,
|
|
191
|
+
|
|
192
|
+
async execute<T>(
|
|
193
|
+
providerId: string,
|
|
194
|
+
request: () => Promise<T>
|
|
195
|
+
): Promise<{ success: boolean; value?: T; error?: Error }> {
|
|
196
|
+
metrics.totalRequests++;
|
|
197
|
+
|
|
198
|
+
// Check rate limit first
|
|
199
|
+
const canProceed = limiter.tryAcquire(providerId);
|
|
200
|
+
if (!canProceed) {
|
|
201
|
+
metrics.rateLimitedRequests++;
|
|
202
|
+
// Wait for rate limit or throw
|
|
203
|
+
try {
|
|
204
|
+
await limiter.acquire(providerId, 1);
|
|
205
|
+
} catch {
|
|
206
|
+
return {
|
|
207
|
+
success: false,
|
|
208
|
+
error: new Error("Rate limit exceeded"),
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Get circuit breaker for provider
|
|
214
|
+
const breaker = getBreaker(providerId);
|
|
215
|
+
|
|
216
|
+
// Check circuit state
|
|
217
|
+
if (breaker.getState() === "OPEN") {
|
|
218
|
+
metrics.circuitTrips++;
|
|
219
|
+
return {
|
|
220
|
+
success: false,
|
|
221
|
+
error: new CircuitOpenError(providerId, breaker.getTimeUntilReset()),
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Execute through circuit breaker
|
|
226
|
+
try {
|
|
227
|
+
const result = await breaker.execute(request);
|
|
228
|
+
metrics.successfulRequests++;
|
|
229
|
+
return {
|
|
230
|
+
success: true,
|
|
231
|
+
value: result,
|
|
232
|
+
};
|
|
233
|
+
} catch (error) {
|
|
234
|
+
metrics.failedRequests++;
|
|
235
|
+
|
|
236
|
+
// If circuit opened, record it
|
|
237
|
+
if (error instanceof CircuitOpenError) {
|
|
238
|
+
metrics.circuitTrips++;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return {
|
|
242
|
+
success: false,
|
|
243
|
+
error: error as Error,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
},
|
|
247
|
+
|
|
248
|
+
getCircuitState(providerId: string): string {
|
|
249
|
+
const breaker = breakers.get(providerId);
|
|
250
|
+
return breaker?.getState() ?? "unknown";
|
|
251
|
+
},
|
|
252
|
+
|
|
253
|
+
getRateLimiterStats() {
|
|
254
|
+
return {
|
|
255
|
+
allowedRequests: metrics.totalRequests - metrics.rateLimitedRequests,
|
|
256
|
+
throttledRequests: metrics.rateLimitedRequests,
|
|
257
|
+
};
|
|
258
|
+
},
|
|
259
|
+
|
|
260
|
+
resetAllCircuits() {
|
|
261
|
+
for (const breaker of breakers.values()) {
|
|
262
|
+
breaker.reset();
|
|
263
|
+
}
|
|
264
|
+
metrics.circuitTrips = 0;
|
|
265
|
+
},
|
|
266
|
+
|
|
267
|
+
dispose() {
|
|
268
|
+
breakers.clear();
|
|
269
|
+
},
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Create a simple circuit breaker for a single operation.
|
|
275
|
+
*
|
|
276
|
+
* @param id - Circuit breaker identifier
|
|
277
|
+
* @param options - Circuit breaker options
|
|
278
|
+
* @returns Circuit breaker instance
|
|
279
|
+
*
|
|
280
|
+
* @example
|
|
281
|
+
* ```typescript
|
|
282
|
+
* const breaker = createCircuitBreaker("api", { failureThreshold: 3 });
|
|
283
|
+
* try {
|
|
284
|
+
* const result = await breaker.execute(() => apiCall());
|
|
285
|
+
* } catch (error) {
|
|
286
|
+
* if (error instanceof CircuitOpenError) {
|
|
287
|
+
* console.log("Circuit is open, service unavailable");
|
|
288
|
+
* }
|
|
289
|
+
* }
|
|
290
|
+
* ```
|
|
291
|
+
*/
|
|
292
|
+
export function createCircuitBreaker(
|
|
293
|
+
id: string = "default",
|
|
294
|
+
options: Partial<CircuitBreakerOptions> = {}
|
|
295
|
+
): CircuitBreaker {
|
|
296
|
+
return new CircuitBreaker(id, {
|
|
297
|
+
...DEFAULT_CIRCUIT_BREAKER_OPTIONS,
|
|
298
|
+
...options,
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Create a rate limiter for API calls.
|
|
304
|
+
*
|
|
305
|
+
* @param config - Rate limiter configuration
|
|
306
|
+
* @returns Rate limiter instance
|
|
307
|
+
*
|
|
308
|
+
* @example
|
|
309
|
+
* ```typescript
|
|
310
|
+
* const limiter = createApiRateLimiter({
|
|
311
|
+
* defaultBucket: { capacity: 100, refillRate: 10 },
|
|
312
|
+
* });
|
|
313
|
+
*
|
|
314
|
+
* if (limiter.tryAcquire("api-key")) {
|
|
315
|
+
* await makeApiCall();
|
|
316
|
+
* } else {
|
|
317
|
+
* console.log("Rate limited, please wait");
|
|
318
|
+
* }
|
|
319
|
+
* ```
|
|
320
|
+
*/
|
|
321
|
+
export function createApiRateLimiter(config: Partial<RateLimiterConfig> = {}): RateLimiter {
|
|
322
|
+
return createRateLimiter({
|
|
323
|
+
...DEFAULT_RATE_LIMITER_CONFIG,
|
|
324
|
+
...config,
|
|
325
|
+
} as RateLimiterConfig);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// =============================================================================
|
|
329
|
+
// Exports
|
|
330
|
+
// =============================================================================
|
|
331
|
+
|
|
332
|
+
export {
|
|
333
|
+
CircuitBreaker,
|
|
334
|
+
type CircuitBreakerOptions,
|
|
335
|
+
CircuitOpenError,
|
|
336
|
+
ProviderFallbackChain,
|
|
337
|
+
type FallbackConfig,
|
|
338
|
+
type FallbackResult,
|
|
339
|
+
type FallbackProvider,
|
|
340
|
+
RateLimiter,
|
|
341
|
+
type RateLimiterConfig,
|
|
342
|
+
};
|