@sohqureshi/tokenwise 1.0.4 → 1.0.9

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/README.md CHANGED
@@ -3,8 +3,8 @@
3
3
  TokenWise is a lightweight utility for preparing JSON before sending it to AI models. It helps reduce payload noise, shrink token usage, and turn structured data into formats that are easier for LLMs to consume.
4
4
 
5
5
 
6
- ![npm version](https://img.shields.io/npm/v/tokenwise)
7
- ![downloads](https://img.shields.io/npm/dw/tokenwise)
6
+ [![npm version](https://img.shields.io/npm/v/%40sohqureshi%2Ftokenwise)](https://www.npmjs.com/package/@sohqureshi/tokenwise)
7
+ [![downloads](https://img.shields.io/npm/dw/%40sohqureshi%2Ftokenwise)](https://www.npmjs.com/package/@sohqureshi/tokenwise)
8
8
  ![license](https://img.shields.io/github/license/sohqureshi/tokenwise)
9
9
  ![stars](https://img.shields.io/github/stars/sohqureshi/tokenwise?style=social)
10
10
  [![Demo](https://img.shields.io/badge/Live%20Demo-Visit-brightgreen)](https://sohqureshi.github.io/tokenwise/)
@@ -36,6 +36,8 @@ Raw JSON is:
36
36
  npm install @sohqureshi/tokenwise
37
37
  ```
38
38
 
39
+ Available on [npm](https://www.npmjs.com/package/@sohqureshi/tokenwise).
40
+
39
41
  ## Quick Usage
40
42
 
41
43
  ```js
@@ -68,7 +70,7 @@ console.log(ai(product).compact().value());
68
70
  Removes fields you do not want to send to the model. By default it also removes `null`, `undefined`, and empty objects.
69
71
 
70
72
  ```js
71
- import { prune } from "tokenwise";
73
+ import { prune } from "@sohqureshi/tokenwise";
72
74
 
73
75
  const input = {
74
76
  user: { name: "John", age: 28 },
@@ -208,9 +210,24 @@ LLMs charge and reason over tokens. Sending raw JSON often includes repeated key
208
210
  node demo.js
209
211
  ```
210
212
 
211
- Use `--analyze` to compare serialized input and output with TokenWise's
212
- four-characters-per-token heuristic. This is an estimate, not a replacement
213
- for a model-specific tokenizer or a provider's billed token count.
213
+ Use `--analyze` to compare serialized input and output with the model's
214
+ `tiktoken` encoding. `estimateTokens()` and `analyze()` use exact
215
+ model-aware counts by default; pass `exact: false` to opt into the
216
+ four-characters-per-token heuristic. Use `model` to select the tokenizer
217
+ used by `tiktoken`.
218
+
219
+ ---
220
+
221
+ ## Release Notes
222
+
223
+ ### v1.0.8 — 2026-09-06
224
+
225
+ - Expose exact tokenizer metadata when available (model + encoding), and fall back to a clear, model-aware estimator in browser demos.
226
+ - Demo updated to show selected model and expected encoding when the exact tokenizer (tiktoken) is not available in-browser.
227
+ - Updated analyze() to surface tokenizer encoding in analysis output so the demo shows "Estimator: model=gpt-4 expected_encoding=cl100k_base" even when using the heuristic fallback.
228
+ - Misc: build artifacts updated and docs demo import bumped to v1.0.7 CDN bundle.
229
+
230
+ If you want the browser demo to display truly exact token counts, run the demo against a small Node endpoint (or local server) that has tiktoken installed and uses analyze(..., { exact: true }). The estimator will display the real tokenizer encoding when tiktoken is present.
214
231
 
215
232
  ---
216
233
 
@@ -11,12 +11,17 @@ import {
11
11
  prune
12
12
  } from "./chunk-ZD536GZF.js";
13
13
  import {
14
- estimateTokens,
14
+ estimateTokensWithMeta,
15
15
  serializeForTokenEstimate
16
- } from "./chunk-7CL7GVRF.js";
16
+ } from "./chunk-L3HY6AWL.js";
17
17
 
18
18
  // src/core/analyze.ts
19
19
  function analyze(input, options = {}) {
20
+ const {
21
+ exact = true,
22
+ model = "gpt-4o-mini",
23
+ fallbackToHeuristic = true
24
+ } = options;
20
25
  if (!input || typeof input === "object" && input !== null && Object.keys(input).length === 0) {
21
26
  return {
22
27
  originalTokens: 0,
@@ -27,10 +32,15 @@ function analyze(input, options = {}) {
27
32
  reductionRatio: 1,
28
33
  originalCharacters: 0,
29
34
  optimizedCharacters: 0,
30
- estimator: "heuristic: 1 token \u2248 4 characters"
35
+ estimator: exact ? `exact tokenizer: model=${model}` : "heuristic: 1 token \u2248 4 characters"
31
36
  };
32
37
  }
33
- const originalTokens = estimateTokens(input);
38
+ const originalMeta = estimateTokensWithMeta(input, {
39
+ exact,
40
+ model,
41
+ fallbackToHeuristic
42
+ });
43
+ const originalTokens = originalMeta.count;
34
44
  let optimizedData = input;
35
45
  if (options.prune && Array.isArray(options.prune)) {
36
46
  optimizedData = prune(optimizedData, options.prune);
@@ -44,10 +54,16 @@ function analyze(input, options = {}) {
44
54
  if (options.toTOON === true || options.toon === true) {
45
55
  optimizedData = toTOON(optimizedData);
46
56
  }
47
- const optimizedTokens = estimateTokens(optimizedData);
57
+ const optimizedMeta = estimateTokensWithMeta(optimizedData, {
58
+ exact,
59
+ model,
60
+ fallbackToHeuristic
61
+ });
62
+ const optimizedTokens = optimizedMeta.count;
48
63
  const savings = Math.max(0, originalTokens - optimizedTokens);
49
64
  const savingsPercent = originalTokens > 0 ? Math.round(savings / originalTokens * 100) : 0;
50
65
  const reductionRatio = originalTokens > 0 ? optimizedTokens / originalTokens : 1;
66
+ const estimatorLabel = originalMeta && originalMeta.estimator ? originalMeta.estimator : optimizedMeta.estimator;
51
67
  return {
52
68
  originalTokens,
53
69
  optimizedTokens,
@@ -57,7 +73,7 @@ function analyze(input, options = {}) {
57
73
  reductionRatio,
58
74
  originalCharacters: serializeForTokenEstimate(input).length,
59
75
  optimizedCharacters: serializeForTokenEstimate(optimizedData).length,
60
- estimator: "heuristic: 1 token \u2248 4 characters"
76
+ estimator: estimatorLabel
61
77
  };
62
78
  }
63
79
 
@@ -0,0 +1,69 @@
1
+ // src/core/token.ts
2
+ import { createRequire } from "module";
3
+ var require2 = createRequire(import.meta.url);
4
+ function serializeForTokenEstimate(value) {
5
+ if (typeof value === "string") return value;
6
+ const serialized = JSON.stringify(value);
7
+ return serialized ?? "";
8
+ }
9
+ function estimateTokensHeuristic(value) {
10
+ return Math.ceil(serializeForTokenEstimate(value).length / 4);
11
+ }
12
+ function expectedEncodingForModel(model) {
13
+ const normalizedModel = model.toLowerCase();
14
+ if (normalizedModel.includes("gpt-4o") || normalizedModel.includes("gpt-4.1") || normalizedModel.includes("o1") || normalizedModel.includes("o3") || normalizedModel.includes("o4")) {
15
+ return "o200k_base";
16
+ }
17
+ if (normalizedModel.includes("davinci") || normalizedModel.startsWith("text-") || normalizedModel.includes("babbage") || normalizedModel.includes("curie")) {
18
+ return "r50k_base";
19
+ }
20
+ return "cl100k_base";
21
+ }
22
+ function estimateTokens(value, options = {}) {
23
+ return estimateTokensWithMeta(value, options).count;
24
+ }
25
+ function estimateTokensWithMeta(value, options = {}) {
26
+ const { exact = true, model = "gpt-4o-mini", fallbackToHeuristic = true } = options;
27
+ const heuristic = estimateTokensHeuristic(value);
28
+ const heuristicEstimator = "heuristic: 1 token \u2248 4 characters";
29
+ if (!exact) {
30
+ return { count: heuristic, estimator: heuristicEstimator };
31
+ }
32
+ try {
33
+ const tiktoken = require2("tiktoken");
34
+ const encoder = tiktoken.encoding_for_model(model);
35
+ const count = encoder.encode(serializeForTokenEstimate(value)).length;
36
+ let encodingName = encoder.name;
37
+ if (!encodingName && typeof tiktoken.model_to_encoding === "function") {
38
+ try {
39
+ encodingName = tiktoken.model_to_encoding(model);
40
+ } catch {
41
+ encodingName = void 0;
42
+ }
43
+ }
44
+ encodingName ?? (encodingName = expectedEncodingForModel(model));
45
+ encoder.free?.();
46
+ return {
47
+ count,
48
+ estimator: `exact tokenizer: model=${model} encoding=${encodingName}`
49
+ };
50
+ } catch {
51
+ const expectedEncoding = expectedEncodingForModel(model);
52
+ if (!fallbackToHeuristic) {
53
+ return {
54
+ count: heuristic,
55
+ estimator: `exact requested but tokenizer unavailable (expected encoding=${expectedEncoding} for model=${model})`
56
+ };
57
+ }
58
+ return {
59
+ count: heuristic,
60
+ estimator: `${heuristicEstimator} (model=${model} expected_encoding=${expectedEncoding})`
61
+ };
62
+ }
63
+ }
64
+
65
+ export {
66
+ serializeForTokenEstimate,
67
+ estimateTokens,
68
+ estimateTokensWithMeta
69
+ };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  analyze
3
- } from "./chunk-ZVQRYJU5.js";
3
+ } from "./chunk-2TDVL6BJ.js";
4
4
  import {
5
5
  toTOON
6
6
  } from "./chunk-6VRLDRJK.js";
@@ -41,8 +41,8 @@ var AIChain = class {
41
41
  toNatural() {
42
42
  return toNatural(this.data);
43
43
  }
44
- analyze() {
45
- return analyze(this.data);
44
+ analyze(options) {
45
+ return analyze(this.data, options);
46
46
  }
47
47
  value() {
48
48
  return this.data;
package/dist/cli.cjs CHANGED
@@ -142,17 +142,74 @@ function isPlainObject(value) {
142
142
  }
143
143
 
144
144
  // src/core/token.ts
145
+ var import_node_module = require("module");
146
+ var import_meta = {};
147
+ var require2 = (0, import_node_module.createRequire)(import_meta.url);
145
148
  function serializeForTokenEstimate(value) {
146
149
  if (typeof value === "string") return value;
147
150
  const serialized = JSON.stringify(value);
148
151
  return serialized ?? "";
149
152
  }
150
- function estimateTokens(value) {
153
+ function estimateTokensHeuristic(value) {
151
154
  return Math.ceil(serializeForTokenEstimate(value).length / 4);
152
155
  }
156
+ function expectedEncodingForModel(model) {
157
+ const normalizedModel = model.toLowerCase();
158
+ if (normalizedModel.includes("gpt-4o") || normalizedModel.includes("gpt-4.1") || normalizedModel.includes("o1") || normalizedModel.includes("o3") || normalizedModel.includes("o4")) {
159
+ return "o200k_base";
160
+ }
161
+ if (normalizedModel.includes("davinci") || normalizedModel.startsWith("text-") || normalizedModel.includes("babbage") || normalizedModel.includes("curie")) {
162
+ return "r50k_base";
163
+ }
164
+ return "cl100k_base";
165
+ }
166
+ function estimateTokensWithMeta(value, options = {}) {
167
+ const { exact = true, model = "gpt-4o-mini", fallbackToHeuristic = true } = options;
168
+ const heuristic = estimateTokensHeuristic(value);
169
+ const heuristicEstimator = "heuristic: 1 token \u2248 4 characters";
170
+ if (!exact) {
171
+ return { count: heuristic, estimator: heuristicEstimator };
172
+ }
173
+ try {
174
+ const tiktoken = require2("tiktoken");
175
+ const encoder = tiktoken.encoding_for_model(model);
176
+ const count = encoder.encode(serializeForTokenEstimate(value)).length;
177
+ let encodingName = encoder.name;
178
+ if (!encodingName && typeof tiktoken.model_to_encoding === "function") {
179
+ try {
180
+ encodingName = tiktoken.model_to_encoding(model);
181
+ } catch {
182
+ encodingName = void 0;
183
+ }
184
+ }
185
+ encodingName ?? (encodingName = expectedEncodingForModel(model));
186
+ encoder.free?.();
187
+ return {
188
+ count,
189
+ estimator: `exact tokenizer: model=${model} encoding=${encodingName}`
190
+ };
191
+ } catch {
192
+ const expectedEncoding = expectedEncodingForModel(model);
193
+ if (!fallbackToHeuristic) {
194
+ return {
195
+ count: heuristic,
196
+ estimator: `exact requested but tokenizer unavailable (expected encoding=${expectedEncoding} for model=${model})`
197
+ };
198
+ }
199
+ return {
200
+ count: heuristic,
201
+ estimator: `${heuristicEstimator} (model=${model} expected_encoding=${expectedEncoding})`
202
+ };
203
+ }
204
+ }
153
205
 
154
206
  // src/core/analyze.ts
155
207
  function analyze(input, options = {}) {
208
+ const {
209
+ exact = true,
210
+ model = "gpt-4o-mini",
211
+ fallbackToHeuristic = true
212
+ } = options;
156
213
  if (!input || typeof input === "object" && input !== null && Object.keys(input).length === 0) {
157
214
  return {
158
215
  originalTokens: 0,
@@ -163,10 +220,15 @@ function analyze(input, options = {}) {
163
220
  reductionRatio: 1,
164
221
  originalCharacters: 0,
165
222
  optimizedCharacters: 0,
166
- estimator: "heuristic: 1 token \u2248 4 characters"
223
+ estimator: exact ? `exact tokenizer: model=${model}` : "heuristic: 1 token \u2248 4 characters"
167
224
  };
168
225
  }
169
- const originalTokens = estimateTokens(input);
226
+ const originalMeta = estimateTokensWithMeta(input, {
227
+ exact,
228
+ model,
229
+ fallbackToHeuristic
230
+ });
231
+ const originalTokens = originalMeta.count;
170
232
  let optimizedData = input;
171
233
  if (options.prune && Array.isArray(options.prune)) {
172
234
  optimizedData = prune(optimizedData, options.prune);
@@ -180,10 +242,16 @@ function analyze(input, options = {}) {
180
242
  if (options.toTOON === true || options.toon === true) {
181
243
  optimizedData = toTOON(optimizedData);
182
244
  }
183
- const optimizedTokens = estimateTokens(optimizedData);
245
+ const optimizedMeta = estimateTokensWithMeta(optimizedData, {
246
+ exact,
247
+ model,
248
+ fallbackToHeuristic
249
+ });
250
+ const optimizedTokens = optimizedMeta.count;
184
251
  const savings = Math.max(0, originalTokens - optimizedTokens);
185
252
  const savingsPercent = originalTokens > 0 ? Math.round(savings / originalTokens * 100) : 0;
186
253
  const reductionRatio = originalTokens > 0 ? optimizedTokens / originalTokens : 1;
254
+ const estimatorLabel = originalMeta && originalMeta.estimator ? originalMeta.estimator : optimizedMeta.estimator;
187
255
  return {
188
256
  originalTokens,
189
257
  optimizedTokens,
@@ -193,7 +261,7 @@ function analyze(input, options = {}) {
193
261
  reductionRatio,
194
262
  originalCharacters: serializeForTokenEstimate(input).length,
195
263
  optimizedCharacters: serializeForTokenEstimate(optimizedData).length,
196
- estimator: "heuristic: 1 token \u2248 4 characters"
264
+ estimator: estimatorLabel
197
265
  };
198
266
  }
199
267
 
@@ -323,8 +391,8 @@ var AIChain = class {
323
391
  toNatural() {
324
392
  return toNatural(this.data);
325
393
  }
326
- analyze() {
327
- return analyze(this.data);
394
+ analyze(options) {
395
+ return analyze(this.data, options);
328
396
  }
329
397
  value() {
330
398
  return this.data;
@@ -341,7 +409,7 @@ Usage:
341
409
  Options:
342
410
  --toon Convert JSON to TOON format
343
411
  --compact Convert JSON to compact format
344
- --analyze Show token analysis (heuristic estimate)
412
+ --analyze Show token analysis (tiktoken estimate)
345
413
  `);
346
414
  process.exit(0);
347
415
  }
package/dist/cli.js CHANGED
@@ -1,14 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  AIChain
4
- } from "./chunk-HFMW3K22.js";
5
- import "./chunk-ZVQRYJU5.js";
4
+ } from "./chunk-QHCUNOUH.js";
5
+ import "./chunk-2TDVL6BJ.js";
6
6
  import "./chunk-6VRLDRJK.js";
7
7
  import "./chunk-XE36GLJP.js";
8
8
  import "./chunk-L7BC62MT.js";
9
9
  import "./chunk-D4CFTFM3.js";
10
10
  import "./chunk-ZD536GZF.js";
11
- import "./chunk-7CL7GVRF.js";
11
+ import "./chunk-L3HY6AWL.js";
12
12
 
13
13
  // src/cli.ts
14
14
  import fs from "fs";
@@ -22,7 +22,7 @@ Usage:
22
22
  Options:
23
23
  --toon Convert JSON to TOON format
24
24
  --compact Convert JSON to compact format
25
- --analyze Show token analysis (heuristic estimate)
25
+ --analyze Show token analysis (tiktoken estimate)
26
26
  `);
27
27
  process.exit(0);
28
28
  }
@@ -139,17 +139,74 @@ function isPlainObject(value) {
139
139
  }
140
140
 
141
141
  // src/core/token.ts
142
+ var import_node_module = require("module");
143
+ var import_meta = {};
144
+ var require2 = (0, import_node_module.createRequire)(import_meta.url);
142
145
  function serializeForTokenEstimate(value) {
143
146
  if (typeof value === "string") return value;
144
147
  const serialized = JSON.stringify(value);
145
148
  return serialized ?? "";
146
149
  }
147
- function estimateTokens(value) {
150
+ function estimateTokensHeuristic(value) {
148
151
  return Math.ceil(serializeForTokenEstimate(value).length / 4);
149
152
  }
153
+ function expectedEncodingForModel(model) {
154
+ const normalizedModel = model.toLowerCase();
155
+ if (normalizedModel.includes("gpt-4o") || normalizedModel.includes("gpt-4.1") || normalizedModel.includes("o1") || normalizedModel.includes("o3") || normalizedModel.includes("o4")) {
156
+ return "o200k_base";
157
+ }
158
+ if (normalizedModel.includes("davinci") || normalizedModel.startsWith("text-") || normalizedModel.includes("babbage") || normalizedModel.includes("curie")) {
159
+ return "r50k_base";
160
+ }
161
+ return "cl100k_base";
162
+ }
163
+ function estimateTokensWithMeta(value, options = {}) {
164
+ const { exact = true, model = "gpt-4o-mini", fallbackToHeuristic = true } = options;
165
+ const heuristic = estimateTokensHeuristic(value);
166
+ const heuristicEstimator = "heuristic: 1 token \u2248 4 characters";
167
+ if (!exact) {
168
+ return { count: heuristic, estimator: heuristicEstimator };
169
+ }
170
+ try {
171
+ const tiktoken = require2("tiktoken");
172
+ const encoder = tiktoken.encoding_for_model(model);
173
+ const count = encoder.encode(serializeForTokenEstimate(value)).length;
174
+ let encodingName = encoder.name;
175
+ if (!encodingName && typeof tiktoken.model_to_encoding === "function") {
176
+ try {
177
+ encodingName = tiktoken.model_to_encoding(model);
178
+ } catch {
179
+ encodingName = void 0;
180
+ }
181
+ }
182
+ encodingName ?? (encodingName = expectedEncodingForModel(model));
183
+ encoder.free?.();
184
+ return {
185
+ count,
186
+ estimator: `exact tokenizer: model=${model} encoding=${encodingName}`
187
+ };
188
+ } catch {
189
+ const expectedEncoding = expectedEncodingForModel(model);
190
+ if (!fallbackToHeuristic) {
191
+ return {
192
+ count: heuristic,
193
+ estimator: `exact requested but tokenizer unavailable (expected encoding=${expectedEncoding} for model=${model})`
194
+ };
195
+ }
196
+ return {
197
+ count: heuristic,
198
+ estimator: `${heuristicEstimator} (model=${model} expected_encoding=${expectedEncoding})`
199
+ };
200
+ }
201
+ }
150
202
 
151
203
  // src/core/analyze.ts
152
204
  function analyze(input, options = {}) {
205
+ const {
206
+ exact = true,
207
+ model = "gpt-4o-mini",
208
+ fallbackToHeuristic = true
209
+ } = options;
153
210
  if (!input || typeof input === "object" && input !== null && Object.keys(input).length === 0) {
154
211
  return {
155
212
  originalTokens: 0,
@@ -160,10 +217,15 @@ function analyze(input, options = {}) {
160
217
  reductionRatio: 1,
161
218
  originalCharacters: 0,
162
219
  optimizedCharacters: 0,
163
- estimator: "heuristic: 1 token \u2248 4 characters"
220
+ estimator: exact ? `exact tokenizer: model=${model}` : "heuristic: 1 token \u2248 4 characters"
164
221
  };
165
222
  }
166
- const originalTokens = estimateTokens(input);
223
+ const originalMeta = estimateTokensWithMeta(input, {
224
+ exact,
225
+ model,
226
+ fallbackToHeuristic
227
+ });
228
+ const originalTokens = originalMeta.count;
167
229
  let optimizedData = input;
168
230
  if (options.prune && Array.isArray(options.prune)) {
169
231
  optimizedData = prune(optimizedData, options.prune);
@@ -177,10 +239,16 @@ function analyze(input, options = {}) {
177
239
  if (options.toTOON === true || options.toon === true) {
178
240
  optimizedData = toTOON(optimizedData);
179
241
  }
180
- const optimizedTokens = estimateTokens(optimizedData);
242
+ const optimizedMeta = estimateTokensWithMeta(optimizedData, {
243
+ exact,
244
+ model,
245
+ fallbackToHeuristic
246
+ });
247
+ const optimizedTokens = optimizedMeta.count;
181
248
  const savings = Math.max(0, originalTokens - optimizedTokens);
182
249
  const savingsPercent = originalTokens > 0 ? Math.round(savings / originalTokens * 100) : 0;
183
250
  const reductionRatio = originalTokens > 0 ? optimizedTokens / originalTokens : 1;
251
+ const estimatorLabel = originalMeta && originalMeta.estimator ? originalMeta.estimator : optimizedMeta.estimator;
184
252
  return {
185
253
  originalTokens,
186
254
  optimizedTokens,
@@ -190,7 +258,7 @@ function analyze(input, options = {}) {
190
258
  reductionRatio,
191
259
  originalCharacters: serializeForTokenEstimate(input).length,
192
260
  optimizedCharacters: serializeForTokenEstimate(optimizedData).length,
193
- estimator: "heuristic: 1 token \u2248 4 characters"
261
+ estimator: estimatorLabel
194
262
  };
195
263
  }
196
264
  // Annotate the CommonJS export names for ESM import in node:
@@ -7,6 +7,9 @@ type AnalyzeOptions = {
7
7
  flatten?: boolean;
8
8
  toTOON?: boolean;
9
9
  toon?: boolean;
10
+ exact?: boolean;
11
+ model?: string;
12
+ fallbackToHeuristic?: boolean;
10
13
  };
11
14
  declare function analyze(input: unknown, options?: AnalyzeOptions): {
12
15
  originalTokens: number;
@@ -7,6 +7,9 @@ type AnalyzeOptions = {
7
7
  flatten?: boolean;
8
8
  toTOON?: boolean;
9
9
  toon?: boolean;
10
+ exact?: boolean;
11
+ model?: string;
12
+ fallbackToHeuristic?: boolean;
10
13
  };
11
14
  declare function analyze(input: unknown, options?: AnalyzeOptions): {
12
15
  originalTokens: number;
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  analyze
3
- } from "../chunk-ZVQRYJU5.js";
3
+ } from "../chunk-2TDVL6BJ.js";
4
4
  import "../chunk-6VRLDRJK.js";
5
5
  import "../chunk-XE36GLJP.js";
6
6
  import "../chunk-L7BC62MT.js";
7
7
  import "../chunk-ZD536GZF.js";
8
- import "../chunk-7CL7GVRF.js";
8
+ import "../chunk-L3HY6AWL.js";
9
9
  export {
10
10
  analyze
11
11
  };
@@ -21,19 +21,76 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var token_exports = {};
22
22
  __export(token_exports, {
23
23
  estimateTokens: () => estimateTokens,
24
+ estimateTokensWithMeta: () => estimateTokensWithMeta,
24
25
  serializeForTokenEstimate: () => serializeForTokenEstimate
25
26
  });
26
27
  module.exports = __toCommonJS(token_exports);
28
+ var import_node_module = require("module");
29
+ var import_meta = {};
30
+ var require2 = (0, import_node_module.createRequire)(import_meta.url);
27
31
  function serializeForTokenEstimate(value) {
28
32
  if (typeof value === "string") return value;
29
33
  const serialized = JSON.stringify(value);
30
34
  return serialized ?? "";
31
35
  }
32
- function estimateTokens(value) {
36
+ function estimateTokensHeuristic(value) {
33
37
  return Math.ceil(serializeForTokenEstimate(value).length / 4);
34
38
  }
39
+ function expectedEncodingForModel(model) {
40
+ const normalizedModel = model.toLowerCase();
41
+ if (normalizedModel.includes("gpt-4o") || normalizedModel.includes("gpt-4.1") || normalizedModel.includes("o1") || normalizedModel.includes("o3") || normalizedModel.includes("o4")) {
42
+ return "o200k_base";
43
+ }
44
+ if (normalizedModel.includes("davinci") || normalizedModel.startsWith("text-") || normalizedModel.includes("babbage") || normalizedModel.includes("curie")) {
45
+ return "r50k_base";
46
+ }
47
+ return "cl100k_base";
48
+ }
49
+ function estimateTokens(value, options = {}) {
50
+ return estimateTokensWithMeta(value, options).count;
51
+ }
52
+ function estimateTokensWithMeta(value, options = {}) {
53
+ const { exact = true, model = "gpt-4o-mini", fallbackToHeuristic = true } = options;
54
+ const heuristic = estimateTokensHeuristic(value);
55
+ const heuristicEstimator = "heuristic: 1 token \u2248 4 characters";
56
+ if (!exact) {
57
+ return { count: heuristic, estimator: heuristicEstimator };
58
+ }
59
+ try {
60
+ const tiktoken = require2("tiktoken");
61
+ const encoder = tiktoken.encoding_for_model(model);
62
+ const count = encoder.encode(serializeForTokenEstimate(value)).length;
63
+ let encodingName = encoder.name;
64
+ if (!encodingName && typeof tiktoken.model_to_encoding === "function") {
65
+ try {
66
+ encodingName = tiktoken.model_to_encoding(model);
67
+ } catch {
68
+ encodingName = void 0;
69
+ }
70
+ }
71
+ encodingName ?? (encodingName = expectedEncodingForModel(model));
72
+ encoder.free?.();
73
+ return {
74
+ count,
75
+ estimator: `exact tokenizer: model=${model} encoding=${encodingName}`
76
+ };
77
+ } catch {
78
+ const expectedEncoding = expectedEncodingForModel(model);
79
+ if (!fallbackToHeuristic) {
80
+ return {
81
+ count: heuristic,
82
+ estimator: `exact requested but tokenizer unavailable (expected encoding=${expectedEncoding} for model=${model})`
83
+ };
84
+ }
85
+ return {
86
+ count: heuristic,
87
+ estimator: `${heuristicEstimator} (model=${model} expected_encoding=${expectedEncoding})`
88
+ };
89
+ }
90
+ }
35
91
  // Annotate the CommonJS export names for ESM import in node:
36
92
  0 && (module.exports = {
37
93
  estimateTokens,
94
+ estimateTokensWithMeta,
38
95
  serializeForTokenEstimate
39
96
  });
@@ -1,18 +1,18 @@
1
- /**
2
- * Estimates token count from string length.
3
- *
4
- * Approximation:
5
- * 1 token ≈ 4 characters (common for GPT models)
6
- *
7
- * @param text - Input string
8
- * @returns Estimated token count
9
- */
1
+ interface TokenEstimateOptions {
2
+ exact?: boolean;
3
+ model?: string;
4
+ fallbackToHeuristic?: boolean;
5
+ }
10
6
  declare function serializeForTokenEstimate(value: unknown): string;
7
+ type TokenEstimateResult = {
8
+ count: number;
9
+ estimator: string;
10
+ };
11
+ declare function estimateTokens(value: unknown, options?: TokenEstimateOptions): number;
11
12
  /**
12
- * Estimates tokens using a transparent four-characters-per-token heuristic.
13
- * JSON values are serialized first, matching the compact form normally sent
14
- * to an API. Use a model-specific tokenizer for billing-accurate counts.
13
+ * Returns an exact model tokenizer count by default. Set exact=false to use the
14
+ * lightweight four-characters-per-token fallback explicitly.
15
15
  */
16
- declare function estimateTokens(value: unknown): number;
16
+ declare function estimateTokensWithMeta(value: unknown, options?: TokenEstimateOptions): TokenEstimateResult;
17
17
 
18
- export { estimateTokens, serializeForTokenEstimate };
18
+ export { type TokenEstimateOptions, type TokenEstimateResult, estimateTokens, estimateTokensWithMeta, serializeForTokenEstimate };
@@ -1,18 +1,18 @@
1
- /**
2
- * Estimates token count from string length.
3
- *
4
- * Approximation:
5
- * 1 token ≈ 4 characters (common for GPT models)
6
- *
7
- * @param text - Input string
8
- * @returns Estimated token count
9
- */
1
+ interface TokenEstimateOptions {
2
+ exact?: boolean;
3
+ model?: string;
4
+ fallbackToHeuristic?: boolean;
5
+ }
10
6
  declare function serializeForTokenEstimate(value: unknown): string;
7
+ type TokenEstimateResult = {
8
+ count: number;
9
+ estimator: string;
10
+ };
11
+ declare function estimateTokens(value: unknown, options?: TokenEstimateOptions): number;
11
12
  /**
12
- * Estimates tokens using a transparent four-characters-per-token heuristic.
13
- * JSON values are serialized first, matching the compact form normally sent
14
- * to an API. Use a model-specific tokenizer for billing-accurate counts.
13
+ * Returns an exact model tokenizer count by default. Set exact=false to use the
14
+ * lightweight four-characters-per-token fallback explicitly.
15
15
  */
16
- declare function estimateTokens(value: unknown): number;
16
+ declare function estimateTokensWithMeta(value: unknown, options?: TokenEstimateOptions): TokenEstimateResult;
17
17
 
18
- export { estimateTokens, serializeForTokenEstimate };
18
+ export { type TokenEstimateOptions, type TokenEstimateResult, estimateTokens, estimateTokensWithMeta, serializeForTokenEstimate };
@@ -1,8 +1,10 @@
1
1
  import {
2
2
  estimateTokens,
3
+ estimateTokensWithMeta,
3
4
  serializeForTokenEstimate
4
- } from "../chunk-7CL7GVRF.js";
5
+ } from "../chunk-L3HY6AWL.js";
5
6
  export {
6
7
  estimateTokens,
8
+ estimateTokensWithMeta,
7
9
  serializeForTokenEstimate
8
10
  };
package/dist/index.cjs CHANGED
@@ -148,17 +148,77 @@ function isPlainObject(value) {
148
148
  }
149
149
 
150
150
  // src/core/token.ts
151
+ var import_node_module = require("module");
152
+ var import_meta = {};
153
+ var require2 = (0, import_node_module.createRequire)(import_meta.url);
151
154
  function serializeForTokenEstimate(value) {
152
155
  if (typeof value === "string") return value;
153
156
  const serialized = JSON.stringify(value);
154
157
  return serialized ?? "";
155
158
  }
156
- function estimateTokens(value) {
159
+ function estimateTokensHeuristic(value) {
157
160
  return Math.ceil(serializeForTokenEstimate(value).length / 4);
158
161
  }
162
+ function expectedEncodingForModel(model) {
163
+ const normalizedModel = model.toLowerCase();
164
+ if (normalizedModel.includes("gpt-4o") || normalizedModel.includes("gpt-4.1") || normalizedModel.includes("o1") || normalizedModel.includes("o3") || normalizedModel.includes("o4")) {
165
+ return "o200k_base";
166
+ }
167
+ if (normalizedModel.includes("davinci") || normalizedModel.startsWith("text-") || normalizedModel.includes("babbage") || normalizedModel.includes("curie")) {
168
+ return "r50k_base";
169
+ }
170
+ return "cl100k_base";
171
+ }
172
+ function estimateTokens(value, options = {}) {
173
+ return estimateTokensWithMeta(value, options).count;
174
+ }
175
+ function estimateTokensWithMeta(value, options = {}) {
176
+ const { exact = true, model = "gpt-4o-mini", fallbackToHeuristic = true } = options;
177
+ const heuristic = estimateTokensHeuristic(value);
178
+ const heuristicEstimator = "heuristic: 1 token \u2248 4 characters";
179
+ if (!exact) {
180
+ return { count: heuristic, estimator: heuristicEstimator };
181
+ }
182
+ try {
183
+ const tiktoken = require2("tiktoken");
184
+ const encoder = tiktoken.encoding_for_model(model);
185
+ const count = encoder.encode(serializeForTokenEstimate(value)).length;
186
+ let encodingName = encoder.name;
187
+ if (!encodingName && typeof tiktoken.model_to_encoding === "function") {
188
+ try {
189
+ encodingName = tiktoken.model_to_encoding(model);
190
+ } catch {
191
+ encodingName = void 0;
192
+ }
193
+ }
194
+ encodingName ?? (encodingName = expectedEncodingForModel(model));
195
+ encoder.free?.();
196
+ return {
197
+ count,
198
+ estimator: `exact tokenizer: model=${model} encoding=${encodingName}`
199
+ };
200
+ } catch {
201
+ const expectedEncoding = expectedEncodingForModel(model);
202
+ if (!fallbackToHeuristic) {
203
+ return {
204
+ count: heuristic,
205
+ estimator: `exact requested but tokenizer unavailable (expected encoding=${expectedEncoding} for model=${model})`
206
+ };
207
+ }
208
+ return {
209
+ count: heuristic,
210
+ estimator: `${heuristicEstimator} (model=${model} expected_encoding=${expectedEncoding})`
211
+ };
212
+ }
213
+ }
159
214
 
160
215
  // src/core/analyze.ts
161
216
  function analyze(input, options = {}) {
217
+ const {
218
+ exact = true,
219
+ model = "gpt-4o-mini",
220
+ fallbackToHeuristic = true
221
+ } = options;
162
222
  if (!input || typeof input === "object" && input !== null && Object.keys(input).length === 0) {
163
223
  return {
164
224
  originalTokens: 0,
@@ -169,10 +229,15 @@ function analyze(input, options = {}) {
169
229
  reductionRatio: 1,
170
230
  originalCharacters: 0,
171
231
  optimizedCharacters: 0,
172
- estimator: "heuristic: 1 token \u2248 4 characters"
232
+ estimator: exact ? `exact tokenizer: model=${model}` : "heuristic: 1 token \u2248 4 characters"
173
233
  };
174
234
  }
175
- const originalTokens = estimateTokens(input);
235
+ const originalMeta = estimateTokensWithMeta(input, {
236
+ exact,
237
+ model,
238
+ fallbackToHeuristic
239
+ });
240
+ const originalTokens = originalMeta.count;
176
241
  let optimizedData = input;
177
242
  if (options.prune && Array.isArray(options.prune)) {
178
243
  optimizedData = prune(optimizedData, options.prune);
@@ -186,10 +251,16 @@ function analyze(input, options = {}) {
186
251
  if (options.toTOON === true || options.toon === true) {
187
252
  optimizedData = toTOON(optimizedData);
188
253
  }
189
- const optimizedTokens = estimateTokens(optimizedData);
254
+ const optimizedMeta = estimateTokensWithMeta(optimizedData, {
255
+ exact,
256
+ model,
257
+ fallbackToHeuristic
258
+ });
259
+ const optimizedTokens = optimizedMeta.count;
190
260
  const savings = Math.max(0, originalTokens - optimizedTokens);
191
261
  const savingsPercent = originalTokens > 0 ? Math.round(savings / originalTokens * 100) : 0;
192
262
  const reductionRatio = originalTokens > 0 ? optimizedTokens / originalTokens : 1;
263
+ const estimatorLabel = originalMeta && originalMeta.estimator ? originalMeta.estimator : optimizedMeta.estimator;
193
264
  return {
194
265
  originalTokens,
195
266
  optimizedTokens,
@@ -199,7 +270,7 @@ function analyze(input, options = {}) {
199
270
  reductionRatio,
200
271
  originalCharacters: serializeForTokenEstimate(input).length,
201
272
  optimizedCharacters: serializeForTokenEstimate(optimizedData).length,
202
- estimator: "heuristic: 1 token \u2248 4 characters"
273
+ estimator: estimatorLabel
203
274
  };
204
275
  }
205
276
 
@@ -329,8 +400,8 @@ var AIChain = class {
329
400
  toNatural() {
330
401
  return toNatural(this.data);
331
402
  }
332
- analyze() {
333
- return analyze(this.data);
403
+ analyze(options) {
404
+ return analyze(this.data, options);
334
405
  }
335
406
  value() {
336
407
  return this.data;
package/dist/index.d.cts CHANGED
@@ -14,7 +14,7 @@ declare class AIChain {
14
14
  flatten(): this;
15
15
  toTOON(): this;
16
16
  toNatural(): string;
17
- analyze(): {
17
+ analyze(options?: any): {
18
18
  originalTokens: number;
19
19
  optimizedTokens: number;
20
20
  savings: number;
package/dist/index.d.ts CHANGED
@@ -14,7 +14,7 @@ declare class AIChain {
14
14
  flatten(): this;
15
15
  toTOON(): this;
16
16
  toNatural(): string;
17
- analyze(): {
17
+ analyze(options?: any): {
18
18
  originalTokens: number;
19
19
  optimizedTokens: number;
20
20
  savings: number;
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  AIChain
3
- } from "./chunk-HFMW3K22.js";
3
+ } from "./chunk-QHCUNOUH.js";
4
4
  import {
5
5
  analyze
6
- } from "./chunk-ZVQRYJU5.js";
6
+ } from "./chunk-2TDVL6BJ.js";
7
7
  import {
8
8
  toTOON
9
9
  } from "./chunk-6VRLDRJK.js";
@@ -22,7 +22,7 @@ import {
22
22
  import {
23
23
  estimateTokens,
24
24
  serializeForTokenEstimate
25
- } from "./chunk-7CL7GVRF.js";
25
+ } from "./chunk-L3HY6AWL.js";
26
26
 
27
27
  // src/index.ts
28
28
  function ai(data) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sohqureshi/tokenwise",
3
- "version": "1.0.4",
3
+ "version": "1.0.9",
4
4
  "description": "Optimize JSON data for AI by reducing token usage",
5
5
  "repository": {
6
6
  "type": "git",
@@ -75,6 +75,9 @@
75
75
  ],
76
76
  "author": "Mohammad Sohail",
77
77
  "license": "MIT",
78
+ "dependencies": {
79
+ "tiktoken": "^1.0.22"
80
+ },
78
81
  "devDependencies": {
79
82
  "@types/node": "^25.6.0",
80
83
  "tsup": "^8.0.0",
@@ -1,14 +0,0 @@
1
- // src/core/token.ts
2
- function serializeForTokenEstimate(value) {
3
- if (typeof value === "string") return value;
4
- const serialized = JSON.stringify(value);
5
- return serialized ?? "";
6
- }
7
- function estimateTokens(value) {
8
- return Math.ceil(serializeForTokenEstimate(value).length / 4);
9
- }
10
-
11
- export {
12
- serializeForTokenEstimate,
13
- estimateTokens
14
- };