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
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LLM Cost Estimator
|
|
3
|
+
* Estimates API costs for different LLM providers
|
|
4
|
+
* FEAT-010 feature
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { getLogger } from './logger.js';
|
|
8
|
+
import { LLMDetector } from './llm-detector.js';
|
|
9
|
+
|
|
10
|
+
const logger = getLogger('LLMCostEstimator');
|
|
11
|
+
|
|
12
|
+
/** Provider key for a profile's vendor, e.g. `OpenAI` → `openai`, `xAI` → `xai` */
|
|
13
|
+
function providerKey(vendor) {
|
|
14
|
+
return String(vendor || 'other')
|
|
15
|
+
.toLowerCase()
|
|
16
|
+
.replace(/[^a-z0-9]/g, '');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Per-1M-token prices (USD) of the models in .ctxman/llm-profiles.json, grouped by
|
|
21
|
+
* provider. The profiles are the single source: they carry the context window, the
|
|
22
|
+
* output limit and the pricing, with the date and the provider pages they were checked
|
|
23
|
+
* against. Deprecated and retired models are left out unless asked for.
|
|
24
|
+
* @param {object} [options]
|
|
25
|
+
* @param {boolean} [options.includeInactive=false] - Include deprecated/retired models
|
|
26
|
+
* @returns {Object<string, Object<string, object>>} provider → model id → pricing
|
|
27
|
+
*/
|
|
28
|
+
export function buildPricingTable({ includeInactive = false } = {}) {
|
|
29
|
+
const table = {};
|
|
30
|
+
for (const [id, profile] of Object.entries(LLMDetector.getAllProfiles())) {
|
|
31
|
+
if (!profile.pricing) continue;
|
|
32
|
+
if (!includeInactive && ['deprecated', 'retired'].includes(profile.status)) continue;
|
|
33
|
+
const provider = providerKey(profile.vendor);
|
|
34
|
+
table[provider] ??= {};
|
|
35
|
+
table[provider][id] = {
|
|
36
|
+
name: profile.name,
|
|
37
|
+
input: profile.pricing.input,
|
|
38
|
+
output: profile.pricing.output,
|
|
39
|
+
tiers: profile.pricing.tiers || [],
|
|
40
|
+
context: profile.contextWindow,
|
|
41
|
+
maxOutput: profile.outputWindow || null,
|
|
42
|
+
tier: profile.tier || null,
|
|
43
|
+
featured: Boolean(profile.featured),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
return table;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Pricing of the current models, read from the LLM profiles */
|
|
50
|
+
export const LLM_PRICING = buildPricingTable();
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Cost Estimator class
|
|
54
|
+
* Calculates and compares LLM API costs
|
|
55
|
+
*/
|
|
56
|
+
export class LLMCostEstimator {
|
|
57
|
+
/**
|
|
58
|
+
* Create a cost estimator
|
|
59
|
+
* @param {number} tokens - Total tokens in repository
|
|
60
|
+
*/
|
|
61
|
+
constructor(tokens) {
|
|
62
|
+
this.tokens = tokens;
|
|
63
|
+
logger.debug(`Initialized cost estimator with ${tokens.toLocaleString()} tokens`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Calculate cost for a specific model
|
|
68
|
+
* @param {string} modelId - Model identifier (e.g., 'gpt-4o')
|
|
69
|
+
* @param {string} provider - Provider name (e.g., 'openai')
|
|
70
|
+
* @returns {object|null} Cost breakdown or null if model not found
|
|
71
|
+
*/
|
|
72
|
+
calculateCost(modelId, provider) {
|
|
73
|
+
const pricing = LLM_PRICING[provider]?.[modelId];
|
|
74
|
+
if (!pricing) {
|
|
75
|
+
logger.debug(`Model not found: ${provider}/${modelId}`);
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Some models charge a higher rate for every token once the prompt is long
|
|
80
|
+
const tier = pricing.tiers.filter((t) => this.tokens > t.above).at(-1);
|
|
81
|
+
const rates = tier || pricing;
|
|
82
|
+
|
|
83
|
+
// Input cost: straightforward
|
|
84
|
+
const inputCost = (this.tokens / 1_000_000) * rates.input;
|
|
85
|
+
|
|
86
|
+
// Output cost: estimate output tokens as 2/3 of input (typical conversation ratio),
|
|
87
|
+
// but a response can't be longer than the model's output limit
|
|
88
|
+
const estimatedOutputTokens = Math.min(
|
|
89
|
+
Math.round(this.tokens * (2 / 3)),
|
|
90
|
+
pricing.maxOutput || Infinity
|
|
91
|
+
);
|
|
92
|
+
const outputCost = (estimatedOutputTokens / 1_000_000) * rates.output;
|
|
93
|
+
|
|
94
|
+
// Full conversation cost
|
|
95
|
+
const fullCost = inputCost + outputCost;
|
|
96
|
+
|
|
97
|
+
// Check if fits in context
|
|
98
|
+
const fitsInContext = this.tokens <= pricing.context;
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
modelId,
|
|
102
|
+
name: pricing.name,
|
|
103
|
+
provider,
|
|
104
|
+
inputCost,
|
|
105
|
+
outputCost,
|
|
106
|
+
fullCost,
|
|
107
|
+
estimatedOutputTokens,
|
|
108
|
+
fitsInContext,
|
|
109
|
+
contextLimit: pricing.context,
|
|
110
|
+
longContextRate: rates !== pricing,
|
|
111
|
+
tier: pricing.tier,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Compare costs across all models
|
|
117
|
+
* @returns {object[]} Array of cost comparisons sorted by full cost
|
|
118
|
+
*/
|
|
119
|
+
compareAll() {
|
|
120
|
+
const comparisons = [];
|
|
121
|
+
|
|
122
|
+
for (const [provider, models] of Object.entries(LLM_PRICING)) {
|
|
123
|
+
for (const modelId of Object.keys(models)) {
|
|
124
|
+
const cost = this.calculateCost(modelId, provider);
|
|
125
|
+
if (cost) {
|
|
126
|
+
comparisons.push(cost);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Sort by full cost (ascending)
|
|
132
|
+
comparisons.sort((a, b) => a.fullCost - b.fullCost);
|
|
133
|
+
|
|
134
|
+
logger.debug(`Compared costs across ${comparisons.length} models`);
|
|
135
|
+
return comparisons;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Get cheapest option that fits in context
|
|
140
|
+
* @param {object[]} comparisons - Cost comparisons array
|
|
141
|
+
* @returns {object|null} Cheapest option or null
|
|
142
|
+
*/
|
|
143
|
+
getCheapest(comparisons) {
|
|
144
|
+
const validOptions = comparisons.filter((c) => c.fitsInContext);
|
|
145
|
+
return validOptions.length > 0 ? validOptions[0] : null;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Get best value recommendation: the cheapest fitting model of the providers'
|
|
150
|
+
* balanced tier (Sonnet-class models), falling back to the cheapest fitting model
|
|
151
|
+
* @param {object[]} comparisons - Cost comparisons array
|
|
152
|
+
* @returns {object|null} Best value option or null
|
|
153
|
+
*/
|
|
154
|
+
getBestValue(comparisons) {
|
|
155
|
+
const validOptions = comparisons.filter((c) => c.fitsInContext);
|
|
156
|
+
|
|
157
|
+
if (validOptions.length === 0) {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return validOptions.find((c) => c.tier === 'balanced') || validOptions[0];
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Get recommendation message
|
|
166
|
+
* @param {object[]} comparisons - Cost comparisons array
|
|
167
|
+
* @returns {object} Recommendation object
|
|
168
|
+
*/
|
|
169
|
+
getRecommendation(comparisons) {
|
|
170
|
+
const cheapest = this.getCheapest(comparisons);
|
|
171
|
+
const bestValue = this.getBestValue(comparisons);
|
|
172
|
+
|
|
173
|
+
if (!cheapest) {
|
|
174
|
+
return {
|
|
175
|
+
type: 'warning',
|
|
176
|
+
message: 'Context exceeds all model limits. Consider chunking or filtering files.',
|
|
177
|
+
cheapest: null,
|
|
178
|
+
bestValue: null,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
type: 'recommendation',
|
|
184
|
+
cheapest,
|
|
185
|
+
bestValue,
|
|
186
|
+
message:
|
|
187
|
+
bestValue.modelId === cheapest.modelId
|
|
188
|
+
? `${cheapest.name} offers the best value`
|
|
189
|
+
: `${bestValue.name} offers the best balance of cost and quality`,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Format cost estimates for display
|
|
195
|
+
* @param {object[]} comparisons - Cost comparisons array
|
|
196
|
+
* @param {object} recommendation - Recommendation object
|
|
197
|
+
* @returns {string} Formatted output
|
|
198
|
+
*/
|
|
199
|
+
formatEstimates(comparisons, recommendation) {
|
|
200
|
+
const lines = [];
|
|
201
|
+
|
|
202
|
+
lines.push('');
|
|
203
|
+
lines.push('💰 LLM Cost Estimation:');
|
|
204
|
+
lines.push('════════════════════════════════════════════════════');
|
|
205
|
+
lines.push(` Repository: ${this.tokens.toLocaleString()} tokens`);
|
|
206
|
+
lines.push('');
|
|
207
|
+
lines.push(' Cost by Provider:');
|
|
208
|
+
lines.push(' ┌──────────────────────────┬────────────┬────────────┐');
|
|
209
|
+
lines.push(' │ Model │ Input Cost │ Full Cost │');
|
|
210
|
+
lines.push(' ├──────────────────────────┼────────────┼────────────┤');
|
|
211
|
+
|
|
212
|
+
// One or two featured models per provider keep the table short
|
|
213
|
+
const featured = comparisons
|
|
214
|
+
.filter((c) => LLM_PRICING[c.provider]?.[c.modelId]?.featured)
|
|
215
|
+
.sort((a, b) => a.provider.localeCompare(b.provider) || b.fullCost - a.fullCost);
|
|
216
|
+
|
|
217
|
+
for (const cost of featured) {
|
|
218
|
+
const inputStr = this.formatCost(cost.inputCost).padStart(10);
|
|
219
|
+
const fullStr = cost.fitsInContext
|
|
220
|
+
? this.formatCost(cost.fullCost).padStart(10)
|
|
221
|
+
: "won't fit".padStart(10);
|
|
222
|
+
lines.push(` │ ${cost.name.padEnd(24)} │${inputStr} │${fullStr} │`);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
lines.push(' └──────────────────────────┴────────────┴────────────┘');
|
|
226
|
+
const checked = LLMDetector.getDataDate();
|
|
227
|
+
lines.push(
|
|
228
|
+
` Full cost assumes a response 2/3 the size of the input.${checked ? ` Prices checked ${checked}.` : ''}`
|
|
229
|
+
);
|
|
230
|
+
lines.push('');
|
|
231
|
+
|
|
232
|
+
// Show recommendation
|
|
233
|
+
if (recommendation.type === 'warning') {
|
|
234
|
+
lines.push(` ⚠️ ${recommendation.message}`);
|
|
235
|
+
} else if (recommendation.cheapest) {
|
|
236
|
+
lines.push(
|
|
237
|
+
` 💡 Cheapest: ${recommendation.cheapest.name} (${this.formatCost(recommendation.cheapest.inputCost)})`
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (
|
|
242
|
+
recommendation.bestValue &&
|
|
243
|
+
recommendation.bestValue.modelId !== recommendation.cheapest?.modelId
|
|
244
|
+
) {
|
|
245
|
+
lines.push(
|
|
246
|
+
` ⚡ Best Value: ${recommendation.bestValue.name} (${this.formatCost(recommendation.bestValue.inputCost)})`
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
lines.push('');
|
|
251
|
+
|
|
252
|
+
return lines.join('\n');
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Format cost with proper precision
|
|
257
|
+
* @param {number} cost - Cost in USD
|
|
258
|
+
* @returns {string} Formatted cost string
|
|
259
|
+
*/
|
|
260
|
+
formatCost(cost) {
|
|
261
|
+
if (cost < 0.01) {
|
|
262
|
+
return `$${cost.toFixed(4)}`;
|
|
263
|
+
} else if (cost < 1) {
|
|
264
|
+
return `$${cost.toFixed(3)}`;
|
|
265
|
+
} else {
|
|
266
|
+
return `$${cost.toFixed(2)}`;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Run full cost estimation and return formatted output
|
|
272
|
+
* @returns {string} Formatted cost estimation
|
|
273
|
+
*/
|
|
274
|
+
estimate() {
|
|
275
|
+
const comparisons = this.compareAll();
|
|
276
|
+
const recommendation = this.getRecommendation(comparisons);
|
|
277
|
+
return this.formatEstimates(comparisons, recommendation);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
export default LLMCostEstimator;
|