ctxman 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (190) hide show
  1. package/.ctxman/custom-profiles.example.json +20 -0
  2. package/.ctxman/llm-profiles.json +131 -0
  3. package/.ctxman/logs/ctxman-2026-07-02.log +156 -0
  4. package/.ctxman/self-knowledge/documents.lance/_transactions/0-b3dd1558-f95b-44b9-a154-18e8c330ae7a.txn +0 -0
  5. package/.ctxman/self-knowledge/documents.lance/_transactions/1-44bff6d4-e552-4010-a769-e09ad822698d.txn +0 -0
  6. package/.ctxman/self-knowledge/documents.lance/_transactions/10-37260794-5947-4ecb-b4c8-d732f58f40f1.txn +0 -0
  7. package/.ctxman/self-knowledge/documents.lance/_transactions/11-0d52a883-5518-463c-9b6d-986c3af89dfd.txn +0 -0
  8. package/.ctxman/self-knowledge/documents.lance/_transactions/12-ae354646-52f2-49c9-90fb-2e6ed8932663.txn +0 -0
  9. package/.ctxman/self-knowledge/documents.lance/_transactions/13-5d7d5e57-27da-498b-b734-091b9d38da13.txn +0 -0
  10. package/.ctxman/self-knowledge/documents.lance/_transactions/14-558ce928-810b-4252-a364-fdd1ee6f8a25.txn +0 -0
  11. package/.ctxman/self-knowledge/documents.lance/_transactions/15-27e3a58f-8b64-43b8-a9f4-e3369446968b.txn +0 -0
  12. package/.ctxman/self-knowledge/documents.lance/_transactions/16-a110c390-e5c8-4464-b976-bc7f5195fa15.txn +0 -0
  13. package/.ctxman/self-knowledge/documents.lance/_transactions/17-97513225-53de-4216-b61a-6337bd0cc4ad.txn +0 -0
  14. package/.ctxman/self-knowledge/documents.lance/_transactions/18-5ba450f0-c527-459c-bf4f-6d79b5e4285f.txn +0 -0
  15. package/.ctxman/self-knowledge/documents.lance/_transactions/19-600d3a63-5225-49e9-b8b7-30e037b564ab.txn +0 -0
  16. package/.ctxman/self-knowledge/documents.lance/_transactions/2-48a61b4e-92cf-4342-8376-914c1b99a9f9.txn +0 -0
  17. package/.ctxman/self-knowledge/documents.lance/_transactions/20-121dd912-5410-410e-84ff-82c7a23604a0.txn +0 -0
  18. package/.ctxman/self-knowledge/documents.lance/_transactions/3-bc6844ab-7ea3-4bb3-9ee1-538ce5664a9c.txn +0 -0
  19. package/.ctxman/self-knowledge/documents.lance/_transactions/4-d5e1754f-dbb5-454e-a9be-2a7da8d7bcf9.txn +0 -0
  20. package/.ctxman/self-knowledge/documents.lance/_transactions/5-4fae93df-0568-4b6f-8acf-8bef4bba6047.txn +0 -0
  21. package/.ctxman/self-knowledge/documents.lance/_transactions/6-4b1b8b87-068e-49a3-ba2b-d8dae628b252.txn +0 -0
  22. package/.ctxman/self-knowledge/documents.lance/_transactions/7-63d040fb-d2fa-4c0f-bed0-53411918a7b0.txn +0 -0
  23. package/.ctxman/self-knowledge/documents.lance/_transactions/8-c5628cf6-264c-420f-aab8-bc96a9083591.txn +0 -0
  24. package/.ctxman/self-knowledge/documents.lance/_transactions/9-c94570aa-5e53-4fcb-971f-cecc1d1d5492.txn +0 -0
  25. package/.ctxman/self-knowledge/documents.lance/_versions/1.manifest +0 -0
  26. package/.ctxman/self-knowledge/documents.lance/_versions/10.manifest +0 -0
  27. package/.ctxman/self-knowledge/documents.lance/_versions/11.manifest +0 -0
  28. package/.ctxman/self-knowledge/documents.lance/_versions/12.manifest +0 -0
  29. package/.ctxman/self-knowledge/documents.lance/_versions/13.manifest +0 -0
  30. package/.ctxman/self-knowledge/documents.lance/_versions/14.manifest +0 -0
  31. package/.ctxman/self-knowledge/documents.lance/_versions/15.manifest +0 -0
  32. package/.ctxman/self-knowledge/documents.lance/_versions/16.manifest +0 -0
  33. package/.ctxman/self-knowledge/documents.lance/_versions/17.manifest +0 -0
  34. package/.ctxman/self-knowledge/documents.lance/_versions/18.manifest +0 -0
  35. package/.ctxman/self-knowledge/documents.lance/_versions/19.manifest +0 -0
  36. package/.ctxman/self-knowledge/documents.lance/_versions/2.manifest +0 -0
  37. package/.ctxman/self-knowledge/documents.lance/_versions/20.manifest +0 -0
  38. package/.ctxman/self-knowledge/documents.lance/_versions/21.manifest +0 -0
  39. package/.ctxman/self-knowledge/documents.lance/_versions/3.manifest +0 -0
  40. package/.ctxman/self-knowledge/documents.lance/_versions/4.manifest +0 -0
  41. package/.ctxman/self-knowledge/documents.lance/_versions/5.manifest +0 -0
  42. package/.ctxman/self-knowledge/documents.lance/_versions/6.manifest +0 -0
  43. package/.ctxman/self-knowledge/documents.lance/_versions/7.manifest +0 -0
  44. package/.ctxman/self-knowledge/documents.lance/_versions/8.manifest +0 -0
  45. package/.ctxman/self-knowledge/documents.lance/_versions/9.manifest +0 -0
  46. package/.ctxman/self-knowledge/documents.lance/data/000010010011011101000010de8621405d9d5cb06540dbf02c.lance +0 -0
  47. package/.ctxman/self-knowledge/documents.lance/data/0000101001011100011000003fb62a4aa8978974075c144f1c.lance +0 -0
  48. package/.ctxman/self-knowledge/documents.lance/data/000011010000100111001011c6cc824d628f3bb1f4e04cd5d0.lance +0 -0
  49. package/.ctxman/self-knowledge/documents.lance/data/001000001000011001011100d8f74349ada090b332cd1df669.lance +0 -0
  50. package/.ctxman/self-knowledge/documents.lance/data/01000001111010001000010045ab44467bb1e7e4d8ae06bd58.lance +0 -0
  51. package/.ctxman/self-knowledge/documents.lance/data/01000101101110111110011131ef904241a89e1ffb6ddaf8e3.lance +0 -0
  52. package/.ctxman/self-knowledge/documents.lance/data/010010110000010000011010fbfcc145c6a8a827b28057a07e.lance +0 -0
  53. package/.ctxman/self-knowledge/documents.lance/data/1000011000101010001110003fb4f045c3835982efa1405324.lance +0 -0
  54. package/.ctxman/self-knowledge/documents.lance/data/100111011101110111101011f4396b410fa06af3b028d204e6.lance +0 -0
  55. package/.ctxman/self-knowledge/documents.lance/data/100111100111101000001100596b174e00995d6cc2969cc8dc.lance +0 -0
  56. package/.ctxman/self-knowledge/documents.lance/data/1010001100011000110010102ea16b484d887c7c9725feffd6.lance +0 -0
  57. package/.ctxman/self-knowledge/documents.lance/data/101101100010101111110111c5230944d9b3af7301b6392dda.lance +0 -0
  58. package/.ctxman/self-knowledge/documents.lance/data/1011011100100111111110105892bf41b5a530fedd7cf516f1.lance +0 -0
  59. package/.ctxman/self-knowledge/documents.lance/data/10111110000010001011110164e8774742bade87b483c10f36.lance +0 -0
  60. package/.ctxman/self-knowledge/documents.lance/data/1100000100010110001110103ffaca40a2a42c85067ebaf9ae.lance +0 -0
  61. package/.ctxman/self-knowledge/documents.lance/data/110000010001110110101110664bde4c76a78dfc14b05e83b8.lance +0 -0
  62. package/.ctxman/self-knowledge/documents.lance/data/11001100111000010100110050645c4281b1f35b2022234f7f.lance +0 -0
  63. package/.ctxman/self-knowledge/documents.lance/data/1101111011001100110111017214924a368f5719eab2246619.lance +0 -0
  64. package/.ctxman/self-knowledge/documents.lance/data/1110000111001010001000002150c24dcab653d39d07032e63.lance +0 -0
  65. package/.ctxman/self-knowledge/documents.lance/data/1110100000000100000011014d20534967a18b5c87721659ff.lance +0 -0
  66. package/.ctxman/self-knowledge/documents.lance/data/11110101111010000101011135a0114ef2b35dc6f1297a3381.lance +0 -0
  67. package/.ctxman/wizard-profiles/code-review/.contextignore +41 -0
  68. package/.ctxman/wizard-profiles/code-review/.contextinclude +30 -0
  69. package/.ctxman/wizard-profiles/code-review/.methodignore +26 -0
  70. package/.ctxman/wizard-profiles/code-review/.methodinclude +47 -0
  71. package/.ctxman/wizard-profiles/code-review/profile.json +26 -0
  72. package/.ctxman/wizard-profiles/documentation/.contextignore +21 -0
  73. package/.ctxman/wizard-profiles/documentation/.contextinclude +24 -0
  74. package/.ctxman/wizard-profiles/documentation/.methodignore +19 -0
  75. package/.ctxman/wizard-profiles/documentation/.methodinclude +44 -0
  76. package/.ctxman/wizard-profiles/documentation/profile.json +27 -0
  77. package/.ctxman/wizard-profiles/full/.contextignore +23 -0
  78. package/.ctxman/wizard-profiles/full/.contextinclude +38 -0
  79. package/.ctxman/wizard-profiles/full/.methodignore +5 -0
  80. package/.ctxman/wizard-profiles/full/.methodinclude +5 -0
  81. package/.ctxman/wizard-profiles/full/profile.json +28 -0
  82. package/.ctxman/wizard-profiles/llm-explain/.contextignore +25 -0
  83. package/.ctxman/wizard-profiles/llm-explain/.contextinclude +33 -0
  84. package/.ctxman/wizard-profiles/llm-explain/.methodignore +19 -0
  85. package/.ctxman/wizard-profiles/llm-explain/.methodinclude +48 -0
  86. package/.ctxman/wizard-profiles/llm-explain/profile.json +27 -0
  87. package/.ctxman/wizard-profiles/minimal/.contextignore +27 -0
  88. package/.ctxman/wizard-profiles/minimal/.contextinclude +14 -0
  89. package/.ctxman/wizard-profiles/minimal/.methodignore +22 -0
  90. package/.ctxman/wizard-profiles/minimal/.methodinclude +18 -0
  91. package/.ctxman/wizard-profiles/minimal/profile.json +26 -0
  92. package/.ctxman/wizard-profiles/security-audit/.contextignore +36 -0
  93. package/.ctxman/wizard-profiles/security-audit/.contextinclude +37 -0
  94. package/.ctxman/wizard-profiles/security-audit/.methodignore +29 -0
  95. package/.ctxman/wizard-profiles/security-audit/.methodinclude +60 -0
  96. package/.ctxman/wizard-profiles/security-audit/profile.json +34 -0
  97. package/README.md +1167 -0
  98. package/bin/cli.js +650 -0
  99. package/bin/cm-ask.js +95 -0
  100. package/bin/cm-gitingest.js +134 -0
  101. package/bin/cm-update.js +175 -0
  102. package/bin/mcp-server.js +31 -0
  103. package/ctxman.js +236 -0
  104. package/examples/README.md +298 -0
  105. package/examples/custom-llm-profiles.example.json +19 -0
  106. package/examples/wizard-profiles/code-review/.contextignore +41 -0
  107. package/examples/wizard-profiles/code-review/.contextinclude +30 -0
  108. package/examples/wizard-profiles/code-review/.methodignore +26 -0
  109. package/examples/wizard-profiles/code-review/.methodinclude +47 -0
  110. package/examples/wizard-profiles/code-review/profile.json +26 -0
  111. package/examples/wizard-profiles/documentation/.contextignore +21 -0
  112. package/examples/wizard-profiles/documentation/.contextinclude +24 -0
  113. package/examples/wizard-profiles/documentation/.methodignore +19 -0
  114. package/examples/wizard-profiles/documentation/.methodinclude +44 -0
  115. package/examples/wizard-profiles/documentation/profile.json +27 -0
  116. package/examples/wizard-profiles/full/.contextignore +23 -0
  117. package/examples/wizard-profiles/full/.contextinclude +38 -0
  118. package/examples/wizard-profiles/full/.methodignore +5 -0
  119. package/examples/wizard-profiles/full/.methodinclude +5 -0
  120. package/examples/wizard-profiles/full/profile.json +28 -0
  121. package/examples/wizard-profiles/llm-explain/.contextignore +25 -0
  122. package/examples/wizard-profiles/llm-explain/.contextinclude +33 -0
  123. package/examples/wizard-profiles/llm-explain/.methodignore +19 -0
  124. package/examples/wizard-profiles/llm-explain/.methodinclude +48 -0
  125. package/examples/wizard-profiles/llm-explain/profile.json +27 -0
  126. package/examples/wizard-profiles/minimal/.contextignore +27 -0
  127. package/examples/wizard-profiles/minimal/.contextinclude +14 -0
  128. package/examples/wizard-profiles/minimal/.methodignore +22 -0
  129. package/examples/wizard-profiles/minimal/.methodinclude +18 -0
  130. package/examples/wizard-profiles/minimal/profile.json +26 -0
  131. package/examples/wizard-profiles/security-audit/.contextignore +36 -0
  132. package/examples/wizard-profiles/security-audit/.contextinclude +37 -0
  133. package/examples/wizard-profiles/security-audit/.methodignore +29 -0
  134. package/examples/wizard-profiles/security-audit/.methodinclude +60 -0
  135. package/examples/wizard-profiles/security-audit/profile.json +34 -0
  136. package/index.js +68 -0
  137. package/lib/agents/pr-failure-agent.js +201 -0
  138. package/lib/analyzers/go-method-analyzer.js +126 -0
  139. package/lib/analyzers/method-analyzer.js +457 -0
  140. package/lib/analyzers/token-calculator.js +577 -0
  141. package/lib/api/mcp/prompts.js +319 -0
  142. package/lib/api/mcp/resources.js +269 -0
  143. package/lib/api/mcp/server.js +339 -0
  144. package/lib/api/mcp/transports/HttpSseTransport.js +63 -0
  145. package/lib/api/rest/server.js +405 -0
  146. package/lib/cache/CacheManager.js +388 -0
  147. package/lib/core/Analyzer.js +180 -0
  148. package/lib/core/ContextBuilder.js +325 -0
  149. package/lib/core/Reporter.js +270 -0
  150. package/lib/core/Scanner.js +129 -0
  151. package/lib/formatters/format-registry.js +419 -0
  152. package/lib/formatters/gitingest-formatter.js +746 -0
  153. package/lib/formatters/toon-formatter-v1.3.js +731 -0
  154. package/lib/formatters/toon-formatter.js +291 -0
  155. package/lib/integrations/git/BlameTracker.js +142 -0
  156. package/lib/integrations/git/DiffAnalyzer.js +260 -0
  157. package/lib/integrations/git/GitClient.js +336 -0
  158. package/lib/parsers/gitignore-parser.js +127 -0
  159. package/lib/parsers/method-filter-parser.js +50 -0
  160. package/lib/plugins/ExporterPlugin.js +102 -0
  161. package/lib/plugins/LanguagePlugin.js +100 -0
  162. package/lib/plugins/PluginManager.js +302 -0
  163. package/lib/rag/DataSourcePlugin.js +31 -0
  164. package/lib/rag/EmbeddingProvider.js +75 -0
  165. package/lib/rag/Indexer.js +83 -0
  166. package/lib/rag/VectorStoreFactory.js +24 -0
  167. package/lib/rag/sources/FileDataSource.js +70 -0
  168. package/lib/rag/stores/BaseVectorStore.js +43 -0
  169. package/lib/rag/stores/ChromaDBStore.js +85 -0
  170. package/lib/rag/stores/LanceDBStore.js +109 -0
  171. package/lib/rag/stores/PineconeStore.js +74 -0
  172. package/lib/ui/dashboard.js +109 -0
  173. package/lib/ui/index.js +15 -0
  174. package/lib/ui/progress-bar.js +110 -0
  175. package/lib/ui/select-input.js +51 -0
  176. package/lib/ui/wizard.js +359 -0
  177. package/lib/utils/clipboard-utils.js +66 -0
  178. package/lib/utils/config-utils.js +94 -0
  179. package/lib/utils/error-handler.js +145 -0
  180. package/lib/utils/file-utils.js +89 -0
  181. package/lib/utils/format-converter.js +325 -0
  182. package/lib/utils/git-utils.js +337 -0
  183. package/lib/utils/llm-detector.js +409 -0
  184. package/lib/utils/logger.js +278 -0
  185. package/lib/utils/token-utils.js +200 -0
  186. package/lib/utils/tokenizer-adapter.js +454 -0
  187. package/lib/utils/updater.js +565 -0
  188. package/lib/watch/FileWatcher.js +208 -0
  189. package/lib/watch/IncrementalAnalyzer.js +206 -0
  190. package/package.json +118 -0
