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
|
@@ -7,122 +7,161 @@
|
|
|
7
7
|
* Base adapter interface for all tokenizers
|
|
8
8
|
*/
|
|
9
9
|
export class TokenizerAdapter {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
constructor() {
|
|
11
|
+
this.available = false;
|
|
12
|
+
this.name = 'Base';
|
|
13
|
+
// Whether counts match the model's own tokenizer, or only approximate it
|
|
14
|
+
this.exact = false;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Initialize the tokenizer (load libraries, etc.)
|
|
19
|
+
* @returns {Promise<boolean>} True if initialized successfully
|
|
20
|
+
*/
|
|
21
|
+
async initialize() {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Count tokens in content
|
|
27
|
+
* @param {string} content - Text to count tokens for
|
|
28
|
+
* @returns {number} Token count
|
|
29
|
+
*/
|
|
30
|
+
count(_content) {
|
|
31
|
+
throw new Error('count() must be implemented by subclass');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Check if tokenizer is available
|
|
36
|
+
* @returns {boolean}
|
|
37
|
+
*/
|
|
38
|
+
isAvailable() {
|
|
39
|
+
return this.available;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Get tokenizer name
|
|
44
|
+
* @returns {string}
|
|
45
|
+
*/
|
|
46
|
+
getName() {
|
|
47
|
+
return this.name;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
14
50
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
51
|
+
/**
|
|
52
|
+
* OpenAI models since GPT-4o use o200k_base; GPT-4, GPT-3.5 and embeddings use cl100k_base.
|
|
53
|
+
* OpenAI has not published GPT-6's encoding, so o200k_base only approximates it.
|
|
54
|
+
*/
|
|
55
|
+
const O200K_MODEL = /^(gpt-4o|gpt-4\.1|gpt-4\.5|gpt-5|gpt-6|gpt-oss|o\d|chatgpt|codex)/;
|
|
56
|
+
const UNPUBLISHED_ENCODING = /^gpt-6/;
|
|
22
57
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
58
|
+
/**
|
|
59
|
+
* tiktoken encoding for an OpenAI model name
|
|
60
|
+
* @param {string} modelName - Model name, e.g. `gpt-5`, `gpt-4o-mini`, `gpt-4`
|
|
61
|
+
* @returns {'o200k_base'|'cl100k_base'}
|
|
62
|
+
*/
|
|
63
|
+
export function encodingForModel(modelName = '') {
|
|
64
|
+
return O200K_MODEL.test(String(modelName).toLowerCase()) ? 'o200k_base' : 'cl100k_base';
|
|
65
|
+
}
|
|
31
66
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
67
|
+
/**
|
|
68
|
+
* Whether tiktoken's count is the model's own count
|
|
69
|
+
* @param {string} modelName - OpenAI model name
|
|
70
|
+
* @returns {boolean}
|
|
71
|
+
*/
|
|
72
|
+
export function isExactEncoding(modelName = '') {
|
|
73
|
+
return !UNPUBLISHED_ENCODING.test(String(modelName).toLowerCase());
|
|
74
|
+
}
|
|
39
75
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
*/
|
|
44
|
-
getName() {
|
|
45
|
-
return this.name;
|
|
46
|
-
}
|
|
76
|
+
/** OpenAI model names: gpt-*, o-series reasoning models, codex */
|
|
77
|
+
function isOpenAIModel(lowerModel) {
|
|
78
|
+
return /^(gpt|chatgpt|codex|o\d)/.test(lowerModel);
|
|
47
79
|
}
|
|
48
80
|
|
|
49
81
|
/**
|
|
50
82
|
* OpenAI Tokenizer (tiktoken)
|
|
51
|
-
* For GPT
|
|
83
|
+
* For GPT and o-series models; picks o200k_base or cl100k_base per model
|
|
52
84
|
*/
|
|
53
85
|
export class TiktokenAdapter extends TokenizerAdapter {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
86
|
+
constructor() {
|
|
87
|
+
super();
|
|
88
|
+
this.name = 'Tiktoken (OpenAI)';
|
|
89
|
+
this.exact = true;
|
|
90
|
+
this.tiktoken = null;
|
|
91
|
+
this.encodings = new Map();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async initialize() {
|
|
95
|
+
try {
|
|
96
|
+
const tiktokenModule = await import('tiktoken');
|
|
97
|
+
this.tiktoken = tiktokenModule.default || tiktokenModule;
|
|
98
|
+
this.available = true;
|
|
99
|
+
return true;
|
|
100
|
+
} catch (_err) {
|
|
101
|
+
this.available = false;
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* @param {string} content - Text to count
|
|
108
|
+
* @param {string} [encodingName='cl100k_base'] - tiktoken encoding
|
|
109
|
+
* @returns {number} Token count
|
|
110
|
+
*/
|
|
111
|
+
count(content, encodingName = 'cl100k_base') {
|
|
112
|
+
if (!this.available || !this.tiktoken) {
|
|
113
|
+
throw new Error('Tiktoken not available');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
// Built once per encoding: get_encoding() costs ~150 ms, encode() a few ms
|
|
118
|
+
if (!this.encodings.has(encodingName)) {
|
|
119
|
+
this.encodings.set(encodingName, this.tiktoken.get_encoding(encodingName));
|
|
120
|
+
}
|
|
121
|
+
return this.encodings.get(encodingName).encode(content).length;
|
|
122
|
+
} catch (error) {
|
|
123
|
+
throw new Error(`Tiktoken encoding failed: ${error.message}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
89
126
|
}
|
|
90
127
|
|
|
91
128
|
/**
|
|
92
|
-
* Anthropic
|
|
93
|
-
*
|
|
129
|
+
* Anthropic's legacy tokenizer package (@anthropic-ai/tokenizer)
|
|
130
|
+
* It implements the Claude 2 tokenizer; Anthropic documents that it is not accurate
|
|
131
|
+
* for Claude 3 and later, whose exact counts come only from the count_tokens API.
|
|
132
|
+
* Used as an approximation for Claude models when installed.
|
|
94
133
|
*/
|
|
95
134
|
export class AnthropicAdapter extends TokenizerAdapter {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
135
|
+
constructor() {
|
|
136
|
+
super();
|
|
137
|
+
this.name = 'Anthropic legacy tokenizer (approximate)';
|
|
138
|
+
this.tokenizer = null;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async initialize() {
|
|
142
|
+
try {
|
|
143
|
+
const anthropicModule = await import('@anthropic-ai/tokenizer');
|
|
144
|
+
this.tokenizer = anthropicModule.default || anthropicModule;
|
|
145
|
+
this.available = true;
|
|
146
|
+
return true;
|
|
147
|
+
} catch (_err) {
|
|
148
|
+
this.available = false;
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
count(content) {
|
|
154
|
+
if (!this.available || !this.tokenizer) {
|
|
155
|
+
throw new Error('Anthropic tokenizer not available');
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
try {
|
|
159
|
+
// Anthropic tokenizer API
|
|
160
|
+
return this.tokenizer.countTokens(content);
|
|
161
|
+
} catch (error) {
|
|
162
|
+
throw new Error(`Anthropic tokenizer failed: ${error.message}`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
126
165
|
}
|
|
127
166
|
|
|
128
167
|
/**
|
|
@@ -130,22 +169,22 @@ export class AnthropicAdapter extends TokenizerAdapter {
|
|
|
130
169
|
* Currently uses estimation (no official tokenizer package)
|
|
131
170
|
*/
|
|
132
171
|
export class GeminiAdapter extends TokenizerAdapter {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
172
|
+
constructor() {
|
|
173
|
+
super();
|
|
174
|
+
this.name = 'Gemini (Estimation)';
|
|
175
|
+
this.available = true; // Always available (estimation)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async initialize() {
|
|
179
|
+
this.available = true;
|
|
180
|
+
return true;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
count(content) {
|
|
184
|
+
// Gemini uses similar tokenization to GPT models
|
|
185
|
+
// Estimation: ~3.5 chars per token for English text
|
|
186
|
+
return Math.round(content.length / 3.5);
|
|
187
|
+
}
|
|
149
188
|
}
|
|
150
189
|
|
|
151
190
|
/**
|
|
@@ -153,31 +192,32 @@ export class GeminiAdapter extends TokenizerAdapter {
|
|
|
153
192
|
* Uses tiktoken-compatible encoding (OpenAI-compatible API)
|
|
154
193
|
*/
|
|
155
194
|
export class DeepSeekAdapter extends TokenizerAdapter {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
195
|
+
constructor() {
|
|
196
|
+
super();
|
|
197
|
+
// DeepSeek has its own tokenizer; cl100k_base only approximates it
|
|
198
|
+
this.name = 'DeepSeek (tiktoken approximation)';
|
|
199
|
+
this.tiktokenAdapter = null;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
async initialize() {
|
|
203
|
+
try {
|
|
204
|
+
this.tiktokenAdapter = new TiktokenAdapter();
|
|
205
|
+
await this.tiktokenAdapter.initialize();
|
|
206
|
+
this.available = this.tiktokenAdapter.isAvailable();
|
|
207
|
+
return this.available;
|
|
208
|
+
} catch (_err) {
|
|
209
|
+
this.available = false;
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
count(content) {
|
|
215
|
+
if (!this.available || !this.tiktokenAdapter) {
|
|
216
|
+
// Fallback to estimation
|
|
217
|
+
return Math.round(content.length / 3.5);
|
|
218
|
+
}
|
|
219
|
+
return this.tiktokenAdapter.count(content);
|
|
220
|
+
}
|
|
181
221
|
}
|
|
182
222
|
|
|
183
223
|
/**
|
|
@@ -185,37 +225,38 @@ export class DeepSeekAdapter extends TokenizerAdapter {
|
|
|
185
225
|
* For Llama 3.x models
|
|
186
226
|
*/
|
|
187
227
|
export class LlamaAdapter extends TokenizerAdapter {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
}
|
|
228
|
+
constructor() {
|
|
229
|
+
super();
|
|
230
|
+
this.name = 'Llama 3 (llama3-tokenizer-js)';
|
|
231
|
+
this.exact = true;
|
|
232
|
+
this.tokenizer = null;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
async initialize() {
|
|
236
|
+
try {
|
|
237
|
+
const llamaModule = await import('llama3-tokenizer-js');
|
|
238
|
+
this.tokenizer = llamaModule.default || llamaModule;
|
|
239
|
+
this.available = true;
|
|
240
|
+
return true;
|
|
241
|
+
} catch (_err) {
|
|
242
|
+
this.available = false;
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
count(content) {
|
|
248
|
+
if (!this.available || !this.tokenizer) {
|
|
249
|
+
throw new Error('Llama tokenizer not available');
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
try {
|
|
253
|
+
// llama3-tokenizer-js API
|
|
254
|
+
const tokens = this.tokenizer.encode(content);
|
|
255
|
+
return tokens.length;
|
|
256
|
+
} catch (error) {
|
|
257
|
+
throw new Error(`Llama tokenizer failed: ${error.message}`);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
219
260
|
}
|
|
220
261
|
|
|
221
262
|
/**
|
|
@@ -223,219 +264,222 @@ export class LlamaAdapter extends TokenizerAdapter {
|
|
|
223
264
|
* Uses character count heuristics
|
|
224
265
|
*/
|
|
225
266
|
export class EstimationAdapter extends TokenizerAdapter {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
267
|
+
constructor() {
|
|
268
|
+
super();
|
|
269
|
+
this.name = 'Estimation (Fallback)';
|
|
270
|
+
this.available = true; // Always available
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
async initialize() {
|
|
274
|
+
this.available = true;
|
|
275
|
+
return true;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
count(content) {
|
|
279
|
+
// Conservative estimation: ~3.5 chars per token
|
|
280
|
+
// Works reasonably well for most languages
|
|
281
|
+
return Math.round(content.length / 3.5);
|
|
282
|
+
}
|
|
242
283
|
}
|
|
243
284
|
|
|
244
285
|
/**
|
|
245
286
|
* Telemetry tracker for tokenizer usage
|
|
246
287
|
*/
|
|
247
288
|
class TokenizerTelemetry {
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
289
|
+
constructor() {
|
|
290
|
+
this.stats = {
|
|
291
|
+
totalCalls: 0,
|
|
292
|
+
byTokenizer: {},
|
|
293
|
+
byModel: {},
|
|
294
|
+
totalTokens: 0,
|
|
295
|
+
errors: 0,
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
track(tokenizerName, modelName, tokens, error = null) {
|
|
300
|
+
this.stats.totalCalls++;
|
|
301
|
+
|
|
302
|
+
if (error) {
|
|
303
|
+
this.stats.errors++;
|
|
304
|
+
} else {
|
|
305
|
+
// Track by tokenizer
|
|
306
|
+
if (!this.stats.byTokenizer[tokenizerName]) {
|
|
307
|
+
this.stats.byTokenizer[tokenizerName] = {
|
|
308
|
+
calls: 0,
|
|
309
|
+
tokens: 0,
|
|
255
310
|
};
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
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,
|
|
311
|
+
}
|
|
312
|
+
this.stats.byTokenizer[tokenizerName].calls++;
|
|
313
|
+
this.stats.byTokenizer[tokenizerName].tokens += tokens;
|
|
314
|
+
|
|
315
|
+
// Track by model
|
|
316
|
+
if (!this.stats.byModel[modelName]) {
|
|
317
|
+
this.stats.byModel[modelName] = {
|
|
318
|
+
calls: 0,
|
|
319
|
+
tokens: 0,
|
|
299
320
|
};
|
|
300
|
-
|
|
321
|
+
}
|
|
322
|
+
this.stats.byModel[modelName].calls++;
|
|
323
|
+
this.stats.byModel[modelName].tokens += tokens;
|
|
324
|
+
|
|
325
|
+
this.stats.totalTokens += tokens;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
getStats() {
|
|
330
|
+
return { ...this.stats };
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
reset() {
|
|
334
|
+
this.stats = {
|
|
335
|
+
totalCalls: 0,
|
|
336
|
+
byTokenizer: {},
|
|
337
|
+
byModel: {},
|
|
338
|
+
totalTokens: 0,
|
|
339
|
+
errors: 0,
|
|
340
|
+
};
|
|
341
|
+
}
|
|
301
342
|
}
|
|
302
343
|
|
|
303
344
|
/**
|
|
304
345
|
* Tokenizer factory and manager with telemetry
|
|
305
346
|
*/
|
|
306
347
|
export class TokenizerManager {
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
348
|
+
constructor(options = {}) {
|
|
349
|
+
this.adapters = new Map();
|
|
350
|
+
this.initialized = false;
|
|
351
|
+
this.telemetry = new TokenizerTelemetry();
|
|
352
|
+
this.enableTelemetry = options.enableTelemetry !== false; // Default: enabled
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Initialize all available tokenizers
|
|
357
|
+
*/
|
|
358
|
+
async initialize() {
|
|
359
|
+
if (this.initialized) return;
|
|
360
|
+
|
|
361
|
+
const adapters = [
|
|
362
|
+
{ key: 'tiktoken', adapter: new TiktokenAdapter() },
|
|
363
|
+
{ key: 'anthropic', adapter: new AnthropicAdapter() },
|
|
364
|
+
{ key: 'gemini', adapter: new GeminiAdapter() },
|
|
365
|
+
{ key: 'deepseek', adapter: new DeepSeekAdapter() },
|
|
366
|
+
{ key: 'llama', adapter: new LlamaAdapter() },
|
|
367
|
+
{ key: 'estimation', adapter: new EstimationAdapter() },
|
|
368
|
+
];
|
|
369
|
+
|
|
370
|
+
for (const { key, adapter } of adapters) {
|
|
371
|
+
await adapter.initialize();
|
|
372
|
+
this.adapters.set(key, adapter);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
this.initialized = true;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Get tokenizer for specific model with telemetry
|
|
380
|
+
* @param {string} modelName - Model name
|
|
381
|
+
* @returns {TokenizerAdapter}
|
|
382
|
+
*/
|
|
383
|
+
getTokenizerForModel(modelName) {
|
|
384
|
+
if (!this.initialized) {
|
|
385
|
+
throw new Error('TokenizerManager not initialized. Call initialize() first.');
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const lowerModel = modelName.toLowerCase();
|
|
389
|
+
|
|
390
|
+
// OpenAI models
|
|
391
|
+
if (isOpenAIModel(lowerModel)) {
|
|
392
|
+
const tiktoken = this.adapters.get('tiktoken');
|
|
393
|
+
if (tiktoken.isAvailable()) return tiktoken;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Anthropic models
|
|
397
|
+
if (lowerModel.includes('claude')) {
|
|
398
|
+
const anthropic = this.adapters.get('anthropic');
|
|
399
|
+
if (anthropic.isAvailable()) return anthropic;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Google models
|
|
403
|
+
if (lowerModel.includes('gemini')) {
|
|
404
|
+
return this.adapters.get('gemini');
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// DeepSeek models
|
|
408
|
+
if (lowerModel.includes('deepseek')) {
|
|
409
|
+
const deepseek = this.adapters.get('deepseek');
|
|
410
|
+
if (deepseek.isAvailable()) return deepseek;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// Llama models
|
|
414
|
+
if (lowerModel.includes('llama')) {
|
|
415
|
+
const llama = this.adapters.get('llama');
|
|
416
|
+
if (llama.isAvailable()) return llama;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// Fallback to estimation
|
|
420
|
+
return this.adapters.get('estimation');
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Count tokens with telemetry tracking
|
|
425
|
+
* @param {string} content - Content to tokenize
|
|
426
|
+
* @param {string} modelName - Model name
|
|
427
|
+
* @returns {number} Token count
|
|
428
|
+
*/
|
|
429
|
+
countWithTelemetry(content, modelName) {
|
|
430
|
+
const tokenizer = this.getTokenizerForModel(modelName);
|
|
431
|
+
let tokens = 0;
|
|
432
|
+
let _error = null;
|
|
433
|
+
|
|
434
|
+
try {
|
|
435
|
+
tokens =
|
|
436
|
+
tokenizer instanceof TiktokenAdapter
|
|
437
|
+
? tokenizer.count(content, encodingForModel(modelName))
|
|
438
|
+
: tokenizer.count(content);
|
|
439
|
+
if (this.enableTelemetry) {
|
|
440
|
+
this.telemetry.track(tokenizer.getName(), modelName, tokens);
|
|
441
|
+
}
|
|
442
|
+
} catch (err) {
|
|
443
|
+
if (this.enableTelemetry) {
|
|
444
|
+
this.telemetry.track(tokenizer.getName(), modelName, 0, err);
|
|
445
|
+
}
|
|
446
|
+
throw err;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
return tokens;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Get telemetry stats
|
|
454
|
+
* @returns {Object} Telemetry statistics
|
|
455
|
+
*/
|
|
456
|
+
getTelemetry() {
|
|
457
|
+
return this.telemetry.getStats();
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Reset telemetry
|
|
462
|
+
*/
|
|
463
|
+
resetTelemetry() {
|
|
464
|
+
this.telemetry.reset();
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Get all available tokenizers
|
|
469
|
+
* @returns {Array<{key: string, name: string, available: boolean}>}
|
|
470
|
+
*/
|
|
471
|
+
getAvailableTokenizers() {
|
|
472
|
+
const result = [];
|
|
473
|
+
for (const [key, adapter] of this.adapters.entries()) {
|
|
474
|
+
result.push({
|
|
475
|
+
key,
|
|
476
|
+
name: adapter.getName(),
|
|
477
|
+
available: adapter.isAvailable(),
|
|
478
|
+
exact: adapter.exact,
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
return result;
|
|
482
|
+
}
|
|
439
483
|
}
|
|
440
484
|
|
|
441
485
|
// Singleton instance
|
|
@@ -446,9 +490,9 @@ let managerInstance = null;
|
|
|
446
490
|
* @returns {Promise<TokenizerManager>}
|
|
447
491
|
*/
|
|
448
492
|
export async function getTokenizerManager() {
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
493
|
+
if (!managerInstance) {
|
|
494
|
+
managerInstance = new TokenizerManager();
|
|
495
|
+
await managerInstance.initialize();
|
|
496
|
+
}
|
|
497
|
+
return managerInstance;
|
|
454
498
|
}
|