codevault 1.8.3 → 1.8.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -5
- package/dist/chunking/file-grouper.d.ts +1 -1
- package/dist/chunking/file-grouper.d.ts.map +1 -1
- package/dist/chunking/file-grouper.js +3 -3
- package/dist/chunking/file-grouper.js.map +1 -1
- package/dist/chunking/semantic-chunker.d.ts +1 -1
- package/dist/chunking/semantic-chunker.d.ts.map +1 -1
- package/dist/chunking/token-counter.d.ts +1 -1
- package/dist/chunking/token-counter.d.ts.map +1 -1
- package/dist/chunking/token-counter.js +16 -10
- package/dist/chunking/token-counter.js.map +1 -1
- package/dist/cli/commands/ask-cmd.js +15 -15
- package/dist/cli/commands/ask-cmd.js.map +1 -1
- package/dist/cli/commands/chat-cmd.d.ts.map +1 -1
- package/dist/cli/commands/chat-cmd.js +40 -40
- package/dist/cli/commands/chat-cmd.js.map +1 -1
- package/dist/cli/commands/config-cmd.d.ts.map +1 -1
- package/dist/cli/commands/config-cmd.js +61 -52
- package/dist/cli/commands/config-cmd.js.map +1 -1
- package/dist/cli/commands/context.d.ts.map +1 -1
- package/dist/cli/commands/context.js +20 -11
- package/dist/cli/commands/context.js.map +1 -1
- package/dist/cli/commands/index-cmd.d.ts.map +1 -1
- package/dist/cli/commands/index-cmd.js +109 -85
- package/dist/cli/commands/index-cmd.js.map +1 -1
- package/dist/cli/commands/info-cmd.d.ts.map +1 -1
- package/dist/cli/commands/info-cmd.js +12 -11
- package/dist/cli/commands/info-cmd.js.map +1 -1
- package/dist/cli/commands/interactive-config.d.ts.map +1 -1
- package/dist/cli/commands/interactive-config.js +60 -20
- package/dist/cli/commands/interactive-config.js.map +1 -1
- package/dist/cli/commands/search-cmd.d.ts.map +1 -1
- package/dist/cli/commands/search-cmd.js +22 -11
- package/dist/cli/commands/search-cmd.js.map +1 -1
- package/dist/cli/commands/search-with-code-cmd.d.ts.map +1 -1
- package/dist/cli/commands/search-with-code-cmd.js +25 -16
- package/dist/cli/commands/search-with-code-cmd.js.map +1 -1
- package/dist/cli/commands/update-cmd.d.ts.map +1 -1
- package/dist/cli/commands/update-cmd.js +16 -7
- package/dist/cli/commands/update-cmd.js.map +1 -1
- package/dist/cli/commands/watch-cmd.d.ts.map +1 -1
- package/dist/cli/commands/watch-cmd.js +21 -11
- package/dist/cli/commands/watch-cmd.js.map +1 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/utils.d.ts +56 -0
- package/dist/cli/utils.d.ts.map +1 -0
- package/dist/cli/utils.js +98 -0
- package/dist/cli/utils.js.map +1 -0
- package/dist/cli.js +0 -0
- package/dist/codemap/io.js.map +1 -1
- package/dist/config/constants.d.ts +4 -0
- package/dist/config/constants.d.ts.map +1 -1
- package/dist/config/constants.js +2 -0
- package/dist/config/constants.js.map +1 -1
- package/dist/config/loader.js.map +1 -1
- package/dist/context/packs.d.ts +2 -2
- package/dist/context/packs.d.ts.map +1 -1
- package/dist/context/packs.js +7 -4
- package/dist/context/packs.js.map +1 -1
- package/dist/core/IndexerEngine.d.ts +2 -0
- package/dist/core/IndexerEngine.d.ts.map +1 -1
- package/dist/core/IndexerEngine.js +34 -26
- package/dist/core/IndexerEngine.js.map +1 -1
- package/dist/core/SearchService.d.ts +2 -1
- package/dist/core/SearchService.d.ts.map +1 -1
- package/dist/core/SearchService.js +25 -18
- package/dist/core/SearchService.js.map +1 -1
- package/dist/core/batch-indexer.d.ts +4 -3
- package/dist/core/batch-indexer.d.ts.map +1 -1
- package/dist/core/batch-indexer.js +32 -35
- package/dist/core/batch-indexer.js.map +1 -1
- package/dist/core/indexing/FileProcessor.d.ts +1 -0
- package/dist/core/indexing/FileProcessor.d.ts.map +1 -1
- package/dist/core/indexing/FileProcessor.js +32 -9
- package/dist/core/indexing/FileProcessor.js.map +1 -1
- package/dist/core/indexing/IndexContext.d.ts +6 -4
- package/dist/core/indexing/IndexContext.d.ts.map +1 -1
- package/dist/core/indexing/IndexContext.js +3 -3
- package/dist/core/indexing/IndexContext.js.map +1 -1
- package/dist/core/indexing/IndexFinalizationStage.d.ts +6 -1
- package/dist/core/indexing/IndexFinalizationStage.d.ts.map +1 -1
- package/dist/core/indexing/IndexFinalizationStage.js +22 -3
- package/dist/core/indexing/IndexFinalizationStage.js.map +1 -1
- package/dist/core/indexing/IndexState.d.ts +3 -8
- package/dist/core/indexing/IndexState.d.ts.map +1 -1
- package/dist/core/indexing/IndexState.js.map +1 -1
- package/dist/core/indexing/PersistManager.d.ts +1 -1
- package/dist/core/indexing/PersistManager.d.ts.map +1 -1
- package/dist/core/indexing/PersistManager.js +17 -17
- package/dist/core/indexing/PersistManager.js.map +1 -1
- package/dist/core/indexing/chunk-pipeline.d.ts +33 -7
- package/dist/core/indexing/chunk-pipeline.d.ts.map +1 -1
- package/dist/core/indexing/chunk-pipeline.js +20 -8
- package/dist/core/indexing/chunk-pipeline.js.map +1 -1
- package/dist/core/metadata.d.ts +1 -1
- package/dist/core/metadata.d.ts.map +1 -1
- package/dist/core/metadata.js +1 -1
- package/dist/core/metadata.js.map +1 -1
- package/dist/core/search/CandidateRetriever.d.ts +1 -1
- package/dist/core/search/CandidateRetriever.d.ts.map +1 -1
- package/dist/core/search/CandidateRetriever.js +7 -5
- package/dist/core/search/CandidateRetriever.js.map +1 -1
- package/dist/core/search/HybridFusion.d.ts +1 -2
- package/dist/core/search/HybridFusion.d.ts.map +1 -1
- package/dist/core/search/HybridFusion.js +5 -17
- package/dist/core/search/HybridFusion.js.map +1 -1
- package/dist/core/search/ResultMapper.d.ts +0 -1
- package/dist/core/search/ResultMapper.d.ts.map +1 -1
- package/dist/core/search/ResultMapper.js +3 -14
- package/dist/core/search/ResultMapper.js.map +1 -1
- package/dist/core/search/SearchContextManager.d.ts +4 -1
- package/dist/core/search/SearchContextManager.d.ts.map +1 -1
- package/dist/core/search/SearchContextManager.js +19 -6
- package/dist/core/search/SearchContextManager.js.map +1 -1
- package/dist/core/search.d.ts.map +1 -1
- package/dist/core/search.js +9 -4
- package/dist/core/search.js.map +1 -1
- package/dist/core/types.d.ts +2 -2
- package/dist/core/types.d.ts.map +1 -1
- package/dist/database/db.d.ts +14 -14
- package/dist/database/db.d.ts.map +1 -1
- package/dist/database/db.js +14 -14
- package/dist/database/db.js.map +1 -1
- package/dist/indexer/merkle.js +1 -1
- package/dist/indexer/merkle.js.map +1 -1
- package/dist/languages/rules.d.ts +2 -1
- package/dist/languages/rules.d.ts.map +1 -1
- package/dist/languages/rules.js +14 -5
- package/dist/languages/rules.js.map +1 -1
- package/dist/languages/tree-sitter-loader.d.ts +24 -24
- package/dist/languages/tree-sitter-loader.d.ts.map +1 -1
- package/dist/languages/tree-sitter-loader.js +14 -10
- package/dist/languages/tree-sitter-loader.js.map +1 -1
- package/dist/mcp/handlers/context.d.ts +5 -11
- package/dist/mcp/handlers/context.d.ts.map +1 -1
- package/dist/mcp/handlers/context.js.map +1 -1
- package/dist/mcp/handlers/project.d.ts +9 -27
- package/dist/mcp/handlers/project.d.ts.map +1 -1
- package/dist/mcp/handlers/search.d.ts +24 -18
- package/dist/mcp/handlers/search.d.ts.map +1 -1
- package/dist/mcp/handlers/search.js +8 -2
- package/dist/mcp/handlers/search.js.map +1 -1
- package/dist/mcp/handlers/synthesis.d.ts +15 -11
- package/dist/mcp/handlers/synthesis.d.ts.map +1 -1
- package/dist/mcp/handlers/synthesis.js +4 -1
- package/dist/mcp/handlers/synthesis.js.map +1 -1
- package/dist/mcp/schemas.d.ts +2 -2
- package/dist/mcp/tools/ask-codebase.d.ts +10 -28
- package/dist/mcp/tools/ask-codebase.d.ts.map +1 -1
- package/dist/mcp/tools/ask-codebase.js +1 -1
- package/dist/mcp/tools/ask-codebase.js.map +1 -1
- package/dist/mcp/tools/use-context-pack.d.ts +14 -23
- package/dist/mcp/tools/use-context-pack.d.ts.map +1 -1
- package/dist/mcp/tools/use-context-pack.js +4 -3
- package/dist/mcp/tools/use-context-pack.js.map +1 -1
- package/dist/mcp-server.d.ts +1 -0
- package/dist/mcp-server.d.ts.map +1 -1
- package/dist/mcp-server.js +39 -24
- package/dist/mcp-server.js.map +1 -1
- package/dist/providers/base.d.ts +3 -2
- package/dist/providers/base.d.ts.map +1 -1
- package/dist/providers/base.js +3 -10
- package/dist/providers/base.js.map +1 -1
- package/dist/providers/chat-llm.d.ts +3 -2
- package/dist/providers/chat-llm.d.ts.map +1 -1
- package/dist/providers/chat-llm.js +13 -9
- package/dist/providers/chat-llm.js.map +1 -1
- package/dist/providers/mock.d.ts.map +1 -1
- package/dist/providers/mock.js +6 -5
- package/dist/providers/mock.js.map +1 -1
- package/dist/providers/openai.d.ts +2 -1
- package/dist/providers/openai.d.ts.map +1 -1
- package/dist/providers/openai.js +15 -19
- package/dist/providers/openai.js.map +1 -1
- package/dist/providers/token-counter.d.ts.map +1 -1
- package/dist/providers/token-counter.js +11 -3
- package/dist/providers/token-counter.js.map +1 -1
- package/dist/ranking/api-reranker.d.ts +1 -1
- package/dist/ranking/api-reranker.d.ts.map +1 -1
- package/dist/ranking/api-reranker.js +50 -13
- package/dist/ranking/api-reranker.js.map +1 -1
- package/dist/ranking/symbol-boost.d.ts.map +1 -1
- package/dist/ranking/symbol-boost.js +4 -11
- package/dist/ranking/symbol-boost.js.map +1 -1
- package/dist/search/bm25.d.ts +10 -0
- package/dist/search/bm25.d.ts.map +1 -1
- package/dist/search/bm25.js +16 -0
- package/dist/search/bm25.js.map +1 -1
- package/dist/search/hybrid.js.map +1 -1
- package/dist/search/scope.d.ts.map +1 -1
- package/dist/search/scope.js +3 -2
- package/dist/search/scope.js.map +1 -1
- package/dist/storage/encrypted-chunks.d.ts +3 -0
- package/dist/storage/encrypted-chunks.d.ts.map +1 -1
- package/dist/storage/encrypted-chunks.js +126 -47
- package/dist/storage/encrypted-chunks.js.map +1 -1
- package/dist/symbols/extract.d.ts.map +1 -1
- package/dist/symbols/extract.js +3 -2
- package/dist/symbols/extract.js.map +1 -1
- package/dist/symbols/graph.d.ts.map +1 -1
- package/dist/symbols/graph.js +14 -8
- package/dist/symbols/graph.js.map +1 -1
- package/dist/synthesis/conversational-synthesizer.d.ts +2 -1
- package/dist/synthesis/conversational-synthesizer.d.ts.map +1 -1
- package/dist/synthesis/conversational-synthesizer.js +6 -1
- package/dist/synthesis/conversational-synthesizer.js.map +1 -1
- package/dist/synthesis/markdown-formatter.d.ts.map +1 -1
- package/dist/synthesis/markdown-formatter.js +1 -1
- package/dist/synthesis/markdown-formatter.js.map +1 -1
- package/dist/synthesis/prompt-builder.d.ts.map +1 -1
- package/dist/synthesis/prompt-builder.js +42 -15
- package/dist/synthesis/prompt-builder.js.map +1 -1
- package/dist/synthesis/synthesizer.d.ts.map +1 -1
- package/dist/synthesis/synthesizer.js +23 -10
- package/dist/synthesis/synthesizer.js.map +1 -1
- package/dist/tests/api-reranker.test.d.ts +2 -0
- package/dist/tests/api-reranker.test.d.ts.map +1 -0
- package/dist/tests/api-reranker.test.js +575 -0
- package/dist/tests/api-reranker.test.js.map +1 -0
- package/dist/tests/bm25.test.d.ts +2 -0
- package/dist/tests/bm25.test.d.ts.map +1 -0
- package/dist/tests/bm25.test.js +340 -0
- package/dist/tests/bm25.test.js.map +1 -0
- package/dist/tests/chunking/file-grouper.test.d.ts +2 -0
- package/dist/tests/chunking/file-grouper.test.d.ts.map +1 -0
- package/dist/tests/chunking/file-grouper.test.js +495 -0
- package/dist/tests/chunking/file-grouper.test.js.map +1 -0
- package/dist/tests/chunking/semantic-chunker.test.d.ts +2 -0
- package/dist/tests/chunking/semantic-chunker.test.d.ts.map +1 -0
- package/dist/tests/chunking/semantic-chunker.test.js +509 -0
- package/dist/tests/chunking/semantic-chunker.test.js.map +1 -0
- package/dist/tests/chunking/token-counter.test.d.ts +2 -0
- package/dist/tests/chunking/token-counter.test.d.ts.map +1 -0
- package/dist/tests/chunking/token-counter.test.js +441 -0
- package/dist/tests/chunking/token-counter.test.js.map +1 -0
- package/dist/tests/cli/ask-cmd.test.d.ts +2 -0
- package/dist/tests/cli/ask-cmd.test.d.ts.map +1 -0
- package/dist/tests/cli/ask-cmd.test.js +152 -0
- package/dist/tests/cli/ask-cmd.test.js.map +1 -0
- package/dist/tests/cli/chat-cmd.test.d.ts +2 -0
- package/dist/tests/cli/chat-cmd.test.d.ts.map +1 -0
- package/dist/tests/cli/chat-cmd.test.js +118 -0
- package/dist/tests/cli/chat-cmd.test.js.map +1 -0
- package/dist/tests/cli/config-cmd.test.d.ts +2 -0
- package/dist/tests/cli/config-cmd.test.d.ts.map +1 -0
- package/dist/tests/cli/config-cmd.test.js +226 -0
- package/dist/tests/cli/config-cmd.test.js.map +1 -0
- package/dist/tests/cli/context.test.d.ts +2 -0
- package/dist/tests/cli/context.test.d.ts.map +1 -0
- package/dist/tests/cli/context.test.js +158 -0
- package/dist/tests/cli/context.test.js.map +1 -0
- package/dist/tests/cli/index-cmd.test.d.ts +2 -0
- package/dist/tests/cli/index-cmd.test.d.ts.map +1 -0
- package/dist/tests/cli/index-cmd.test.js +89 -0
- package/dist/tests/cli/index-cmd.test.js.map +1 -0
- package/dist/tests/cli/index.test.d.ts +2 -0
- package/dist/tests/cli/index.test.d.ts.map +1 -0
- package/dist/tests/cli/index.test.js +167 -0
- package/dist/tests/cli/index.test.js.map +1 -0
- package/dist/tests/cli/info-cmd.test.d.ts +2 -0
- package/dist/tests/cli/info-cmd.test.d.ts.map +1 -0
- package/dist/tests/cli/info-cmd.test.js +47 -0
- package/dist/tests/cli/info-cmd.test.js.map +1 -0
- package/dist/tests/cli/interactive-config.test.d.ts +2 -0
- package/dist/tests/cli/interactive-config.test.d.ts.map +1 -0
- package/dist/tests/cli/interactive-config.test.js +30 -0
- package/dist/tests/cli/interactive-config.test.js.map +1 -0
- package/dist/tests/cli/mcp-cmd.test.d.ts +2 -0
- package/dist/tests/cli/mcp-cmd.test.d.ts.map +1 -0
- package/dist/tests/cli/mcp-cmd.test.js +47 -0
- package/dist/tests/cli/mcp-cmd.test.js.map +1 -0
- package/dist/tests/cli/search-cmd.test.d.ts +2 -0
- package/dist/tests/cli/search-cmd.test.d.ts.map +1 -0
- package/dist/tests/cli/search-cmd.test.js +120 -0
- package/dist/tests/cli/search-cmd.test.js.map +1 -0
- package/dist/tests/cli/search-with-code-cmd.test.d.ts +2 -0
- package/dist/tests/cli/search-with-code-cmd.test.d.ts.map +1 -0
- package/dist/tests/cli/search-with-code-cmd.test.js +140 -0
- package/dist/tests/cli/search-with-code-cmd.test.js.map +1 -0
- package/dist/tests/cli/update-cmd.test.d.ts +2 -0
- package/dist/tests/cli/update-cmd.test.d.ts.map +1 -0
- package/dist/tests/cli/update-cmd.test.js +75 -0
- package/dist/tests/cli/update-cmd.test.js.map +1 -0
- package/dist/tests/cli/utils.test.d.ts +2 -0
- package/dist/tests/cli/utils.test.d.ts.map +1 -0
- package/dist/tests/cli/utils.test.js +119 -0
- package/dist/tests/cli/utils.test.js.map +1 -0
- package/dist/tests/cli/watch-cmd.test.d.ts +2 -0
- package/dist/tests/cli/watch-cmd.test.d.ts.map +1 -0
- package/dist/tests/cli/watch-cmd.test.js +84 -0
- package/dist/tests/cli/watch-cmd.test.js.map +1 -0
- package/dist/tests/cli-ui.test.d.ts +2 -0
- package/dist/tests/cli-ui.test.d.ts.map +1 -0
- package/dist/tests/cli-ui.test.js +608 -0
- package/dist/tests/cli-ui.test.js.map +1 -0
- package/dist/tests/codemap-io.test.d.ts +2 -0
- package/dist/tests/codemap-io.test.d.ts.map +1 -0
- package/dist/tests/codemap-io.test.js +992 -0
- package/dist/tests/codemap-io.test.js.map +1 -0
- package/dist/tests/config/apply-env.test.d.ts +2 -0
- package/dist/tests/config/apply-env.test.d.ts.map +1 -0
- package/dist/tests/config/apply-env.test.js +717 -0
- package/dist/tests/config/apply-env.test.js.map +1 -0
- package/dist/tests/config/constants.test.d.ts +2 -0
- package/dist/tests/config/constants.test.d.ts.map +1 -0
- package/dist/tests/config/constants.test.js +406 -0
- package/dist/tests/config/constants.test.js.map +1 -0
- package/dist/tests/config/loader.test.d.ts +2 -0
- package/dist/tests/config/loader.test.d.ts.map +1 -0
- package/dist/tests/config/loader.test.js +716 -0
- package/dist/tests/config/loader.test.js.map +1 -0
- package/dist/tests/config/resolver.test.d.ts +2 -0
- package/dist/tests/config/resolver.test.d.ts.map +1 -0
- package/dist/tests/config/resolver.test.js +402 -0
- package/dist/tests/config/resolver.test.js.map +1 -0
- package/dist/tests/config/types.test.d.ts +2 -0
- package/dist/tests/config/types.test.d.ts.map +1 -0
- package/dist/tests/config/types.test.js +460 -0
- package/dist/tests/config/types.test.js.map +1 -0
- package/dist/tests/context-packs.test.d.ts +2 -0
- package/dist/tests/context-packs.test.d.ts.map +1 -0
- package/dist/tests/context-packs.test.js +826 -0
- package/dist/tests/context-packs.test.js.map +1 -0
- package/dist/tests/conversational-synthesizer.test.d.ts +2 -0
- package/dist/tests/conversational-synthesizer.test.d.ts.map +1 -0
- package/dist/tests/conversational-synthesizer.test.js +595 -0
- package/dist/tests/conversational-synthesizer.test.js.map +1 -0
- package/dist/tests/database.test.d.ts +2 -0
- package/dist/tests/database.test.d.ts.map +1 -0
- package/dist/tests/database.test.js +965 -0
- package/dist/tests/database.test.js.map +1 -0
- package/dist/tests/encrypted-chunks.test.d.ts +2 -0
- package/dist/tests/encrypted-chunks.test.d.ts.map +1 -0
- package/dist/tests/encrypted-chunks.test.js +1470 -0
- package/dist/tests/encrypted-chunks.test.js.map +1 -0
- package/dist/tests/hybrid.test.d.ts +2 -0
- package/dist/tests/hybrid.test.d.ts.map +1 -0
- package/dist/tests/hybrid.test.js +456 -0
- package/dist/tests/hybrid.test.js.map +1 -0
- package/dist/tests/indexer/ChangeQueue.test.d.ts +12 -0
- package/dist/tests/indexer/ChangeQueue.test.d.ts.map +1 -0
- package/dist/tests/indexer/ChangeQueue.test.js +441 -0
- package/dist/tests/indexer/ChangeQueue.test.js.map +1 -0
- package/dist/tests/indexer/ProviderManager.test.d.ts +12 -0
- package/dist/tests/indexer/ProviderManager.test.d.ts.map +1 -0
- package/dist/tests/indexer/ProviderManager.test.js +290 -0
- package/dist/tests/indexer/ProviderManager.test.js.map +1 -0
- package/dist/tests/indexer/WatchService.test.d.ts +14 -0
- package/dist/tests/indexer/WatchService.test.d.ts.map +1 -0
- package/dist/tests/indexer/WatchService.test.js +667 -0
- package/dist/tests/indexer/WatchService.test.js.map +1 -0
- package/dist/tests/indexer/merkle.test.d.ts +11 -0
- package/dist/tests/indexer/merkle.test.d.ts.map +1 -0
- package/dist/tests/indexer/merkle.test.js +497 -0
- package/dist/tests/indexer/merkle.test.js.map +1 -0
- package/dist/tests/indexer/update.test.d.ts +10 -0
- package/dist/tests/indexer/update.test.d.ts.map +1 -0
- package/dist/tests/indexer/update.test.js +317 -0
- package/dist/tests/indexer/update.test.js.map +1 -0
- package/dist/tests/indexer/watch.test.d.ts +8 -0
- package/dist/tests/indexer/watch.test.d.ts.map +1 -0
- package/dist/tests/indexer/watch.test.js +95 -0
- package/dist/tests/indexer/watch.test.js.map +1 -0
- package/dist/tests/integration/index-search.integration.test.js +6 -4
- package/dist/tests/integration/index-search.integration.test.js.map +1 -1
- package/dist/tests/languages.test.d.ts +2 -0
- package/dist/tests/languages.test.d.ts.map +1 -0
- package/dist/tests/languages.test.js +575 -0
- package/dist/tests/languages.test.js.map +1 -0
- package/dist/tests/logger-redaction.test.d.ts +2 -0
- package/dist/tests/logger-redaction.test.d.ts.map +1 -0
- package/dist/tests/logger-redaction.test.js +48 -0
- package/dist/tests/logger-redaction.test.js.map +1 -0
- package/dist/tests/logger.test.d.ts +2 -0
- package/dist/tests/logger.test.d.ts.map +1 -0
- package/dist/tests/logger.test.js +468 -0
- package/dist/tests/logger.test.js.map +1 -0
- package/dist/tests/markdown-formatter.test.d.ts +2 -0
- package/dist/tests/markdown-formatter.test.d.ts.map +1 -0
- package/dist/tests/markdown-formatter.test.js +453 -0
- package/dist/tests/markdown-formatter.test.js.map +1 -0
- package/dist/tests/mcp/tools/use-context-pack.test.d.ts +7 -0
- package/dist/tests/mcp/tools/use-context-pack.test.d.ts.map +1 -0
- package/dist/tests/mcp/tools/use-context-pack.test.js +505 -0
- package/dist/tests/mcp/tools/use-context-pack.test.js.map +1 -0
- package/dist/tests/mutex.test.d.ts +2 -0
- package/dist/tests/mutex.test.d.ts.map +1 -0
- package/dist/tests/mutex.test.js +489 -0
- package/dist/tests/mutex.test.js.map +1 -0
- package/dist/tests/path-helpers.test.d.ts +2 -0
- package/dist/tests/path-helpers.test.d.ts.map +1 -0
- package/dist/tests/path-helpers.test.js +332 -0
- package/dist/tests/path-helpers.test.js.map +1 -0
- package/dist/tests/prompt-builder.test.d.ts +2 -0
- package/dist/tests/prompt-builder.test.d.ts.map +1 -0
- package/dist/tests/prompt-builder.test.js +417 -0
- package/dist/tests/prompt-builder.test.js.map +1 -0
- package/dist/tests/providers/base.test.d.ts +2 -0
- package/dist/tests/providers/base.test.d.ts.map +1 -0
- package/dist/tests/providers/base.test.js +299 -0
- package/dist/tests/providers/base.test.js.map +1 -0
- package/dist/tests/providers/chat-llm.test.d.ts +2 -0
- package/dist/tests/providers/chat-llm.test.d.ts.map +1 -0
- package/dist/tests/providers/chat-llm.test.js +435 -0
- package/dist/tests/providers/chat-llm.test.js.map +1 -0
- package/dist/tests/providers/index.test.d.ts +2 -0
- package/dist/tests/providers/index.test.d.ts.map +1 -0
- package/dist/tests/providers/index.test.js +204 -0
- package/dist/tests/providers/index.test.js.map +1 -0
- package/dist/tests/providers/mock.test.d.ts +2 -0
- package/dist/tests/providers/mock.test.d.ts.map +1 -0
- package/dist/tests/providers/mock.test.js +225 -0
- package/dist/tests/providers/mock.test.js.map +1 -0
- package/dist/tests/providers/openai.test.d.ts +2 -0
- package/dist/tests/providers/openai.test.d.ts.map +1 -0
- package/dist/tests/providers/openai.test.js +408 -0
- package/dist/tests/providers/openai.test.js.map +1 -0
- package/dist/tests/providers/token-counter.test.d.ts +2 -0
- package/dist/tests/providers/token-counter.test.d.ts.map +1 -0
- package/dist/tests/providers/token-counter.test.js +247 -0
- package/dist/tests/providers/token-counter.test.js.map +1 -0
- package/dist/tests/rate-limiter.test.js +392 -1
- package/dist/tests/rate-limiter.test.js.map +1 -1
- package/dist/tests/scope.test.d.ts +2 -0
- package/dist/tests/scope.test.d.ts.map +1 -0
- package/dist/tests/scope.test.js +529 -0
- package/dist/tests/scope.test.js.map +1 -0
- package/dist/tests/search-normalization.test.js.map +1 -1
- package/dist/tests/semantic-chunker.test.js.map +1 -1
- package/dist/tests/simple-lru.test.js +377 -0
- package/dist/tests/simple-lru.test.js.map +1 -1
- package/dist/tests/symbol-boost.test.js +730 -10
- package/dist/tests/symbol-boost.test.js.map +1 -1
- package/dist/tests/symbols-extract.test.d.ts +2 -0
- package/dist/tests/symbols-extract.test.d.ts.map +1 -0
- package/dist/tests/symbols-extract.test.js +536 -0
- package/dist/tests/symbols-extract.test.js.map +1 -0
- package/dist/tests/symbols-graph.test.d.ts +2 -0
- package/dist/tests/symbols-graph.test.d.ts.map +1 -0
- package/dist/tests/symbols-graph.test.js +656 -0
- package/dist/tests/symbols-graph.test.js.map +1 -0
- package/dist/tests/synthesizer.test.d.ts +2 -0
- package/dist/tests/synthesizer.test.d.ts.map +1 -0
- package/dist/tests/synthesizer.test.js +381 -0
- package/dist/tests/synthesizer.test.js.map +1 -0
- package/dist/types/codemap.d.ts +2 -2
- package/dist/types/codemap.d.ts.map +1 -1
- package/dist/types/codemap.js +17 -9
- package/dist/types/codemap.js.map +1 -1
- package/dist/types/context-pack.d.ts +5 -5
- package/dist/types/context-pack.d.ts.map +1 -1
- package/dist/types/context-pack.js +6 -3
- package/dist/types/context-pack.js.map +1 -1
- package/dist/utils/cli-ui.d.ts +1 -1
- package/dist/utils/cli-ui.d.ts.map +1 -1
- package/dist/utils/cli-ui.js +26 -26
- package/dist/utils/cli-ui.js.map +1 -1
- package/dist/utils/indexer-with-progress.d.ts.map +1 -1
- package/dist/utils/indexer-with-progress.js +0 -6
- package/dist/utils/indexer-with-progress.js.map +1 -1
- package/dist/utils/logger.d.ts +10 -1
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +158 -6
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/mutex.d.ts +7 -2
- package/dist/utils/mutex.d.ts.map +1 -1
- package/dist/utils/mutex.js +35 -7
- package/dist/utils/mutex.js.map +1 -1
- package/dist/utils/path-helpers.d.ts.map +1 -1
- package/dist/utils/path-helpers.js +5 -2
- package/dist/utils/path-helpers.js.map +1 -1
- package/dist/utils/rate-limiter.d.ts.map +1 -1
- package/dist/utils/rate-limiter.js +23 -4
- package/dist/utils/rate-limiter.js.map +1 -1
- package/dist/utils/simple-lru.d.ts +6 -0
- package/dist/utils/simple-lru.d.ts.map +1 -1
- package/dist/utils/simple-lru.js +26 -0
- package/dist/utils/simple-lru.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { getTokenCounter } from '../../providers/token-counter.js';
|
|
4
|
+
// ============= getTokenCounter basic tests =============
|
|
5
|
+
test('getTokenCounter returns function for text-embedding models', async () => {
|
|
6
|
+
const counter = await getTokenCounter('text-embedding-3-large');
|
|
7
|
+
if (counter) {
|
|
8
|
+
assert.equal(typeof counter, 'function');
|
|
9
|
+
}
|
|
10
|
+
// If tiktoken not available, counter will be null
|
|
11
|
+
// This is acceptable behavior
|
|
12
|
+
});
|
|
13
|
+
test('getTokenCounter returns function for text-embedding-3-small', async () => {
|
|
14
|
+
const counter = await getTokenCounter('text-embedding-3-small');
|
|
15
|
+
if (counter) {
|
|
16
|
+
assert.equal(typeof counter, 'function');
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
test('getTokenCounter returns function for ada-002 model', async () => {
|
|
20
|
+
const counter = await getTokenCounter('text-embedding-ada-002');
|
|
21
|
+
if (counter) {
|
|
22
|
+
assert.equal(typeof counter, 'function');
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
test('getTokenCounter returns character-based counter for non-embedding models', async () => {
|
|
26
|
+
const counter = await getTokenCounter('gpt-4');
|
|
27
|
+
assert.ok(counter, 'Counter should exist for non-embedding models');
|
|
28
|
+
assert.equal(typeof counter, 'function');
|
|
29
|
+
});
|
|
30
|
+
test('getTokenCounter returns character-based counter for unknown models', async () => {
|
|
31
|
+
const counter = await getTokenCounter('unknown-model-xyz');
|
|
32
|
+
assert.ok(counter, 'Counter should exist');
|
|
33
|
+
assert.equal(typeof counter, 'function');
|
|
34
|
+
});
|
|
35
|
+
// ============= Character-based estimation tests =============
|
|
36
|
+
test('character-based counter estimates 1 token per 4 characters', async () => {
|
|
37
|
+
const counter = await getTokenCounter('gpt-4');
|
|
38
|
+
assert.ok(counter, 'Counter should exist');
|
|
39
|
+
assert.equal(counter('abcd'), 1); // 4 chars = 1 token
|
|
40
|
+
assert.equal(counter('abcdefgh'), 2); // 8 chars = 2 tokens
|
|
41
|
+
});
|
|
42
|
+
test('character-based counter rounds up', async () => {
|
|
43
|
+
const counter = await getTokenCounter('some-chat-model');
|
|
44
|
+
assert.ok(counter, 'Counter should exist');
|
|
45
|
+
assert.equal(counter('abc'), 1); // 3 chars, ceil(3/4) = 1
|
|
46
|
+
assert.equal(counter('abcde'), 2); // 5 chars, ceil(5/4) = 2
|
|
47
|
+
});
|
|
48
|
+
test('character-based counter handles empty string', async () => {
|
|
49
|
+
const counter = await getTokenCounter('chat-model');
|
|
50
|
+
assert.ok(counter, 'Counter should exist');
|
|
51
|
+
assert.equal(counter(''), 0);
|
|
52
|
+
});
|
|
53
|
+
test('character-based counter handles long strings', async () => {
|
|
54
|
+
const counter = await getTokenCounter('any-model');
|
|
55
|
+
assert.ok(counter, 'Counter should exist');
|
|
56
|
+
const longText = 'a'.repeat(10000);
|
|
57
|
+
assert.equal(counter(longText), 2500);
|
|
58
|
+
});
|
|
59
|
+
// ============= Tiktoken-based counter tests (when available) =============
|
|
60
|
+
test('tiktoken counter returns positive count for text', async () => {
|
|
61
|
+
const counter = await getTokenCounter('text-embedding-3-large');
|
|
62
|
+
if (counter) {
|
|
63
|
+
const count = counter('Hello, world!');
|
|
64
|
+
assert.equal(typeof count, 'number');
|
|
65
|
+
assert.ok(count > 0, 'Token count should be positive');
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
test('tiktoken counter handles code snippets', async () => {
|
|
69
|
+
const counter = await getTokenCounter('text-embedding-ada-002');
|
|
70
|
+
if (counter) {
|
|
71
|
+
const code = 'function hello() { return "world"; }';
|
|
72
|
+
const count = counter(code);
|
|
73
|
+
assert.equal(typeof count, 'number');
|
|
74
|
+
assert.ok(count > 0);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
test('tiktoken counter handles unicode', async () => {
|
|
78
|
+
const counter = await getTokenCounter('text-embedding-3-small');
|
|
79
|
+
if (counter) {
|
|
80
|
+
const unicode = 'Hello World';
|
|
81
|
+
const count = counter(unicode);
|
|
82
|
+
assert.equal(typeof count, 'number');
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
test('tiktoken counter handles empty string', async () => {
|
|
86
|
+
const counter = await getTokenCounter('text-embedding-3-large');
|
|
87
|
+
if (counter) {
|
|
88
|
+
const count = counter('');
|
|
89
|
+
assert.equal(count, 0);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
test('tiktoken counter handles whitespace', async () => {
|
|
93
|
+
const counter = await getTokenCounter('text-embedding-ada-002');
|
|
94
|
+
if (counter) {
|
|
95
|
+
const whitespace = ' \n\t ';
|
|
96
|
+
const count = counter(whitespace);
|
|
97
|
+
assert.equal(typeof count, 'number');
|
|
98
|
+
assert.ok(count >= 0);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
// ============= Encoder caching tests =============
|
|
102
|
+
test('getTokenCounter reuses encoder for same model family', async () => {
|
|
103
|
+
// Call twice for embedding models - should reuse encoder
|
|
104
|
+
const counter1 = await getTokenCounter('text-embedding-3-large');
|
|
105
|
+
const counter2 = await getTokenCounter('text-embedding-3-small');
|
|
106
|
+
// Both should work (either tiktoken or fallback)
|
|
107
|
+
if (counter1 && counter2) {
|
|
108
|
+
const count1 = counter1('test');
|
|
109
|
+
const count2 = counter2('test');
|
|
110
|
+
assert.equal(typeof count1, 'number');
|
|
111
|
+
assert.equal(typeof count2, 'number');
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
test('getTokenCounter called multiple times returns consistent results', async () => {
|
|
115
|
+
const counter1 = await getTokenCounter('text-embedding-3-large');
|
|
116
|
+
const counter2 = await getTokenCounter('text-embedding-3-large');
|
|
117
|
+
if (counter1 && counter2) {
|
|
118
|
+
const text = 'This is a test sentence for token counting.';
|
|
119
|
+
assert.equal(counter1(text), counter2(text));
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
// ============= Model name pattern matching tests =============
|
|
123
|
+
test('getTokenCounter matches "text-embedding" pattern', async () => {
|
|
124
|
+
// These should use tiktoken if available
|
|
125
|
+
const models = [
|
|
126
|
+
'text-embedding-3-large',
|
|
127
|
+
'text-embedding-3-small',
|
|
128
|
+
'text-embedding-ada-002',
|
|
129
|
+
'some-text-embedding-model'
|
|
130
|
+
];
|
|
131
|
+
for (const model of models) {
|
|
132
|
+
const counter = await getTokenCounter(model);
|
|
133
|
+
// Should return counter (tiktoken or fallback)
|
|
134
|
+
// For text-embedding models, may return null if tiktoken unavailable
|
|
135
|
+
// which is valid behavior
|
|
136
|
+
assert.ok(counter === null || typeof counter === 'function');
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
test('getTokenCounter matches "ada-002" pattern', async () => {
|
|
140
|
+
const counter = await getTokenCounter('ada-002');
|
|
141
|
+
// Should use tiktoken if available
|
|
142
|
+
assert.ok(counter === null || typeof counter === 'function');
|
|
143
|
+
});
|
|
144
|
+
test('getTokenCounter uses fallback for non-matching models', async () => {
|
|
145
|
+
const models = [
|
|
146
|
+
'gpt-4',
|
|
147
|
+
'gpt-3.5-turbo',
|
|
148
|
+
'claude-2',
|
|
149
|
+
'custom-llm',
|
|
150
|
+
''
|
|
151
|
+
];
|
|
152
|
+
for (const model of models) {
|
|
153
|
+
const counter = await getTokenCounter(model);
|
|
154
|
+
assert.ok(counter, `Counter should exist for "${model}"`);
|
|
155
|
+
// Should use character-based estimation
|
|
156
|
+
assert.equal(counter('abcd'), 1, `Should estimate 1 token for 4 chars with "${model}"`);
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
// ============= Edge cases =============
|
|
160
|
+
test('getTokenCounter handles special characters', async () => {
|
|
161
|
+
const counter = await getTokenCounter('text-embedding-3-large');
|
|
162
|
+
if (counter) {
|
|
163
|
+
const special = '!@#$%^&*()_+-=[]{}|;:\'",.<>?/`~';
|
|
164
|
+
const count = counter(special);
|
|
165
|
+
assert.equal(typeof count, 'number');
|
|
166
|
+
assert.ok(count >= 0);
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
test('getTokenCounter handles newlines and tabs', async () => {
|
|
170
|
+
const counter = await getTokenCounter('text-embedding-ada-002');
|
|
171
|
+
if (counter) {
|
|
172
|
+
const text = 'line1\nline2\tline3';
|
|
173
|
+
const count = counter(text);
|
|
174
|
+
assert.equal(typeof count, 'number');
|
|
175
|
+
assert.ok(count > 0);
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
test('getTokenCounter handles very long text', async () => {
|
|
179
|
+
const counter = await getTokenCounter('gpt-4');
|
|
180
|
+
assert.ok(counter, 'Counter should exist');
|
|
181
|
+
const longText = 'word '.repeat(10000);
|
|
182
|
+
const count = counter(longText);
|
|
183
|
+
assert.equal(typeof count, 'number');
|
|
184
|
+
assert.ok(count > 0);
|
|
185
|
+
});
|
|
186
|
+
test('getTokenCounter handles JSON content', async () => {
|
|
187
|
+
const counter = await getTokenCounter('text-embedding-3-small');
|
|
188
|
+
if (counter) {
|
|
189
|
+
const json = JSON.stringify({ key: 'value', nested: { array: [1, 2, 3] } });
|
|
190
|
+
const count = counter(json);
|
|
191
|
+
assert.equal(typeof count, 'number');
|
|
192
|
+
assert.ok(count > 0);
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
test('getTokenCounter handles code with comments', async () => {
|
|
196
|
+
const counter = await getTokenCounter('text-embedding-3-large');
|
|
197
|
+
if (counter) {
|
|
198
|
+
const code = `
|
|
199
|
+
// This is a comment
|
|
200
|
+
function test() {
|
|
201
|
+
/* Multi-line
|
|
202
|
+
comment */
|
|
203
|
+
return 42;
|
|
204
|
+
}
|
|
205
|
+
`;
|
|
206
|
+
const count = counter(code);
|
|
207
|
+
assert.equal(typeof count, 'number');
|
|
208
|
+
assert.ok(count > 0);
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
// ============= Consistency tests =============
|
|
212
|
+
test('getTokenCounter produces consistent counts for same input', async () => {
|
|
213
|
+
const counter = await getTokenCounter('text-embedding-3-large');
|
|
214
|
+
if (counter) {
|
|
215
|
+
const text = 'Consistent counting test';
|
|
216
|
+
const count1 = counter(text);
|
|
217
|
+
const count2 = counter(text);
|
|
218
|
+
const count3 = counter(text);
|
|
219
|
+
assert.equal(count1, count2);
|
|
220
|
+
assert.equal(count2, count3);
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
test('getTokenCounter character fallback is deterministic', async () => {
|
|
224
|
+
const counter = await getTokenCounter('some-random-model');
|
|
225
|
+
assert.ok(counter, 'Counter should exist');
|
|
226
|
+
const text = 'Deterministic test';
|
|
227
|
+
const counts = [counter(text), counter(text), counter(text)];
|
|
228
|
+
assert.equal(counts[0], counts[1]);
|
|
229
|
+
assert.equal(counts[1], counts[2]);
|
|
230
|
+
});
|
|
231
|
+
// ============= Return type tests =============
|
|
232
|
+
test('getTokenCounter always returns number (not Promise) from returned function', async () => {
|
|
233
|
+
const counter = await getTokenCounter('gpt-4');
|
|
234
|
+
assert.ok(counter, 'Counter should exist');
|
|
235
|
+
const result = counter('test');
|
|
236
|
+
assert.equal(typeof result, 'number');
|
|
237
|
+
// Ensure it's not a thenable (Promise-like) - typeof check already confirms it's number
|
|
238
|
+
assert.ok(typeof result === 'number', 'Result should be a number, not a Promise');
|
|
239
|
+
});
|
|
240
|
+
test('getTokenCounter for embedding model returns sync function', async () => {
|
|
241
|
+
const counter = await getTokenCounter('text-embedding-3-large');
|
|
242
|
+
if (counter) {
|
|
243
|
+
const result = counter('test');
|
|
244
|
+
assert.equal(typeof result, 'number');
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
//# sourceMappingURL=token-counter.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-counter.test.js","sourceRoot":"","sources":["../../../src/tests/providers/token-counter.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,36 @@
|
|
|
1
1
|
import test from 'node:test';
|
|
2
2
|
import assert from 'node:assert/strict';
|
|
3
|
-
import {
|
|
3
|
+
import { setTimeout as delay } from 'timers/promises';
|
|
4
|
+
import { RateLimiter, createRateLimiter } from '../utils/rate-limiter.js';
|
|
5
|
+
// ============================================================================
|
|
6
|
+
// Constructor Tests
|
|
7
|
+
// ============================================================================
|
|
8
|
+
test('RateLimiter constructor with explicit RPM and TPM', () => {
|
|
9
|
+
const limiter = new RateLimiter(100, 10000);
|
|
10
|
+
const stats = limiter.getStats();
|
|
11
|
+
assert.equal(stats.rpm, 100);
|
|
12
|
+
assert.equal(stats.tpm, 10000);
|
|
13
|
+
assert.equal(stats.isLimited, true);
|
|
14
|
+
});
|
|
15
|
+
test('RateLimiter constructor with null limits uses defaults from environment', () => {
|
|
16
|
+
const limiter = new RateLimiter(null, null);
|
|
17
|
+
const stats = limiter.getStats();
|
|
18
|
+
// Default behavior when no env vars set - may or may not have limits
|
|
19
|
+
assert.ok(stats.rpm === null || typeof stats.rpm === 'number');
|
|
20
|
+
});
|
|
21
|
+
test('RateLimiter constructor with custom maxQueueSize', () => {
|
|
22
|
+
const limiter = new RateLimiter(100, null, 500);
|
|
23
|
+
const stats = limiter.getStats();
|
|
24
|
+
assert.equal(stats.maxQueueSize, 500);
|
|
25
|
+
});
|
|
26
|
+
test('RateLimiter default maxQueueSize is 10000', () => {
|
|
27
|
+
const limiter = new RateLimiter(100, null);
|
|
28
|
+
const stats = limiter.getStats();
|
|
29
|
+
assert.equal(stats.maxQueueSize, 10000);
|
|
30
|
+
});
|
|
31
|
+
// ============================================================================
|
|
32
|
+
// Basic Execution Tests
|
|
33
|
+
// ============================================================================
|
|
4
34
|
test('RateLimiter executes tasks under default limits', async () => {
|
|
5
35
|
const limiter = new RateLimiter(1000, null, 10);
|
|
6
36
|
const result = await limiter.execute(() => Promise.resolve('ok'));
|
|
@@ -8,4 +38,365 @@ test('RateLimiter executes tasks under default limits', async () => {
|
|
|
8
38
|
const stats = limiter.getStats();
|
|
9
39
|
assert.ok(stats.requestsInLastMinute >= 0);
|
|
10
40
|
});
|
|
41
|
+
test('RateLimiter executes and returns correct value', async () => {
|
|
42
|
+
const limiter = new RateLimiter(100, null);
|
|
43
|
+
const result = await limiter.execute(async () => {
|
|
44
|
+
await delay(10);
|
|
45
|
+
return 42;
|
|
46
|
+
});
|
|
47
|
+
assert.equal(result, 42);
|
|
48
|
+
});
|
|
49
|
+
test('RateLimiter preserves error from executed function', async () => {
|
|
50
|
+
const limiter = new RateLimiter(100, null);
|
|
51
|
+
await assert.rejects(limiter.execute(async () => {
|
|
52
|
+
throw new Error('Task failed');
|
|
53
|
+
}), { message: 'Task failed' });
|
|
54
|
+
});
|
|
55
|
+
test('RateLimiter executes multiple tasks sequentially', async () => {
|
|
56
|
+
const limiter = new RateLimiter(100, null);
|
|
57
|
+
const results = [];
|
|
58
|
+
await limiter.execute(async () => { results.push(1); });
|
|
59
|
+
await limiter.execute(async () => { results.push(2); });
|
|
60
|
+
await limiter.execute(async () => { results.push(3); });
|
|
61
|
+
assert.deepEqual(results, [1, 2, 3]);
|
|
62
|
+
});
|
|
63
|
+
// ============================================================================
|
|
64
|
+
// Queue Management Tests
|
|
65
|
+
// ============================================================================
|
|
66
|
+
test('RateLimiter rejects when queue is full', async () => {
|
|
67
|
+
const limiter = new RateLimiter(1, null, 2); // max 2 items in queue
|
|
68
|
+
// Fill the queue with slow tasks
|
|
69
|
+
const slowTask = async () => {
|
|
70
|
+
await delay(1000);
|
|
71
|
+
return 'done';
|
|
72
|
+
};
|
|
73
|
+
// Start tasks that will fill the queue
|
|
74
|
+
const task1 = limiter.execute(slowTask);
|
|
75
|
+
const task2 = limiter.execute(slowTask);
|
|
76
|
+
const task3 = limiter.execute(slowTask);
|
|
77
|
+
// Third task should be rejected since queue limit is 2
|
|
78
|
+
await assert.rejects(task3, {
|
|
79
|
+
message: 'Rate limiter queue is full (2 items). Too many concurrent requests.'
|
|
80
|
+
});
|
|
81
|
+
// Clean up - reset the limiter so tests don't hang
|
|
82
|
+
limiter.reset();
|
|
83
|
+
});
|
|
84
|
+
test('RateLimiter getStats shows queue utilization', async () => {
|
|
85
|
+
const limiter = new RateLimiter(1, null, 100);
|
|
86
|
+
const slowTask = async () => {
|
|
87
|
+
await delay(50);
|
|
88
|
+
return 'done';
|
|
89
|
+
};
|
|
90
|
+
// Add some tasks to queue
|
|
91
|
+
const tasks = [
|
|
92
|
+
limiter.execute(slowTask),
|
|
93
|
+
limiter.execute(slowTask),
|
|
94
|
+
];
|
|
95
|
+
// Check queue stats
|
|
96
|
+
const stats = limiter.getStats();
|
|
97
|
+
assert.ok(stats.queueLength >= 0);
|
|
98
|
+
assert.ok(stats.queueUtilization.endsWith('%'));
|
|
99
|
+
// Wait for tasks to complete
|
|
100
|
+
limiter.reset();
|
|
101
|
+
});
|
|
102
|
+
// ============================================================================
|
|
103
|
+
// RPM (Requests Per Minute) Limiting Tests
|
|
104
|
+
// ============================================================================
|
|
105
|
+
test('RateLimiter records requests in last minute', async () => {
|
|
106
|
+
const limiter = new RateLimiter(100, null);
|
|
107
|
+
await limiter.execute(() => Promise.resolve(1));
|
|
108
|
+
await limiter.execute(() => Promise.resolve(2));
|
|
109
|
+
await limiter.execute(() => Promise.resolve(3));
|
|
110
|
+
const stats = limiter.getStats();
|
|
111
|
+
assert.equal(stats.requestsInLastMinute, 3);
|
|
112
|
+
});
|
|
113
|
+
test('RateLimiter tracks RPM limits correctly', async () => {
|
|
114
|
+
const limiter = new RateLimiter(5, null);
|
|
115
|
+
// Execute 5 requests (at limit)
|
|
116
|
+
for (let i = 0; i < 5; i++) {
|
|
117
|
+
await limiter.execute(() => Promise.resolve(i));
|
|
118
|
+
}
|
|
119
|
+
const stats = limiter.getStats();
|
|
120
|
+
assert.equal(stats.requestsInLastMinute, 5);
|
|
121
|
+
assert.equal(stats.isRpmLimited, true);
|
|
122
|
+
});
|
|
123
|
+
// ============================================================================
|
|
124
|
+
// TPM (Tokens Per Minute) Limiting Tests
|
|
125
|
+
// ============================================================================
|
|
126
|
+
test('RateLimiter tracks token usage', async () => {
|
|
127
|
+
const limiter = new RateLimiter(null, 10000);
|
|
128
|
+
// Execute with estimated tokens
|
|
129
|
+
await limiter.execute(() => Promise.resolve('result'), 0, 100);
|
|
130
|
+
await limiter.execute(() => Promise.resolve('result'), 0, 200);
|
|
131
|
+
const stats = limiter.getStats();
|
|
132
|
+
assert.equal(stats.tokensInLastMinute, 300);
|
|
133
|
+
assert.equal(stats.isTpmLimited, true);
|
|
134
|
+
});
|
|
135
|
+
test('RateLimiter extracts tokens from response usage field', async () => {
|
|
136
|
+
const limiter = new RateLimiter(null, 10000);
|
|
137
|
+
// Execute with response containing usage info
|
|
138
|
+
await limiter.execute(() => Promise.resolve({
|
|
139
|
+
usage: { total_tokens: 500 }
|
|
140
|
+
}));
|
|
141
|
+
const stats = limiter.getStats();
|
|
142
|
+
assert.equal(stats.tokensInLastMinute, 500);
|
|
143
|
+
});
|
|
144
|
+
test('RateLimiter uses estimated tokens when no usage in response', async () => {
|
|
145
|
+
const limiter = new RateLimiter(null, 10000);
|
|
146
|
+
await limiter.execute(() => Promise.resolve('plain result'), 0, 150);
|
|
147
|
+
const stats = limiter.getStats();
|
|
148
|
+
assert.equal(stats.tokensInLastMinute, 150);
|
|
149
|
+
});
|
|
150
|
+
// ============================================================================
|
|
151
|
+
// Rate Limit Error Handling Tests
|
|
152
|
+
// ============================================================================
|
|
153
|
+
test('RateLimiter retries on 429 rate limit error', async () => {
|
|
154
|
+
const limiter = new RateLimiter(1000, null);
|
|
155
|
+
let attempts = 0;
|
|
156
|
+
const result = await limiter.execute(async () => {
|
|
157
|
+
attempts++;
|
|
158
|
+
if (attempts === 1) {
|
|
159
|
+
const error = new Error('Rate limit hit');
|
|
160
|
+
error.status = 429;
|
|
161
|
+
throw error;
|
|
162
|
+
}
|
|
163
|
+
return 'success';
|
|
164
|
+
});
|
|
165
|
+
assert.equal(result, 'success');
|
|
166
|
+
assert.equal(attempts, 2);
|
|
167
|
+
});
|
|
168
|
+
test('RateLimiter retries on rate limit message in error', async () => {
|
|
169
|
+
const limiter = new RateLimiter(1000, null);
|
|
170
|
+
let attempts = 0;
|
|
171
|
+
const result = await limiter.execute(async () => {
|
|
172
|
+
attempts++;
|
|
173
|
+
if (attempts === 1) {
|
|
174
|
+
throw new Error('too many requests');
|
|
175
|
+
}
|
|
176
|
+
return 'success';
|
|
177
|
+
});
|
|
178
|
+
assert.equal(result, 'success');
|
|
179
|
+
assert.equal(attempts, 2);
|
|
180
|
+
});
|
|
181
|
+
test('RateLimiter fails after max retries', async () => {
|
|
182
|
+
const limiter = new RateLimiter(1000, null);
|
|
183
|
+
let attempts = 0;
|
|
184
|
+
await assert.rejects(limiter.execute(async () => {
|
|
185
|
+
attempts++;
|
|
186
|
+
const error = new Error('Rate limit exceeded');
|
|
187
|
+
error.status = 429;
|
|
188
|
+
throw error;
|
|
189
|
+
}), /Rate limit exceeded after 4 retries/);
|
|
190
|
+
// Should have attempted 4 times (initial + 3 retries, or 1 + retryDelays.length)
|
|
191
|
+
assert.ok(attempts > 1);
|
|
192
|
+
});
|
|
193
|
+
test('RateLimiter does not retry non-rate-limit errors', async () => {
|
|
194
|
+
const limiter = new RateLimiter(1000, null);
|
|
195
|
+
let attempts = 0;
|
|
196
|
+
await assert.rejects(limiter.execute(async () => {
|
|
197
|
+
attempts++;
|
|
198
|
+
throw new Error('Some other error');
|
|
199
|
+
}), { message: 'Some other error' });
|
|
200
|
+
assert.equal(attempts, 1);
|
|
201
|
+
});
|
|
202
|
+
// ============================================================================
|
|
203
|
+
// Reset Tests
|
|
204
|
+
// ============================================================================
|
|
205
|
+
test('RateLimiter reset clears all state', async () => {
|
|
206
|
+
const limiter = new RateLimiter(100, 10000);
|
|
207
|
+
// Add some requests
|
|
208
|
+
await limiter.execute(() => Promise.resolve(1), 0, 100);
|
|
209
|
+
await limiter.execute(() => Promise.resolve(2), 0, 200);
|
|
210
|
+
const beforeReset = limiter.getStats();
|
|
211
|
+
assert.equal(beforeReset.requestsInLastMinute, 2);
|
|
212
|
+
assert.equal(beforeReset.tokensInLastMinute, 300);
|
|
213
|
+
// Reset
|
|
214
|
+
limiter.reset();
|
|
215
|
+
const afterReset = limiter.getStats();
|
|
216
|
+
assert.equal(afterReset.requestsInLastMinute, 0);
|
|
217
|
+
assert.equal(afterReset.tokensInLastMinute, 0);
|
|
218
|
+
assert.equal(afterReset.queueLength, 0);
|
|
219
|
+
});
|
|
220
|
+
test('RateLimiter reset allows new requests', async () => {
|
|
221
|
+
const limiter = new RateLimiter(100, null);
|
|
222
|
+
await limiter.execute(() => Promise.resolve(1));
|
|
223
|
+
limiter.reset();
|
|
224
|
+
const result = await limiter.execute(() => Promise.resolve(42));
|
|
225
|
+
assert.equal(result, 42);
|
|
226
|
+
const stats = limiter.getStats();
|
|
227
|
+
assert.equal(stats.requestsInLastMinute, 1);
|
|
228
|
+
});
|
|
229
|
+
// ============================================================================
|
|
230
|
+
// getStats Tests
|
|
231
|
+
// ============================================================================
|
|
232
|
+
test('RateLimiter getStats returns complete statistics', () => {
|
|
233
|
+
const limiter = new RateLimiter(100, 50000, 500);
|
|
234
|
+
const stats = limiter.getStats();
|
|
235
|
+
assert.equal(stats.rpm, 100);
|
|
236
|
+
assert.equal(stats.tpm, 50000);
|
|
237
|
+
assert.equal(stats.maxQueueSize, 500);
|
|
238
|
+
assert.equal(stats.queueLength, 0);
|
|
239
|
+
assert.equal(stats.requestsInLastMinute, 0);
|
|
240
|
+
assert.equal(stats.tokensInLastMinute, 0);
|
|
241
|
+
assert.equal(stats.isRpmLimited, true);
|
|
242
|
+
assert.equal(stats.isTpmLimited, true);
|
|
243
|
+
assert.equal(stats.isLimited, true);
|
|
244
|
+
});
|
|
245
|
+
test('RateLimiter getStats isLimited false when no limits', () => {
|
|
246
|
+
const limiter = new RateLimiter(null, null);
|
|
247
|
+
const stats = limiter.getStats();
|
|
248
|
+
// Will only be false if env vars are not set
|
|
249
|
+
if (stats.rpm === null && stats.tpm === null) {
|
|
250
|
+
assert.equal(stats.isLimited, false);
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
// ============================================================================
|
|
254
|
+
// createRateLimiter Factory Tests
|
|
255
|
+
// ============================================================================
|
|
256
|
+
test('createRateLimiter uses default limits for OpenAI', () => {
|
|
257
|
+
const limiter = createRateLimiter('OpenAI');
|
|
258
|
+
const stats = limiter.getStats();
|
|
259
|
+
// OpenAI default is rpm: 50, tpm: null
|
|
260
|
+
assert.equal(stats.rpm, 50);
|
|
261
|
+
assert.equal(stats.tpm, null);
|
|
262
|
+
});
|
|
263
|
+
test('createRateLimiter uses default limits for Qwen', () => {
|
|
264
|
+
const limiter = createRateLimiter('Qwen');
|
|
265
|
+
const stats = limiter.getStats();
|
|
266
|
+
// Qwen default is rpm: 10000, tpm: 600000
|
|
267
|
+
assert.equal(stats.rpm, 10000);
|
|
268
|
+
assert.equal(stats.tpm, 600000);
|
|
269
|
+
});
|
|
270
|
+
test('createRateLimiter uses null limits for unknown provider', () => {
|
|
271
|
+
const limiter = createRateLimiter('UnknownProvider');
|
|
272
|
+
const stats = limiter.getStats();
|
|
273
|
+
// Unknown provider gets null limits (unless env vars set)
|
|
274
|
+
if (!process.env.CODEVAULT_RATE_LIMIT_RPM && !process.env.CODEVAULT_RATE_LIMIT_TPM && !process.env.CODEVAULT_RATE_LIMIT) {
|
|
275
|
+
assert.equal(stats.rpm, null);
|
|
276
|
+
assert.equal(stats.tpm, null);
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
// ============================================================================
|
|
280
|
+
// Concurrent Processing Tests
|
|
281
|
+
// ============================================================================
|
|
282
|
+
test('RateLimiter processes queued tasks in order', async () => {
|
|
283
|
+
const limiter = new RateLimiter(100, null);
|
|
284
|
+
const results = [];
|
|
285
|
+
// Queue multiple tasks concurrently
|
|
286
|
+
const tasks = [
|
|
287
|
+
limiter.execute(async () => { results.push(1); return 1; }),
|
|
288
|
+
limiter.execute(async () => { results.push(2); return 2; }),
|
|
289
|
+
limiter.execute(async () => { results.push(3); return 3; }),
|
|
290
|
+
];
|
|
291
|
+
await Promise.all(tasks);
|
|
292
|
+
assert.deepEqual(results, [1, 2, 3]);
|
|
293
|
+
});
|
|
294
|
+
test('RateLimiter handles concurrent execution with delays', async () => {
|
|
295
|
+
const limiter = new RateLimiter(100, null);
|
|
296
|
+
const startTime = Date.now();
|
|
297
|
+
// Execute tasks with small delays
|
|
298
|
+
await Promise.all([
|
|
299
|
+
limiter.execute(async () => { await delay(10); return 1; }),
|
|
300
|
+
limiter.execute(async () => { await delay(10); return 2; }),
|
|
301
|
+
limiter.execute(async () => { await delay(10); return 3; }),
|
|
302
|
+
]);
|
|
303
|
+
const elapsed = Date.now() - startTime;
|
|
304
|
+
// Should take at least 30ms since tasks are sequential
|
|
305
|
+
assert.ok(elapsed >= 25, `Expected >= 25ms but got ${elapsed}ms`);
|
|
306
|
+
});
|
|
307
|
+
// ============================================================================
|
|
308
|
+
// Edge Cases
|
|
309
|
+
// ============================================================================
|
|
310
|
+
test('RateLimiter handles zero estimated tokens', async () => {
|
|
311
|
+
const limiter = new RateLimiter(100, 10000);
|
|
312
|
+
await limiter.execute(() => Promise.resolve('result'), 0, 0);
|
|
313
|
+
const stats = limiter.getStats();
|
|
314
|
+
assert.equal(stats.tokensInLastMinute, 0);
|
|
315
|
+
});
|
|
316
|
+
test('RateLimiter handles negative retry count gracefully', async () => {
|
|
317
|
+
const limiter = new RateLimiter(100, null);
|
|
318
|
+
// This should work even with negative retry count (treated as 0)
|
|
319
|
+
const result = await limiter.execute(() => Promise.resolve('ok'), -1);
|
|
320
|
+
assert.equal(result, 'ok');
|
|
321
|
+
});
|
|
322
|
+
test('RateLimiter extracts statusCode as well as status', async () => {
|
|
323
|
+
const limiter = new RateLimiter(1000, null);
|
|
324
|
+
let attempts = 0;
|
|
325
|
+
const result = await limiter.execute(async () => {
|
|
326
|
+
attempts++;
|
|
327
|
+
if (attempts === 1) {
|
|
328
|
+
const error = new Error('Rate limit');
|
|
329
|
+
error.statusCode = 429;
|
|
330
|
+
throw error;
|
|
331
|
+
}
|
|
332
|
+
return 'success';
|
|
333
|
+
});
|
|
334
|
+
assert.equal(result, 'success');
|
|
335
|
+
assert.equal(attempts, 2);
|
|
336
|
+
});
|
|
337
|
+
test('RateLimiter handles error with 429 in message', async () => {
|
|
338
|
+
const limiter = new RateLimiter(1000, null);
|
|
339
|
+
let attempts = 0;
|
|
340
|
+
const result = await limiter.execute(async () => {
|
|
341
|
+
attempts++;
|
|
342
|
+
if (attempts === 1) {
|
|
343
|
+
throw new Error('HTTP 429: rate limit exceeded');
|
|
344
|
+
}
|
|
345
|
+
return 'success';
|
|
346
|
+
});
|
|
347
|
+
assert.equal(result, 'success');
|
|
348
|
+
assert.equal(attempts, 2);
|
|
349
|
+
});
|
|
350
|
+
test('RateLimiter handles null/undefined error gracefully', async () => {
|
|
351
|
+
const limiter = new RateLimiter(1000, null);
|
|
352
|
+
// Throwing null should not crash
|
|
353
|
+
await assert.rejects(limiter.execute(async () => {
|
|
354
|
+
throw null;
|
|
355
|
+
}));
|
|
356
|
+
});
|
|
357
|
+
test('RateLimiter getStats filters old request times', async () => {
|
|
358
|
+
const limiter = new RateLimiter(100, null);
|
|
359
|
+
await limiter.execute(() => Promise.resolve(1));
|
|
360
|
+
// Stats should show 1 request
|
|
361
|
+
let stats = limiter.getStats();
|
|
362
|
+
assert.equal(stats.requestsInLastMinute, 1);
|
|
363
|
+
// After a minute, it should be filtered out
|
|
364
|
+
// We can't wait a minute in tests, so just verify the filter logic exists
|
|
365
|
+
// by checking the stats structure
|
|
366
|
+
assert.ok('requestsInLastMinute' in stats);
|
|
367
|
+
});
|
|
368
|
+
// ============================================================================
|
|
369
|
+
// Token Extraction Edge Cases
|
|
370
|
+
// ============================================================================
|
|
371
|
+
test('RateLimiter handles response without usage field', async () => {
|
|
372
|
+
const limiter = new RateLimiter(null, 10000);
|
|
373
|
+
await limiter.execute(() => Promise.resolve({ data: 'no usage' }));
|
|
374
|
+
const stats = limiter.getStats();
|
|
375
|
+
assert.equal(stats.tokensInLastMinute, 0);
|
|
376
|
+
});
|
|
377
|
+
test('RateLimiter handles response with malformed usage field', async () => {
|
|
378
|
+
const limiter = new RateLimiter(null, 10000);
|
|
379
|
+
await limiter.execute(() => Promise.resolve({
|
|
380
|
+
usage: 'not an object'
|
|
381
|
+
}));
|
|
382
|
+
const stats = limiter.getStats();
|
|
383
|
+
assert.equal(stats.tokensInLastMinute, 0);
|
|
384
|
+
});
|
|
385
|
+
test('RateLimiter handles response with non-numeric total_tokens', async () => {
|
|
386
|
+
const limiter = new RateLimiter(null, 10000);
|
|
387
|
+
await limiter.execute(() => Promise.resolve({
|
|
388
|
+
usage: { total_tokens: 'invalid' }
|
|
389
|
+
}));
|
|
390
|
+
const stats = limiter.getStats();
|
|
391
|
+
assert.equal(stats.tokensInLastMinute, 0);
|
|
392
|
+
});
|
|
393
|
+
test('RateLimiter prefers estimated tokens over response usage when available', async () => {
|
|
394
|
+
const limiter = new RateLimiter(null, 10000);
|
|
395
|
+
// When estimatedTokens is provided and non-zero, it should be used
|
|
396
|
+
await limiter.execute(() => Promise.resolve({
|
|
397
|
+
usage: { total_tokens: 100 }
|
|
398
|
+
}), 0, 0); // estimatedTokens = 0, so it falls back to response
|
|
399
|
+
const stats = limiter.getStats();
|
|
400
|
+
assert.equal(stats.tokensInLastMinute, 100);
|
|
401
|
+
});
|
|
11
402
|
//# sourceMappingURL=rate-limiter.test.js.map
|