codevault 1.8.4 → 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.
Files changed (351) hide show
  1. package/README.md +3 -2
  2. package/dist/chunking/token-counter.d.ts.map +1 -1
  3. package/dist/chunking/token-counter.js +16 -10
  4. package/dist/chunking/token-counter.js.map +1 -1
  5. package/dist/cli/commands/index-cmd.d.ts.map +1 -1
  6. package/dist/cli/commands/index-cmd.js +108 -97
  7. package/dist/cli/commands/index-cmd.js.map +1 -1
  8. package/dist/cli/commands/interactive-config.d.ts.map +1 -1
  9. package/dist/cli/commands/interactive-config.js +40 -3
  10. package/dist/cli/commands/interactive-config.js.map +1 -1
  11. package/dist/cli/commands/search-cmd.d.ts.map +1 -1
  12. package/dist/cli/commands/search-cmd.js +11 -7
  13. package/dist/cli/commands/search-cmd.js.map +1 -1
  14. package/dist/cli/commands/search-with-code-cmd.d.ts.map +1 -1
  15. package/dist/cli/commands/search-with-code-cmd.js +3 -1
  16. package/dist/cli/commands/search-with-code-cmd.js.map +1 -1
  17. package/dist/cli/utils.d.ts +56 -0
  18. package/dist/cli/utils.d.ts.map +1 -0
  19. package/dist/cli/utils.js +98 -0
  20. package/dist/cli/utils.js.map +1 -0
  21. package/dist/config/constants.d.ts +4 -0
  22. package/dist/config/constants.d.ts.map +1 -1
  23. package/dist/config/constants.js +2 -0
  24. package/dist/config/constants.js.map +1 -1
  25. package/dist/context/packs.d.ts.map +1 -1
  26. package/dist/context/packs.js +3 -1
  27. package/dist/context/packs.js.map +1 -1
  28. package/dist/core/IndexerEngine.d.ts +2 -0
  29. package/dist/core/IndexerEngine.d.ts.map +1 -1
  30. package/dist/core/IndexerEngine.js +34 -26
  31. package/dist/core/IndexerEngine.js.map +1 -1
  32. package/dist/core/SearchService.d.ts +1 -0
  33. package/dist/core/SearchService.d.ts.map +1 -1
  34. package/dist/core/SearchService.js +18 -12
  35. package/dist/core/SearchService.js.map +1 -1
  36. package/dist/core/batch-indexer.d.ts.map +1 -1
  37. package/dist/core/batch-indexer.js +5 -13
  38. package/dist/core/batch-indexer.js.map +1 -1
  39. package/dist/core/indexing/IndexFinalizationStage.d.ts.map +1 -1
  40. package/dist/core/indexing/IndexFinalizationStage.js +21 -2
  41. package/dist/core/indexing/IndexFinalizationStage.js.map +1 -1
  42. package/dist/core/indexing/IndexState.d.ts +3 -8
  43. package/dist/core/indexing/IndexState.d.ts.map +1 -1
  44. package/dist/core/indexing/IndexState.js.map +1 -1
  45. package/dist/core/indexing/PersistManager.d.ts +1 -1
  46. package/dist/core/indexing/PersistManager.d.ts.map +1 -1
  47. package/dist/core/indexing/PersistManager.js +17 -17
  48. package/dist/core/indexing/PersistManager.js.map +1 -1
  49. package/dist/core/search/HybridFusion.d.ts +0 -1
  50. package/dist/core/search/HybridFusion.d.ts.map +1 -1
  51. package/dist/core/search/HybridFusion.js +2 -14
  52. package/dist/core/search/HybridFusion.js.map +1 -1
  53. package/dist/core/search/ResultMapper.d.ts +0 -1
  54. package/dist/core/search/ResultMapper.d.ts.map +1 -1
  55. package/dist/core/search/ResultMapper.js +2 -13
  56. package/dist/core/search/ResultMapper.js.map +1 -1
  57. package/dist/core/search/SearchContextManager.d.ts +3 -0
  58. package/dist/core/search/SearchContextManager.d.ts.map +1 -1
  59. package/dist/core/search/SearchContextManager.js +15 -2
  60. package/dist/core/search/SearchContextManager.js.map +1 -1
  61. package/dist/core/search.d.ts.map +1 -1
  62. package/dist/core/search.js +9 -4
  63. package/dist/core/search.js.map +1 -1
  64. package/dist/languages/rules.d.ts.map +1 -1
  65. package/dist/languages/rules.js +14 -5
  66. package/dist/languages/rules.js.map +1 -1
  67. package/dist/mcp/schemas.d.ts +2 -2
  68. package/dist/mcp-server.d.ts +1 -0
  69. package/dist/mcp-server.d.ts.map +1 -1
  70. package/dist/mcp-server.js +32 -21
  71. package/dist/mcp-server.js.map +1 -1
  72. package/dist/providers/base.d.ts +3 -2
  73. package/dist/providers/base.d.ts.map +1 -1
  74. package/dist/providers/base.js +3 -1
  75. package/dist/providers/base.js.map +1 -1
  76. package/dist/providers/chat-llm.d.ts +2 -2
  77. package/dist/providers/chat-llm.d.ts.map +1 -1
  78. package/dist/providers/chat-llm.js +4 -1
  79. package/dist/providers/chat-llm.js.map +1 -1
  80. package/dist/providers/openai.d.ts.map +1 -1
  81. package/dist/providers/openai.js +4 -1
  82. package/dist/providers/openai.js.map +1 -1
  83. package/dist/ranking/api-reranker.d.ts.map +1 -1
  84. package/dist/ranking/api-reranker.js +27 -8
  85. package/dist/ranking/api-reranker.js.map +1 -1
  86. package/dist/ranking/symbol-boost.d.ts.map +1 -1
  87. package/dist/ranking/symbol-boost.js +4 -11
  88. package/dist/ranking/symbol-boost.js.map +1 -1
  89. package/dist/search/bm25.d.ts +10 -0
  90. package/dist/search/bm25.d.ts.map +1 -1
  91. package/dist/search/bm25.js +16 -0
  92. package/dist/search/bm25.js.map +1 -1
  93. package/dist/storage/encrypted-chunks.d.ts +3 -0
  94. package/dist/storage/encrypted-chunks.d.ts.map +1 -1
  95. package/dist/storage/encrypted-chunks.js +108 -16
  96. package/dist/storage/encrypted-chunks.js.map +1 -1
  97. package/dist/synthesis/conversational-synthesizer.d.ts +2 -1
  98. package/dist/synthesis/conversational-synthesizer.d.ts.map +1 -1
  99. package/dist/synthesis/conversational-synthesizer.js +6 -1
  100. package/dist/synthesis/conversational-synthesizer.js.map +1 -1
  101. package/dist/synthesis/prompt-builder.d.ts.map +1 -1
  102. package/dist/synthesis/prompt-builder.js +40 -13
  103. package/dist/synthesis/prompt-builder.js.map +1 -1
  104. package/dist/synthesis/synthesizer.d.ts.map +1 -1
  105. package/dist/synthesis/synthesizer.js +14 -2
  106. package/dist/synthesis/synthesizer.js.map +1 -1
  107. package/dist/tests/api-reranker.test.d.ts +2 -0
  108. package/dist/tests/api-reranker.test.d.ts.map +1 -0
  109. package/dist/tests/api-reranker.test.js +575 -0
  110. package/dist/tests/api-reranker.test.js.map +1 -0
  111. package/dist/tests/bm25.test.d.ts +2 -0
  112. package/dist/tests/bm25.test.d.ts.map +1 -0
  113. package/dist/tests/bm25.test.js +340 -0
  114. package/dist/tests/bm25.test.js.map +1 -0
  115. package/dist/tests/chunking/file-grouper.test.d.ts +2 -0
  116. package/dist/tests/chunking/file-grouper.test.d.ts.map +1 -0
  117. package/dist/tests/chunking/file-grouper.test.js +495 -0
  118. package/dist/tests/chunking/file-grouper.test.js.map +1 -0
  119. package/dist/tests/chunking/semantic-chunker.test.d.ts +2 -0
  120. package/dist/tests/chunking/semantic-chunker.test.d.ts.map +1 -0
  121. package/dist/tests/chunking/semantic-chunker.test.js +509 -0
  122. package/dist/tests/chunking/semantic-chunker.test.js.map +1 -0
  123. package/dist/tests/chunking/token-counter.test.d.ts +2 -0
  124. package/dist/tests/chunking/token-counter.test.d.ts.map +1 -0
  125. package/dist/tests/chunking/token-counter.test.js +441 -0
  126. package/dist/tests/chunking/token-counter.test.js.map +1 -0
  127. package/dist/tests/cli/ask-cmd.test.d.ts +2 -0
  128. package/dist/tests/cli/ask-cmd.test.d.ts.map +1 -0
  129. package/dist/tests/cli/ask-cmd.test.js +152 -0
  130. package/dist/tests/cli/ask-cmd.test.js.map +1 -0
  131. package/dist/tests/cli/chat-cmd.test.d.ts +2 -0
  132. package/dist/tests/cli/chat-cmd.test.d.ts.map +1 -0
  133. package/dist/tests/cli/chat-cmd.test.js +118 -0
  134. package/dist/tests/cli/chat-cmd.test.js.map +1 -0
  135. package/dist/tests/cli/config-cmd.test.d.ts +2 -0
  136. package/dist/tests/cli/config-cmd.test.d.ts.map +1 -0
  137. package/dist/tests/cli/config-cmd.test.js +226 -0
  138. package/dist/tests/cli/config-cmd.test.js.map +1 -0
  139. package/dist/tests/cli/context.test.d.ts +2 -0
  140. package/dist/tests/cli/context.test.d.ts.map +1 -0
  141. package/dist/tests/cli/context.test.js +158 -0
  142. package/dist/tests/cli/context.test.js.map +1 -0
  143. package/dist/tests/cli/index-cmd.test.d.ts +2 -0
  144. package/dist/tests/cli/index-cmd.test.d.ts.map +1 -0
  145. package/dist/tests/cli/index-cmd.test.js +89 -0
  146. package/dist/tests/cli/index-cmd.test.js.map +1 -0
  147. package/dist/tests/cli/index.test.d.ts +2 -0
  148. package/dist/tests/cli/index.test.d.ts.map +1 -0
  149. package/dist/tests/cli/index.test.js +167 -0
  150. package/dist/tests/cli/index.test.js.map +1 -0
  151. package/dist/tests/cli/info-cmd.test.d.ts +2 -0
  152. package/dist/tests/cli/info-cmd.test.d.ts.map +1 -0
  153. package/dist/tests/cli/info-cmd.test.js +47 -0
  154. package/dist/tests/cli/info-cmd.test.js.map +1 -0
  155. package/dist/tests/cli/interactive-config.test.d.ts +2 -0
  156. package/dist/tests/cli/interactive-config.test.d.ts.map +1 -0
  157. package/dist/tests/cli/interactive-config.test.js +30 -0
  158. package/dist/tests/cli/interactive-config.test.js.map +1 -0
  159. package/dist/tests/cli/mcp-cmd.test.d.ts +2 -0
  160. package/dist/tests/cli/mcp-cmd.test.d.ts.map +1 -0
  161. package/dist/tests/cli/mcp-cmd.test.js +47 -0
  162. package/dist/tests/cli/mcp-cmd.test.js.map +1 -0
  163. package/dist/tests/cli/search-cmd.test.d.ts +2 -0
  164. package/dist/tests/cli/search-cmd.test.d.ts.map +1 -0
  165. package/dist/tests/cli/search-cmd.test.js +120 -0
  166. package/dist/tests/cli/search-cmd.test.js.map +1 -0
  167. package/dist/tests/cli/search-with-code-cmd.test.d.ts +2 -0
  168. package/dist/tests/cli/search-with-code-cmd.test.d.ts.map +1 -0
  169. package/dist/tests/cli/search-with-code-cmd.test.js +140 -0
  170. package/dist/tests/cli/search-with-code-cmd.test.js.map +1 -0
  171. package/dist/tests/cli/update-cmd.test.d.ts +2 -0
  172. package/dist/tests/cli/update-cmd.test.d.ts.map +1 -0
  173. package/dist/tests/cli/update-cmd.test.js +75 -0
  174. package/dist/tests/cli/update-cmd.test.js.map +1 -0
  175. package/dist/tests/cli/utils.test.d.ts +2 -0
  176. package/dist/tests/cli/utils.test.d.ts.map +1 -0
  177. package/dist/tests/cli/utils.test.js +119 -0
  178. package/dist/tests/cli/utils.test.js.map +1 -0
  179. package/dist/tests/cli/watch-cmd.test.d.ts +2 -0
  180. package/dist/tests/cli/watch-cmd.test.d.ts.map +1 -0
  181. package/dist/tests/cli/watch-cmd.test.js +84 -0
  182. package/dist/tests/cli/watch-cmd.test.js.map +1 -0
  183. package/dist/tests/cli-ui.test.d.ts +2 -0
  184. package/dist/tests/cli-ui.test.d.ts.map +1 -0
  185. package/dist/tests/cli-ui.test.js +608 -0
  186. package/dist/tests/cli-ui.test.js.map +1 -0
  187. package/dist/tests/codemap-io.test.d.ts +2 -0
  188. package/dist/tests/codemap-io.test.d.ts.map +1 -0
  189. package/dist/tests/codemap-io.test.js +992 -0
  190. package/dist/tests/codemap-io.test.js.map +1 -0
  191. package/dist/tests/config/apply-env.test.d.ts +2 -0
  192. package/dist/tests/config/apply-env.test.d.ts.map +1 -0
  193. package/dist/tests/config/apply-env.test.js +717 -0
  194. package/dist/tests/config/apply-env.test.js.map +1 -0
  195. package/dist/tests/config/constants.test.d.ts +2 -0
  196. package/dist/tests/config/constants.test.d.ts.map +1 -0
  197. package/dist/tests/config/constants.test.js +406 -0
  198. package/dist/tests/config/constants.test.js.map +1 -0
  199. package/dist/tests/config/loader.test.d.ts +2 -0
  200. package/dist/tests/config/loader.test.d.ts.map +1 -0
  201. package/dist/tests/config/loader.test.js +716 -0
  202. package/dist/tests/config/loader.test.js.map +1 -0
  203. package/dist/tests/config/resolver.test.d.ts +2 -0
  204. package/dist/tests/config/resolver.test.d.ts.map +1 -0
  205. package/dist/tests/config/resolver.test.js +402 -0
  206. package/dist/tests/config/resolver.test.js.map +1 -0
  207. package/dist/tests/config/types.test.d.ts +2 -0
  208. package/dist/tests/config/types.test.d.ts.map +1 -0
  209. package/dist/tests/config/types.test.js +460 -0
  210. package/dist/tests/config/types.test.js.map +1 -0
  211. package/dist/tests/context-packs.test.d.ts +2 -0
  212. package/dist/tests/context-packs.test.d.ts.map +1 -0
  213. package/dist/tests/context-packs.test.js +826 -0
  214. package/dist/tests/context-packs.test.js.map +1 -0
  215. package/dist/tests/conversational-synthesizer.test.d.ts +2 -0
  216. package/dist/tests/conversational-synthesizer.test.d.ts.map +1 -0
  217. package/dist/tests/conversational-synthesizer.test.js +595 -0
  218. package/dist/tests/conversational-synthesizer.test.js.map +1 -0
  219. package/dist/tests/database.test.d.ts +2 -0
  220. package/dist/tests/database.test.d.ts.map +1 -0
  221. package/dist/tests/database.test.js +965 -0
  222. package/dist/tests/database.test.js.map +1 -0
  223. package/dist/tests/encrypted-chunks.test.d.ts +2 -0
  224. package/dist/tests/encrypted-chunks.test.d.ts.map +1 -0
  225. package/dist/tests/encrypted-chunks.test.js +1470 -0
  226. package/dist/tests/encrypted-chunks.test.js.map +1 -0
  227. package/dist/tests/hybrid.test.d.ts +2 -0
  228. package/dist/tests/hybrid.test.d.ts.map +1 -0
  229. package/dist/tests/hybrid.test.js +456 -0
  230. package/dist/tests/hybrid.test.js.map +1 -0
  231. package/dist/tests/indexer/ChangeQueue.test.d.ts +12 -0
  232. package/dist/tests/indexer/ChangeQueue.test.d.ts.map +1 -0
  233. package/dist/tests/indexer/ChangeQueue.test.js +441 -0
  234. package/dist/tests/indexer/ChangeQueue.test.js.map +1 -0
  235. package/dist/tests/indexer/ProviderManager.test.d.ts +12 -0
  236. package/dist/tests/indexer/ProviderManager.test.d.ts.map +1 -0
  237. package/dist/tests/indexer/ProviderManager.test.js +290 -0
  238. package/dist/tests/indexer/ProviderManager.test.js.map +1 -0
  239. package/dist/tests/indexer/WatchService.test.d.ts +14 -0
  240. package/dist/tests/indexer/WatchService.test.d.ts.map +1 -0
  241. package/dist/tests/indexer/WatchService.test.js +667 -0
  242. package/dist/tests/indexer/WatchService.test.js.map +1 -0
  243. package/dist/tests/indexer/merkle.test.d.ts +11 -0
  244. package/dist/tests/indexer/merkle.test.d.ts.map +1 -0
  245. package/dist/tests/indexer/merkle.test.js +497 -0
  246. package/dist/tests/indexer/merkle.test.js.map +1 -0
  247. package/dist/tests/indexer/update.test.d.ts +10 -0
  248. package/dist/tests/indexer/update.test.d.ts.map +1 -0
  249. package/dist/tests/indexer/update.test.js +317 -0
  250. package/dist/tests/indexer/update.test.js.map +1 -0
  251. package/dist/tests/indexer/watch.test.d.ts +8 -0
  252. package/dist/tests/indexer/watch.test.d.ts.map +1 -0
  253. package/dist/tests/indexer/watch.test.js +95 -0
  254. package/dist/tests/indexer/watch.test.js.map +1 -0
  255. package/dist/tests/integration/index-search.integration.test.js +1 -0
  256. package/dist/tests/integration/index-search.integration.test.js.map +1 -1
  257. package/dist/tests/languages.test.d.ts +2 -0
  258. package/dist/tests/languages.test.d.ts.map +1 -0
  259. package/dist/tests/languages.test.js +575 -0
  260. package/dist/tests/languages.test.js.map +1 -0
  261. package/dist/tests/logger-redaction.test.d.ts +2 -0
  262. package/dist/tests/logger-redaction.test.d.ts.map +1 -0
  263. package/dist/tests/logger-redaction.test.js +48 -0
  264. package/dist/tests/logger-redaction.test.js.map +1 -0
  265. package/dist/tests/logger.test.d.ts +2 -0
  266. package/dist/tests/logger.test.d.ts.map +1 -0
  267. package/dist/tests/logger.test.js +468 -0
  268. package/dist/tests/logger.test.js.map +1 -0
  269. package/dist/tests/markdown-formatter.test.d.ts +2 -0
  270. package/dist/tests/markdown-formatter.test.d.ts.map +1 -0
  271. package/dist/tests/markdown-formatter.test.js +453 -0
  272. package/dist/tests/markdown-formatter.test.js.map +1 -0
  273. package/dist/tests/mcp/tools/use-context-pack.test.d.ts +7 -0
  274. package/dist/tests/mcp/tools/use-context-pack.test.d.ts.map +1 -0
  275. package/dist/tests/mcp/tools/use-context-pack.test.js +505 -0
  276. package/dist/tests/mcp/tools/use-context-pack.test.js.map +1 -0
  277. package/dist/tests/mutex.test.d.ts +2 -0
  278. package/dist/tests/mutex.test.d.ts.map +1 -0
  279. package/dist/tests/mutex.test.js +489 -0
  280. package/dist/tests/mutex.test.js.map +1 -0
  281. package/dist/tests/path-helpers.test.d.ts +2 -0
  282. package/dist/tests/path-helpers.test.d.ts.map +1 -0
  283. package/dist/tests/path-helpers.test.js +332 -0
  284. package/dist/tests/path-helpers.test.js.map +1 -0
  285. package/dist/tests/prompt-builder.test.d.ts +2 -0
  286. package/dist/tests/prompt-builder.test.d.ts.map +1 -0
  287. package/dist/tests/prompt-builder.test.js +417 -0
  288. package/dist/tests/prompt-builder.test.js.map +1 -0
  289. package/dist/tests/providers/base.test.d.ts +2 -0
  290. package/dist/tests/providers/base.test.d.ts.map +1 -0
  291. package/dist/tests/providers/base.test.js +299 -0
  292. package/dist/tests/providers/base.test.js.map +1 -0
  293. package/dist/tests/providers/chat-llm.test.d.ts +2 -0
  294. package/dist/tests/providers/chat-llm.test.d.ts.map +1 -0
  295. package/dist/tests/providers/chat-llm.test.js +435 -0
  296. package/dist/tests/providers/chat-llm.test.js.map +1 -0
  297. package/dist/tests/providers/index.test.d.ts +2 -0
  298. package/dist/tests/providers/index.test.d.ts.map +1 -0
  299. package/dist/tests/providers/index.test.js +204 -0
  300. package/dist/tests/providers/index.test.js.map +1 -0
  301. package/dist/tests/providers/mock.test.d.ts +2 -0
  302. package/dist/tests/providers/mock.test.d.ts.map +1 -0
  303. package/dist/tests/providers/mock.test.js +225 -0
  304. package/dist/tests/providers/mock.test.js.map +1 -0
  305. package/dist/tests/providers/openai.test.d.ts +2 -0
  306. package/dist/tests/providers/openai.test.d.ts.map +1 -0
  307. package/dist/tests/providers/openai.test.js +408 -0
  308. package/dist/tests/providers/openai.test.js.map +1 -0
  309. package/dist/tests/providers/token-counter.test.d.ts +2 -0
  310. package/dist/tests/providers/token-counter.test.d.ts.map +1 -0
  311. package/dist/tests/providers/token-counter.test.js +247 -0
  312. package/dist/tests/providers/token-counter.test.js.map +1 -0
  313. package/dist/tests/rate-limiter.test.js +392 -1
  314. package/dist/tests/rate-limiter.test.js.map +1 -1
  315. package/dist/tests/scope.test.d.ts +2 -0
  316. package/dist/tests/scope.test.d.ts.map +1 -0
  317. package/dist/tests/scope.test.js +529 -0
  318. package/dist/tests/scope.test.js.map +1 -0
  319. package/dist/tests/simple-lru.test.js +377 -0
  320. package/dist/tests/simple-lru.test.js.map +1 -1
  321. package/dist/tests/symbol-boost.test.js +730 -10
  322. package/dist/tests/symbol-boost.test.js.map +1 -1
  323. package/dist/tests/symbols-extract.test.d.ts +2 -0
  324. package/dist/tests/symbols-extract.test.d.ts.map +1 -0
  325. package/dist/tests/symbols-extract.test.js +536 -0
  326. package/dist/tests/symbols-extract.test.js.map +1 -0
  327. package/dist/tests/symbols-graph.test.d.ts +2 -0
  328. package/dist/tests/symbols-graph.test.d.ts.map +1 -0
  329. package/dist/tests/symbols-graph.test.js +656 -0
  330. package/dist/tests/symbols-graph.test.js.map +1 -0
  331. package/dist/tests/synthesizer.test.d.ts +2 -0
  332. package/dist/tests/synthesizer.test.d.ts.map +1 -0
  333. package/dist/tests/synthesizer.test.js +381 -0
  334. package/dist/tests/synthesizer.test.js.map +1 -0
  335. package/dist/types/context-pack.d.ts +3 -3
  336. package/dist/utils/logger.d.ts +5 -1
  337. package/dist/utils/logger.d.ts.map +1 -1
  338. package/dist/utils/logger.js +149 -4
  339. package/dist/utils/logger.js.map +1 -1
  340. package/dist/utils/mutex.d.ts +7 -2
  341. package/dist/utils/mutex.d.ts.map +1 -1
  342. package/dist/utils/mutex.js +31 -7
  343. package/dist/utils/mutex.js.map +1 -1
  344. package/dist/utils/path-helpers.d.ts.map +1 -1
  345. package/dist/utils/path-helpers.js +5 -2
  346. package/dist/utils/path-helpers.js.map +1 -1
  347. package/dist/utils/simple-lru.d.ts +6 -0
  348. package/dist/utils/simple-lru.d.ts.map +1 -1
  349. package/dist/utils/simple-lru.js +26 -0
  350. package/dist/utils/simple-lru.js.map +1 -1
  351. 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 { RateLimiter } from '../utils/rate-limiter.js';
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