@@ -0,0 +1,200 @@
1
+ /**
2
+ * Token Counting and Formatting Utilities
3
+ * Supports multiple tokenizers: OpenAI (tiktoken), Anthropic, Gemini
4
+ */
5
+
6
+ import path from 'path';
7
+ import { getTokenizerManager } from './tokenizer-adapter.js';
8
+
9
+ // Load tiktoken for backward compatibility (dynamic import for optional dep)
10
+ let tiktoken = null;
11
+ try {
12
+ const tiktokenModule = await import('tiktoken');
13
+ tiktoken = tiktokenModule.default || tiktokenModule;
14
+ } catch (err) {
15
+ // tiktoken is optional
16
+ }
17
+
18
+ // Tokenizer manager singleton
19
+ let tokenizerManager = null;
20
+
21
+ export default class TokenUtils {
22
+ /**
23
+ * Calculate tokens using model-specific tokenizer
24
+ * @param {string} content - File content
25
+ * @param {string} modelName - Model name (e.g., 'gpt-5', 'claude-sonnet-4.5', 'gemini-pro')
26
+ * @returns {Promise<number>} Token count
27
+ */
28
+ static async calculateForModel(content, modelName) {
29
+ if (!tokenizerManager) {
30
+ tokenizerManager = await getTokenizerManager();
31
+ }
32
+
33
+ try {
34
+ return tokenizerManager.countWithTelemetry(content, modelName);
35
+ } catch (error) {
36
+ // Fallback to estimation
37
+ return this.estimate(content, 'default.txt');
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Calculate exact or estimated tokens for content (backward compatible)
43
+ * Uses tiktoken if available, otherwise estimation
44
+ * @param {string} content - File content
45
+ * @param {string} filePath - File path for extension-based estimation
46
+ * @returns {number} Token count
47
+ */
48
+ static calculate(content, filePath) {
49
+ if (tiktoken) {
50
+ try {
51
+ const encoding = tiktoken.get_encoding('cl100k_base');
52
+ const tokens = encoding.encode(content);
53
+ encoding.free();
54
+ return tokens.length;
55
+ } catch (error) {
56
+ return this.estimate(content, filePath);
57
+ }
58
+ }
59
+ return this.estimate(content, filePath);
60
+ }
61
+
62
+ /**
63
+ * Estimate tokens based on file type and content length
64
+ * @param {string} content - File content
65
+ * @param {string} filePath - File path for extension detection
66
+ * @returns {number} Estimated token count
67
+ */
68
+ static estimate(content, filePath) {
69
+ const ext = path.extname(filePath).toLowerCase();
70
+
71
+ const charsPerToken = {
72
+ '.json': 2.5,
73
+ '.js': 3.2,
74
+ '.ts': 3.2,
75
+ '.jsx': 3.2,
76
+ '.tsx': 3.2,
77
+ '.py': 3.0,
78
+ '.java': 3.5,
79
+ '.go': 3.3,
80
+ '.rs': 3.4,
81
+ '.php': 3.1,
82
+ '.rb': 3.0,
83
+ '.swift': 3.3,
84
+ '.kt': 3.5,
85
+ '.cs': 3.5,
86
+ '.cpp': 3.6,
87
+ '.c': 3.6,
88
+ '.h': 3.6,
89
+ '.hpp': 3.6,
90
+ '.scala': 3.5,
91
+ '.md': 3.5,
92
+ '.txt': 4.0,
93
+ 'default': 3.5
94
+ };
95
+
96
+ const ratio = charsPerToken[ext] || charsPerToken['default'];
97
+ return Math.round(content.length / ratio);
98
+ }
99
+
100
+ /**
101
+ * Format token count with K/M suffixes
102
+ * @param {number} tokens - Token count
103
+ * @returns {string} Formatted string
104
+ */
105
+ static format(tokens) {
106
+ if (tokens >= 1000000) {
107
+ return `${(tokens / 1000000).toFixed(1)}M`;
108
+ } else if (tokens >= 1000) {
109
+ return `${(tokens / 1000).toFixed(1)}K`;
110
+ }
111
+ return tokens.toString();
112
+ }
113
+
114
+ /**
115
+ * Get token counting method description for a specific model
116
+ * @param {string} modelName - Model name
117
+ * @returns {Promise<string>} Description
118
+ */
119
+ static async getMethodForModel(modelName) {
120
+ if (!tokenizerManager) {
121
+ tokenizerManager = await getTokenizerManager();
122
+ }
123
+
124
+ try {
125
+ const tokenizer = tokenizerManager.getTokenizerForModel(modelName);
126
+ return `✅ Exact (using ${tokenizer.getName()})`;
127
+ } catch (error) {
128
+ return '⚠️ Estimated (~95% accurate)';
129
+ }
130
+ }
131
+
132
+ /**
133
+ * Get token counting method description (backward compatible)
134
+ * @returns {string} Description
135
+ */
136
+ static getMethod() {
137
+ return tiktoken ? '✅ Exact (using tiktoken)' : '⚠️ Estimated (~95% accurate)';
138
+ }
139
+
140
+ /**
141
+ * Check if exact token counting is available
142
+ * @returns {boolean} True if tiktoken is available
143
+ */
144
+ static isExact() {
145
+ return tiktoken !== null;
146
+ }
147
+
148
+ /**
149
+ * Alias for isExact() for backward compatibility
150
+ * @returns {boolean} True if tiktoken is available
151
+ */
152
+ static hasExactCounting() {
153
+ return this.isExact();
154
+ }
155
+
156
+ /**
157
+ * Get list of available tokenizers
158
+ * @returns {Promise<Array>} List of available tokenizers
159
+ */
160
+ static async getAvailableTokenizers() {
161
+ if (!tokenizerManager) {
162
+ tokenizerManager = await getTokenizerManager();
163
+ }
164
+ return tokenizerManager.getAvailableTokenizers();
165
+ }
166
+
167
+ /**
168
+ * Detect which tokenizer would be used for a model
169
+ * @param {string} modelName - Model name
170
+ * @returns {Promise<string>} Tokenizer name
171
+ */
172
+ static async detectTokenizer(modelName) {
173
+ if (!tokenizerManager) {
174
+ tokenizerManager = await getTokenizerManager();
175
+ }
176
+ const tokenizer = tokenizerManager.getTokenizerForModel(modelName);
177
+ return tokenizer.getName();
178
+ }
179
+
180
+ /**
181
+ * Get telemetry statistics
182
+ * @returns {Promise<Object>} Telemetry data
183
+ */
184
+ static async getTelemetry() {
185
+ if (!tokenizerManager) {
186
+ tokenizerManager = await getTokenizerManager();
187
+ }
188
+ return tokenizerManager.getTelemetry();
189
+ }
190
+
191
+ /**
192
+ * Reset telemetry statistics
193
+ */
194
+ static async resetTelemetry() {
195
+ if (!tokenizerManager) {
196
+ tokenizerManager = await getTokenizerManager();
197
+ }
198
+ tokenizerManager.resetTelemetry();
199
+ }
200
+ }
@@ -0,0 +1,454 @@
1
+ /**
2
+ * Tokenizer Adapter System
3
+ * Provides unified interface for different LLM tokenizers
4
+ */
5
+
6
+ /**
7
+ * Base adapter interface for all tokenizers
8
+ */
9
+ export class TokenizerAdapter {
10
+ constructor() {
11
+ this.available = false;
12
+ this.name = 'Base';
13
+ }
14
+
15
+ /**
16
+ * Initialize the tokenizer (load libraries, etc.)
17
+ * @returns {Promise<boolean>} True if initialized successfully
18
+ */
19
+ async initialize() {
20
+ return false;
21
+ }
22
+
23
+ /**
24
+ * Count tokens in content
25
+ * @param {string} content - Text to count tokens for
26
+ * @returns {number} Token count
27
+ */
28
+ count(content) {
29
+ throw new Error('count() must be implemented by subclass');
30
+ }
31
+
32
+ /**
33
+ * Check if tokenizer is available
34
+ * @returns {boolean}
35
+ */
36
+ isAvailable() {
37
+ return this.available;
38
+ }
39
+
40
+ /**
41
+ * Get tokenizer name
42
+ * @returns {string}
43
+ */
44
+ getName() {
45
+ return this.name;
46
+ }
47
+ }
48
+
49
+ /**
50
+ * OpenAI Tokenizer (tiktoken)
51
+ * For GPT-4, GPT-5, o1 models
52
+ */
53
+ export class TiktokenAdapter extends TokenizerAdapter {
54
+ constructor() {
55
+ super();
56
+ this.name = 'Tiktoken (OpenAI)';
57
+ this.tiktoken = null;
58
+ this.encoding = null;
59
+ }
60
+
61
+ async initialize() {
62
+ try {
63
+ const tiktokenModule = await import('tiktoken');
64
+ this.tiktoken = tiktokenModule.default || tiktokenModule;
65
+ this.available = true;
66
+ return true;
67
+ } catch (err) {
68
+ this.available = false;
69
+ return false;
70
+ }
71
+ }
72
+
73
+ count(content) {
74
+ if (!this.available || !this.tiktoken) {
75
+ throw new Error('Tiktoken not available');
76
+ }
77
+
78
+ try {
79
+ // Use cl100k_base for GPT-4, GPT-4o, GPT-5
80
+ // Use o200k_base for newer models if needed
81
+ const encoding = this.tiktoken.get_encoding('cl100k_base');
82
+ const tokens = encoding.encode(content);
83
+ encoding.free();
84
+ return tokens.length;
85
+ } catch (error) {
86
+ throw new Error(`Tiktoken encoding failed: ${error.message}`);
87
+ }
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Anthropic Tokenizer
93
+ * For Claude models (Claude 3, Claude Sonnet, etc.)
94
+ */
95
+ export class AnthropicAdapter extends TokenizerAdapter {
96
+ constructor() {
97
+ super();
98
+ this.name = 'Anthropic (Claude)';
99
+ this.tokenizer = null;
100
+ }
101
+
102
+ async initialize() {
103
+ try {
104
+ const anthropicModule = await import('@anthropic-ai/tokenizer');
105
+ this.tokenizer = anthropicModule.default || anthropicModule;
106
+ this.available = true;
107
+ return true;
108
+ } catch (err) {
109
+ this.available = false;
110
+ return false;
111
+ }
112
+ }
113
+
114
+ count(content) {
115
+ if (!this.available || !this.tokenizer) {
116
+ throw new Error('Anthropic tokenizer not available');
117
+ }
118
+
119
+ try {
120
+ // Anthropic tokenizer API
121
+ return this.tokenizer.countTokens(content);
122
+ } catch (error) {
123
+ throw new Error(`Anthropic tokenizer failed: ${error.message}`);
124
+ }
125
+ }
126
+ }
127
+
128
+ /**
129
+ * Google Gemini Tokenizer
130
+ * Currently uses estimation (no official tokenizer package)
131
+ */
132
+ export class GeminiAdapter extends TokenizerAdapter {
133
+ constructor() {
134
+ super();
135
+ this.name = 'Gemini (Estimation)';
136
+ this.available = true; // Always available (estimation)
137
+ }
138
+
139
+ async initialize() {
140
+ this.available = true;
141
+ return true;
142
+ }
143
+
144
+ count(content) {
145
+ // Gemini uses similar tokenization to GPT models
146
+ // Estimation: ~3.5 chars per token for English text
147
+ return Math.round(content.length / 3.5);
148
+ }
149
+ }
150
+
151
+ /**
152
+ * DeepSeek Tokenizer
153
+ * Uses tiktoken-compatible encoding (OpenAI-compatible API)
154
+ */
155
+ export class DeepSeekAdapter extends TokenizerAdapter {
156
+ constructor() {
157
+ super();
158
+ this.name = 'DeepSeek (tiktoken)';
159
+ this.tiktokenAdapter = null;
160
+ }
161
+
162
+ async initialize() {
163
+ try {
164
+ this.tiktokenAdapter = new TiktokenAdapter();
165
+ await this.tiktokenAdapter.initialize();
166
+ this.available = this.tiktokenAdapter.isAvailable();
167
+ return this.available;
168
+ } catch (err) {
169
+ this.available = false;
170
+ return false;
171
+ }
172
+ }
173
+
174
+ count(content) {
175
+ if (!this.available || !this.tiktokenAdapter) {
176
+ // Fallback to estimation
177
+ return Math.round(content.length / 3.5);
178
+ }
179
+ return this.tiktokenAdapter.count(content);
180
+ }
181
+ }
182
+
183
+ /**
184
+ * Llama Tokenizer
185
+ * For Llama 3.x models
186
+ */
187
+ export class LlamaAdapter extends TokenizerAdapter {
188
+ constructor() {
189
+ super();
190
+ this.name = 'Llama 3 (llama3-tokenizer-js)';
191
+ this.tokenizer = null;
192
+ }
193
+
194
+ async initialize() {
195
+ try {
196
+ const llamaModule = await import('llama3-tokenizer-js');
197
+ this.tokenizer = llamaModule.default || llamaModule;
198
+ this.available = true;
199
+ return true;
200
+ } catch (err) {
201
+ this.available = false;
202
+ return false;
203
+ }
204
+ }
205
+
206
+ count(content) {
207
+ if (!this.available || !this.tokenizer) {
208
+ throw new Error('Llama tokenizer not available');
209
+ }
210
+
211
+ try {
212
+ // llama3-tokenizer-js API
213
+ const tokens = this.tokenizer.encode(content);
214
+ return tokens.length;
215
+ } catch (error) {
216
+ throw new Error(`Llama tokenizer failed: ${error.message}`);
217
+ }
218
+ }
219
+ }
220
+
221
+ /**
222
+ * Estimation-based tokenizer (fallback)
223
+ * Uses character count heuristics
224
+ */
225
+ export class EstimationAdapter extends TokenizerAdapter {
226
+ constructor() {
227
+ super();
228
+ this.name = 'Estimation (Fallback)';
229
+ this.available = true; // Always available
230
+ }
231
+
232
+ async initialize() {
233
+ this.available = true;
234
+ return true;
235
+ }
236
+
237
+ count(content) {
238
+ // Conservative estimation: ~3.5 chars per token
239
+ // Works reasonably well for most languages
240
+ return Math.round(content.length / 3.5);
241
+ }
242
+ }
243
+
244
+ /**
245
+ * Telemetry tracker for tokenizer usage
246
+ */
247
+ class TokenizerTelemetry {
248
+ constructor() {
249
+ this.stats = {
250
+ totalCalls: 0,
251
+ byTokenizer: {},
252
+ byModel: {},
253
+ totalTokens: 0,
254
+ errors: 0,
255
+ };
256
+ }
257
+
258
+ track(tokenizerName, modelName, tokens, error = null) {
259
+ this.stats.totalCalls++;
260
+
261
+ if (error) {
262
+ this.stats.errors++;
263
+ } else {
264
+ // Track by tokenizer
265
+ if (!this.stats.byTokenizer[tokenizerName]) {
266
+ this.stats.byTokenizer[tokenizerName] = {
267
+ calls: 0,
268
+ tokens: 0,
269
+ };
270
+ }
271
+ this.stats.byTokenizer[tokenizerName].calls++;
272
+ this.stats.byTokenizer[tokenizerName].tokens += tokens;
273
+
274
+ // Track by model
275
+ if (!this.stats.byModel[modelName]) {
276
+ this.stats.byModel[modelName] = {
277
+ calls: 0,
278
+ tokens: 0,
279
+ };
280
+ }
281
+ this.stats.byModel[modelName].calls++;
282
+ this.stats.byModel[modelName].tokens += tokens;
283
+
284
+ this.stats.totalTokens += tokens;
285
+ }
286
+ }
287
+
288
+ getStats() {
289
+ return { ...this.stats };
290
+ }
291
+
292
+ reset() {
293
+ this.stats = {
294
+ totalCalls: 0,
295
+ byTokenizer: {},
296
+ byModel: {},
297
+ totalTokens: 0,
298
+ errors: 0,
299
+ };
300
+ }
301
+ }
302
+
303
+ /**
304
+ * Tokenizer factory and manager with telemetry
305
+ */
306
+ export class TokenizerManager {
307
+ constructor(options = {}) {
308
+ this.adapters = new Map();
309
+ this.initialized = false;
310
+ this.telemetry = new TokenizerTelemetry();
311
+ this.enableTelemetry = options.enableTelemetry !== false; // Default: enabled
312
+ }
313
+
314
+ /**
315
+ * Initialize all available tokenizers
316
+ */
317
+ async initialize() {
318
+ if (this.initialized) return;
319
+
320
+ const adapters = [
321
+ { key: 'tiktoken', adapter: new TiktokenAdapter() },
322
+ { key: 'anthropic', adapter: new AnthropicAdapter() },
323
+ { key: 'gemini', adapter: new GeminiAdapter() },
324
+ { key: 'deepseek', adapter: new DeepSeekAdapter() },
325
+ { key: 'llama', adapter: new LlamaAdapter() },
326
+ { key: 'estimation', adapter: new EstimationAdapter() },
327
+ ];
328
+
329
+ for (const { key, adapter } of adapters) {
330
+ await adapter.initialize();
331
+ this.adapters.set(key, adapter);
332
+ }
333
+
334
+ this.initialized = true;
335
+ }
336
+
337
+ /**
338
+ * Get tokenizer for specific model with telemetry
339
+ * @param {string} modelName - Model name
340
+ * @returns {TokenizerAdapter}
341
+ */
342
+ getTokenizerForModel(modelName) {
343
+ if (!this.initialized) {
344
+ throw new Error('TokenizerManager not initialized. Call initialize() first.');
345
+ }
346
+
347
+ const lowerModel = modelName.toLowerCase();
348
+
349
+ // OpenAI models
350
+ if (lowerModel.includes('gpt') || lowerModel.includes('o1') || lowerModel.includes('o3')) {
351
+ const tiktoken = this.adapters.get('tiktoken');
352
+ if (tiktoken.isAvailable()) return tiktoken;
353
+ }
354
+
355
+ // Anthropic models
356
+ if (lowerModel.includes('claude')) {
357
+ const anthropic = this.adapters.get('anthropic');
358
+ if (anthropic.isAvailable()) return anthropic;
359
+ }
360
+
361
+ // Google models
362
+ if (lowerModel.includes('gemini')) {
363
+ return this.adapters.get('gemini');
364
+ }
365
+
366
+ // DeepSeek models
367
+ if (lowerModel.includes('deepseek')) {
368
+ const deepseek = this.adapters.get('deepseek');
369
+ if (deepseek.isAvailable()) return deepseek;
370
+ }
371
+
372
+ // Llama models
373
+ if (lowerModel.includes('llama')) {
374
+ const llama = this.adapters.get('llama');
375
+ if (llama.isAvailable()) return llama;
376
+ }
377
+
378
+ // Fallback to estimation
379
+ return this.adapters.get('estimation');
380
+ }
381
+
382
+ /**
383
+ * Count tokens with telemetry tracking
384
+ * @param {string} content - Content to tokenize
385
+ * @param {string} modelName - Model name
386
+ * @returns {number} Token count
387
+ */
388
+ countWithTelemetry(content, modelName) {
389
+ const tokenizer = this.getTokenizerForModel(modelName);
390
+ let tokens = 0;
391
+ let error = null;
392
+
393
+ try {
394
+ tokens = tokenizer.count(content);
395
+ if (this.enableTelemetry) {
396
+ this.telemetry.track(tokenizer.getName(), modelName, tokens);
397
+ }
398
+ } catch (err) {
399
+ error = err;
400
+ if (this.enableTelemetry) {
401
+ this.telemetry.track(tokenizer.getName(), modelName, 0, err);
402
+ }
403
+ throw err;
404
+ }
405
+
406
+ return tokens;
407
+ }
408
+
409
+ /**
410
+ * Get telemetry stats
411
+ * @returns {Object} Telemetry statistics
412
+ */
413
+ getTelemetry() {
414
+ return this.telemetry.getStats();
415
+ }
416
+
417
+ /**
418
+ * Reset telemetry
419
+ */
420
+ resetTelemetry() {
421
+ this.telemetry.reset();
422
+ }
423
+
424
+ /**
425
+ * Get all available tokenizers
426
+ * @returns {Array<{key: string, name: string, available: boolean}>}
427
+ */
428
+ getAvailableTokenizers() {
429
+ const result = [];
430
+ for (const [key, adapter] of this.adapters.entries()) {
431
+ result.push({
432
+ key,
433
+ name: adapter.getName(),
434
+ available: adapter.isAvailable(),
435
+ });
436
+ }
437
+ return result;
438
+ }
439
+ }
440
+
441
+ // Singleton instance
442
+ let managerInstance = null;
443
+
444
+ /**
445
+ * Get singleton tokenizer manager instance
446
+ * @returns {Promise<TokenizerManager>}
447
+ */
448
+ export async function getTokenizerManager() {
449
+ if (!managerInstance) {
450
+ managerInstance = new TokenizerManager();
451
+ await managerInstance.initialize();
452
+ }
453
+ return managerInstance;
454
+ }