slash-tokens 1.6.5 → 1.6.7

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/CHANGELOG.md CHANGED
@@ -1,5 +1,42 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.6.7] — The Fixed Deal Edition
4
+
5
+ *2026-10-08*
6
+
7
+ One very large request (about 1 MB of text) no longer skews every count after it.
8
+
9
+ Solo $20 mailbox, 10% waived. Team $39 for the data.
10
+
11
+ ### Fixed
12
+ - **A single request over ~1.04 MB of text no longer skews every later estimate in the same process.** Input was written into WASM memory from byte 4096, so a big one ran over the module's stack and lookup tables (at 1 MiB), and later estimates came out wrong — a 14-token prompt read as 28. Input now starts above them (byte 1,114,112). Estimates for inputs under the threshold are unchanged (checked against 1.6.6 on the 29-sample corpus and five models). Older versions are affected too (any version that writes input at byte 4096, including 1.6.6).
13
+ - **The test suite now passes file by file.** `preflight.test.ts` failed when run alone: on a tiny prompt the routed model's cost rounds to the same 6-decimal value as the original's. It passed in the full suite only because the bug above had already inflated the counts. The test now checks a lower list price, and strictly lower cost on a prompt big enough to show it.
14
+
15
+ No Team/Solo price change. No routing or pricing change.
16
+
17
+ ## [1.6.6] — The Fixed Deal Edition
18
+
19
+ *2026-10-07*
20
+
21
+ `--version` / `--help` answer, and today's models price correctly.
22
+
23
+ Solo $20 mailbox, 10% waived. Team $39 for the data.
24
+
25
+ ### Fixed
26
+ - **`slash-tokens --version` and `--help` print and exit.** In 1.6.5 every flag fell through to a full scan of the current folder, so `--version` run from `~` scanned the home folder. An empty `bunx` run now tells you to run it in an app.
27
+ - **`preflight()` no longer throws on current models or real API IDs.** IDs are canonicalised strictly (`claude-opus-4-7` → `claude-opus-4.7`, date stamps dropped). There is no family guessing: an unknown version still fails with "Unknown model", so a new model never silently gets an older model's price.
28
+ - **`npm publish` always builds first** (`prepublishOnly`), so a stale `dist/` can't ship again (1.6.4).
29
+ - **`report()` sends numbers only.** A test pins its payload to `tokens_estimated`, `tokens_saved`, `model`, `action`, `cost_saved_usd`; prompt content never leaves the machine.
30
+ - **`npm test` runs offline.** The live integration suite (`tests/z-integration.test.ts`, which registers keys on mcpaas.live) runs only with `SLASH_LIVE=1`; the weekly `integration.yml` sets it.
31
+
32
+ ### Changed
33
+ - **Model table, prices as of 2026-10-07** (checked against the Anthropic, OpenAI, xAI and Google pricing pages). Added Claude Opus 5.5 ($4/$20), Sonnet 5.5 ($2/$10), Opus 4.8 / 4.6 / 4.5, Sonnet 4.6 / 4.5, Fable 5 / 5.1 and Mythos 5 / 5.1 ($10/$50); Grok 4.7 and 4.5 ($2/$6, doubling above 200K), grok-build-0.1 ($1/$2); GPT-6 Astra ($10/$50), GPT-6.1 Sol and GPT-6 Sol ($2/$10), GPT-6 Luna ($0.10/$0.50); Gemini 3.8 / 3.7 / 3.6 Flash ($0.75/$3.75 launch price through 2026-12-31), 3.5 Flash ($1.50/$9), 3.1 Flash-Lite ($0.25/$1.50), 3.1 Pro Preview.
34
+ - **`slash-tokens/auto` routing is unchanged within 1.6.x.** `/auto` rewrites live requests only to the models it routed to in 1.6.5 (`AUTO_ROUTE_TARGETS`), using the same identification, so a patch upgrade never changes where production calls go. New models are now recognised and priced correctly in `/auto` events (`identifyModel`: `claude-opus-5-5` at $4/$20, `gpt-6-astra` at $10/$50); /auto starts routing to them in 1.7.0.
35
+ - **Routing (`preflightRoute`) follows the new ladder.** Cheapest same-provider is now GPT-6 Luna for OpenAI and Gemini 3.1 Flash-Lite for Google (Anthropic and xAI unchanged). grok-build-0.1 is priced but never a routing target (`NOT_ROUTE_TARGETS`): it's a coding-agent model. When two alternatives' costs round to the same value (tiny prompts), the lower list price wins.
36
+ - **Calibration:** new Claude, Grok and Gemini models use their family's measured factor. GPT-6 is a new generation with an unbenchmarked tokenizer, so it takes the conservative default until a bench run adds it (slash never under-reports).
37
+
38
+ No Team/Solo price change.
39
+
3
40
  ## [1.6.5] — The Fixed Deal Edition
4
41
 
5
42
  *2026-08-25*
package/README.md CHANGED
@@ -15,10 +15,16 @@ Know the cost before the call leaves your machine.
15
15
  Models change. Windows grow. Slash adapts — you keep building.
16
16
  Cheaper tokens haven't shrunk the bill — usage has.
17
17
 
18
- ## v1.6.5 — The Fixed Deal Edition
18
+ ## v1.6.7 — The Fixed Deal Edition
19
+
20
+ One very large request (about 1 MB of text) no longer skews every count after it.
19
21
 
20
22
  Solo $20 mailbox, 10% waived. Team $39 for the data.
21
23
 
24
+ New in 1.6.7: a single request over ~1 MB of text used to overwrite the token counter's lookup tables, so every later count in that process came out wrong; prompts now go above them. Counts for normal prompts are unchanged.
25
+
26
+ New in 1.6.6: Claude Opus 5.5 / Sonnet 5.5 / Fable 5.1, Grok 4.7, GPT-6 (Astra, Sol, Luna), Gemini 3.6–3.8 Flash and 3.1 Flash-Lite, priced as of 2026-10-07. Real API IDs (`claude-opus-4-7`) work in `preflight()`. `preflightRoute()` now finds GPT-6 Luna and Gemini 3.1 Flash-Lite as the cheapest same-provider options.
27
+
22
28
  **Free forever is bunx** — no account. A one-person account is email → key, **$20 on the house**. We show the savings. We don't charge. 10% is the model, waived. Team is **$39 for the data** (`$390`/year).
23
29
 
24
30
  ```bash
@@ -26,7 +32,7 @@ bunx slash-tokens
26
32
  # or: npx --yes slash-tokens
27
33
  ```
28
34
 
29
- Run it in a project that already calls an LLM. An empty folder prints that nothing was found — that's normal.
35
+ Run it in a project that already calls an LLM. An empty folder prints that nothing was found, then tells you to run it in an app — that's normal. `--version` / `--help` print and exit (they do not scan). Pin proof: `slash-tokens --version` or `npm view slash-tokens version`.
30
36
 
