@gguf/coder 0.3.0 → 0.3.1
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/.editorconfig +16 -0
- package/.env.example +63 -0
- package/.gitattributes +1 -0
- package/.semgrepignore +19 -0
- package/coder-dummy-file.ts +52 -0
- package/coder.config.example.json +59 -0
- package/coder.config.json +13 -0
- package/color_picker.html +36 -0
- package/package.json +2 -14
- package/scripts/extract-changelog.js +73 -0
- package/scripts/fetch-models.js +143 -0
- package/scripts/test.sh +40 -0
- package/scripts/update-homebrew-formula.sh +125 -0
- package/scripts/update-nix-version.sh +157 -0
- package/source/ai-sdk-client/AISDKClient.spec.ts +117 -0
- package/source/ai-sdk-client/AISDKClient.ts +155 -0
- package/source/ai-sdk-client/chat/chat-handler.spec.ts +121 -0
- package/source/ai-sdk-client/chat/chat-handler.ts +276 -0
- package/source/ai-sdk-client/chat/streaming-handler.spec.ts +173 -0
- package/source/ai-sdk-client/chat/streaming-handler.ts +110 -0
- package/source/ai-sdk-client/chat/tool-processor.spec.ts +92 -0
- package/source/ai-sdk-client/chat/tool-processor.ts +70 -0
- package/source/ai-sdk-client/converters/message-converter.spec.ts +220 -0
- package/source/ai-sdk-client/converters/message-converter.ts +113 -0
- package/source/ai-sdk-client/converters/tool-converter.spec.ts +90 -0
- package/source/ai-sdk-client/converters/tool-converter.ts +46 -0
- package/source/ai-sdk-client/error-handling/error-extractor.spec.ts +55 -0
- package/source/ai-sdk-client/error-handling/error-extractor.ts +15 -0
- package/source/ai-sdk-client/error-handling/error-parser.spec.ts +169 -0
- package/source/ai-sdk-client/error-handling/error-parser.ts +161 -0
- package/source/ai-sdk-client/index.ts +7 -0
- package/source/ai-sdk-client/providers/provider-factory.spec.ts +71 -0
- package/source/ai-sdk-client/providers/provider-factory.ts +41 -0
- package/source/ai-sdk-client/types.ts +9 -0
- package/source/ai-sdk-client-empty-message.spec.ts +141 -0
- package/source/ai-sdk-client-error-handling.spec.ts +186 -0
- package/source/ai-sdk-client-maxretries.spec.ts +114 -0
- package/source/ai-sdk-client-preparestep.spec.ts +279 -0
- package/source/app/App.spec.tsx +32 -0
- package/source/app/App.tsx +480 -0
- package/source/app/components/AppContainer.spec.tsx +96 -0
- package/source/app/components/AppContainer.tsx +56 -0
- package/source/app/components/ChatInterface.spec.tsx +163 -0
- package/source/app/components/ChatInterface.tsx +144 -0
- package/source/app/components/ModalSelectors.spec.tsx +141 -0
- package/source/app/components/ModalSelectors.tsx +135 -0
- package/source/app/helpers.spec.ts +97 -0
- package/source/app/helpers.ts +63 -0
- package/source/app/index.ts +4 -0
- package/source/app/types.ts +39 -0
- package/source/app/utils/appUtils.ts +294 -0
- package/source/app/utils/conversationState.ts +310 -0
- package/source/app.spec.tsx +244 -0
- package/source/cli.spec.ts +73 -0
- package/source/cli.tsx +51 -0
- package/source/client-factory.spec.ts +48 -0
- package/source/client-factory.ts +178 -0
- package/source/command-parser.spec.ts +127 -0
- package/source/command-parser.ts +36 -0
- package/source/commands/checkpoint.spec.tsx +277 -0
- package/source/commands/checkpoint.tsx +366 -0
- package/source/commands/clear.tsx +22 -0
- package/source/commands/custom-commands.tsx +121 -0
- package/source/commands/exit.ts +21 -0
- package/source/commands/export.spec.tsx +131 -0
- package/source/commands/export.tsx +79 -0
- package/source/commands/help.tsx +120 -0
- package/source/commands/index.ts +17 -0
- package/source/commands/init.tsx +339 -0
- package/source/commands/lsp-command.spec.tsx +281 -0
- package/source/commands/lsp.tsx +120 -0
- package/source/commands/mcp-command.spec.tsx +313 -0
- package/source/commands/mcp.tsx +162 -0
- package/source/commands/model-database.spec.tsx +758 -0
- package/source/commands/model-database.tsx +418 -0
- package/source/commands/model.ts +12 -0
- package/source/commands/provider.ts +12 -0
- package/source/commands/setup-config.tsx +16 -0
- package/source/commands/simple-commands.spec.tsx +175 -0
- package/source/commands/status.ts +12 -0
- package/source/commands/theme.ts +12 -0
- package/source/commands/update.spec.tsx +261 -0
- package/source/commands/update.tsx +201 -0
- package/source/commands/usage.spec.tsx +495 -0
- package/source/commands/usage.tsx +100 -0
- package/source/commands.spec.ts +436 -0
- package/source/commands.ts +83 -0
- package/source/components/assistant-message.spec.tsx +796 -0
- package/source/components/assistant-message.tsx +34 -0
- package/source/components/bash-execution-indicator.tsx +21 -0
- package/source/components/cancelling-indicator.tsx +16 -0
- package/source/components/chat-queue.spec.tsx +83 -0
- package/source/components/chat-queue.tsx +36 -0
- package/source/components/checkpoint-display.spec.tsx +219 -0
- package/source/components/checkpoint-display.tsx +126 -0
- package/source/components/checkpoint-selector.spec.tsx +173 -0
- package/source/components/checkpoint-selector.tsx +173 -0
- package/source/components/development-mode-indicator.spec.tsx +268 -0
- package/source/components/development-mode-indicator.tsx +38 -0
- package/source/components/message-box.spec.tsx +427 -0
- package/source/components/message-box.tsx +87 -0
- package/source/components/model-selector.tsx +132 -0
- package/source/components/provider-selector.tsx +75 -0
- package/source/components/random-spinner.tsx +19 -0
- package/source/components/security-disclaimer.tsx +73 -0
- package/source/components/status-connection-display.spec.tsx +133 -0
- package/source/components/status.tsx +267 -0
- package/source/components/theme-selector.tsx +126 -0
- package/source/components/tool-confirmation.tsx +190 -0
- package/source/components/tool-execution-indicator.tsx +33 -0
- package/source/components/tool-message.tsx +85 -0
- package/source/components/ui/titled-box.spec.tsx +207 -0
- package/source/components/ui/titled-box.tsx +57 -0
- package/source/components/usage/progress-bar.spec.tsx +398 -0
- package/source/components/usage/progress-bar.tsx +30 -0
- package/source/components/usage/usage-display.spec.tsx +780 -0
- package/source/components/usage/usage-display.tsx +291 -0
- package/source/components/user-input.spec.tsx +327 -0
- package/source/components/user-input.tsx +533 -0
- package/source/components/user-message.spec.tsx +230 -0
- package/source/components/user-message.tsx +84 -0
- package/source/components/welcome-message.tsx +76 -0
- package/source/config/env-substitution.ts +65 -0
- package/source/config/index.spec.ts +171 -0
- package/source/config/index.ts +154 -0
- package/source/config/paths.spec.ts +241 -0
- package/source/config/paths.ts +55 -0
- package/source/config/preferences.ts +51 -0
- package/source/config/themes.ts +315 -0
- package/source/constants.ts +130 -0
- package/source/context/mode-context.spec.ts +79 -0
- package/source/context/mode-context.ts +24 -0
- package/source/custom-commands/executor.spec.ts +142 -0
- package/source/custom-commands/executor.ts +64 -0
- package/source/custom-commands/loader.spec.ts +314 -0
- package/source/custom-commands/loader.ts +153 -0
- package/source/custom-commands/parser.ts +196 -0
- package/source/hooks/chat-handler/conversation/conversation-loop.spec.ts +39 -0
- package/source/hooks/chat-handler/conversation/conversation-loop.tsx +511 -0
- package/source/hooks/chat-handler/conversation/tool-executor.spec.ts +50 -0
- package/source/hooks/chat-handler/conversation/tool-executor.tsx +109 -0
- package/source/hooks/chat-handler/index.ts +12 -0
- package/source/hooks/chat-handler/state/streaming-state.spec.ts +26 -0
- package/source/hooks/chat-handler/state/streaming-state.ts +19 -0
- package/source/hooks/chat-handler/types.ts +38 -0
- package/source/hooks/chat-handler/useChatHandler.spec.tsx +321 -0
- package/source/hooks/chat-handler/useChatHandler.tsx +194 -0
- package/source/hooks/chat-handler/utils/context-checker.spec.ts +60 -0
- package/source/hooks/chat-handler/utils/context-checker.tsx +73 -0
- package/source/hooks/chat-handler/utils/message-helpers.spec.ts +42 -0
- package/source/hooks/chat-handler/utils/message-helpers.tsx +36 -0
- package/source/hooks/chat-handler/utils/tool-filters.spec.ts +109 -0
- package/source/hooks/chat-handler/utils/tool-filters.ts +64 -0
- package/source/hooks/useAppHandlers.tsx +291 -0
- package/source/hooks/useAppInitialization.tsx +422 -0
- package/source/hooks/useAppState.tsx +311 -0
- package/source/hooks/useDirectoryTrust.tsx +98 -0
- package/source/hooks/useInputState.ts +414 -0
- package/source/hooks/useModeHandlers.tsx +302 -0
- package/source/hooks/useNonInteractiveMode.ts +140 -0
- package/source/hooks/useTerminalWidth.tsx +81 -0
- package/source/hooks/useTheme.ts +18 -0
- package/source/hooks/useToolHandler.tsx +349 -0
- package/source/hooks/useUIState.ts +61 -0
- package/source/init/agents-template-generator.ts +421 -0
- package/source/init/existing-rules-extractor.ts +319 -0
- package/source/init/file-scanner.spec.ts +227 -0
- package/source/init/file-scanner.ts +238 -0
- package/source/init/framework-detector.ts +382 -0
- package/source/init/language-detector.ts +269 -0
- package/source/init/project-analyzer.spec.ts +231 -0
- package/source/init/project-analyzer.ts +458 -0
- package/source/lsp/index.ts +31 -0
- package/source/lsp/lsp-client.spec.ts +508 -0
- package/source/lsp/lsp-client.ts +487 -0
- package/source/lsp/lsp-manager.spec.ts +477 -0
- package/source/lsp/lsp-manager.ts +419 -0
- package/source/lsp/protocol.spec.ts +502 -0
- package/source/lsp/protocol.ts +360 -0
- package/source/lsp/server-discovery.spec.ts +654 -0
- package/source/lsp/server-discovery.ts +515 -0
- package/source/markdown-parser/html-entities.spec.ts +88 -0
- package/source/markdown-parser/html-entities.ts +45 -0
- package/source/markdown-parser/index.spec.ts +281 -0
- package/source/markdown-parser/index.ts +126 -0
- package/source/markdown-parser/table-parser.spec.ts +133 -0
- package/source/markdown-parser/table-parser.ts +114 -0
- package/source/markdown-parser/utils.spec.ts +70 -0
- package/source/markdown-parser/utils.ts +13 -0
- package/source/mcp/mcp-client.spec.ts +81 -0
- package/source/mcp/mcp-client.ts +625 -0
- package/source/mcp/transport-factory.spec.ts +406 -0
- package/source/mcp/transport-factory.ts +312 -0
- package/source/message-handler.ts +67 -0
- package/source/model-database/database-engine.spec.ts +494 -0
- package/source/model-database/database-engine.ts +50 -0
- package/source/model-database/model-database.spec.ts +363 -0
- package/source/model-database/model-database.ts +91 -0
- package/source/model-database/model-engine.spec.ts +447 -0
- package/source/model-database/model-engine.ts +65 -0
- package/source/model-database/model-fetcher.spec.ts +583 -0
- package/source/model-database/model-fetcher.ts +330 -0
- package/source/models/index.ts +1 -0
- package/source/models/models-cache.spec.ts +214 -0
- package/source/models/models-cache.ts +78 -0
- package/source/models/models-dev-client.spec.ts +379 -0
- package/source/models/models-dev-client.ts +329 -0
- package/source/models/models-types.ts +68 -0
- package/source/prompt-history.ts +155 -0
- package/source/security/command-injection.spec.ts +240 -0
- package/source/services/checkpoint-manager.spec.ts +523 -0
- package/source/services/checkpoint-manager.ts +466 -0
- package/source/services/file-snapshot.spec.ts +569 -0
- package/source/services/file-snapshot.ts +220 -0
- package/source/test-utils/render-with-theme.tsx +48 -0
- package/source/tokenization/index.ts +1 -0
- package/source/tokenization/tokenizer-factory.spec.ts +170 -0
- package/source/tokenization/tokenizer-factory.ts +125 -0
- package/source/tokenization/tokenizers/anthropic-tokenizer.spec.ts +200 -0
- package/source/tokenization/tokenizers/anthropic-tokenizer.ts +43 -0
- package/source/tokenization/tokenizers/fallback-tokenizer.spec.ts +236 -0
- package/source/tokenization/tokenizers/fallback-tokenizer.ts +26 -0
- package/source/tokenization/tokenizers/llama-tokenizer.spec.ts +224 -0
- package/source/tokenization/tokenizers/llama-tokenizer.ts +41 -0
- package/source/tokenization/tokenizers/openai-tokenizer.spec.ts +184 -0
- package/source/tokenization/tokenizers/openai-tokenizer.ts +57 -0
- package/source/tool-calling/index.ts +5 -0
- package/source/tool-calling/json-parser.spec.ts +639 -0
- package/source/tool-calling/json-parser.ts +247 -0
- package/source/tool-calling/tool-parser.spec.ts +395 -0
- package/source/tool-calling/tool-parser.ts +120 -0
- package/source/tool-calling/xml-parser.spec.ts +662 -0
- package/source/tool-calling/xml-parser.ts +289 -0
- package/source/tools/execute-bash.spec.tsx +353 -0
- package/source/tools/execute-bash.tsx +219 -0
- package/source/tools/execute-function.spec.ts +130 -0
- package/source/tools/fetch-url.spec.tsx +342 -0
- package/source/tools/fetch-url.tsx +172 -0
- package/source/tools/find-files.spec.tsx +924 -0
- package/source/tools/find-files.tsx +293 -0
- package/source/tools/index.ts +102 -0
- package/source/tools/lsp-get-diagnostics.tsx +192 -0
- package/source/tools/needs-approval.spec.ts +282 -0
- package/source/tools/read-file.spec.tsx +801 -0
- package/source/tools/read-file.tsx +387 -0
- package/source/tools/search-file-contents.spec.tsx +1273 -0
- package/source/tools/search-file-contents.tsx +293 -0
- package/source/tools/string-replace.spec.tsx +730 -0
- package/source/tools/string-replace.tsx +548 -0
- package/source/tools/tool-manager.ts +210 -0
- package/source/tools/tool-registry.spec.ts +415 -0
- package/source/tools/tool-registry.ts +228 -0
- package/source/tools/web-search.tsx +223 -0
- package/source/tools/write-file.spec.tsx +559 -0
- package/source/tools/write-file.tsx +228 -0
- package/source/types/app.ts +37 -0
- package/source/types/checkpoint.ts +48 -0
- package/source/types/commands.ts +46 -0
- package/source/types/components.ts +27 -0
- package/source/types/config.ts +103 -0
- package/source/types/core-connection-status.spec.ts +67 -0
- package/source/types/core.ts +181 -0
- package/source/types/hooks.ts +50 -0
- package/source/types/index.ts +12 -0
- package/source/types/markdown-parser.ts +11 -0
- package/source/types/mcp.ts +52 -0
- package/source/types/system.ts +16 -0
- package/source/types/tokenization.ts +41 -0
- package/source/types/ui.ts +40 -0
- package/source/types/usage.ts +58 -0
- package/source/types/utils.ts +16 -0
- package/source/usage/calculator.spec.ts +385 -0
- package/source/usage/calculator.ts +104 -0
- package/source/usage/storage.spec.ts +703 -0
- package/source/usage/storage.ts +238 -0
- package/source/usage/tracker.spec.ts +456 -0
- package/source/usage/tracker.ts +102 -0
- package/source/utils/atomic-deletion.spec.ts +194 -0
- package/source/utils/atomic-deletion.ts +127 -0
- package/source/utils/bounded-map.spec.ts +300 -0
- package/source/utils/bounded-map.ts +193 -0
- package/source/utils/checkpoint-utils.spec.ts +222 -0
- package/source/utils/checkpoint-utils.ts +92 -0
- package/source/utils/error-formatter.spec.ts +169 -0
- package/source/utils/error-formatter.ts +194 -0
- package/source/utils/file-autocomplete.spec.ts +173 -0
- package/source/utils/file-autocomplete.ts +196 -0
- package/source/utils/file-cache.spec.ts +309 -0
- package/source/utils/file-cache.ts +195 -0
- package/source/utils/file-content-loader.spec.ts +180 -0
- package/source/utils/file-content-loader.ts +179 -0
- package/source/utils/file-mention-handler.spec.ts +261 -0
- package/source/utils/file-mention-handler.ts +84 -0
- package/source/utils/file-mention-parser.spec.ts +182 -0
- package/source/utils/file-mention-parser.ts +170 -0
- package/source/utils/fuzzy-matching.spec.ts +149 -0
- package/source/utils/fuzzy-matching.ts +146 -0
- package/source/utils/indentation-normalizer.spec.ts +216 -0
- package/source/utils/indentation-normalizer.ts +76 -0
- package/source/utils/installation-detector.spec.ts +178 -0
- package/source/utils/installation-detector.ts +153 -0
- package/source/utils/logging/config.spec.ts +311 -0
- package/source/utils/logging/config.ts +210 -0
- package/source/utils/logging/console-facade.spec.ts +184 -0
- package/source/utils/logging/console-facade.ts +384 -0
- package/source/utils/logging/correlation.spec.ts +679 -0
- package/source/utils/logging/correlation.ts +474 -0
- package/source/utils/logging/formatters.spec.ts +464 -0
- package/source/utils/logging/formatters.ts +207 -0
- package/source/utils/logging/health-monitor/alerts/alert-manager.spec.ts +93 -0
- package/source/utils/logging/health-monitor/alerts/alert-manager.ts +79 -0
- package/source/utils/logging/health-monitor/checks/configuration-check.spec.ts +56 -0
- package/source/utils/logging/health-monitor/checks/configuration-check.ts +43 -0
- package/source/utils/logging/health-monitor/checks/logging-check.spec.ts +56 -0
- package/source/utils/logging/health-monitor/checks/logging-check.ts +58 -0
- package/source/utils/logging/health-monitor/checks/memory-check.spec.ts +100 -0
- package/source/utils/logging/health-monitor/checks/memory-check.ts +78 -0
- package/source/utils/logging/health-monitor/checks/performance-check.spec.ts +56 -0
- package/source/utils/logging/health-monitor/checks/performance-check.ts +56 -0
- package/source/utils/logging/health-monitor/checks/request-check.spec.ts +56 -0
- package/source/utils/logging/health-monitor/checks/request-check.ts +76 -0
- package/source/utils/logging/health-monitor/core/health-check-runner.spec.ts +70 -0
- package/source/utils/logging/health-monitor/core/health-check-runner.ts +138 -0
- package/source/utils/logging/health-monitor/core/health-monitor.spec.ts +58 -0
- package/source/utils/logging/health-monitor/core/health-monitor.ts +344 -0
- package/source/utils/logging/health-monitor/core/scoring.spec.ts +65 -0
- package/source/utils/logging/health-monitor/core/scoring.ts +91 -0
- package/source/utils/logging/health-monitor/index.ts +15 -0
- package/source/utils/logging/health-monitor/instances.ts +48 -0
- package/source/utils/logging/health-monitor/middleware/http-middleware.spec.ts +141 -0
- package/source/utils/logging/health-monitor/middleware/http-middleware.ts +75 -0
- package/source/utils/logging/health-monitor/types.ts +126 -0
- package/source/utils/logging/index.spec.ts +284 -0
- package/source/utils/logging/index.ts +236 -0
- package/source/utils/logging/integration.spec.ts +441 -0
- package/source/utils/logging/log-method-factory.spec.ts +573 -0
- package/source/utils/logging/log-method-factory.ts +233 -0
- package/source/utils/logging/log-query/aggregation/aggregator.spec.ts +277 -0
- package/source/utils/logging/log-query/aggregation/aggregator.ts +159 -0
- package/source/utils/logging/log-query/aggregation/facet-generator.spec.ts +159 -0
- package/source/utils/logging/log-query/aggregation/facet-generator.ts +47 -0
- package/source/utils/logging/log-query/index.ts +23 -0
- package/source/utils/logging/log-query/query/filter-predicates.spec.ts +247 -0
- package/source/utils/logging/log-query/query/filter-predicates.ts +154 -0
- package/source/utils/logging/log-query/query/query-builder.spec.ts +182 -0
- package/source/utils/logging/log-query/query/query-builder.ts +151 -0
- package/source/utils/logging/log-query/query/query-engine.spec.ts +214 -0
- package/source/utils/logging/log-query/query/query-engine.ts +45 -0
- package/source/utils/logging/log-query/storage/circular-buffer.spec.ts +143 -0
- package/source/utils/logging/log-query/storage/circular-buffer.ts +75 -0
- package/source/utils/logging/log-query/storage/index-manager.spec.ts +150 -0
- package/source/utils/logging/log-query/storage/index-manager.ts +71 -0
- package/source/utils/logging/log-query/storage/log-storage.spec.ts +257 -0
- package/source/utils/logging/log-query/storage/log-storage.ts +80 -0
- package/source/utils/logging/log-query/types.ts +163 -0
- package/source/utils/logging/log-query/utils/helpers.spec.ts +263 -0
- package/source/utils/logging/log-query/utils/helpers.ts +72 -0
- package/source/utils/logging/log-query/utils/sorting.spec.ts +182 -0
- package/source/utils/logging/log-query/utils/sorting.ts +61 -0
- package/source/utils/logging/logger-provider.spec.ts +262 -0
- package/source/utils/logging/logger-provider.ts +362 -0
- package/source/utils/logging/performance.spec.ts +209 -0
- package/source/utils/logging/performance.ts +757 -0
- package/source/utils/logging/pino-logger.spec.ts +425 -0
- package/source/utils/logging/pino-logger.ts +514 -0
- package/source/utils/logging/redaction.spec.ts +490 -0
- package/source/utils/logging/redaction.ts +267 -0
- package/source/utils/logging/request-tracker.spec.ts +1198 -0
- package/source/utils/logging/request-tracker.ts +803 -0
- package/source/utils/logging/transports.spec.ts +505 -0
- package/source/utils/logging/transports.ts +305 -0
- package/source/utils/logging/types.ts +216 -0
- package/source/utils/message-builder.spec.ts +179 -0
- package/source/utils/message-builder.ts +101 -0
- package/source/utils/message-queue.tsx +486 -0
- package/source/utils/paste-detection.spec.ts +69 -0
- package/source/utils/paste-detection.ts +124 -0
- package/source/utils/paste-roundtrip.spec.ts +442 -0
- package/source/utils/paste-utils.spec.ts +128 -0
- package/source/utils/paste-utils.ts +52 -0
- package/source/utils/programming-language-helper.spec.ts +74 -0
- package/source/utils/programming-language-helper.ts +32 -0
- package/source/utils/prompt-assembly.spec.ts +221 -0
- package/source/utils/prompt-processor.ts +173 -0
- package/source/utils/tool-args-parser.spec.ts +136 -0
- package/source/utils/tool-args-parser.ts +54 -0
- package/source/utils/tool-cancellation.spec.ts +230 -0
- package/source/utils/tool-cancellation.ts +28 -0
- package/source/utils/tool-result-display.spec.tsx +469 -0
- package/source/utils/tool-result-display.tsx +90 -0
- package/source/utils/update-checker.spec.ts +383 -0
- package/source/utils/update-checker.ts +183 -0
- package/source/wizard/config-wizard.spec.tsx +103 -0
- package/source/wizard/config-wizard.tsx +382 -0
- package/source/wizard/steps/location-step.spec.tsx +186 -0
- package/source/wizard/steps/location-step.tsx +147 -0
- package/source/wizard/steps/mcp-step.spec.tsx +607 -0
- package/source/wizard/steps/mcp-step.tsx +632 -0
- package/source/wizard/steps/provider-step.spec.tsx +342 -0
- package/source/wizard/steps/provider-step.tsx +957 -0
- package/source/wizard/steps/summary-step.spec.tsx +749 -0
- package/source/wizard/steps/summary-step.tsx +228 -0
- package/source/wizard/templates/mcp-templates.spec.ts +613 -0
- package/source/wizard/templates/mcp-templates.ts +570 -0
- package/source/wizard/templates/provider-templates.spec.ts +152 -0
- package/source/wizard/templates/provider-templates.ts +485 -0
- package/source/wizard/utils/fetch-cloud-models.spec.ts +428 -0
- package/source/wizard/utils/fetch-cloud-models.ts +223 -0
- package/source/wizard/utils/fetch-local-models.spec.ts +297 -0
- package/source/wizard/utils/fetch-local-models.ts +192 -0
- package/source/wizard/validation-array.spec.ts +264 -0
- package/source/wizard/validation.spec.ts +373 -0
- package/source/wizard/validation.ts +232 -0
- package/source/app/prompts/main-prompt.md +0 -122
|
@@ -0,0 +1,703 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as os from 'node:os';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
import test from 'ava';
|
|
5
|
+
import type {SessionUsage, TokenBreakdown, UsageData} from '../types/usage.js';
|
|
6
|
+
import {
|
|
7
|
+
addSession,
|
|
8
|
+
clearUsageData,
|
|
9
|
+
getLastNDaysAggregate,
|
|
10
|
+
getTodayAggregate,
|
|
11
|
+
readUsageData,
|
|
12
|
+
writeUsageData,
|
|
13
|
+
} from './storage.js';
|
|
14
|
+
|
|
15
|
+
console.log('\nstorage.spec.ts');
|
|
16
|
+
|
|
17
|
+
// ============================================================================
|
|
18
|
+
// Test Setup
|
|
19
|
+
// ============================================================================
|
|
20
|
+
|
|
21
|
+
// Create a temporary test directory for each test
|
|
22
|
+
function createTestDir(): string {
|
|
23
|
+
const testDir = path.join(
|
|
24
|
+
os.tmpdir(),
|
|
25
|
+
`coder-test-${Date.now()}-${Math.random().toString(36).substring(7)}`,
|
|
26
|
+
);
|
|
27
|
+
fs.mkdirSync(testDir, {recursive: true});
|
|
28
|
+
return testDir;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Use XDG_DATA_HOME to control app data directory for tests
|
|
32
|
+
let originalEnv: NodeJS.ProcessEnv;
|
|
33
|
+
|
|
34
|
+
test.before(() => {
|
|
35
|
+
originalEnv = {...process.env};
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test.beforeEach(() => {
|
|
39
|
+
// Create a fresh test directory for each test
|
|
40
|
+
const testDir = createTestDir();
|
|
41
|
+
// Override XDG_DATA_HOME to point to test directory
|
|
42
|
+
process.env.XDG_DATA_HOME = testDir;
|
|
43
|
+
// Clear any existing data
|
|
44
|
+
clearUsageData();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test.afterEach(() => {
|
|
48
|
+
// Clear usage data first
|
|
49
|
+
clearUsageData();
|
|
50
|
+
// Clean up test directory
|
|
51
|
+
try {
|
|
52
|
+
if (process.env.XDG_DATA_HOME) {
|
|
53
|
+
fs.rmSync(process.env.XDG_DATA_HOME, {recursive: true, force: true});
|
|
54
|
+
}
|
|
55
|
+
} catch (error) {
|
|
56
|
+
// Ignore cleanup errors
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test.after(() => {
|
|
61
|
+
process.env = originalEnv;
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// ============================================================================
|
|
65
|
+
// Migration Tests
|
|
66
|
+
// ============================================================================
|
|
67
|
+
|
|
68
|
+
test('migrates usage data from legacy config directory to app data directory', t => {
|
|
69
|
+
// Arrange: create a legacy usage.json at the old config location.
|
|
70
|
+
// Legacy usage.json lived in the config directory (getConfigPath()).
|
|
71
|
+
// When CODER_CONFIG_DIR is set, getConfigPath() returns it directly.
|
|
72
|
+
// So here we create a fake legacy config dir and point CODER_CONFIG_DIR at it.
|
|
73
|
+
const legacyConfigDir = path.join(os.tmpdir(), 'coder-legacy-config');
|
|
74
|
+
fs.mkdirSync(legacyConfigDir, {recursive: true});
|
|
75
|
+
|
|
76
|
+
const legacyFilePath = path.join(legacyConfigDir, 'usage.json');
|
|
77
|
+
const legacyData: UsageData = {
|
|
78
|
+
sessions: [createMockSession('legacy', 'model', 123)],
|
|
79
|
+
dailyAggregates: [],
|
|
80
|
+
totalLifetime: 123,
|
|
81
|
+
lastUpdated: Date.now(),
|
|
82
|
+
};
|
|
83
|
+
fs.writeFileSync(legacyFilePath, JSON.stringify(legacyData), 'utf-8');
|
|
84
|
+
|
|
85
|
+
// Point CODER_CONFIG_DIR to our legacy config dir to simulate pre-change behavior
|
|
86
|
+
process.env.CODER_CONFIG_DIR = legacyConfigDir;
|
|
87
|
+
|
|
88
|
+
// Act: first read should trigger migration into getAppDataPath() directory
|
|
89
|
+
const data = readUsageData();
|
|
90
|
+
|
|
91
|
+
// Assert: data is preserved
|
|
92
|
+
t.is(data.totalLifetime, 123);
|
|
93
|
+
t.is(data.sessions.length, 1);
|
|
94
|
+
|
|
95
|
+
// And the new file exists at the app data path
|
|
96
|
+
const appDataHome = process.env.XDG_DATA_HOME!;
|
|
97
|
+
const appDataDir = path.join(appDataHome, 'coder');
|
|
98
|
+
const newFilePath = path.join(appDataDir, 'usage.json');
|
|
99
|
+
t.true(fs.existsSync(newFilePath));
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test('migration removes legacy file after successful migration', t => {
|
|
103
|
+
// Create legacy file
|
|
104
|
+
const legacyConfigDir = path.join(os.tmpdir(), 'coder-legacy-config-2');
|
|
105
|
+
fs.mkdirSync(legacyConfigDir, {recursive: true});
|
|
106
|
+
const legacyFilePath = path.join(legacyConfigDir, 'usage.json');
|
|
107
|
+
const legacyData: UsageData = {
|
|
108
|
+
sessions: [createMockSession('legacy', 'model', 456)],
|
|
109
|
+
dailyAggregates: [],
|
|
110
|
+
totalLifetime: 456,
|
|
111
|
+
lastUpdated: Date.now(),
|
|
112
|
+
};
|
|
113
|
+
fs.writeFileSync(legacyFilePath, JSON.stringify(legacyData), 'utf-8');
|
|
114
|
+
|
|
115
|
+
process.env.CODER_CONFIG_DIR = legacyConfigDir;
|
|
116
|
+
|
|
117
|
+
// Trigger migration
|
|
118
|
+
readUsageData();
|
|
119
|
+
|
|
120
|
+
// Legacy file should be gone
|
|
121
|
+
t.false(fs.existsSync(legacyFilePath));
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test('migration skips when new file already exists', t => {
|
|
125
|
+
// Create both legacy and new files
|
|
126
|
+
const legacyConfigDir = path.join(os.tmpdir(), 'coder-legacy-config-3');
|
|
127
|
+
fs.mkdirSync(legacyConfigDir, {recursive: true});
|
|
128
|
+
const legacyFilePath = path.join(legacyConfigDir, 'usage.json');
|
|
129
|
+
const legacyData: UsageData = {
|
|
130
|
+
sessions: [createMockSession('legacy', 'model', 111)],
|
|
131
|
+
dailyAggregates: [],
|
|
132
|
+
totalLifetime: 111,
|
|
133
|
+
lastUpdated: Date.now(),
|
|
134
|
+
};
|
|
135
|
+
fs.writeFileSync(legacyFilePath, JSON.stringify(legacyData), 'utf-8');
|
|
136
|
+
|
|
137
|
+
// Create new file with different data
|
|
138
|
+
const appDataHome = process.env.XDG_DATA_HOME!;
|
|
139
|
+
const appDataDir = path.join(appDataHome, 'coder');
|
|
140
|
+
fs.mkdirSync(appDataDir, {recursive: true});
|
|
141
|
+
const newFilePath = path.join(appDataDir, 'usage.json');
|
|
142
|
+
const newData: UsageData = {
|
|
143
|
+
sessions: [createMockSession('new', 'model', 999)],
|
|
144
|
+
dailyAggregates: [],
|
|
145
|
+
totalLifetime: 999,
|
|
146
|
+
lastUpdated: Date.now(),
|
|
147
|
+
};
|
|
148
|
+
fs.writeFileSync(newFilePath, JSON.stringify(newData), 'utf-8');
|
|
149
|
+
|
|
150
|
+
process.env.CODER_CONFIG_DIR = legacyConfigDir;
|
|
151
|
+
|
|
152
|
+
// Read should use new file, not migrate
|
|
153
|
+
const data = readUsageData();
|
|
154
|
+
|
|
155
|
+
// Should have data from new file, not legacy
|
|
156
|
+
t.is(data.totalLifetime, 999);
|
|
157
|
+
|
|
158
|
+
// Legacy file should still exist (wasn't touched)
|
|
159
|
+
t.true(fs.existsSync(legacyFilePath));
|
|
160
|
+
t.true(fs.existsSync(newFilePath));
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test('migration handles missing legacy config directory gracefully', t => {
|
|
164
|
+
// Don't create legacy directory, just set CODER_CONFIG_DIR to non-existent path
|
|
165
|
+
process.env.CODER_CONFIG_DIR = path.join(
|
|
166
|
+
os.tmpdir(),
|
|
167
|
+
'coder-nonexistent',
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
// Should not throw and should return empty data
|
|
171
|
+
const data = readUsageData();
|
|
172
|
+
t.is(data.sessions.length, 0);
|
|
173
|
+
t.is(data.totalLifetime, 0);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test('migration handles corrupt legacy file gracefully', t => {
|
|
177
|
+
// Create legacy file with invalid JSON
|
|
178
|
+
const legacyConfigDir = path.join(os.tmpdir(), 'coder-legacy-config-4');
|
|
179
|
+
fs.mkdirSync(legacyConfigDir, {recursive: true});
|
|
180
|
+
const legacyFilePath = path.join(legacyConfigDir, 'usage.json');
|
|
181
|
+
fs.writeFileSync(legacyFilePath, 'not valid json{{{', 'utf-8');
|
|
182
|
+
|
|
183
|
+
process.env.CODER_CONFIG_DIR = legacyConfigDir;
|
|
184
|
+
|
|
185
|
+
// Should not throw, should return empty data
|
|
186
|
+
const data = readUsageData();
|
|
187
|
+
t.is(data.sessions.length, 0);
|
|
188
|
+
t.is(data.totalLifetime, 0);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
test('migration preserves all session data fields', t => {
|
|
192
|
+
const legacyConfigDir = path.join(os.tmpdir(), 'coder-legacy-config-5');
|
|
193
|
+
fs.mkdirSync(legacyConfigDir, {recursive: true});
|
|
194
|
+
const legacyFilePath = path.join(legacyConfigDir, 'usage.json');
|
|
195
|
+
|
|
196
|
+
// Create a session with all fields
|
|
197
|
+
const session = createMockSession('test-provider', 'test-model', 5000);
|
|
198
|
+
const legacyData: UsageData = {
|
|
199
|
+
sessions: [session],
|
|
200
|
+
dailyAggregates: [
|
|
201
|
+
{
|
|
202
|
+
date: '2025-01-01',
|
|
203
|
+
sessions: 1,
|
|
204
|
+
totalTokens: 5000,
|
|
205
|
+
providers: {'test-provider': 5000},
|
|
206
|
+
models: {'test-model': 5000},
|
|
207
|
+
},
|
|
208
|
+
],
|
|
209
|
+
totalLifetime: 5000,
|
|
210
|
+
lastUpdated: Date.now(),
|
|
211
|
+
};
|
|
212
|
+
fs.writeFileSync(legacyFilePath, JSON.stringify(legacyData), 'utf-8');
|
|
213
|
+
|
|
214
|
+
process.env.CODER_CONFIG_DIR = legacyConfigDir;
|
|
215
|
+
|
|
216
|
+
const data = readUsageData();
|
|
217
|
+
|
|
218
|
+
// Verify all fields preserved
|
|
219
|
+
t.is(data.sessions.length, 1);
|
|
220
|
+
t.is(data.sessions[0]!.provider, 'test-provider');
|
|
221
|
+
t.is(data.sessions[0]!.model, 'test-model');
|
|
222
|
+
t.is(data.sessions[0]!.tokens.total, 5000);
|
|
223
|
+
t.is(data.dailyAggregates.length, 1);
|
|
224
|
+
t.is(data.dailyAggregates[0]!.providers['test-provider'], 5000);
|
|
225
|
+
t.is(data.totalLifetime, 5000);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
// Helper to create mock token breakdown
|
|
229
|
+
function createMockBreakdown(total = 1000): TokenBreakdown {
|
|
230
|
+
return {
|
|
231
|
+
system: Math.floor(total * 0.3),
|
|
232
|
+
userMessages: Math.floor(total * 0.2),
|
|
233
|
+
assistantMessages: Math.floor(total * 0.3),
|
|
234
|
+
toolResults: Math.floor(total * 0.1),
|
|
235
|
+
toolDefinitions: Math.floor(total * 0.1),
|
|
236
|
+
total,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Helper to create mock session
|
|
241
|
+
function createMockSession(
|
|
242
|
+
provider = 'test-provider',
|
|
243
|
+
model = 'test-model',
|
|
244
|
+
tokens = 1000,
|
|
245
|
+
): SessionUsage {
|
|
246
|
+
return {
|
|
247
|
+
id: `session-${Date.now()}`,
|
|
248
|
+
timestamp: Date.now(),
|
|
249
|
+
provider,
|
|
250
|
+
model,
|
|
251
|
+
tokens: createMockBreakdown(tokens),
|
|
252
|
+
messageCount: 10,
|
|
253
|
+
duration: 60000,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// ============================================================================
|
|
258
|
+
// readUsageData Tests
|
|
259
|
+
// ============================================================================
|
|
260
|
+
|
|
261
|
+
test('readUsageData returns empty data when file does not exist', t => {
|
|
262
|
+
const data = readUsageData();
|
|
263
|
+
|
|
264
|
+
t.is(data.sessions.length, 0);
|
|
265
|
+
t.is(data.dailyAggregates.length, 0);
|
|
266
|
+
t.is(data.totalLifetime, 0);
|
|
267
|
+
t.truthy(data.lastUpdated);
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
test('readUsageData returns empty data on read error', t => {
|
|
271
|
+
// Write invalid JSON
|
|
272
|
+
const dataHome =
|
|
273
|
+
process.env.XDG_DATA_HOME || path.join(os.tmpdir(), 'test-data');
|
|
274
|
+
const configDir = path.join(dataHome, 'coder');
|
|
275
|
+
fs.mkdirSync(configDir, {recursive: true});
|
|
276
|
+
fs.writeFileSync(path.join(configDir, 'usage.json'), 'invalid json', 'utf-8');
|
|
277
|
+
|
|
278
|
+
const data = readUsageData();
|
|
279
|
+
|
|
280
|
+
t.is(data.sessions.length, 0);
|
|
281
|
+
t.is(data.dailyAggregates.length, 0);
|
|
282
|
+
t.is(data.totalLifetime, 0);
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
test('readUsageData reads existing data', t => {
|
|
286
|
+
// Write valid data first
|
|
287
|
+
const mockData: UsageData = {
|
|
288
|
+
sessions: [createMockSession()],
|
|
289
|
+
dailyAggregates: [],
|
|
290
|
+
totalLifetime: 1000,
|
|
291
|
+
lastUpdated: Date.now(),
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
writeUsageData(mockData);
|
|
295
|
+
|
|
296
|
+
const data = readUsageData();
|
|
297
|
+
|
|
298
|
+
t.is(data.sessions.length, 1);
|
|
299
|
+
t.is(data.totalLifetime, 1000);
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
// ============================================================================
|
|
303
|
+
// writeUsageData Tests
|
|
304
|
+
// ============================================================================
|
|
305
|
+
|
|
306
|
+
test('writeUsageData creates and writes data successfully', t => {
|
|
307
|
+
const mockData: UsageData = {
|
|
308
|
+
sessions: [createMockSession()],
|
|
309
|
+
dailyAggregates: [],
|
|
310
|
+
totalLifetime: 1000,
|
|
311
|
+
lastUpdated: Date.now(),
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
// Write data
|
|
315
|
+
writeUsageData(mockData);
|
|
316
|
+
|
|
317
|
+
// Read it back
|
|
318
|
+
const data = readUsageData();
|
|
319
|
+
t.is(data.sessions.length, 1);
|
|
320
|
+
t.is(data.totalLifetime, 1000);
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
test('writeUsageData writes data to file', t => {
|
|
324
|
+
const mockData: UsageData = {
|
|
325
|
+
sessions: [createMockSession()],
|
|
326
|
+
dailyAggregates: [],
|
|
327
|
+
totalLifetime: 1000,
|
|
328
|
+
lastUpdated: Date.now(),
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
writeUsageData(mockData);
|
|
332
|
+
|
|
333
|
+
const data = readUsageData();
|
|
334
|
+
t.is(data.sessions.length, 1);
|
|
335
|
+
t.is(data.totalLifetime, 1000);
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
test('writeUsageData updates lastUpdated timestamp', t => {
|
|
339
|
+
const oldTimestamp = Date.now() - 10000;
|
|
340
|
+
const mockData: UsageData = {
|
|
341
|
+
sessions: [],
|
|
342
|
+
dailyAggregates: [],
|
|
343
|
+
totalLifetime: 0,
|
|
344
|
+
lastUpdated: oldTimestamp,
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
writeUsageData(mockData);
|
|
348
|
+
|
|
349
|
+
const data = readUsageData();
|
|
350
|
+
t.true(data.lastUpdated > oldTimestamp);
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
test('writeUsageData handles write errors gracefully', t => {
|
|
354
|
+
const dataHome =
|
|
355
|
+
process.env.XDG_DATA_HOME || path.join(os.tmpdir(), 'test-data');
|
|
356
|
+
const configDir = path.join(dataHome, 'coder');
|
|
357
|
+
|
|
358
|
+
// Make directory if needed, then make it read-only
|
|
359
|
+
fs.mkdirSync(configDir, {recursive: true});
|
|
360
|
+
fs.chmodSync(configDir, 0o444);
|
|
361
|
+
|
|
362
|
+
const mockData: UsageData = {
|
|
363
|
+
sessions: [],
|
|
364
|
+
dailyAggregates: [],
|
|
365
|
+
totalLifetime: 0,
|
|
366
|
+
lastUpdated: Date.now(),
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
// Should not throw
|
|
370
|
+
t.notThrows(() => writeUsageData(mockData));
|
|
371
|
+
|
|
372
|
+
// Restore permissions for cleanup
|
|
373
|
+
fs.chmodSync(configDir, 0o755);
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
// ============================================================================
|
|
377
|
+
// addSession Tests
|
|
378
|
+
// ============================================================================
|
|
379
|
+
|
|
380
|
+
test('addSession adds new session to empty data', t => {
|
|
381
|
+
const session = createMockSession();
|
|
382
|
+
|
|
383
|
+
addSession(session);
|
|
384
|
+
|
|
385
|
+
const data = readUsageData();
|
|
386
|
+
t.is(data.sessions.length, 1);
|
|
387
|
+
t.is(data.sessions[0]!.id, session.id);
|
|
388
|
+
t.is(data.totalLifetime, session.tokens.total);
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
test('addSession adds session to beginning of list', t => {
|
|
392
|
+
const session1 = createMockSession('provider1', 'model1', 1000);
|
|
393
|
+
const session2 = createMockSession('provider2', 'model2', 2000);
|
|
394
|
+
|
|
395
|
+
addSession(session1);
|
|
396
|
+
addSession(session2);
|
|
397
|
+
|
|
398
|
+
const data = readUsageData();
|
|
399
|
+
t.is(data.sessions.length, 2);
|
|
400
|
+
t.is(data.sessions[0]!.id, session2.id); // Most recent first
|
|
401
|
+
t.is(data.sessions[1]!.id, session1.id);
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
test('addSession updates total lifetime tokens', t => {
|
|
405
|
+
const session1 = createMockSession('provider1', 'model1', 1000);
|
|
406
|
+
const session2 = createMockSession('provider2', 'model2', 2000);
|
|
407
|
+
|
|
408
|
+
addSession(session1);
|
|
409
|
+
addSession(session2);
|
|
410
|
+
|
|
411
|
+
const data = readUsageData();
|
|
412
|
+
t.is(data.totalLifetime, 3000);
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
test('addSession limits sessions to MAX_SESSIONS (100)', t => {
|
|
416
|
+
// Add 101 sessions
|
|
417
|
+
for (let i = 0; i < 101; i++) {
|
|
418
|
+
const session = createMockSession(`provider-${i}`, `model-${i}`, 100);
|
|
419
|
+
addSession(session);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
const data = readUsageData();
|
|
423
|
+
t.is(data.sessions.length, 100); // Should be limited to 100
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
test('addSession creates daily aggregate', t => {
|
|
427
|
+
const session = createMockSession();
|
|
428
|
+
|
|
429
|
+
addSession(session);
|
|
430
|
+
|
|
431
|
+
const data = readUsageData();
|
|
432
|
+
t.is(data.dailyAggregates.length, 1);
|
|
433
|
+
|
|
434
|
+
const today = new Date().toISOString().split('T')[0];
|
|
435
|
+
const aggregate = data.dailyAggregates.find(agg => agg.date === today);
|
|
436
|
+
|
|
437
|
+
t.truthy(aggregate);
|
|
438
|
+
t.is(aggregate!.sessions, 1);
|
|
439
|
+
t.is(aggregate!.totalTokens, session.tokens.total);
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
test('addSession updates existing daily aggregate', t => {
|
|
443
|
+
const session1 = createMockSession('provider1', 'model1', 1000);
|
|
444
|
+
const session2 = createMockSession('provider2', 'model2', 2000);
|
|
445
|
+
|
|
446
|
+
addSession(session1);
|
|
447
|
+
addSession(session2);
|
|
448
|
+
|
|
449
|
+
const data = readUsageData();
|
|
450
|
+
const today = new Date().toISOString().split('T')[0];
|
|
451
|
+
const aggregate = data.dailyAggregates.find(agg => agg.date === today);
|
|
452
|
+
|
|
453
|
+
t.truthy(aggregate);
|
|
454
|
+
t.is(aggregate!.sessions, 2);
|
|
455
|
+
t.is(aggregate!.totalTokens, 3000);
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
test('addSession tracks provider stats in daily aggregate', t => {
|
|
459
|
+
const session1 = createMockSession('openai', 'gpt-4', 1000);
|
|
460
|
+
const session2 = createMockSession('openai', 'gpt-3.5', 2000);
|
|
461
|
+
const session3 = createMockSession('anthropic', 'claude', 3000);
|
|
462
|
+
|
|
463
|
+
addSession(session1);
|
|
464
|
+
addSession(session2);
|
|
465
|
+
addSession(session3);
|
|
466
|
+
|
|
467
|
+
const data = readUsageData();
|
|
468
|
+
const today = new Date().toISOString().split('T')[0];
|
|
469
|
+
const aggregate = data.dailyAggregates.find(agg => agg.date === today);
|
|
470
|
+
|
|
471
|
+
t.truthy(aggregate);
|
|
472
|
+
t.is(aggregate!.providers.openai, 3000); // 1000 + 2000
|
|
473
|
+
t.is(aggregate!.providers.anthropic, 3000);
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
test('addSession tracks model stats in daily aggregate', t => {
|
|
477
|
+
const session1 = createMockSession('openai', 'gpt-4', 1000);
|
|
478
|
+
const session2 = createMockSession('openai', 'gpt-4', 2000);
|
|
479
|
+
const session3 = createMockSession('openai', 'gpt-3.5', 3000);
|
|
480
|
+
|
|
481
|
+
addSession(session1);
|
|
482
|
+
addSession(session2);
|
|
483
|
+
addSession(session3);
|
|
484
|
+
|
|
485
|
+
const data = readUsageData();
|
|
486
|
+
const today = new Date().toISOString().split('T')[0];
|
|
487
|
+
const aggregate = data.dailyAggregates.find(agg => agg.date === today);
|
|
488
|
+
|
|
489
|
+
t.truthy(aggregate);
|
|
490
|
+
t.is(aggregate!.models['gpt-4'], 3000); // 1000 + 2000
|
|
491
|
+
t.is(aggregate!.models['gpt-3.5'], 3000);
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
test('addSession limits daily aggregates to MAX_DAILY_AGGREGATES (30)', t => {
|
|
495
|
+
// Create sessions with different dates
|
|
496
|
+
for (let i = 0; i < 35; i++) {
|
|
497
|
+
const session = createMockSession();
|
|
498
|
+
// Modify timestamp to be i days ago
|
|
499
|
+
session.timestamp = Date.now() - i * 24 * 60 * 60 * 1000;
|
|
500
|
+
addSession(session);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
const data = readUsageData();
|
|
504
|
+
t.is(data.dailyAggregates.length, 30); // Should be limited to 30
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
// ============================================================================
|
|
508
|
+
// getTodayAggregate Tests
|
|
509
|
+
// ============================================================================
|
|
510
|
+
|
|
511
|
+
test('getTodayAggregate returns null when no data exists', t => {
|
|
512
|
+
const aggregate = getTodayAggregate();
|
|
513
|
+
t.is(aggregate, null);
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
test('getTodayAggregate returns null when no sessions today', t => {
|
|
517
|
+
// Add a session from yesterday
|
|
518
|
+
const session = createMockSession();
|
|
519
|
+
session.timestamp = Date.now() - 24 * 60 * 60 * 1000;
|
|
520
|
+
addSession(session);
|
|
521
|
+
|
|
522
|
+
const aggregate = getTodayAggregate();
|
|
523
|
+
t.is(aggregate, null);
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
test('getTodayAggregate returns today aggregate', t => {
|
|
527
|
+
const session = createMockSession('openai', 'gpt-4', 1000);
|
|
528
|
+
addSession(session);
|
|
529
|
+
|
|
530
|
+
const aggregate = getTodayAggregate();
|
|
531
|
+
|
|
532
|
+
t.truthy(aggregate);
|
|
533
|
+
t.is(aggregate!.sessions, 1);
|
|
534
|
+
t.is(aggregate!.totalTokens, 1000);
|
|
535
|
+
t.is(aggregate!.date, new Date().toISOString().split('T')[0]);
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
// ============================================================================
|
|
539
|
+
// getLastNDaysAggregate Tests
|
|
540
|
+
// ============================================================================
|
|
541
|
+
|
|
542
|
+
test('getLastNDaysAggregate returns zero when no data exists', t => {
|
|
543
|
+
const result = getLastNDaysAggregate(7);
|
|
544
|
+
|
|
545
|
+
t.is(result.totalTokens, 0);
|
|
546
|
+
t.is(result.totalSessions, 0);
|
|
547
|
+
t.is(result.avgTokensPerDay, 0);
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
test('getLastNDaysAggregate calculates totals for last 7 days', t => {
|
|
551
|
+
// Add sessions for the past 5 days
|
|
552
|
+
for (let i = 0; i < 5; i++) {
|
|
553
|
+
const session = createMockSession('openai', 'gpt-4', 1000);
|
|
554
|
+
session.timestamp = Date.now() - i * 24 * 60 * 60 * 1000;
|
|
555
|
+
addSession(session);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
const result = getLastNDaysAggregate(7);
|
|
559
|
+
|
|
560
|
+
t.is(result.totalTokens, 5000);
|
|
561
|
+
t.is(result.totalSessions, 5);
|
|
562
|
+
t.is(result.avgTokensPerDay, Math.round(5000 / 7));
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
test('getLastNDaysAggregate filters out older sessions', t => {
|
|
566
|
+
// Add 3 sessions within last 7 days
|
|
567
|
+
for (let i = 0; i < 3; i++) {
|
|
568
|
+
const session = createMockSession('openai', 'gpt-4', 1000);
|
|
569
|
+
session.timestamp = Date.now() - i * 24 * 60 * 60 * 1000;
|
|
570
|
+
addSession(session);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// Add 2 sessions older than 7 days
|
|
574
|
+
for (let i = 8; i < 10; i++) {
|
|
575
|
+
const session = createMockSession('openai', 'gpt-4', 1000);
|
|
576
|
+
session.timestamp = Date.now() - i * 24 * 60 * 60 * 1000;
|
|
577
|
+
addSession(session);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
const result = getLastNDaysAggregate(7);
|
|
581
|
+
|
|
582
|
+
// Should only count the 3 recent sessions
|
|
583
|
+
t.is(result.totalTokens, 3000);
|
|
584
|
+
t.is(result.totalSessions, 3);
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
test('getLastNDaysAggregate handles different day ranges', t => {
|
|
588
|
+
// Add sessions for the past 10 days
|
|
589
|
+
for (let i = 0; i < 10; i++) {
|
|
590
|
+
const session = createMockSession('openai', 'gpt-4', 1000);
|
|
591
|
+
session.timestamp = Date.now() - i * 24 * 60 * 60 * 1000;
|
|
592
|
+
addSession(session);
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
const result7 = getLastNDaysAggregate(7);
|
|
596
|
+
const result30 = getLastNDaysAggregate(30);
|
|
597
|
+
|
|
598
|
+
// 7 days should include days 0-6 (7 sessions)
|
|
599
|
+
// But depending on timing, might include day 7 too if timestamps align
|
|
600
|
+
t.true(result7.totalTokens >= 7000 && result7.totalTokens <= 8000);
|
|
601
|
+
t.is(result30.totalTokens, 10000);
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
test('getLastNDaysAggregate calculates average correctly', t => {
|
|
605
|
+
// Add 10 sessions over 5 days
|
|
606
|
+
for (let i = 0; i < 10; i++) {
|
|
607
|
+
const session = createMockSession('openai', 'gpt-4', 500);
|
|
608
|
+
session.timestamp = Date.now() - (i % 5) * 24 * 60 * 60 * 1000;
|
|
609
|
+
addSession(session);
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
const result = getLastNDaysAggregate(7);
|
|
613
|
+
|
|
614
|
+
// 10 sessions * 500 tokens = 5000 total
|
|
615
|
+
// Average over 7 days = 5000 / 7 ≈ 714
|
|
616
|
+
t.is(result.totalTokens, 5000);
|
|
617
|
+
t.is(result.avgTokensPerDay, Math.round(5000 / 7));
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
// ============================================================================
|
|
621
|
+
// clearUsageData Tests
|
|
622
|
+
// ============================================================================
|
|
623
|
+
|
|
624
|
+
test('clearUsageData removes usage file', t => {
|
|
625
|
+
// Add some data first
|
|
626
|
+
const session = createMockSession();
|
|
627
|
+
addSession(session);
|
|
628
|
+
|
|
629
|
+
// Verify data exists
|
|
630
|
+
let data = readUsageData();
|
|
631
|
+
t.is(data.sessions.length, 1);
|
|
632
|
+
|
|
633
|
+
// Clear data
|
|
634
|
+
clearUsageData();
|
|
635
|
+
|
|
636
|
+
// Verify data is empty
|
|
637
|
+
data = readUsageData();
|
|
638
|
+
t.is(data.sessions.length, 0);
|
|
639
|
+
t.is(data.totalLifetime, 0);
|
|
640
|
+
});
|
|
641
|
+
|
|
642
|
+
test('clearUsageData handles non-existent file', t => {
|
|
643
|
+
// Should not throw even if file doesn't exist
|
|
644
|
+
t.notThrows(() => clearUsageData());
|
|
645
|
+
});
|
|
646
|
+
|
|
647
|
+
test('clearUsageData is idempotent', t => {
|
|
648
|
+
// Add and clear data
|
|
649
|
+
addSession(createMockSession());
|
|
650
|
+
clearUsageData();
|
|
651
|
+
|
|
652
|
+
// Clear again
|
|
653
|
+
t.notThrows(() => clearUsageData());
|
|
654
|
+
|
|
655
|
+
// Verify still empty
|
|
656
|
+
const data = readUsageData();
|
|
657
|
+
t.is(data.sessions.length, 0);
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
// ============================================================================
|
|
661
|
+
// Integration Tests
|
|
662
|
+
// ============================================================================
|
|
663
|
+
|
|
664
|
+
test('complete usage tracking flow', t => {
|
|
665
|
+
// Start with empty data
|
|
666
|
+
let data = readUsageData();
|
|
667
|
+
t.is(data.sessions.length, 0);
|
|
668
|
+
|
|
669
|
+
// Add multiple sessions
|
|
670
|
+
const session1 = createMockSession('openai', 'gpt-4', 1000);
|
|
671
|
+
const session2 = createMockSession('anthropic', 'claude', 2000);
|
|
672
|
+
const session3 = createMockSession('openai', 'gpt-3.5', 1500);
|
|
673
|
+
|
|
674
|
+
addSession(session1);
|
|
675
|
+
addSession(session2);
|
|
676
|
+
addSession(session3);
|
|
677
|
+
|
|
678
|
+
// Verify sessions added
|
|
679
|
+
data = readUsageData();
|
|
680
|
+
t.is(data.sessions.length, 3);
|
|
681
|
+
t.is(data.totalLifetime, 4500);
|
|
682
|
+
|
|
683
|
+
// Verify daily aggregate
|
|
684
|
+
const today = new Date().toISOString().split('T')[0];
|
|
685
|
+
const aggregate = data.dailyAggregates.find(agg => agg.date === today);
|
|
686
|
+
t.truthy(aggregate);
|
|
687
|
+
t.is(aggregate!.sessions, 3);
|
|
688
|
+
t.is(aggregate!.totalTokens, 4500);
|
|
689
|
+
|
|
690
|
+
// Verify getTodayAggregate
|
|
691
|
+
const todayAgg = getTodayAggregate();
|
|
692
|
+
t.is(todayAgg!.totalTokens, 4500);
|
|
693
|
+
|
|
694
|
+
// Verify getLastNDaysAggregate
|
|
695
|
+
const weekStats = getLastNDaysAggregate(7);
|
|
696
|
+
t.is(weekStats.totalTokens, 4500);
|
|
697
|
+
t.is(weekStats.totalSessions, 3);
|
|
698
|
+
|
|
699
|
+
// Clear and verify
|
|
700
|
+
clearUsageData();
|
|
701
|
+
data = readUsageData();
|
|
702
|
+
t.is(data.sessions.length, 0);
|
|
703
|
+
});
|