ctxman 3.0.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.ctxman/llm-profiles.json +1067 -39
- package/.ctxman/profiles/backend-team.json +13 -0
- package/.ctxman/profiles/default.json +12 -0
- package/.ctxman/profiles/devops-team.json +21 -0
- package/.ctxman/profiles/frontend-team.json +20 -0
- package/.ctxman/templates/bug-fix.json +17 -0
- package/.ctxman/templates/code-review.json +9 -0
- package/.ctxman/templates/documentation.json +9 -0
- package/.ctxman/templates/feature.json +9 -0
- package/.ctxman/templates/full-context.json +9 -0
- package/.ctxman/templates/refactoring.json +18 -0
- package/.ctxman/templates/testing.json +9 -0
- package/.ctxman/wizard-profiles/security-audit/profile.json +7 -1
- package/LICENSE +21 -0
- package/README.md +380 -136
- package/bin/cli.js +1880 -529
- package/bin/cm-ask.js +18 -13
- package/bin/cm-audit.js +124 -0
- package/bin/cm-count.js +151 -0
- package/bin/cm-gitingest.js +128 -115
- package/bin/cm-map.js +242 -0
- package/bin/cm-mcp.js +293 -0
- package/bin/cm-pack.js +405 -0
- package/bin/cm-policy.js +170 -0
- package/bin/cm-pr.js +248 -0
- package/bin/cm-search.js +178 -0
- package/bin/cm-update.js +128 -128
- package/bin/cm-why.js +197 -0
- package/bin/mcp-server.js +8 -24
- package/ctxman.js +237 -183
- package/examples/README.md +19 -4
- package/examples/github-actions/ctxman-check.yml +49 -0
- package/examples/wizard-profiles/security-audit/profile.json +7 -1
- package/index.d.ts +1422 -0
- package/index.js +55 -31
- package/lib/agents/pr-failure-agent.js +48 -45
- package/lib/analyzers/ai-suggester.js +451 -0
- package/lib/analyzers/dependency-scanner.js +505 -0
- package/lib/analyzers/duplicate-detector.js +264 -0
- package/lib/analyzers/go-method-analyzer.js +151 -110
- package/lib/analyzers/import-tracker.js +431 -0
- package/lib/analyzers/method-analyzer.js +1055 -434
- package/lib/analyzers/performance-dashboard.js +683 -0
- package/lib/analyzers/token-calculator.js +895 -504
- package/lib/analyzers/unused-exports-detector.js +336 -0
- package/lib/api/mcp/install.js +424 -0
- package/lib/api/mcp/prompts.js +281 -281
- package/lib/api/mcp/resources.js +300 -241
- package/lib/api/mcp/sandbox.js +89 -0
- package/lib/api/mcp/server.js +675 -312
- package/lib/api/mcp/start.js +125 -0
- package/lib/api/mcp/transports/HttpSseTransport.js +60 -52
- package/lib/api/rest/server.js +105 -28
- package/lib/cache/CacheManager.js +9 -17
- package/lib/core/Analyzer.js +16 -10
- package/lib/core/ContextBuilder.js +19 -28
- package/lib/core/Reporter.js +15 -14
- package/lib/core/Scanner.js +81 -12
- package/lib/count/index.js +100 -0
- package/lib/formatters/format-registry.js +394 -372
- package/lib/formatters/gitingest-formatter.js +656 -621
- package/lib/formatters/toon-formatter-v1.3.js +637 -632
- package/lib/formatters/toon-formatter.js +239 -240
- package/lib/integrations/git/BlameTracker.js +4 -4
- package/lib/integrations/git/DiffAnalyzer.js +38 -31
- package/lib/integrations/git/GitClient.js +71 -53
- package/lib/lsp/server.js +311 -0
- package/lib/map/graph.js +299 -0
- package/lib/map/imports.js +233 -0
- package/lib/map/index.js +33 -0
- package/lib/map/rank.js +253 -0
- package/lib/map/render.js +346 -0
- package/lib/map/resolver.js +574 -0
- package/lib/pack/budget.js +111 -0
- package/lib/pack/code-lexer.js +360 -0
- package/lib/pack/compress.js +414 -0
- package/lib/pack/index.js +354 -0
- package/lib/pack/renderers/common.js +134 -0
- package/lib/pack/renderers/index.js +24 -0
- package/lib/pack/renderers/json.js +47 -0
- package/lib/pack/renderers/markdown.js +54 -0
- package/lib/pack/renderers/plain.js +38 -0
- package/lib/pack/renderers/xml.js +85 -0
- package/lib/pack/select.js +177 -0
- package/lib/pack/split.js +182 -0
- package/lib/pack/tree.js +35 -0
- package/lib/parsers/gitignore-parser.js +113 -107
- package/lib/parsers/method-filter-parser.js +50 -33
- package/lib/plugins/ExporterPlugin.js +3 -3
- package/lib/plugins/LanguagePlugin.js +3 -3
- package/lib/plugins/PluginManager.js +7 -13
- package/lib/policy/audit.js +294 -0
- package/lib/policy/enforce.js +125 -0
- package/lib/policy/ignore-files.js +169 -0
- package/lib/policy/index.js +52 -0
- package/lib/policy/policy.js +489 -0
- package/lib/pr/diff-parser.js +267 -0
- package/lib/pr/method-mapper.js +148 -0
- package/lib/pr/pr-context.js +829 -0
- package/lib/pr/pr-git.js +149 -0
- package/lib/pr/pr-renderer.js +644 -0
- package/lib/pr/related-tests.js +296 -0
- package/lib/rag/DataSourcePlugin.js +5 -5
- package/lib/rag/EmbeddingProvider.js +19 -8
- package/lib/rag/Indexer.js +14 -5
- package/lib/rag/VectorStoreFactory.js +3 -3
- package/lib/rag/sources/FileDataSource.js +6 -6
- package/lib/rag/stores/BaseVectorStore.js +5 -5
- package/lib/rag/stores/ChromaDBStore.js +10 -10
- package/lib/rag/stores/LanceDBStore.js +29 -19
- package/lib/rag/stores/PineconeStore.js +11 -9
- package/lib/search/bm25.js +433 -0
- package/lib/search/code-units.js +201 -0
- package/lib/search/symbols.js +189 -0
- package/lib/ui/dashboard.js +120 -89
- package/lib/ui/index.js +1 -6
- package/lib/ui/init-wizard-ui.js +550 -0
- package/lib/ui/progress-bar.js +94 -88
- package/lib/ui/select-input.js +40 -32
- package/lib/ui/template-selector.js +99 -0
- package/lib/ui/wizard.js +374 -329
- package/lib/utils/claude-token-counter.js +151 -0
- package/lib/utils/cli-output.js +17 -0
- package/lib/utils/clipboard-utils.js +49 -50
- package/lib/utils/config-utils.js +81 -66
- package/lib/utils/context-versioning.js +532 -0
- package/lib/utils/error-handler.js +112 -112
- package/lib/utils/file-utils.js +153 -69
- package/lib/utils/format-converter.js +356 -283
- package/lib/utils/git-utils.js +366 -294
- package/lib/utils/llm-cost-estimator.js +281 -0
- package/lib/utils/llm-detector.js +242 -78
- package/lib/utils/logger.js +243 -237
- package/lib/utils/messages.js +355 -0
- package/lib/utils/multi-repo-manager.js +364 -0
- package/lib/utils/profile-manager.js +380 -0
- package/lib/utils/safe-read.js +61 -0
- package/lib/utils/secret-redactor.js +61 -0
- package/lib/utils/snapshot-manager.js +679 -0
- package/lib/utils/template-manager.js +378 -0
- package/lib/utils/token-utils.js +201 -170
- package/lib/utils/tokenizer-adapter.js +421 -377
- package/lib/utils/updater.js +539 -506
- package/lib/watch/FileWatcher.js +12 -8
- package/lib/watch/IncrementalAnalyzer.js +7 -7
- package/lib/why/coupling.js +81 -0
- package/lib/why/errors.js +23 -0
- package/lib/why/git.js +169 -0
- package/lib/why/history.js +108 -0
- package/lib/why/index.js +720 -0
- package/lib/why/ownership.js +79 -0
- package/lib/why/paths.js +207 -0
- package/lib/why/references.js +259 -0
- package/lib/why/render.js +722 -0
- package/lib/why/search.js +358 -0
- package/lib/why/target.js +389 -0
- package/lib/wizards/config-generator.js +255 -0
- package/lib/wizards/project-detector.js +571 -0
- package/package.json +44 -10
- package/.ctxman/logs/ctxman-2026-07-02.log +0 -156
- package/.ctxman/self-knowledge/documents.lance/_transactions/0-b3dd1558-f95b-44b9-a154-18e8c330ae7a.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/1-44bff6d4-e552-4010-a769-e09ad822698d.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/10-37260794-5947-4ecb-b4c8-d732f58f40f1.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/11-0d52a883-5518-463c-9b6d-986c3af89dfd.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/12-ae354646-52f2-49c9-90fb-2e6ed8932663.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/13-5d7d5e57-27da-498b-b734-091b9d38da13.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/14-558ce928-810b-4252-a364-fdd1ee6f8a25.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/15-27e3a58f-8b64-43b8-a9f4-e3369446968b.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/16-a110c390-e5c8-4464-b976-bc7f5195fa15.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/17-97513225-53de-4216-b61a-6337bd0cc4ad.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/18-5ba450f0-c527-459c-bf4f-6d79b5e4285f.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/19-600d3a63-5225-49e9-b8b7-30e037b564ab.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/2-48a61b4e-92cf-4342-8376-914c1b99a9f9.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/20-121dd912-5410-410e-84ff-82c7a23604a0.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/3-bc6844ab-7ea3-4bb3-9ee1-538ce5664a9c.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/4-d5e1754f-dbb5-454e-a9be-2a7da8d7bcf9.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/5-4fae93df-0568-4b6f-8acf-8bef4bba6047.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/6-4b1b8b87-068e-49a3-ba2b-d8dae628b252.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/7-63d040fb-d2fa-4c0f-bed0-53411918a7b0.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/8-c5628cf6-264c-420f-aab8-bc96a9083591.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/9-c94570aa-5e53-4fcb-971f-cecc1d1d5492.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/1.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/10.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/11.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/12.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/13.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/14.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/15.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/16.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/17.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/18.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/19.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/2.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/20.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/21.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/3.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/4.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/5.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/6.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/7.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/8.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/9.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/000010010011011101000010de8621405d9d5cb06540dbf02c.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/0000101001011100011000003fb62a4aa8978974075c144f1c.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/000011010000100111001011c6cc824d628f3bb1f4e04cd5d0.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/001000001000011001011100d8f74349ada090b332cd1df669.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/01000001111010001000010045ab44467bb1e7e4d8ae06bd58.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/01000101101110111110011131ef904241a89e1ffb6ddaf8e3.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/010010110000010000011010fbfcc145c6a8a827b28057a07e.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/1000011000101010001110003fb4f045c3835982efa1405324.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/100111011101110111101011f4396b410fa06af3b028d204e6.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/100111100111101000001100596b174e00995d6cc2969cc8dc.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/1010001100011000110010102ea16b484d887c7c9725feffd6.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/101101100010101111110111c5230944d9b3af7301b6392dda.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/1011011100100111111110105892bf41b5a530fedd7cf516f1.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/10111110000010001011110164e8774742bade87b483c10f36.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/1100000100010110001110103ffaca40a2a42c85067ebaf9ae.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/110000010001110110101110664bde4c76a78dfc14b05e83b8.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/11001100111000010100110050645c4281b1f35b2022234f7f.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/1101111011001100110111017214924a368f5719eab2246619.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/1110000111001010001000002150c24dcab653d39d07032e63.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/1110100000000100000011014d20534967a18b5c87721659ff.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/11110101111010000101011135a0114ef2b35dc6f1297a3381.lance +0 -0
package/lib/utils/token-utils.js
CHANGED
|
@@ -4,197 +4,228 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import path from 'path';
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
getTokenizerManager,
|
|
9
|
+
TiktokenAdapter,
|
|
10
|
+
encodingForModel,
|
|
11
|
+
isExactEncoding,
|
|
12
|
+
} from './tokenizer-adapter.js';
|
|
8
13
|
|
|
9
14
|
// Load tiktoken for backward compatibility (dynamic import for optional dep)
|
|
10
15
|
let tiktoken = null;
|
|
11
16
|
try {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
} catch (
|
|
15
|
-
|
|
17
|
+
const tiktokenModule = await import('tiktoken');
|
|
18
|
+
tiktoken = tiktokenModule.default || tiktokenModule;
|
|
19
|
+
} catch (_err) {
|
|
20
|
+
// tiktoken is optional
|
|
16
21
|
}
|
|
17
22
|
|
|
18
23
|
// Tokenizer manager singleton
|
|
19
24
|
let tokenizerManager = null;
|
|
20
25
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
}
|
|
26
|
+
// get_encoding() parses the whole BPE table (~150 ms), so build each encoder once
|
|
27
|
+
// and reuse it; encoding itself takes a few ms per file
|
|
28
|
+
const encoders = new Map();
|
|
40
29
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
}
|
|
30
|
+
function getEncoder(encodingName) {
|
|
31
|
+
if (!encoders.has(encodingName)) {
|
|
32
|
+
encoders.set(encodingName, tiktoken.get_encoding(encodingName));
|
|
33
|
+
}
|
|
34
|
+
return encoders.get(encodingName);
|
|
35
|
+
}
|
|
61
36
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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);
|
|
37
|
+
export default class TokenUtils {
|
|
38
|
+
/**
|
|
39
|
+
* Calculate tokens using model-specific tokenizer
|
|
40
|
+
* @param {string} content - File content
|
|
41
|
+
* @param {string} modelName - Model name (e.g., 'gpt-5', 'claude-sonnet-5-5', 'gemini-2.5-pro')
|
|
42
|
+
* @returns {Promise<number>} Token count
|
|
43
|
+
*/
|
|
44
|
+
static async calculateForModel(content, modelName) {
|
|
45
|
+
if (!tokenizerManager) {
|
|
46
|
+
tokenizerManager = await getTokenizerManager();
|
|
98
47
|
}
|
|
99
48
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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();
|
|
49
|
+
try {
|
|
50
|
+
return tokenizerManager.countWithTelemetry(content, modelName);
|
|
51
|
+
} catch (_error) {
|
|
52
|
+
// Fallback to estimation
|
|
53
|
+
return this.estimate(content, 'default.txt');
|
|
112
54
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Calculate exact or estimated tokens for content (backward compatible)
|
|
59
|
+
* Uses tiktoken if available, otherwise estimation
|
|
60
|
+
* @param {string} content - File content
|
|
61
|
+
* @param {string} filePath - File path for extension-based estimation
|
|
62
|
+
* @param {string} [encodingName='cl100k_base'] - tiktoken encoding (see encodingFor)
|
|
63
|
+
* @returns {number} Token count
|
|
64
|
+
*/
|
|
65
|
+
static calculate(content, filePath, encodingName = 'cl100k_base') {
|
|
66
|
+
if (tiktoken) {
|
|
67
|
+
try {
|
|
68
|
+
return getEncoder(encodingName).encode(content).length;
|
|
69
|
+
} catch (_error) {
|
|
70
|
+
return this.estimate(content, filePath);
|
|
71
|
+
}
|
|
130
72
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
73
|
+
return this.estimate(content, filePath);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* tiktoken encoding to count with for a target model: o200k_base for OpenAI models
|
|
78
|
+
* since GPT-4o, cl100k_base for everything else (exact for GPT-4/3.5, an
|
|
79
|
+
* approximation for other vendors' tokenizers)
|
|
80
|
+
* @param {string} [modelName] - Target model
|
|
81
|
+
* @returns {'o200k_base'|'cl100k_base'}
|
|
82
|
+
*/
|
|
83
|
+
static encodingFor(modelName) {
|
|
84
|
+
return modelName ? encodingForModel(modelName) : 'cl100k_base';
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Estimate tokens based on file type and content length
|
|
89
|
+
* @param {string} content - File content
|
|
90
|
+
* @param {string} filePath - File path for extension detection
|
|
91
|
+
* @returns {number} Estimated token count
|
|
92
|
+
*/
|
|
93
|
+
static estimate(content, filePath) {
|
|
94
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
95
|
+
|
|
96
|
+
const charsPerToken = {
|
|
97
|
+
'.json': 2.5,
|
|
98
|
+
'.js': 3.2,
|
|
99
|
+
'.ts': 3.2,
|
|
100
|
+
'.jsx': 3.2,
|
|
101
|
+
'.tsx': 3.2,
|
|
102
|
+
'.py': 3.0,
|
|
103
|
+
'.java': 3.5,
|
|
104
|
+
'.go': 3.3,
|
|
105
|
+
'.rs': 3.4,
|
|
106
|
+
'.php': 3.1,
|
|
107
|
+
'.rb': 3.0,
|
|
108
|
+
'.swift': 3.3,
|
|
109
|
+
'.kt': 3.5,
|
|
110
|
+
'.cs': 3.5,
|
|
111
|
+
'.cpp': 3.6,
|
|
112
|
+
'.c': 3.6,
|
|
113
|
+
'.h': 3.6,
|
|
114
|
+
'.hpp': 3.6,
|
|
115
|
+
'.scala': 3.5,
|
|
116
|
+
'.md': 3.5,
|
|
117
|
+
'.txt': 4.0,
|
|
118
|
+
default: 3.5,
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const ratio = charsPerToken[ext] || charsPerToken['default'];
|
|
122
|
+
return Math.round(content.length / ratio);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Format token count with K/M suffixes
|
|
127
|
+
* @param {number} tokens - Token count
|
|
128
|
+
* @returns {string} Formatted string
|
|
129
|
+
*/
|
|
130
|
+
static format(tokens) {
|
|
131
|
+
if (tokens >= 1000000) {
|
|
132
|
+
return `${(tokens / 1000000).toFixed(1)}M`;
|
|
133
|
+
} else if (tokens >= 1000) {
|
|
134
|
+
return `${(tokens / 1000).toFixed(1)}K`;
|
|
138
135
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
136
|
+
return tokens.toString();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Get token counting method description for a specific model
|
|
141
|
+
* @param {string} modelName - Model name
|
|
142
|
+
* @returns {Promise<string>} Description
|
|
143
|
+
*/
|
|
144
|
+
static async getMethodForModel(modelName) {
|
|
145
|
+
if (!tokenizerManager) {
|
|
146
|
+
tokenizerManager = await getTokenizerManager();
|
|
146
147
|
}
|
|
147
148
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
149
|
+
try {
|
|
150
|
+
const tokenizer = tokenizerManager.getTokenizerForModel(modelName);
|
|
151
|
+
if (tokenizer instanceof TiktokenAdapter) {
|
|
152
|
+
const label = isExactEncoding(modelName) ? '✅ Exact' : '≈ Approximate';
|
|
153
|
+
return `${label} (using ${tokenizer.getName()}, ${encodingForModel(modelName)})`;
|
|
154
|
+
}
|
|
155
|
+
return tokenizer.exact
|
|
156
|
+
? `✅ Exact (using ${tokenizer.getName()})`
|
|
157
|
+
: `≈ Approximate (using ${tokenizer.getName()})`;
|
|
158
|
+
} catch (_error) {
|
|
159
|
+
return '⚠️ Estimated (~95% accurate)';
|
|
154
160
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Get token counting method description (backward compatible)
|
|
165
|
+
* @returns {string} Description
|
|
166
|
+
*/
|
|
167
|
+
static getMethod() {
|
|
168
|
+
return tiktoken ? '✅ Exact (using tiktoken)' : '⚠️ Estimated (~95% accurate)';
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Check if exact token counting is available
|
|
173
|
+
* @returns {boolean} True if tiktoken is available
|
|
174
|
+
*/
|
|
175
|
+
static isExact() {
|
|
176
|
+
return tiktoken !== null;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Alias for isExact() for backward compatibility
|
|
181
|
+
* @returns {boolean} True if tiktoken is available
|
|
182
|
+
*/
|
|
183
|
+
static hasExactCounting() {
|
|
184
|
+
return this.isExact();
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Get list of available tokenizers
|
|
189
|
+
* @returns {Promise<Array>} List of available tokenizers
|
|
190
|
+
*/
|
|
191
|
+
static async getAvailableTokenizers() {
|
|
192
|
+
if (!tokenizerManager) {
|
|
193
|
+
tokenizerManager = await getTokenizerManager();
|
|
165
194
|
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
return tokenizer.getName();
|
|
195
|
+
return tokenizerManager.getAvailableTokenizers();
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Detect which tokenizer would be used for a model
|
|
200
|
+
* @param {string} modelName - Model name
|
|
201
|
+
* @returns {Promise<string>} Tokenizer name
|
|
202
|
+
*/
|
|
203
|
+
static async detectTokenizer(modelName) {
|
|
204
|
+
if (!tokenizerManager) {
|
|
205
|
+
tokenizerManager = await getTokenizerManager();
|
|
178
206
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
207
|
+
const tokenizer = tokenizerManager.getTokenizerForModel(modelName);
|
|
208
|
+
return tokenizer.getName();
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Get telemetry statistics
|
|
213
|
+
* @returns {Promise<Object>} Telemetry data
|
|
214
|
+
*/
|
|
215
|
+
static async getTelemetry() {
|
|
216
|
+
if (!tokenizerManager) {
|
|
217
|
+
tokenizerManager = await getTokenizerManager();
|
|
189
218
|
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
219
|
+
return tokenizerManager.getTelemetry();
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Reset telemetry statistics
|
|
224
|
+
*/
|
|
225
|
+
static async resetTelemetry() {
|
|
226
|
+
if (!tokenizerManager) {
|
|
227
|
+
tokenizerManager = await getTokenizerManager();
|
|
199
228
|
}
|
|
229
|
+
tokenizerManager.resetTelemetry();
|
|
230
|
+
}
|
|
200
231
|
}
|