31
37
  ```bash
32
38
  npm install slash-tokens
package/dist/cli.js CHANGED
@@ -1,12 +1,17 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ // src/cli.ts
4
+ import { readFileSync as readFileSync2 } from "node:fs";
5
+ import { dirname, join as join2 } from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+
3
8
  // src/scanner.ts
4
9
  import { readdirSync, readFileSync, statSync } from "fs";
5
10
  import { join, extname } from "path";
6
11
 
7
12
  // src/wasm.ts
8
13
  var WASM_BASE64 = "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AA4AAABLAxAAEwAAALABEAAQAAAAGgIQABYAAAAwBBAADQAAAJ0DEAAOAAAAsQMQAA0AAAB6AxAADgAAAMsCEAAQAAAAbAMQAA0AAAA+BBAACwAAAFYEEAAKAAAAvwMQAAwAAAAjAxAADAAAAP0DEAAVAAAAsQIQABEAAAAxAhAAEgAAAMEBEAARAAAAiQMQABMAAADoAhAAEgAAAPABEAARAAAAIQQQAA4AAABLAxAAEwAAALABEAAQAAAAGgIQABYAAAAwBBAADQAAAJ0DEAAOAAAAsQMQAA0AAAB6AxAADgAAAMsCEAAQAAAAbAMQAA0AAAA+BBAACwAAAFYEEAAKAAAACgMQABMAAACbAhAAFQAAAG4CEAAQAAAAAgIQABcAAAB/AhAAGwAAANwCEAALAAAA8QMQAAsAAABfAxAADAAAABMEEAANAAAA0wEQABwAAABWAhAAFwAAANsDEAAVAAAAc3RhY2sudWlfbGlicmFyeQBodW1hbl9jb250ZXh0LndoeQBtb25vcmVwby52ZXJzaW9uaW5nX3N0cmF0ZWd5AGh1bWFuX2NvbnRleHQuaG93AG1vbm9yZXBvLnBhY2thZ2VzX2NvdW50AHN0YWNrLnN0YXRlX21hbmFnZW1lbnQAaHVtYW5fY29udGV4dC53aGF0AGRldmVsb3BtZW50IHRlYW1zAG1vbm9yZXBvLnNoYXJlZF9jb25maWdzAHN0YWNrLndvcmtzcGFjZXMAbW9ub3JlcG8uYnVpbGRfb3JjaGVzdHJhdG9yAHN0YWNrLnBhY2thZ2VfbWFuYWdlcgBodW1hbl9jb250ZXh0LndobwB1bmtub3duAHN0YWNrLmNvbm5lY3Rpb24Ac3RhY2suYWRtaW4AaHVtYW5fY29udGV4dC53aGVuAGNsb3VkIHBsYXRmb3JtAHN0YWNrLm1vbm9yZXBvX3Rvb2wAbnVsbABwcm9qZWN0LmdvYWwAZGVzY3JpYmUgeW91ciBwcm9qZWN0IGdvYWwAc3RhY2suY3NzX2ZyYW1ld29yawBzdGFjay5zZWFyY2gAc3RhY2suaG9zdGluZwBzdGFjay5kYXRhYmFzZQBodW1hbl9jb250ZXh0LndoZXJlAHN0YWNrLmFwaV90eXBlAG5vbmUAc3RhY2sucnVudGltZQBwcm9qZWN0Lm5hbWUAbm90IGFwcGxpY2FibGUAbW9ub3JlcG8ucmVtb3RlX2NhY2hlAHN0YWNrLmNhY2hlAHByb2plY3QubWFpbl9sYW5ndWFnZQBzdGFjay5zdG9yYWdlAHN0YWNrLmZyb250ZW5kAHN0YWNrLmJhY2tlbmQAc3RhY2suYnVpbGQAc2xvdGlnbm9yZWQAc3RhY2suY2ljZABuL2EAOgAuACAJDQoiJwAgCSInAA0ACgAgCQAwAxAAGgAAAEQCEAARAAAA+wIQAA4AAAAeAxAABAAAAKwDEAAEAAAAwwIQAAcAAABhBBAAAwAAAMwDEAAOAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAABAAAAAAAAAAAAAAA=";
9
- var WASM_INPUT_OFFSET = 4096;
14
+ var WASM_INPUT_OFFSET = 1114112;
10
15
  var encoder = new TextEncoder;
11
16
  var _instance = null;
12
17
  function decodeBase64(b64) {
@@ -31,7 +36,7 @@ function getInstance() {
31
36
  var MAX_WASM_INPUT_BYTES = 32 * 1024 * 1024;
32
37
  var WASM_PAGE_BYTES = 65536;
33
38
  function ensureCapacity(memory, neededBytes) {
34
- const cappedNeeded = Math.min(neededBytes, MAX_WASM_INPUT_BYTES);
39
+ const cappedNeeded = Math.min(neededBytes, WASM_INPUT_OFFSET + MAX_WASM_INPUT_BYTES);
35
40
  if (memory.buffer.byteLength >= cappedNeeded)
36
41
  return;
37
42
  const additionalPages = Math.ceil((cappedNeeded - memory.buffer.byteLength) / WASM_PAGE_BYTES);
@@ -49,10 +54,136 @@ function writeToMemory(content) {
49
54
  return maxLen;
50
55
  }
51
56
 
57
+ // src/models.ts
58
+ var OPUS = { input: 5, output: 25, context: 1e6 };
59
+ var OPUS_55 = { input: 4, output: 20, context: 1e6 };
60
+ var FABLE = { input: 10, output: 50, context: 1e6 };
61
+ var SONNET_4X = { input: 3, output: 15, context: 1e6 };
62
+ var SONNET = { input: 2, output: 10, context: 1e6 };
63
+ var HAIKU = { input: 1, output: 5, context: 200000 };
64
+ var GROK_46 = {
65
+ input: 2,
66
+ output: 6,
67
+ context: 500000,
68
+ longContextThreshold: 200000,
69
+ longContextInput: 4,
70
+ longContextOutput: 12
71
+ };
72
+ var GROK_43 = {
73
+ input: 1.25,
74
+ output: 2.5,
75
+ context: 1e6,
76
+ longContextThreshold: 200000,
77
+ longContextInput: 2.5,
78
+ longContextOutput: 5
79
+ };
80
+ var GEMINI_PRO = {
81
+ input: 2,
82
+ output: 12,
83
+ context: 1e6,
84
+ longContextThreshold: 200000,
85
+ longContextInput: 4,
86
+ longContextOutput: 18
87
+ };
88
+ var GEMINI_FLASH = { input: 0.3, output: 2.5, context: 1e6 };
89
+ var GEMINI_FLASH_3X = { input: 0.75, output: 3.75, context: 1e6 };
90
+ var GEMINI_35_FLASH = { input: 1.5, output: 9, context: 1e6 };
91
+ var GEMINI_31_FLASH_LITE = { input: 0.25, output: 1.5, context: 1e6 };
92
+ var GROK_BUILD = {
93
+ input: 1,
94
+ output: 2,
95
+ context: 256000,
96
+ longContextThreshold: 200000,
97
+ longContextInput: 2,
98
+ longContextOutput: 4
99
+ };
100
+ var GPT_6_ASTRA = { input: 10, output: 50, context: 1050000 };
101
+ var GPT_6_SOL = { input: 2, output: 10, context: 1050000 };
102
+ var GPT_6_LUNA = { input: 0.1, output: 0.5, context: 1050000 };
103
+ var GPT_SOL = { input: 4, output: 20, context: 1050000 };
104
+ var GPT_TERRA = { input: 2, output: 12, context: 1050000 };
105
+ var GPT_LUNA = { input: 0.2, output: 1.2, context: 1050000 };
106
+ var GPT_54 = { input: 2.5, output: 15, context: 1e6 };
107
+ var GPT_54_MINI = { input: 0.75, output: 4.5, context: 128000 };
108
+ var GPT_54_NANO = { input: 0.2, output: 1.25, context: 128000 };
109
+ var MODELS = {
110
+ "claude-fable-5.1": { ...FABLE },
111
+ "claude-fable-5": { ...FABLE },
112
+ "claude-mythos-5.1": { ...FABLE },
113
+ "claude-mythos-5": { ...FABLE },
114
+ "claude-opus-5.5": { ...OPUS_55 },
115
+ "claude-opus-5": { ...OPUS },
116
+ "claude-opus-4.8": { ...OPUS },
117
+ "claude-opus": { ...OPUS },
118
+ "claude-opus-4.7": { ...OPUS },
119
+ "claude-opus-4.6": { ...OPUS },
120
+ "claude-opus-4.5": { ...OPUS },
121
+ "claude-sonnet-5.5": { ...SONNET },
122
+ "claude-sonnet-5": { ...SONNET },
123
+ "claude-sonnet": { ...SONNET },
124
+ "claude-sonnet-4.6": { ...SONNET_4X },
125
+ "claude-sonnet-4.5": { ...SONNET_4X },
126
+ "claude-haiku-4.5": { ...HAIKU },
127
+ "claude-haiku": { ...HAIKU },
128
+ "grok-4.7": { ...GROK_46 },
129
+ "grok-4.6": { ...GROK_46 },
130
+ "grok-4.5": { ...GROK_46 },
131
+ "grok-build-0.1": { ...GROK_BUILD },
132
+ "grok-4.3": { ...GROK_43 },
133
+ "grok-4.20": { ...GROK_43 },
134
+ "grok-4-1-fast": { ...GROK_43 },
135
+ "gemini-3.1-pro": { ...GEMINI_PRO },
136
+ "gemini-3.1-pro-preview": { ...GEMINI_PRO },
137
+ "gemini-3.8-flash": { ...GEMINI_FLASH_3X },
138
+ "gemini-3.7-flash": { ...GEMINI_FLASH_3X },
139
+ "gemini-3.6-flash": { ...GEMINI_FLASH_3X },
140
+ "gemini-3.5-flash": { ...GEMINI_35_FLASH },
141
+ "gemini-3.1-flash-lite": { ...GEMINI_31_FLASH_LITE },
142
+ "gemini-3.5-flash-lite": { ...GEMINI_FLASH },
143
+ "gemini-2.5-flash": { ...GEMINI_FLASH },
144
+ "gpt-6-astra": { ...GPT_6_ASTRA },
145
+ "gpt-6.1-sol": { ...GPT_6_SOL },
146
+ "gpt-6-sol": { ...GPT_6_SOL },
147
+ "gpt-6-luna": { ...GPT_6_LUNA },
148
+ "gpt-5.6-sol": { ...GPT_SOL },
149
+ "gpt-5.6-terra": { ...GPT_TERRA },
150
+ "gpt-5.6-luna": { ...GPT_LUNA },
151
+ "gpt-5.4": { ...GPT_54 },
152
+ "gpt-5.4-mini": { ...GPT_54_MINI },
153
+ "gpt-5.4-nano": { ...GPT_54_NANO }
154
+ };
155
+ function canonicalModel(name) {
156
+ if (MODELS[name])
157
+ return name;
158
+ let n = name.trim().toLowerCase();
159
+ if (MODELS[n])
160
+ return n;
161
+ n = n.replace(/-\d{8}$/, "");
162
+ if (MODELS[n])
163
+ return n;
164
+ const dotted = n.replace(/(\d)-(\d)(?=$|-)/g, "$1.$2");
165
+ if (MODELS[dotted])
166
+ return dotted;
167
+ return n;
168
+ }
169
+ function getModel(name) {
170
+ return MODELS[canonicalModel(name)];
171
+ }
172
+
52
173
  // src/slash.ts
53
- var WASM_INPUT_OFFSET2 = 4096;
54
174
  var CALIBRATION = {
175
+ "claude-fable-5.1": 2.05,
176
+ "claude-fable-5": 2.05,
177
+ "claude-mythos-5.1": 2.05,
178
+ "claude-mythos-5": 2.05,
179
+ "claude-opus-5.5": 2.05,
55
180
  "claude-opus-5": 2.05,
181
+ "claude-opus-4.8": 2.05,
182
+ "claude-opus-4.6": 2.05,
183
+ "claude-opus-4.5": 2.05,
184
+ "claude-sonnet-5.5": 2.05,
185
+ "claude-sonnet-4.6": 2.05,
186
+ "claude-sonnet-4.5": 2.05,
56
187
  "claude-opus": 2.05,
57
188
  "claude-opus-4.7": 2.05,
58
189
  "claude-sonnet-5": 2.05,
@@ -60,9 +191,18 @@ var CALIBRATION = {
60
191
  "claude-haiku-4.5": 1.45,
61
192
  "claude-haiku": 1.45,
62
193
  "gemini-3.1-pro": 1.45,
194
+ "gemini-3.1-pro-preview": 1.45,
195
+ "gemini-3.8-flash": 1.45,
196
+ "gemini-3.7-flash": 1.45,
197
+ "gemini-3.6-flash": 1.45,
198
+ "gemini-3.5-flash": 1.45,
199
+ "gemini-3.1-flash-lite": 1.45,
63
200
  "gemini-3.5-flash-lite": 1.45,
64
201
  "gemini-2.5-flash": 1.45,
202
+ "grok-4.7": 1.15,
65
203
  "grok-4.6": 1.15,
204
+ "grok-4.5": 1.15,
205
+ "grok-build-0.1": 1.15,
66
206
  "grok-4.3": 1.15,
67
207
  "grok-4.20": 1.15,
68
208
  "grok-4-1-fast": 1.15,
@@ -79,10 +219,10 @@ function slash(content, model) {
79
219
  return 0;
80
220
  const len = writeToMemory(content);
81
221
  const instance = getInstance();
82
- const raw = instance.exports.estimate_tokens(WASM_INPUT_OFFSET2, len);
222
+ const raw = instance.exports.estimate_tokens(WASM_INPUT_OFFSET, len);
83
223
  if (!model)
84
224
  return raw;
85
- const factor = CALIBRATION[model] ?? DEFAULT_UNKNOWN_MODEL_FACTOR;
225
+ const factor = CALIBRATION[canonicalModel(model)] ?? DEFAULT_UNKNOWN_MODEL_FACTOR;
86
226
  return factor === 1 ? raw : Math.ceil(raw * factor);
87
227
  }
88
228
 
@@ -211,67 +351,6 @@ function scan(dir) {
211
351
  };
212
352
  }
213
353
 
214
- // src/models.ts
215
- var OPUS = { input: 5, output: 25, context: 1e6 };
216
- var SONNET = { input: 2, output: 10, context: 1e6 };
217
- var HAIKU = { input: 1, output: 5, context: 200000 };
218
- var GROK_46 = {
219
- input: 2,
220
- output: 6,
221
- context: 500000,
222
- longContextThreshold: 200000,
223
- longContextInput: 4,
224
- longContextOutput: 12
225
- };
226
- var GROK_43 = {
227
- input: 1.25,
228
- output: 2.5,
229
- context: 1e6,
230
- longContextThreshold: 200000,
231
- longContextInput: 2.5,
232
- longContextOutput: 5
233
- };
234
- var GEMINI_PRO = {
235
- input: 2,
236
- output: 12,
237
- context: 1e6,
238
- longContextThreshold: 200000,
239
- longContextInput: 4,
240
- longContextOutput: 18
241
- };
242
- var GEMINI_FLASH = { input: 0.3, output: 2.5, context: 1e6 };
243
- var GPT_SOL = { input: 4, output: 20, context: 1050000 };
244
- var GPT_TERRA = { input: 2, output: 12, context: 1050000 };
245
- var GPT_LUNA = { input: 0.2, output: 1.2, context: 1050000 };
246
- var GPT_54 = { input: 2.5, output: 15, context: 1e6 };
247
- var GPT_54_MINI = { input: 0.75, output: 4.5, context: 128000 };
248
- var GPT_54_NANO = { input: 0.2, output: 1.25, context: 128000 };
249
- var MODELS = {
250
- "claude-opus-5": { ...OPUS },
251
- "claude-opus": { ...OPUS },
252
- "claude-opus-4.7": { ...OPUS },
253
- "claude-sonnet-5": { ...SONNET },
254
- "claude-sonnet": { ...SONNET },
255
- "claude-haiku-4.5": { ...HAIKU },
256
- "claude-haiku": { ...HAIKU },
257
- "grok-4.6": { ...GROK_46 },
258
- "grok-4.3": { ...GROK_43 },
259
- "grok-4.20": { ...GROK_43 },
260
- "grok-4-1-fast": { ...GROK_43 },
261
- "gemini-3.1-pro": { ...GEMINI_PRO },
262
- "gemini-3.5-flash-lite": { ...GEMINI_FLASH },
263
- "gemini-2.5-flash": { ...GEMINI_FLASH },
264
- "gpt-5.6-sol": { ...GPT_SOL },
265
- "gpt-5.6-terra": { ...GPT_TERRA },
266
- "gpt-5.6-luna": { ...GPT_LUNA },
267
- "gpt-5.4": { ...GPT_54 },
268
- "gpt-5.4-mini": { ...GPT_54_MINI },
269
- "gpt-5.4-nano": { ...GPT_54_NANO }
270
- };
271
- function getModel(name) {
272
- return MODELS[name] || MODELS[name.toLowerCase()];
273
- }
274
-
275
354
  // src/report.ts
276
355
  var R = "\x1B[0m";
277
356
  var B = "\x1B[1m";
@@ -296,6 +375,7 @@ function printReport(sites, filesScanned, timeMs, cwd) {
296
375
  if (sites.length === 0) {
297
376
  console.log(`${WHITE} No AI API call sites detected.${R}`);
298
377
  console.log(`${GRAY} Supported: OpenAI, Anthropic, Vercel AI, LangChain, Gemini, Bedrock, Grok${R}`);
378
+ console.log(`${GRAY} Run this in a project that already calls an LLM.${R}`);
299
379
  console.log("");
300
380
  return;
301
381
  }
@@ -367,6 +447,14 @@ function init(opts) {
367
447
 
368
448
  // src/cli.ts
369
449
  var args = process.argv.slice(2);
450
+ if (args.includes("--version") || args.includes("-V")) {
451
+ console.log(packageVersion());
452
+ process.exit(0);
453
+ }
454
+ if (args.includes("--help") || args.includes("-h")) {
455
+ printHelp();
456
+ process.exit(0);
457
+ }
370
458
  var keyArg = args.find((a) => a.startsWith("--key="));
371
459
  var key = keyArg?.split("=")[1] || process.env.SLASH_KEY;
372
460
  if (key)
@@ -388,3 +476,21 @@ if (sites.length > 0) {
388
476
  }
389
477
  console.log("");
390
478
  }
479
+ function packageVersion() {
480
+ const here = dirname(fileURLToPath(import.meta.url));
481
+ const pkg = JSON.parse(readFileSync2(join2(here, "..", "package.json"), "utf8"));
482
+ return pkg.version;
483
+ }
484
+ function printHelp() {
485
+ console.log(`slash-tokens — Token Optimization for Context Engineers
486
+
487
+ bunx slash-tokens try (scan this directory, no account)
488
+ npm install slash-tokens SDK
489
+
490
+ --version, -V print version and exit
491
+ --help, -h print this help and exit
492
+ --key=KEY optional; CLI scans never charge
493
+
494
+ Run in a project that already calls an LLM.
495
+ Empty folder → no call sites. That's normal.`);
496
+ }
@@ -11,6 +11,14 @@ export interface InterceptEvent {
11
11
  routed: boolean;
12
12
  timestamp: string;
13
13
  }
