slash-tokens 1.5.4 → 1.5.6

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/dist/cli.js CHANGED
@@ -90,6 +90,13 @@ var AI_PATTERNS = [
90
90
  { name: "Cohere", regex: /from\s+['"]cohere|CohereClient|cohere\.chat/g },
91
91
  { name: "Mistral", regex: /from\s+['"]@mistralai|MistralClient/g }
92
92
  ];
93
+ var SDK_REPRESENTATIVE_MODEL = {
94
+ Anthropic: "claude-sonnet",
95
+ OpenAI: "gpt-5.4",
96
+ Gemini: "gemini-3.1-pro",
97
+ Grok: "grok-4.20"
98
+ };
99
+ var UNKNOWN_SDK_REPRESENTATIVE_MODEL = "claude-sonnet";
93
100
  var SKIP_DIRS = new Set([
94
101
  "node_modules",
95
102
  ".git",
@@ -159,13 +166,15 @@ function findCallSites(filePath, content) {
159
166
  const endLine = Math.min(lines.length, lineNum + 20);
160
167
  const context = lines.slice(startLine, endLine).join(`
161
168
  `);
162
- const tokensPerCall = slash(context);
169
+ const estimatedModel = SDK_REPRESENTATIVE_MODEL[pattern.name] ?? UNKNOWN_SDK_REPRESENTATIVE_MODEL;
170
+ const tokensPerCall = slash(context, estimatedModel);
163
171
  if (!sites.some((s) => s.line === lineNum && s.sdk === pattern.name)) {
164
172
  sites.push({
165
173
  file: filePath,
166
174
  line: lineNum,
167
175
  sdk: pattern.name,
168
- tokensPerCall
176
+ tokensPerCall,
177
+ estimatedModel
169
178
  });
170
179
  }
171
180
  }
@@ -193,6 +202,24 @@ function scan(dir) {
193
202
  };
194
203
  }
195
204
 
205
+ // src/models.ts
206
+ var MODELS = {
207
+ "claude-opus": { input: 5, output: 25, context: 1e6 },
208
+ "claude-opus-4.7": { input: 5, output: 25, context: 1e6 },
209
+ "claude-sonnet": { input: 2, output: 10, context: 1e6 },
210
+ "claude-haiku": { input: 1, output: 5, context: 200000 },
211
+ "grok-4.20": { input: 1.25, output: 2.5, context: 1e6 },
212
+ "grok-4-1-fast": { input: 1.25, output: 2.5, context: 1e6 },
213
+ "gemini-3.1-pro": { input: 2, output: 12, context: 1e6 },
214
+ "gemini-2.5-flash": { input: 0.3, output: 2.5, context: 1e6 },
215
+ "gpt-5.4": { input: 2.5, output: 15, context: 1e6 },
216
+ "gpt-5.4-mini": { input: 0.75, output: 4.5, context: 128000 },
217
+ "gpt-5.4-nano": { input: 0.2, output: 1.25, context: 128000 }
218
+ };
219
+ function getModel(name) {
220
+ return MODELS[name] || MODELS[name.toLowerCase()];
221
+ }
222
+
196
223
  // src/report.ts
197
224
  var R = "\x1B[0m";
198
225
  var B = "\x1B[1m";
@@ -231,9 +258,21 @@ function printReport(sites, filesScanned, timeMs, cwd) {
231
258
  const dailyInput = sites.reduce((sum, s) => sum + s.tokensPerCall * callsPerDay, 0);
232
259
  const dailyOutput = dailyInput * 2;
233
260
  const dailyTotal = dailyInput + dailyOutput;
234
- const monthlyTotal = dailyTotal * 30;
235
- const monthlyCostInput = dailyInput * 30 / 1e6 * 3;
236
- const monthlyCostOutput = dailyOutput * 30 / 1e6 * 15;
261
+ let monthlyCostInput = 0;
262
+ let monthlyCostOutput = 0;
263
+ let unknownProviderSites = 0;
264
+ for (const site of sites) {
265
+ const info = getModel(site.estimatedModel);
266
+ if (!info)
267
+ continue;
268
+ if (site.estimatedModel === UNKNOWN_SDK_REPRESENTATIVE_MODEL && !["Anthropic"].includes(site.sdk)) {
269
+ unknownProviderSites++;
270
+ }
271
+ const siteDailyInput = site.tokensPerCall * callsPerDay;
272
+ const siteDailyOutput = siteDailyInput * 2;
273
+ monthlyCostInput += siteDailyInput * 30 / 1e6 * info.input;
274
+ monthlyCostOutput += siteDailyOutput * 30 / 1e6 * info.output;
275
+ }
237
276
  const monthlyCost = monthlyCostInput + monthlyCostOutput;
238
277
  console.log(`${ORANGE} DAILY TOKEN VOLUME ${GRAY}(${callsPerDay} calls/site/day)${R}`);
239
278
  console.log(`${DIM} ${"─".repeat(60)}${R}`);
@@ -241,11 +280,14 @@ function printReport(sites, filesScanned, timeMs, cwd) {
241
280
  console.log(`${WHITE} Output tokens: ${B}${formatNum(dailyOutput)}${R} ${GRAY}(estimated)${R}`);
242
281
  console.log(`${WHITE} Total: ${ORANGE}${B}${formatNum(dailyTotal)} tokens/day${R}`);
243
282
  console.log("");
244
- console.log(`${ORANGE} MONTHLY COST ${GRAY}($3/$15 per MTok)${R}`);
283
+ console.log(`${ORANGE} MONTHLY COST ${GRAY}(real per-provider pricing)${R}`);
245
284
  console.log(`${DIM} ${"─".repeat(60)}${R}`);
246
285
  console.log(`${WHITE} Input: ${B}$${monthlyCostInput.toFixed(2)}/mo${R}`);
247
286
  console.log(`${WHITE} Output: ${B}$${monthlyCostOutput.toFixed(2)}/mo${R}`);
248
287
  console.log(`${WHITE} Total: ${ORANGE}${B}$${monthlyCost.toFixed(2)}/mo${R}`);
288
+ if (unknownProviderSites > 0) {
289
+ console.log(`${GRAY} (${unknownProviderSites} call site${unknownProviderSites === 1 ? "" : "s"} use an assumed price — the detected SDK doesn't reveal the exact provider)${R}`);
290
+ }
249
291
  console.log("");
250
292
  const recoveryPct = 10;
251
293
  const monthlyRecovered = monthlyCost * recoveryPct / 100;
@@ -3,5 +3,7 @@ export interface Pattern {
3
3
  regex: RegExp;
4
4
  }
5
5
  export declare const AI_PATTERNS: Pattern[];
6
+ export declare const SDK_REPRESENTATIVE_MODEL: Record<string, string>;
7
+ export declare const UNKNOWN_SDK_REPRESENTATIVE_MODEL = "claude-sonnet";
6
8
  export declare const SKIP_DIRS: Set<string>;
7
9
  export declare const SCAN_EXTENSIONS: Set<string>;
package/dist/patterns.js CHANGED
@@ -20,6 +20,31 @@ export const AI_PATTERNS = [
20
20
  // Mistral
21
21
  { name: 'Mistral', regex: /from\s+['"]@mistralai|MistralClient/g },
22
22
  ];
23
+ // Representative model per detected SDK — the scanner only ever sees an
24
+ // SDK name (a regex match on import/call syntax), never the actual model
25
+ // string passed at runtime, so there's no way to know the exact model a
26
+ // found call site uses. Fixed 2026-08-23: this used to mean every SDK got
27
+ // the raw, uncalibrated token estimate AND one flat hardcoded $3/$15
28
+ // "GPT-4o class" price regardless of provider — the product's own
29
+ // top-of-funnel "here's what you'd save" numbers were both uncalibrated
30
+ // and mispriced for every provider except whichever one happened to match
31
+ // that flat rate by coincidence. Mapping each identified SDK to one real,
32
+ // representative model lets the scanner apply that model's real
33
+ // calibration factor and real price — still an approximation (we don't
34
+ // know the exact model), but a real per-provider one instead of a single
35
+ // guess applied to everyone.
36
+ export const SDK_REPRESENTATIVE_MODEL = {
37
+ 'Anthropic': 'claude-sonnet',
38
+ 'OpenAI': 'gpt-5.4',
39
+ 'Gemini': 'gemini-3.1-pro',
40
+ 'Grok': 'grok-4.20',
41
+ };
42
+ // Fallback for SDKs that don't map to one specific provider (Vercel AI,
43
+ // LangChain, and Bedrock can all wrap any underlying provider; raw
44
+ // fetch-to-AI-endpoint and Cohere/Mistral have no pricing data in MODELS
45
+ // at all). claude-sonnet is used as a documented, honest middle-of-the-
46
+ // road placeholder — not a claim about which model is actually running.
47
+ export const UNKNOWN_SDK_REPRESENTATIVE_MODEL = 'claude-sonnet';
23
48
  export const SKIP_DIRS = new Set([
24
49
  'node_modules', '.git', 'dist', 'build', '.next', '.nuxt', '.svelte-kit',
25
50
  'coverage', '.turbo', '.cache', '__pycache__', '.venv', 'venv',
@@ -48,8 +48,15 @@ export declare function preflight(content: string, model: string): PreflightResu
48
48
  * - Cheapest SAME-PROVIDER alternative by input price. If two alternatives
49
49
  * tie on price (unlikely but possible), returns the first encountered in
50
50
  * PROVIDER_MODELS order.
51
- * - Must agree with intercept.ts findCheapestRoute for identical inputs.
52
- * Cross-function semantic test: given the same (provider, tokens, model),
53
- * both functions return the same model name.
51
+ * - Must agree with intercept.ts findCheapestRoute for identical inputs,
52
+ * INCLUDING respecting the same init() config gates: shouldRoute()
53
+ * (init({route: false}) must make this always return null, matching
54
+ * patchFetch() never routing) and isModelAllowed() (init({models: [...]})
55
+ * must exclude any candidate not in that list, matching
56
+ * findCheapestRoute's real filtering). Fixed 2026-08-23 — this
57
+ * function used to ignore both, so a call site previewing "what will
58
+ * Slash route to" via preflightRoute() would show a route recommendation
59
+ * that real traffic through patchFetch() would never actually take
60
+ * under the same config, silently breaking the "matches exactly" promise.
54
61
  */
55
62
  export declare function preflightRoute(content: string, model: string): Alternative | null;
package/dist/preflight.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { slash } from './slash.js';
2
2
  import { getModel, MODELS } from './models.js';
3
3
  import { PROVIDER_MODELS, providerOf } from './providers.js';
4
+ import { shouldRoute, isModelAllowed } from './config.js';
4
5
  /**
5
6
  * Compute cost of a prompt of `tokens` tokens on the given ModelInfo.
6
7
  * Uses input-token price only (output is not known at preflight time).
@@ -69,11 +70,20 @@ export function preflight(content, model) {
69
70
  * - Cheapest SAME-PROVIDER alternative by input price. If two alternatives
70
71
  * tie on price (unlikely but possible), returns the first encountered in
71
72
  * PROVIDER_MODELS order.
72
- * - Must agree with intercept.ts findCheapestRoute for identical inputs.
73
- * Cross-function semantic test: given the same (provider, tokens, model),
74
- * both functions return the same model name.
73
+ * - Must agree with intercept.ts findCheapestRoute for identical inputs,
74
+ * INCLUDING respecting the same init() config gates: shouldRoute()
75
+ * (init({route: false}) must make this always return null, matching
76
+ * patchFetch() never routing) and isModelAllowed() (init({models: [...]})
77
+ * must exclude any candidate not in that list, matching
78
+ * findCheapestRoute's real filtering). Fixed 2026-08-23 — this
79
+ * function used to ignore both, so a call site previewing "what will
80
+ * Slash route to" via preflightRoute() would show a route recommendation
81
+ * that real traffic through patchFetch() would never actually take
82
+ * under the same config, silently breaking the "matches exactly" promise.
75
83
  */
76
84
  export function preflightRoute(content, model) {
85
+ if (!shouldRoute())
86
+ return null;
77
87
  const tokens = slash(content, model);
78
88
  const info = getModel(model);
79
89
  if (!info)
@@ -89,6 +99,8 @@ export function preflightRoute(content, model) {
89
99
  for (const m of providerModels) {
90
100
  if (m === model)
91
101
  continue;
102
+ if (!isModelAllowed(m))
103
+ continue; // user excluded this model
92
104
  const altInfo = getModel(m);
93
105
  if (!altInfo)
94
106
  continue;
package/dist/report.js CHANGED
@@ -1,3 +1,5 @@
1
+ import { getModel } from './models.js';
2
+ import { UNKNOWN_SDK_REPRESENTATIVE_MODEL } from './patterns.js';
1
3
  // ANSI — zero deps
2
4
  const R = '\x1b[0m';
3
5
  const B = '\x1b[1m';
@@ -38,10 +40,26 @@ export function printReport(sites, filesScanned, timeMs, cwd) {
38
40
  const dailyInput = sites.reduce((sum, s) => sum + s.tokensPerCall * callsPerDay, 0);
39
41
  const dailyOutput = dailyInput * 2; // Estimate output at 2x input
40
42
  const dailyTotal = dailyInput + dailyOutput;
41
- const monthlyTotal = dailyTotal * 30;
42
- // Cost at $3/MTok input, $15/MTok output (GPT-4o class)
43
- const monthlyCostInput = (dailyInput * 30 / 1000000) * 3;
44
- const monthlyCostOutput = (dailyOutput * 30 / 1000000) * 15;
43
+ // Cost per site using its own representative model's REAL current price
44
+ // (see patterns.ts SDK_REPRESENTATIVE_MODEL) — fixed 2026-08-23: this
45
+ // used to be one flat hardcoded $3/$15 "GPT-4o class" rate applied to
46
+ // every site regardless of detected provider, understating cost by up
47
+ // to ~67% for premium providers and overstating it for cheap ones.
48
+ let monthlyCostInput = 0;
49
+ let monthlyCostOutput = 0;
50
+ let unknownProviderSites = 0;
51
+ for (const site of sites) {
52
+ const info = getModel(site.estimatedModel);
53
+ if (!info)
54
+ continue; // shouldn't happen — estimatedModel always maps to a real MODELS entry
55
+ if (site.estimatedModel === UNKNOWN_SDK_REPRESENTATIVE_MODEL && !['Anthropic'].includes(site.sdk)) {
56
+ unknownProviderSites++;
57
+ }
58
+ const siteDailyInput = site.tokensPerCall * callsPerDay;
59
+ const siteDailyOutput = siteDailyInput * 2;
60
+ monthlyCostInput += (siteDailyInput * 30 / 1000000) * info.input;
61
+ monthlyCostOutput += (siteDailyOutput * 30 / 1000000) * info.output;
62
+ }
45
63
  const monthlyCost = monthlyCostInput + monthlyCostOutput;
46
64
  console.log(`${ORANGE} DAILY TOKEN VOLUME ${GRAY}(${callsPerDay} calls/site/day)${R}`);
47
65
  console.log(`${DIM} ${'─'.repeat(60)}${R}`);
@@ -49,11 +67,14 @@ export function printReport(sites, filesScanned, timeMs, cwd) {
49
67
  console.log(`${WHITE} Output tokens: ${B}${formatNum(dailyOutput)}${R} ${GRAY}(estimated)${R}`);
50
68
  console.log(`${WHITE} Total: ${ORANGE}${B}${formatNum(dailyTotal)} tokens/day${R}`);
51
69
  console.log('');
52
- console.log(`${ORANGE} MONTHLY COST ${GRAY}($3/$15 per MTok)${R}`);
70
+ console.log(`${ORANGE} MONTHLY COST ${GRAY}(real per-provider pricing)${R}`);
53
71
  console.log(`${DIM} ${'─'.repeat(60)}${R}`);
54
72
  console.log(`${WHITE} Input: ${B}$${monthlyCostInput.toFixed(2)}/mo${R}`);
55
73
  console.log(`${WHITE} Output: ${B}$${monthlyCostOutput.toFixed(2)}/mo${R}`);
56
74
  console.log(`${WHITE} Total: ${ORANGE}${B}$${monthlyCost.toFixed(2)}/mo${R}`);
75
+ if (unknownProviderSites > 0) {
76
+ console.log(`${GRAY} (${unknownProviderSites} call site${unknownProviderSites === 1 ? '' : 's'} use an assumed price — the detected SDK doesn't reveal the exact provider)${R}`);
77
+ }
57
78
  console.log('');
58
79
  // Slash — sunk cost recovery (conservative 10% gate efficiency)
59
80
  const recoveryPct = 10;
package/dist/scanner.d.ts CHANGED
@@ -3,6 +3,10 @@ export interface CallSite {
3
3
  line: number;
4
4
  sdk: string;
5
5
  tokensPerCall: number;
6
+ /** The representative model used to calibrate/price this site — see
7
+ * patterns.ts SDK_REPRESENTATIVE_MODEL. Not the actual runtime model
8
+ * (the scanner can't know that from source alone). */
9
+ estimatedModel: string;
6
10
  }
7
11
  export declare function scan(dir: string): {
8
12
  sites: CallSite[];
package/dist/scanner.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { readdirSync, readFileSync, statSync } from 'fs';
2
2
  import { join, extname } from 'path';
3
3
  import { slash } from './slash.js';
4
- import { AI_PATTERNS, SKIP_DIRS, SCAN_EXTENSIONS } from './patterns.js';
4
+ import { AI_PATTERNS, SKIP_DIRS, SCAN_EXTENSIONS, SDK_REPRESENTATIVE_MODEL, UNKNOWN_SDK_REPRESENTATIVE_MODEL, } from './patterns.js';
5
5
  function walkDir(dir, files) {
6
6
  let entries;
7
7
  try {
@@ -44,7 +44,8 @@ function findCallSites(filePath, content) {
44
44
  const startLine = Math.max(0, lineNum - 5);
45
45
  const endLine = Math.min(lines.length, lineNum + 20);
46
46
  const context = lines.slice(startLine, endLine).join('\n');
47
- const tokensPerCall = slash(context);
47
+ const estimatedModel = SDK_REPRESENTATIVE_MODEL[pattern.name] ?? UNKNOWN_SDK_REPRESENTATIVE_MODEL;
48
+ const tokensPerCall = slash(context, estimatedModel);
48
49
  // Avoid duplicates at same location
49
50
  if (!sites.some(s => s.line === lineNum && s.sdk === pattern.name)) {
50
51
  sites.push({
@@ -52,6 +53,7 @@ function findCallSites(filePath, content) {
52
53
  line: lineNum,
53
54
  sdk: pattern.name,
54
55
  tokensPerCall,
56
+ estimatedModel,
55
57
  });
56
58
  }
57
59
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "slash-tokens",
3
- "version": "1.5.4",
3
+ "version": "1.5.6",
4
4
  "description": "Token Optimization for Context Engineers. 4.8 KB WASM. Sub-millisecond. Zero dependencies.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",