14
+ /**
15
+ * The model a request actually names, for PRICING: the exact table entry when
16
+ * the strict canonical ID matches (claude-opus-5-5 → claude-opus-5.5,
17
+ * gpt-6-luna), else the legacy family mapping. Routing keeps using
18
+ * normalizeModel() so /auto decisions are unchanged within 1.6.x.
19
+ */
20
+ export declare function identifyModel(raw: string): string;
14
21
  export declare function normalizeModel(raw: string): string;
22
+ export declare function findCheapestRoute(provider: string, tokens: number, currentModel: string): string | null;
15
23
  export declare function onIntercept(handler: (event: InterceptEvent) => void): void;
16
24
  export declare function patchFetch(): void;
package/dist/intercept.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { slash } from './slash.js';
2
- import { getModel } from './models.js';
2
+ import { getModel, MODELS, canonicalModel } from './models.js';
3
3
  import { shouldRoute, isModelAllowed } from './config.js';
4
- import { PROVIDER_MODELS } from './providers.js';
4
+ import { PROVIDER_MODELS, AUTO_ROUTE_TARGETS, NOT_ROUTE_TARGETS } from './providers.js';
5
5
  // Reverse lookup: model name → provider model names in the API
6
6
  // (what to put back in the request body)
7
7
  const MODEL_API_NAMES = {
@@ -62,6 +62,16 @@ const AI_ENDPOINTS = [
62
62
  // (getModel() returning undefined → $0 reported cost, see slash.ts's
63
63
  // DEFAULT_UNKNOWN_MODEL_FACTOR comment for why "unrecognized" defaulting
64
64
  // to a falsely-safe-looking value is the dangerous case).
65
+ /**
66
+ * The model a request actually names, for PRICING: the exact table entry when
67
+ * the strict canonical ID matches (claude-opus-5-5 → claude-opus-5.5,
68
+ * gpt-6-luna), else the legacy family mapping. Routing keeps using
69
+ * normalizeModel() so /auto decisions are unchanged within 1.6.x.
70
+ */
71
+ export function identifyModel(raw) {
72
+ const c = canonicalModel(raw);
73
+ return MODELS[c] ? c : normalizeModel(raw);
74
+ }
65
75
  export function normalizeModel(raw) {
66
76
  const lower = raw.toLowerCase();
67
77
  // Anthropic — specific versions before generic family
@@ -148,7 +158,7 @@ function extractContent(body) {
148
158
  return JSON.stringify(body);
149
159
  }
150
160
  // Find cheapest model from same provider that fits
151
- function findCheapestRoute(provider, tokens, currentModel) {
161
+ export function findCheapestRoute(provider, tokens, currentModel) {
152
162
  const providerModels = PROVIDER_MODELS[provider];
153
163
  if (!providerModels)
154
164
  return null;
@@ -156,6 +166,10 @@ function findCheapestRoute(provider, tokens, currentModel) {
156
166
  for (const model of providerModels) {
157
167
  if (model === currentModel)
158
168
  continue;
169
+ if (!AUTO_ROUTE_TARGETS.has(model))
170
+ continue; // 1.6.x: /auto rewrites only to its 1.6.5 targets
171
+ if (NOT_ROUTE_TARGETS.has(model))
172
+ continue; // specialised models are never targets
159
173
  if (!isModelAllowed(model))
160
174
  continue; // user excluded this model
161
175
  const info = getModel(model);
@@ -188,13 +202,14 @@ export function patchFetch() {
188
202
  const body = JSON.parse(bodyStr);
189
203
  const content = extractContent(body);
190
204
  const rawModel = match.modelExtractor(body, url);
191
- const originalModel = normalizeModel(rawModel);
205
+ const routingModel = normalizeModel(rawModel); // decides routes (unchanged in 1.6.x)
206
+ const originalModel = identifyModel(rawModel); // prices the request it really names
192
207
  const tokens = slash(content, originalModel);
193
208
  const originalInfo = getModel(originalModel);
194
209
  const originalCost = originalInfo ? Math.round(((tokens / 1000000) * originalInfo.input) * 1000000) / 1000000 : 0;
195
210
  const fits = originalInfo ? tokens <= originalInfo.context : true;
196
211
  // Find cheapest route within same provider (if routing enabled)
197
- const routeModel = shouldRoute() ? findCheapestRoute(match.provider, tokens, originalModel) : null;
212
+ const routeModel = shouldRoute() ? findCheapestRoute(match.provider, tokens, routingModel) : null;
198
213
  const routedInfo = routeModel ? getModel(routeModel) : null;
199
214
  const routedCost = routedInfo ? Math.round(((tokens / 1000000) * routedInfo.input) * 1000000) / 1000000 : originalCost;
200
215
  const salvaged = routeModel ? Math.round((originalCost - routedCost) * 1000000) / 1000000 : 0;
package/dist/models.d.ts CHANGED
@@ -7,6 +7,13 @@ export interface ModelInfo {
7
7
  longContextOutput?: number;
8
8
  }
9
9
  export declare const MODELS: Record<string, ModelInfo>;
10
+ /**
11
+ * Real API IDs → table keys, strictly: lowercase, drop a trailing date stamp
12
+ * (`-20250514`), and write version numbers with dots (`claude-opus-4-7` →
13
+ * `claude-opus-4.7`). No family guessing: an unknown version stays unknown,
14
+ * so a new model never silently gets an older model's price.
15
+ */
16
+ export declare function canonicalModel(name: string): string;
10
17
  export declare function getModel(name: string): ModelInfo | undefined;
11
18
  export declare function effectiveRate(tokens: number, info: ModelInfo): {
12
19
  input: number;
package/dist/models.js CHANGED
@@ -1,4 +1,7 @@
1
1
  const OPUS = { input: 5.00, output: 25.00, context: 1000000 };
2
+ const OPUS_55 = { input: 4.00, output: 20.00, context: 1000000 };
3
+ const FABLE = { input: 10.00, output: 50.00, context: 1000000 };
4
+ const SONNET_4X = { input: 3.00, output: 15.00, context: 1000000 };
2
5
  const SONNET = { input: 2.00, output: 10.00, context: 1000000 };
3
6
  const HAIKU = { input: 1.00, output: 5.00, context: 200000 };
4
7
  const GROK_46 = {
@@ -14,13 +17,25 @@ const GEMINI_PRO = {
14
17
  longContextThreshold: 200000, longContextInput: 4.00, longContextOutput: 18.00,
15
18
  };
16
19
  const GEMINI_FLASH = { input: 0.30, output: 2.50, context: 1000000 };
20
+ // Gemini 3.6–3.8 Flash: launch price through 2026-12-31; Google lists $1.50/$7.50
21
+ // from 2027-01-01. Update this entry before then.
22
+ const GEMINI_FLASH_3X = { input: 0.75, output: 3.75, context: 1000000 };
23
+ const GEMINI_35_FLASH = { input: 1.50, output: 9.00, context: 1000000 };
24
+ const GEMINI_31_FLASH_LITE = { input: 0.25, output: 1.50, context: 1000000 };
25
+ const GROK_BUILD = {
26
+ input: 1.00, output: 2.00, context: 256000,
27
+ longContextThreshold: 200000, longContextInput: 2.00, longContextOutput: 4.00,
28
+ };
29
+ const GPT_6_ASTRA = { input: 10.00, output: 50.00, context: 1050000 };
30
+ const GPT_6_SOL = { input: 2.00, output: 10.00, context: 1050000 };
31
+ const GPT_6_LUNA = { input: 0.10, output: 0.50, context: 1050000 };
17
32
  const GPT_SOL = { input: 4.00, output: 20.00, context: 1050000 };
18
33
  const GPT_TERRA = { input: 2.00, output: 12.00, context: 1050000 };
19
34
  const GPT_LUNA = { input: 0.20, output: 1.20, context: 1050000 };
20
35
  const GPT_54 = { input: 2.50, output: 15.00, context: 1000000 };
21
36
  const GPT_54_MINI = { input: 0.75, output: 4.50, context: 128000 };
22
37
  const GPT_54_NANO = { input: 0.20, output: 1.25, context: 128000 };
23
- // Pricing as of 2026-08-25 — USD per million tokens.
38
+ // Pricing as of 2026-10-07 — USD per million tokens (checked against the four pages below).
24
39
  // First-party: platform.claude.com/docs/en/about-claude/pricing
25
40
  // developers.openai.com/api/docs/models
26
41
  // docs.x.ai/developers/models
@@ -28,23 +43,47 @@ const GPT_54_NANO = { input: 0.20, output: 1.25, context: 128000 };
28
43
  // Old keys stay as aliases so existing call sites don't throw.
29
44
  export const MODELS = {
30
45
  // Anthropic — live names + generic aliases (same rates)
46
+ 'claude-fable-5.1': { ...FABLE },
47
+ 'claude-fable-5': { ...FABLE },
48
+ 'claude-mythos-5.1': { ...FABLE },
49
+ 'claude-mythos-5': { ...FABLE },
50
+ 'claude-opus-5.5': { ...OPUS_55 },
31
51
  'claude-opus-5': { ...OPUS },
52
+ 'claude-opus-4.8': { ...OPUS },
32
53
  'claude-opus': { ...OPUS },
33
54
  'claude-opus-4.7': { ...OPUS },
55
+ 'claude-opus-4.6': { ...OPUS },
56
+ 'claude-opus-4.5': { ...OPUS },
57
+ 'claude-sonnet-5.5': { ...SONNET },
34
58
  'claude-sonnet-5': { ...SONNET },
35
59
  'claude-sonnet': { ...SONNET },
60
+ 'claude-sonnet-4.6': { ...SONNET_4X },
61
+ 'claude-sonnet-4.5': { ...SONNET_4X },
36
62
  'claude-haiku-4.5': { ...HAIKU },
37
63
  'claude-haiku': { ...HAIKU },
38
64
  // xAI — flagship 4.6, cheap same-provider 4.3. 4.20 / fast are aliases.
65
+ 'grok-4.7': { ...GROK_46 },
39
66
  'grok-4.6': { ...GROK_46 },
67
+ 'grok-4.5': { ...GROK_46 },
68
+ 'grok-build-0.1': { ...GROK_BUILD },
40
69
  'grok-4.3': { ...GROK_43 },
41
70
  'grok-4.20': { ...GROK_43 },
42
71
  'grok-4-1-fast': { ...GROK_43 },
43
72
  // Google
44
73
  'gemini-3.1-pro': { ...GEMINI_PRO },
74
+ 'gemini-3.1-pro-preview': { ...GEMINI_PRO },
75
+ 'gemini-3.8-flash': { ...GEMINI_FLASH_3X },
76
+ 'gemini-3.7-flash': { ...GEMINI_FLASH_3X },
77
+ 'gemini-3.6-flash': { ...GEMINI_FLASH_3X },
78
+ 'gemini-3.5-flash': { ...GEMINI_35_FLASH },
79
+ 'gemini-3.1-flash-lite': { ...GEMINI_31_FLASH_LITE },
45
80
  'gemini-3.5-flash-lite': { ...GEMINI_FLASH },
46
81
  'gemini-2.5-flash': { ...GEMINI_FLASH },
47
82
  // OpenAI — live 5.6 ladder. 5.4 family kept as aliases (old prices).
83
+ 'gpt-6-astra': { ...GPT_6_ASTRA },
84
+ 'gpt-6.1-sol': { ...GPT_6_SOL },
85
+ 'gpt-6-sol': { ...GPT_6_SOL },
86
+ 'gpt-6-luna': { ...GPT_6_LUNA },
48
87
  'gpt-5.6-sol': { ...GPT_SOL },
49
88
  'gpt-5.6-terra': { ...GPT_TERRA },
50
89
  'gpt-5.6-luna': { ...GPT_LUNA },
@@ -52,8 +91,28 @@ export const MODELS = {
52
91
  'gpt-5.4-mini': { ...GPT_54_MINI },
53
92
  'gpt-5.4-nano': { ...GPT_54_NANO },
54
93
  };
94
+ /**
95
+ * Real API IDs → table keys, strictly: lowercase, drop a trailing date stamp
96
+ * (`-20250514`), and write version numbers with dots (`claude-opus-4-7` →
97
+ * `claude-opus-4.7`). No family guessing: an unknown version stays unknown,
98
+ * so a new model never silently gets an older model's price.
99
+ */
100
+ export function canonicalModel(name) {
101
+ if (MODELS[name])
102
+ return name;
103
+ let n = name.trim().toLowerCase();
104
+ if (MODELS[n])
105
+ return n;
106
+ n = n.replace(/-\d{8}$/, '');
107
+ if (MODELS[n])
108
+ return n;
109
+ const dotted = n.replace(/(\d)-(\d)(?=$|-)/g, '$1.$2');
110
+ if (MODELS[dotted])
111
+ return dotted;
112
+ return n;
113
+ }
55
114
  export function getModel(name) {
56
- return MODELS[name] || MODELS[name.toLowerCase()];
115
+ return MODELS[canonicalModel(name)];
57
116
  }
58
117
  export function effectiveRate(tokens, info) {
59
118
  if (info.longContextThreshold !== undefined && tokens > info.longContextThreshold) {
package/dist/preflight.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { slash } from './slash.js';
2
- import { getModel, MODELS, effectiveRate } from './models.js';
3
- import { PROVIDER_MODELS, providerOf } from './providers.js';
2
+ import { getModel, MODELS, effectiveRate, canonicalModel } from './models.js';
3
+ import { PROVIDER_MODELS, providerOf, NOT_ROUTE_TARGETS } from './providers.js';
4
4
  import { shouldRoute, isModelAllowed } from './config.js';
5
5
  /**
6
6
  * Compute cost of a prompt of `tokens` tokens on the given ModelInfo.
@@ -99,9 +99,12 @@ export function preflightRoute(content, model) {
99
99
  return null;
100
100
  const originalCost = computeCost(tokens, info);
101
101
  let cheapest = null;
102
+ const self = canonicalModel(model);
102
103
  for (const m of providerModels) {
103
- if (m === model)
104
+ if (m === self)
104
105
  continue;
106
+ if (NOT_ROUTE_TARGETS.has(m))
107
+ continue; // specialised: never a target
105
108
  if (!isModelAllowed(m))
106
109
  continue; // user excluded this model
107
110
  const altInfo = getModel(m);
@@ -112,7 +115,10 @@ export function preflightRoute(content, model) {
112
115
  if (altInfo.input >= info.input)
113
116
  continue; // not cheaper
114
117
  const alt = buildAlternative(m, originalCost, tokens, altInfo);
115
- if (!cheapest || alt.cost < cheapest.cost) {
118
+ // Tiny prompts round costs to the same value; break ties on the list price
119
+ // so the genuinely cheaper model wins (gpt-5.6-sol → gpt-6-luna, not 5.4-nano).
120
+ if (!cheapest || alt.cost < cheapest.cost ||
121
+ (alt.cost === cheapest.cost && altInfo.input < (getModel(cheapest.model)?.input ?? Infinity))) {
116
122
  cheapest = alt;
117
123
  }
118
124
  }
@@ -8,4 +8,17 @@
8
8
  * strictly-cheaper hit wins (findCheapestRoute / preflightRoute).
9
9
  */
10
10
  export declare const PROVIDER_MODELS: Record<string, string[]>;
11
+ /**
12
+ * Priced and grouped, but never a routing TARGET: specialised models a general
13
+ * call shouldn't be moved to (grok-build-0.1 is a coding-agent model).
14
+ */
15
+ export declare const NOT_ROUTE_TARGETS: ReadonlySet<string>;
16
+ /**
17
+ * Models `slash-tokens/auto` may rewrite a live request TO. Frozen at the
18
+ * 1.6.5 set so a patch release never changes where production calls go:
19
+ * models added in 1.6.6 (GPT-6, Gemini 3.1 Flash-Lite / 3.6–3.8 Flash, Grok
20
+ * 4.7 / 4.5, Claude 5.5 / Fable …) are priced and shown by preflight() /
21
+ * preflightRoute(), but /auto only starts routing to them in 1.7.0.
22
+ */
23
+ export declare const AUTO_ROUTE_TARGETS: ReadonlySet<string>;
11
24
  export declare function providerOf(model: string): string | null;
package/dist/providers.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { canonicalModel } from './models.js';
1
2
  /**
2
3
  * Provider groups — single source of truth.
3
4
  *
@@ -9,21 +10,49 @@
9
10
  */
10
11
  export const PROVIDER_MODELS = {
11
12
  Anthropic: [
12
- 'claude-opus-5', 'claude-opus', 'claude-opus-4.7',
13
- 'claude-sonnet-5', 'claude-sonnet',
13
+ 'claude-fable-5.1', 'claude-fable-5', 'claude-mythos-5.1', 'claude-mythos-5',
14
+ 'claude-opus-5', 'claude-opus', 'claude-opus-4.8', 'claude-opus-4.7',
15
+ 'claude-opus-4.6', 'claude-opus-4.5', 'claude-opus-5.5',
16
+ 'claude-sonnet-4.6', 'claude-sonnet-4.5',
17
+ 'claude-sonnet-5.5', 'claude-sonnet-5', 'claude-sonnet',
14
18
  'claude-haiku', 'claude-haiku-4.5',
15
19
  ],
16
20
  OpenAI: [
21
+ 'gpt-6-astra',
17
22
  'gpt-5.6-sol', 'gpt-5.6-terra',
18
- 'gpt-5.4', 'gpt-5.4-mini', 'gpt-5.4-nano',
19
- 'gpt-5.6-luna',
23
+ 'gpt-5.4', 'gpt-6.1-sol', 'gpt-6-sol', 'gpt-5.4-mini', 'gpt-5.4-nano',
24
+ 'gpt-5.6-luna', 'gpt-6-luna',
25
+ ],
26
+ xAI: ['grok-4.7', 'grok-4.6', 'grok-4.5', 'grok-4.3', 'grok-4.20', 'grok-4-1-fast', 'grok-build-0.1'],
27
+ Google: [
28
+ 'gemini-3.1-pro', 'gemini-3.1-pro-preview',
29
+ 'gemini-3.5-flash', 'gemini-3.8-flash', 'gemini-3.7-flash', 'gemini-3.6-flash',
30
+ 'gemini-3.5-flash-lite', 'gemini-2.5-flash', 'gemini-3.1-flash-lite',
20
31
  ],
21
- xAI: ['grok-4.6', 'grok-4.3', 'grok-4.20', 'grok-4-1-fast'],
22
- Google: ['gemini-3.1-pro', 'gemini-3.5-flash-lite', 'gemini-2.5-flash'],
23
32
  };
33
+ /**
34
+ * Priced and grouped, but never a routing TARGET: specialised models a general
35
+ * call shouldn't be moved to (grok-build-0.1 is a coding-agent model).
36
+ */
37
+ export const NOT_ROUTE_TARGETS = new Set(['grok-build-0.1']);
38
+ /**
39
+ * Models `slash-tokens/auto` may rewrite a live request TO. Frozen at the
40
+ * 1.6.5 set so a patch release never changes where production calls go:
41
+ * models added in 1.6.6 (GPT-6, Gemini 3.1 Flash-Lite / 3.6–3.8 Flash, Grok
42
+ * 4.7 / 4.5, Claude 5.5 / Fable …) are priced and shown by preflight() /
43
+ * preflightRoute(), but /auto only starts routing to them in 1.7.0.
44
+ */
45
+ export const AUTO_ROUTE_TARGETS = new Set([
46
+ 'claude-opus-5', 'claude-opus', 'claude-opus-4.7', 'claude-sonnet-5', 'claude-sonnet',
47
+ 'claude-haiku', 'claude-haiku-4.5',
48
+ 'gpt-5.6-sol', 'gpt-5.6-terra', 'gpt-5.4', 'gpt-5.4-mini', 'gpt-5.4-nano', 'gpt-5.6-luna',
49
+ 'grok-4.6', 'grok-4.3', 'grok-4.20', 'grok-4-1-fast',
50
+ 'gemini-3.1-pro', 'gemini-3.5-flash-lite', 'gemini-2.5-flash',
51
+ ]);
24
52
  export function providerOf(model) {
53
+ const id = canonicalModel(model);
25
54
  for (const [provider, models] of Object.entries(PROVIDER_MODELS)) {
26
- if (models.includes(model))
55
+ if (models.includes(id))
27
56
  return provider;
28
57
  }
29
58
  return null;
package/dist/report.js CHANGED
@@ -24,6 +24,7 @@ export function printReport(sites, filesScanned, timeMs, cwd) {
24
24
  if (sites.length === 0) {
25
25
  console.log(`${WHITE} No AI API call sites detected.${R}`);
26
26
  console.log(`${GRAY} Supported: OpenAI, Anthropic, Vercel AI, LangChain, Gemini, Bedrock, Grok${R}`);
27
+ console.log(`${GRAY} Run this in a project that already calls an LLM.${R}`);
27
28
  console.log('');
28
29
  return;
29
30
  }
package/dist/slash.js CHANGED
@@ -1,5 +1,5 @@
1
- import { getInstance, writeToMemory, ensureCapacity } from './wasm.js';
2
- const WASM_INPUT_OFFSET = 4096;
1
+ import { getInstance, writeToMemory, ensureCapacity, WASM_INPUT_OFFSET } from './wasm.js';
2
+ import { canonicalModel } from './models.js';
3
3
  /**
4
4
  * Per-model calibration factors.
5
5
  * Factor > 1.0 means "model uses more tokens than WASM predicts."
@@ -94,7 +94,18 @@ const WASM_INPUT_OFFSET = 4096;
94
94
  * Slash must NEVER under-report. Over-reporting is safe (go/no-go only).
95
95
  */
96
96
  const CALIBRATION = {
97
+ 'claude-fable-5.1': 2.05,
98
+ 'claude-fable-5': 2.05,
99
+ 'claude-mythos-5.1': 2.05,
100
+ 'claude-mythos-5': 2.05,
101
+ 'claude-opus-5.5': 2.05,
97
102
  'claude-opus-5': 2.05,
103
+ 'claude-opus-4.8': 2.05,
104
+ 'claude-opus-4.6': 2.05,
105
+ 'claude-opus-4.5': 2.05,
106
+ 'claude-sonnet-5.5': 2.05,
107
+ 'claude-sonnet-4.6': 2.05,
108
+ 'claude-sonnet-4.5': 2.05,
98
109
  'claude-opus': 2.05,
99
110
  'claude-opus-4.7': 2.05,
100
111
  'claude-sonnet-5': 2.05,
@@ -102,9 +113,18 @@ const CALIBRATION = {
102
113
  'claude-haiku-4.5': 1.45,
103
114
  'claude-haiku': 1.45,
104
115
  'gemini-3.1-pro': 1.45,
116
+ 'gemini-3.1-pro-preview': 1.45,
117
+ 'gemini-3.8-flash': 1.45,
118
+ 'gemini-3.7-flash': 1.45,
119
+ 'gemini-3.6-flash': 1.45,
120
+ 'gemini-3.5-flash': 1.45,
121
+ 'gemini-3.1-flash-lite': 1.45,
105
122
  'gemini-3.5-flash-lite': 1.45,
106
123
  'gemini-2.5-flash': 1.45,
124
+ 'grok-4.7': 1.15,
107
125
  'grok-4.6': 1.15,
126
+ 'grok-4.5': 1.15,
127
+ 'grok-build-0.1': 1.15,
108
128
  'grok-4.3': 1.15,
109
129
  'grok-4.20': 1.15,
110
130
  'grok-4-1-fast': 1.15,
@@ -141,7 +161,10 @@ export function slash(content, model) {
141
161
  const raw = instance.exports.estimate_tokens(WASM_INPUT_OFFSET, len);
142
162
  if (!model)
143
163
  return raw;
144
- const factor = CALIBRATION[model] ?? DEFAULT_UNKNOWN_MODEL_FACTOR;
164
+ // GPT-6 (gpt-6-astra / 6.1-sol / 6-sol / 6-luna) is deliberately NOT in
165
+ // CALIBRATION: a new generation with an unbenchmarked tokenizer takes the
166
+ // conservative default (never under-report) until a bench run adds it.
167
+ const factor = CALIBRATION[canonicalModel(model)] ?? DEFAULT_UNKNOWN_MODEL_FACTOR;
145
168
  return factor === 1.0 ? raw : Math.ceil(raw * factor);
146
169
  }
147
170
  /**
package/dist/wasm.d.ts CHANGED
@@ -1,3 +1,14 @@
1
+ /**
2
+ * Where input bytes are written: 1,114,112, the end of the module's initial
3
+ * memory (17 pages). The module's stack grows down from 1,048,576 and its
4
+ * lookup tables sit at 1,048,576–1,049,916; nothing lives above them.
5
+ *
6
+ * Fixed 2026-10-07: input used to start at 4096, so any input over ~1.04 MB ran
7
+ * over the stack and then the lookup tables, and every later estimate in
8
+ * that process came out wrong (a 14-token prompt read as 28). Input above
9
+ * the tables can't reach them, whatever its size.
10
+ */
11
+ export declare const WASM_INPUT_OFFSET = 1114112;
1
12
  export declare function getInstance(): WebAssembly.Instance;
2
13
  /**
3
14
  * Grow WASM linear memory if needed so `neededBytes` of input (plus the
package/dist/wasm.js CHANGED
@@ -1,7 +1,17 @@
1
1
  // Auto-generated WASM binary (4,865 bytes / xai-faf-zig ghost engine)
2
2
  // Contains estimate_tokens(ptr, len) -> u32
3
3
  const WASM_BASE64 = "AGFzbQEAAAABLAZgAn9/AX9gBH9/f38AYAZ/f39/f38AYAV/f39/fwBgBH9/f38Bf2ABfwF/AxEQAAABAQIDBAQBBAMAAAUABQUDAQARBgkBfwFBgIDAAAsHWQYGbWVtb3J5AgAJc2NvcmVfZmFmAAAUc2NvcmVfZmFmX2VudGVycHJpc2UACwx2YWxpZGF0ZV9mYWYADAhnZXRfdGllcgAND2VzdGltYXRlX3Rva2VucwAOCoEaEBAAIAAgARCBgICAAEH/AXELSgEBfyOAgICAAEEQayICJICAgIAAAkACQCABDQBBACEBDAELIAJBC2ogACABQQAQgoCAgAAgAi0ACyEBCyACQRBqJICAgIAAIAEL/wcBE38jgICAgABB0ABrIgQkgICAgABBqIHAgABBgIDAgAAgA0EBcSIFGyEGQSFBFSAFGyEHQQAhCEEAIQlBACEKA0ACQAJAAkAgCCAHRg0AIARBOGogBiAIQQN0aiIFKAIAIgsgBSgCBCIFQeeIwIAAEIOAgIAAAkAgBC0APA0AQQAhDAwCC0EBIQ0gBSAEKAI4Ig5BAWoiD2shECALIA9qIRFBACESQQAhEwNAQQAhDAJAAkAgDUH/AXENAEEAIQ1BACEFQQAhDwwBCyAEQcgAaiABIAIgEkH3iMCAAEEBEISAgIAAIAQoAkgiFCACIAQtAEwiFRsgEmshDyABIBJqIQUgFUEARyENIBRBAWpBACAVGyESCyAFRQ0CIARBMGogBSAPQfWIwIAAQQEQhYCAgAAgBCgCMCEWIAQoAjQiDEUNAEEAIQVBACEPA0ACQAJAIAwgBUYNAAJAIBYgBWotAAAiFEEJRg0AQQEhFSAUQSBHDQEMAgtBAiEVDAELAkAgDw0AIARBGGogFiAMQfmIwIAAQQIQhYCAgABBACETIAQoAhgiDyAEKAIcIgUgCyAOEIaAgIAAQQFxRQ0DIAUgDk0NAyAPIA5qLQAAQTpGIRMMAwsgEyAPQQFHcUUNAiAEQShqIBYgDEH5iMCAAEECEIWAgIAAQQEhEyAEKAIoIhUgBCgCLCIPIBEgEBCGgICAAEEBcUUNAiAPIBBNDQIgFSAQai0AAEE6Rw0CIARBwABqIBUgD0HliMCAABCDgICAACAELQBERQ0CQQAhDEEAIQUgDyAEKAJAQQFqIhRNDQUgBEEgaiAVIBRqIA8gFGtB8IjAgABBBBCFgICAACAEKAIgQQAgBCgCJCIFGyEMIAVBACAFGyEFDAULIAVBAWohBSAVIA9qIQ8MAAsLC0EhQRUgA0EBcRsiBSAJayEPQQAhFQJAIAUgCUH/AXFGDQAgCkH/AXFB5ABsIA9B/wFxIhVBAXZqIBVuIRULIAAgBToABCAAIA86AAMgACAJOgACIAAgCjoAASAAIBVB/wFxIgVB5AAgBUHkAEkbOgAAIARB0ABqJICAgIAADwtBACEFCwJAIAxFDQAgBEEQaiAMIAVB6YjAgABBBhCFgICAAAJAIAQoAhAgBCgCFEHKiMCAAEELEIeAgIAAQQFxDQAgBEEIaiAMIAVB6YjAgABBBhCFgICAACAEKAIMIgxFDQEgBCgCCCEWQbh/IQUCQANAIAVBCGoiD0UNASAFQciJwIAAaiEVIAVBxInAgABqIRQgDyEFIBYgDCAUKAIAIBUoAgAQh4CAgABBAXFFDQAMAwsLIApBAWohCgwBCyAJQQFqIQkLIAhBAWohCAwACws/AQF/I4CAgIAAQRBrIgQkgICAgAAgBEEIaiABIAJBACADQQEQhICAgAAgACAEKQMINwIAIARBEGokgICAgAALsQMDAn8BfgF/I4CAgIAAQZAIayIGJICAgIAAAkACQCACIAVPDQAgAEIANwIADAELAkACQAJAAkACQCAFQQFLDQAgBQ0BIABBAToABAwECyACQTRJDQEgBUEFSQ0BQQAhBwwCCyAGQQhqIAEgAiADIAQtAAAQioCAgAAgACAGKQMINwIADAMLIAIgBWshBwNAAkACQAJAIAMgB00NAEEAIQNCACEIDAELIAEgA2ogBSAEIAUQiYCAgABBAXFFDQFCgICAgBAhCAsgACAIIAOthDcCAAwECyADQQFqIQMMAAsLAkADQCAHQYAIRg0BIAZBEGogB2ogBTYCACAHQQRqIQcMAAsLIAVBf2ohByAEIQkDQAJAIAcNACACIAVrIQkDQAJAIAMgCU0NACAAQgA3AgAMBQsCQCABIANqIgcgBSAEIAUQiYCAgABBAXFFDQAgAEEBOgAEDAQLIAZBEGogByAFakF/ai0AAEECdGooAgAgA2ohAwwACwsgBkEQaiAJLQAAQQJ0aiAHNgIAIAlBAWohCSAHQX9qIQcMAAsLIAAgAzYCAAsgBkGQCGokgICAgAALrgEBBH8jgICAgABBEGsiBSSAgICAAEEAIQYCQANAAkAgAiAGRw0AIAIhBgwCCyAFIAMgBCABIAZqLQAAEIiAgIAAIAUtAARFDQEgBkEBaiEGDAALCyABQX9qIQcCQANAIAIiCCAGTQ0BIAVBCGogAyAEIAcgCGotAAAQiICAgAAgCEF/aiECIAUtAAwNAAsLIAAgCCAGazYCBCAAIAEgBmo2AgAgBUEQaiSAgICAAAskAQF/QQAhBAJAIAMgAUsNACAAIAMgAiADEImAgIAAIQQLIAQLeQECf0EAIQQCQCABIANHDQADQCABRSEEIAFFDQEgAi0AACEDIAAtAAAhBSACQQFqIQIgAEEBaiEAIAFBf2ohASAFQSBqIAUgBUG/f2pB/wFxQRpJG0H/AXEgA0EgaiADIANBv39qQf8BcUEaSRtB/wFxRg0ACwsgBAs9AQF/I4CAgIAAQRBrIgQkgICAgAAgBEEIaiABIAJBACADEIqAgIAAIAAgBCkDCDcCACAEQRBqJICAgIAAC/0CAQV/I4CAgIAAQRBrIgQkgICAgAACQAJAIAEgA0cNAEEBIQUgAUUNASAAQarVqtV6IAEbIgYgAkGq1arVeiADGyIHRg0BAkAgAUEQSw0AAkAgAUEETw0AIAAtAAAgBy0AAEYgACABQX9qIgNqLQAAIAcgA2otAABGcSAAIAFBAXYiA2otAAAgByADai0AAEZxIQUMAwtBACEDIARBADYCACAEIAFBfGoiBTYCBCAEIAFBAXZBDHEiADYCCCAEIAUgAGs2AgxBACEFAkADQCADQRBGDQEgByAEIANqKAIAIgBqKAAAIAYgAGooAABzIAVyIQUgA0EEaiEDDAALCyAFRSEFDAILIAFBf2pBAnZBAWohACAGIQMgByEFAkADQCAAQX9qIgBFDQEgBSgAACECIAMoAAAhCCADQQRqIQMgBUEEaiEFIAggAkcNAgwACwsgBiABQXxqIgNqKAAAIAcgA2ooAABGIQUMAQtBACEFCyAEQRBqJICAgIAAIAULWAACQCADIAJJDQAgAEIANwIADwsgBEH/AXEhBAJAA0AgAiADRg0BAkAgASADai0AACAERw0AIABBAToABCAAIAM2AgAPCyADQQFqIQMMAAsLIABCADcCAAtOAQF/I4CAgIAAQRBrIgIkgICAgAACQAJAIAENAEEAIQEMAQsgAkELaiAAIAFBARCCgICAACACLQALIQELIAJBEGokgICAgAAgAUH/AXELFAAgACABEIGAgIAAQf8BcUHjAEsLCAAgAEHjAEsL5gQBCH8jgICAgABBEGsiAiSAgICAAAJAAkAgAQ0AQQAhAwwBCyACIAAtAAAiBBCPgICAAEEHcSIDOgAPIANBBHNBB3FBAnQiA0GcisCAAGohBSADQfyJwIAAaiEGIANB3InAgABqIQcgA0G8icCAAGohA0EAIQgCQAJAAkAgBEGFf2oOAwECAQALIARBWGpBAk8NAQtBASEICyAFKAIAIQUgBigCACEJIAcoAgAhBiADKAIAIQcgAUF/aiEDIABBAWohAANAAkACQAJAAkACQAJAAkACQCADRQ0AIAAtAAAiBBCPgICAAEEHcQ4GBAQHAQIDBAsgCEHkAGwgAW4hCCAGQeQAbCABbiEDIAdB5ABsIAFuIQAgBUHkAGwgAW4hBCAJIAZqQeQAbCABbkEjSQ0EIANBBU8NBEE4IQMMBQsgCUEBaiEJDAULIAZBAWohBgwECyAHQQFqIQcMAwsgBUEBaiEFDAILAkAgBEHBAEkNACADQQJLDQAgAEEITw0AQTYhAwwBCwJAIAhBAkkNACADQQRJDQAgAEENTQ0AQRohAwwBCwJAIABBFU0NAEEcIQMMAQsCQCADQQRJDQAgAEENTQ0AQSAhAwwBCwJAIABBDkkNACAEQcgASw0AIAhBAk8NAEEeIQMMAQtBIUEjIABBBksbQSMgA0EESxshAwsgA0EBdiABQQpsaiADbiIDQSFuIANqIgNBASADQQFLGyEDDAILAkACQCAEQVhqQQJJDQAgBEGFf2oOAwABAAELIAhBAWohCAsgA0F/aiEDIABBAWohAAwACwsgAkEQaiSAgICAACADC3UBAX8CQCAAQZ9/akH/AXFBGk8NAEEADwsCQCAAQb9/akH/AXFBGk8NAEEBDwsCQCAAQVBqQf8BcUEKTw0AQQIPC0EDIQECQAJAAkACQCAAQf8BcSIAQXdqDgUDAgEBAgALIABBIEYNAgtBBQ8LQQQhAQsgAQsLxgoBAEGAgMAAC7wKvwMQAAwAAAAjAxAADAAAAP0DEAAVAAAAsQIQABEAAAAxAhAAEgAAAMEBEAARAAAAiQMQABMAAADoAhAAEgAAAPABEAARAAAAIQQQAA4AAABLAxAAEwAAALABEAAQAAAAGgIQABYAAAAwBBAADQAAAJ0DEAAOAAAAsQMQAA0AAAB6AxAADgAAAMsCEAAQAAAAbAMQAA0AAAA+BBAACwAAAFYEEAAKAAAAvwMQAAwAAAAjAxAADAAAAP0DEAAVAAAAsQIQABEAAAAxAhAAEgAAAMEBEAARAAAAiQMQABMAAADoAhAAEgAAAPABEAARAAAAIQQQAA4AAABLAxAAEwAAALABEAAQAAAAGgIQABYAAAAwBBAADQAAAJ0DEAAOAAAAsQMQAA0AAAB6AxAADgAAAMsCEAAQAAAAbAMQAA0AAAA+BBAACwAAAFYEEAAKAAAACgMQABMAAACbAhAAFQAAAG4CEAAQAAAAAgIQABcAAAB/AhAAGwAAANwCEAALAAAA8QMQAAsAAABfAxAADAAAABMEEAANAAAA0wEQABwAAABWAhAAFwAAANsDEAAVAAAAc3RhY2sudWlfbGlicmFyeQBodW1hbl9jb250ZXh0LndoeQBtb25vcmVwby52ZXJzaW9uaW5nX3N0cmF0ZWd5AGh1bWFuX2NvbnRleHQuaG93AG1vbm9yZXBvLnBhY2thZ2VzX2NvdW50AHN0YWNrLnN0YXRlX21hbmFnZW1lbnQAaHVtYW5fY29udGV4dC53aGF0AGRldmVsb3BtZW50IHRlYW1zAG1vbm9yZXBvLnNoYXJlZF9jb25maWdzAHN0YWNrLndvcmtzcGFjZXMAbW9ub3JlcG8uYnVpbGRfb3JjaGVzdHJhdG9yAHN0YWNrLnBhY2thZ2VfbWFuYWdlcgBodW1hbl9jb250ZXh0LndobwB1bmtub3duAHN0YWNrLmNvbm5lY3Rpb24Ac3RhY2suYWRtaW4AaHVtYW5fY29udGV4dC53aGVuAGNsb3VkIHBsYXRmb3JtAHN0YWNrLm1vbm9yZXBvX3Rvb2wAbnVsbABwcm9qZWN0LmdvYWwAZGVzY3JpYmUgeW91ciBwcm9qZWN0IGdvYWwAc3RhY2suY3NzX2ZyYW1ld29yawBzdGFjay5zZWFyY2gAc3RhY2suaG9zdGluZwBzdGFjay5kYXRhYmFzZQBodW1hbl9jb250ZXh0LndoZXJlAHN0YWNrLmFwaV90eXBlAG5vbmUAc3RhY2sucnVudGltZQBwcm9qZWN0Lm5hbWUAbm90IGFwcGxpY2FibGUAbW9ub3JlcG8ucmVtb3RlX2NhY2hlAHN0YWNrLmNhY2hlAHByb2plY3QubWFpbl9sYW5ndWFnZQBzdGFjay5zdG9yYWdlAHN0YWNrLmZyb250ZW5kAHN0YWNrLmJhY2tlbmQAc3RhY2suYnVpbGQAc2xvdGlnbm9yZWQAc3RhY2suY2ljZABuL2EAOgAuACAJDQoiJwAgCSInAA0ACgAgCQAwAxAAGgAAAEQCEAARAAAA+wIQAA4AAAAeAxAABAAAAKwDEAAEAAAAwwIQAAcAAABhBBAAAwAAAMwDEAAOAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAABAAAAAAAAAAAAAAA=";
4
- const WASM_INPUT_OFFSET = 4096;
4
+ /**
5
+ * Where input bytes are written: 1,114,112, the end of the module's initial
6
+ * memory (17 pages). The module's stack grows down from 1,048,576 and its
7
+ * lookup tables sit at 1,048,576–1,049,916; nothing lives above them.
8
+ *
9
+ * Fixed 2026-10-07: input used to start at 4096, so any input over ~1.04 MB ran
10
+ * over the stack and then the lookup tables, and every later estimate in
11
+ * that process came out wrong (a 14-token prompt read as 28). Input above
12
+ * the tables can't reach them, whatever its size.
13
+ */
14
+ export const WASM_INPUT_OFFSET = 1114112;
5
15
  const encoder = new TextEncoder();
6
16
  let _instance = null;
7
17
  function decodeBase64(b64) {
@@ -51,7 +61,7 @@ const WASM_PAGE_BYTES = 65536;
51
61
  * do lazily on every call with no effect on correctness for small inputs.
52
62
  */
53
63
  export function ensureCapacity(memory, neededBytes) {
54
- const cappedNeeded = Math.min(neededBytes, MAX_WASM_INPUT_BYTES);
64
+ const cappedNeeded = Math.min(neededBytes, WASM_INPUT_OFFSET + MAX_WASM_INPUT_BYTES);
55
65
  if (memory.buffer.byteLength >= cappedNeeded)
56
66
  return;
57
67
  const additionalPages = Math.ceil((cappedNeeded - memory.buffer.byteLength) / WASM_PAGE_BYTES);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "slash-tokens",
3
- "version": "1.6.5",
3
+ "version": "1.6.7",
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",
@@ -26,8 +26,10 @@
26
26
  ],
27
27
  "sideEffects": false,
28
28
  "scripts": {
29
+ "prepublishOnly": "npm run build",
29
30
  "build": "npx tsc && bun build src/cli.ts --outfile=dist/cli.js --target=node",
30
- "test": "bun test"
31
+ "test": "bun test",
32
+ "check:engines": "node scripts/check-engines.mjs"
31
33
  },
32
34
  "author": "wolfejam",
33
35
  "license": "MIT",
@@ -54,5 +56,8 @@
54
56
  "@types/node": "^25.5.0",
55
57
  "js-tiktoken": "^1.0.21",
56
58
  "typescript": "^6.0.2"
59
+ },
60
+ "engines": {
61
+ "node": ">=22.0.0"
57
62
  }
58
63
  }