@sohqureshi/tokenwise 1.0.4 → 1.0.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/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 },
@@ -209,8 +211,9 @@ node demo.js
209
211
  ```
210
212
 
211
213
  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.
214
+ four-characters-per-token heuristic. For model-accurate counts, pass
215
+ `exact: true` and a `model` name to `estimateTokens()` or `analyze()`.
216
+ This is still only as exact as the tokenizer implementation you use.
214
217
 
215
218
  ---
216
219
 
@@ -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-U433WUUT.js";
17
17
 
18
18
  // src/core/analyze.ts
19
19
  function analyze(input, options = {}) {
20
+ const {
21
+ exact = false,
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
 
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  analyze
3
- } from "./chunk-ZVQRYJU5.js";
3
+ } from "./chunk-IOMOVRC7.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;
@@ -0,0 +1,55 @@
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 estimateTokens(value, options = {}) {
13
+ return estimateTokensWithMeta(value, options).count;
14
+ }
15
+ function estimateTokensWithMeta(value, options = {}) {
16
+ const { exact = false, model = "gpt-4o-mini", fallbackToHeuristic = true } = options;
17
+ const heuristic = estimateTokensHeuristic(value);
18
+ const heuristicEstimator = "heuristic: 1 token \u2248 4 characters";
19
+ if (!exact) {
20
+ return { count: heuristic, estimator: heuristicEstimator };
21
+ }
22
+ try {
23
+ const tiktoken = require2("tiktoken");
24
+ const encoder = tiktoken.encoding_for_model(model);
25
+ const text = serializeForTokenEstimate(value);
26
+ const count = encoder.encode(text).length;
27
+ let encodingName = null;
28
+ if (encoder.name) encodingName = encoder.name;
29
+ if (!encodingName && typeof tiktoken.model_to_encoding === "function") {
30
+ try {
31
+ encodingName = tiktoken.model_to_encoding(model);
32
+ } catch (e) {
33
+ encodingName = null;
34
+ }
35
+ }
36
+ if (!encodingName) {
37
+ const m = String(model || "").toLowerCase();
38
+ if (m.includes("davinci") || m.startsWith("text-")) encodingName = "r50k_base";
39
+ else encodingName = "cl100k_base";
40
+ }
41
+ const estimator = `exact tokenizer: model=${model} encoding=${encodingName}`;
42
+ return { count, estimator };
43
+ } catch (e) {
44
+ if (!fallbackToHeuristic) {
45
+ return { count: heuristic, estimator: "exact requested but tokenizer unavailable" };
46
+ }
47
+ return { count: heuristic, estimator: heuristicEstimator };
48
+ }
49
+ }
50
+
51
+ export {
52
+ serializeForTokenEstimate,
53
+ estimateTokens,
54
+ estimateTokensWithMeta
55
+ };
package/dist/cli.cjs CHANGED
@@ -142,17 +142,60 @@ 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 estimateTokensWithMeta(value, options = {}) {
157
+ const { exact = false, model = "gpt-4o-mini", fallbackToHeuristic = true } = options;
158
+ const heuristic = estimateTokensHeuristic(value);
159
+ const heuristicEstimator = "heuristic: 1 token \u2248 4 characters";
160
+ if (!exact) {
161
+ return { count: heuristic, estimator: heuristicEstimator };
162
+ }
163
+ try {
164
+ const tiktoken = require2("tiktoken");
165
+ const encoder = tiktoken.encoding_for_model(model);
166
+ const text = serializeForTokenEstimate(value);
167
+ const count = encoder.encode(text).length;
168
+ let encodingName = null;
169
+ if (encoder.name) encodingName = encoder.name;
170
+ if (!encodingName && typeof tiktoken.model_to_encoding === "function") {
171
+ try {
172
+ encodingName = tiktoken.model_to_encoding(model);
173
+ } catch (e) {
174
+ encodingName = null;
175
+ }
176
+ }
177
+ if (!encodingName) {
178
+ const m = String(model || "").toLowerCase();
179
+ if (m.includes("davinci") || m.startsWith("text-")) encodingName = "r50k_base";
180
+ else encodingName = "cl100k_base";
181
+ }
182
+ const estimator = `exact tokenizer: model=${model} encoding=${encodingName}`;
183
+ return { count, estimator };
184
+ } catch (e) {
185
+ if (!fallbackToHeuristic) {
186
+ return { count: heuristic, estimator: "exact requested but tokenizer unavailable" };
187
+ }
188
+ return { count: heuristic, estimator: heuristicEstimator };
189
+ }
190
+ }
153
191
 
154
192
  // src/core/analyze.ts
155
193
  function analyze(input, options = {}) {
194
+ const {
195
+ exact = false,
196
+ model = "gpt-4o-mini",
197
+ fallbackToHeuristic = true
198
+ } = options;
156
199
  if (!input || typeof input === "object" && input !== null && Object.keys(input).length === 0) {
157
200
  return {
158
201
  originalTokens: 0,
@@ -163,10 +206,15 @@ function analyze(input, options = {}) {
163
206
  reductionRatio: 1,
164
207
  originalCharacters: 0,
165
208
  optimizedCharacters: 0,
166
- estimator: "heuristic: 1 token \u2248 4 characters"
209
+ estimator: exact ? `exact tokenizer: model=${model}` : "heuristic: 1 token \u2248 4 characters"
167
210
  };
168
211
  }
169
- const originalTokens = estimateTokens(input);
212
+ const originalMeta = estimateTokensWithMeta(input, {
213
+ exact,
214
+ model,
215
+ fallbackToHeuristic
216
+ });
217
+ const originalTokens = originalMeta.count;
170
218
  let optimizedData = input;
171
219
  if (options.prune && Array.isArray(options.prune)) {
172
220
  optimizedData = prune(optimizedData, options.prune);
@@ -180,10 +228,16 @@ function analyze(input, options = {}) {
180
228
  if (options.toTOON === true || options.toon === true) {
181
229
  optimizedData = toTOON(optimizedData);
182
230
  }
183
- const optimizedTokens = estimateTokens(optimizedData);
231
+ const optimizedMeta = estimateTokensWithMeta(optimizedData, {
232
+ exact,
233
+ model,
234
+ fallbackToHeuristic
235
+ });
236
+ const optimizedTokens = optimizedMeta.count;
184
237
  const savings = Math.max(0, originalTokens - optimizedTokens);
185
238
  const savingsPercent = originalTokens > 0 ? Math.round(savings / originalTokens * 100) : 0;
186
239
  const reductionRatio = originalTokens > 0 ? optimizedTokens / originalTokens : 1;
240
+ const estimatorLabel = originalMeta && originalMeta.estimator ? originalMeta.estimator : optimizedMeta.estimator;
187
241
  return {
188
242
  originalTokens,
189
243
  optimizedTokens,
@@ -193,7 +247,7 @@ function analyze(input, options = {}) {
193
247
  reductionRatio,
194
248
  originalCharacters: serializeForTokenEstimate(input).length,
195
249
  optimizedCharacters: serializeForTokenEstimate(optimizedData).length,
196
- estimator: "heuristic: 1 token \u2248 4 characters"
250
+ estimator: estimatorLabel
197
251
  };
198
252
  }
199
253
 
@@ -323,8 +377,8 @@ var AIChain = class {
323
377
  toNatural() {
324
378
  return toNatural(this.data);
325
379
  }
326
- analyze() {
327
- return analyze(this.data);
380
+ analyze(options) {
381
+ return analyze(this.data, options);
328
382
  }
329
383
  value() {
330
384
  return this.data;
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-P6KXISNT.js";
5
+ import "./chunk-IOMOVRC7.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-U433WUUT.js";
12
12
 
13
13
  // src/cli.ts
14
14
  import fs from "fs";
@@ -139,17 +139,60 @@ 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 estimateTokensWithMeta(value, options = {}) {
154
+ const { exact = false, model = "gpt-4o-mini", fallbackToHeuristic = true } = options;
155
+ const heuristic = estimateTokensHeuristic(value);
156
+ const heuristicEstimator = "heuristic: 1 token \u2248 4 characters";
157
+ if (!exact) {
158
+ return { count: heuristic, estimator: heuristicEstimator };
159
+ }
160
+ try {
161
+ const tiktoken = require2("tiktoken");
162
+ const encoder = tiktoken.encoding_for_model(model);
163
+ const text = serializeForTokenEstimate(value);
164
+ const count = encoder.encode(text).length;
165
+ let encodingName = null;
166
+ if (encoder.name) encodingName = encoder.name;
167
+ if (!encodingName && typeof tiktoken.model_to_encoding === "function") {
168
+ try {
169
+ encodingName = tiktoken.model_to_encoding(model);
170
+ } catch (e) {
171
+ encodingName = null;
172
+ }
173
+ }
174
+ if (!encodingName) {
175
+ const m = String(model || "").toLowerCase();
176
+ if (m.includes("davinci") || m.startsWith("text-")) encodingName = "r50k_base";
177
+ else encodingName = "cl100k_base";
178
+ }
179
+ const estimator = `exact tokenizer: model=${model} encoding=${encodingName}`;
180
+ return { count, estimator };
181
+ } catch (e) {
182
+ if (!fallbackToHeuristic) {
183
+ return { count: heuristic, estimator: "exact requested but tokenizer unavailable" };
184
+ }
185
+ return { count: heuristic, estimator: heuristicEstimator };
186
+ }
187
+ }
150
188
 
151
189
  // src/core/analyze.ts
152
190
  function analyze(input, options = {}) {
191
+ const {
192
+ exact = false,
193
+ model = "gpt-4o-mini",
194
+ fallbackToHeuristic = true
195
+ } = options;
153
196
  if (!input || typeof input === "object" && input !== null && Object.keys(input).length === 0) {
154
197
  return {
155
198
  originalTokens: 0,
@@ -160,10 +203,15 @@ function analyze(input, options = {}) {
160
203
  reductionRatio: 1,
161
204
  originalCharacters: 0,
162
205
  optimizedCharacters: 0,
163
- estimator: "heuristic: 1 token \u2248 4 characters"
206
+ estimator: exact ? `exact tokenizer: model=${model}` : "heuristic: 1 token \u2248 4 characters"
164
207
  };
165
208
  }
166
- const originalTokens = estimateTokens(input);
209
+ const originalMeta = estimateTokensWithMeta(input, {
210
+ exact,
211
+ model,
212
+ fallbackToHeuristic
213
+ });
214
+ const originalTokens = originalMeta.count;
167
215
  let optimizedData = input;
168
216
  if (options.prune && Array.isArray(options.prune)) {
169
217
  optimizedData = prune(optimizedData, options.prune);
@@ -177,10 +225,16 @@ function analyze(input, options = {}) {
177
225
  if (options.toTOON === true || options.toon === true) {
178
226
  optimizedData = toTOON(optimizedData);
179
227
  }
180
- const optimizedTokens = estimateTokens(optimizedData);
228
+ const optimizedMeta = estimateTokensWithMeta(optimizedData, {
229
+ exact,
230
+ model,
231
+ fallbackToHeuristic
232
+ });
233
+ const optimizedTokens = optimizedMeta.count;
181
234
  const savings = Math.max(0, originalTokens - optimizedTokens);
182
235
  const savingsPercent = originalTokens > 0 ? Math.round(savings / originalTokens * 100) : 0;
183
236
  const reductionRatio = originalTokens > 0 ? optimizedTokens / originalTokens : 1;
237
+ const estimatorLabel = originalMeta && originalMeta.estimator ? originalMeta.estimator : optimizedMeta.estimator;
184
238
  return {
185
239
  originalTokens,
186
240
  optimizedTokens,
@@ -190,7 +244,7 @@ function analyze(input, options = {}) {
190
244
  reductionRatio,
191
245
  originalCharacters: serializeForTokenEstimate(input).length,
192
246
  optimizedCharacters: serializeForTokenEstimate(optimizedData).length,
193
- estimator: "heuristic: 1 token \u2248 4 characters"
247
+ estimator: estimatorLabel
194
248
  };
195
249
  }
196
250
  // 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-IOMOVRC7.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-U433WUUT.js";
9
9
  export {
10
10
  analyze
11
11
  };
@@ -21,19 +21,62 @@ 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 estimateTokens(value, options = {}) {
40
+ return estimateTokensWithMeta(value, options).count;
41
+ }
42
+ function estimateTokensWithMeta(value, options = {}) {
43
+ const { exact = false, model = "gpt-4o-mini", fallbackToHeuristic = true } = options;
44
+ const heuristic = estimateTokensHeuristic(value);
45
+ const heuristicEstimator = "heuristic: 1 token \u2248 4 characters";
46
+ if (!exact) {
47
+ return { count: heuristic, estimator: heuristicEstimator };
48
+ }
49
+ try {
50
+ const tiktoken = require2("tiktoken");
51
+ const encoder = tiktoken.encoding_for_model(model);
52
+ const text = serializeForTokenEstimate(value);
53
+ const count = encoder.encode(text).length;
54
+ let encodingName = null;
55
+ if (encoder.name) encodingName = encoder.name;
56
+ if (!encodingName && typeof tiktoken.model_to_encoding === "function") {
57
+ try {
58
+ encodingName = tiktoken.model_to_encoding(model);
59
+ } catch (e) {
60
+ encodingName = null;
61
+ }
62
+ }
63
+ if (!encodingName) {
64
+ const m = String(model || "").toLowerCase();
65
+ if (m.includes("davinci") || m.startsWith("text-")) encodingName = "r50k_base";
66
+ else encodingName = "cl100k_base";
67
+ }
68
+ const estimator = `exact tokenizer: model=${model} encoding=${encodingName}`;
69
+ return { count, estimator };
70
+ } catch (e) {
71
+ if (!fallbackToHeuristic) {
72
+ return { count: heuristic, estimator: "exact requested but tokenizer unavailable" };
73
+ }
74
+ return { count: heuristic, estimator: heuristicEstimator };
75
+ }
76
+ }
35
77
  // Annotate the CommonJS export names for ESM import in node:
36
78
  0 && (module.exports = {
37
79
  estimateTokens,
80
+ estimateTokensWithMeta,
38
81
  serializeForTokenEstimate
39
82
  });
@@ -1,18 +1,24 @@
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;
11
7
  /**
12
8
  * Estimates tokens using a transparent four-characters-per-token heuristic.
13
9
  * JSON values are serialized first, matching the compact form normally sent
14
- * to an API. Use a model-specific tokenizer for billing-accurate counts.
10
+ * to an API. Set exact=true to attempt a model-specific tokenizer count when
11
+ * the runtime has the tokenizer installed.
12
+ */
13
+ type TokenEstimateResult = {
14
+ count: number;
15
+ estimator: string;
16
+ };
17
+ declare function estimateTokens(value: unknown, options?: TokenEstimateOptions): number;
18
+ /**
19
+ * Returns both a token count and an estimator string describing how the count was
20
+ * obtained (exact tokenizer + encoding when available, or heuristic otherwise).
15
21
  */
16
- declare function estimateTokens(value: unknown): number;
22
+ declare function estimateTokensWithMeta(value: unknown, options?: TokenEstimateOptions): TokenEstimateResult;
17
23
 
18
- export { estimateTokens, serializeForTokenEstimate };
24
+ export { type TokenEstimateOptions, type TokenEstimateResult, estimateTokens, estimateTokensWithMeta, serializeForTokenEstimate };
@@ -1,18 +1,24 @@
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;
11
7
  /**
12
8
  * Estimates tokens using a transparent four-characters-per-token heuristic.
13
9
  * JSON values are serialized first, matching the compact form normally sent
14
- * to an API. Use a model-specific tokenizer for billing-accurate counts.
10
+ * to an API. Set exact=true to attempt a model-specific tokenizer count when
11
+ * the runtime has the tokenizer installed.
12
+ */
13
+ type TokenEstimateResult = {
14
+ count: number;
15
+ estimator: string;
16
+ };
17
+ declare function estimateTokens(value: unknown, options?: TokenEstimateOptions): number;
18
+ /**
19
+ * Returns both a token count and an estimator string describing how the count was
20
+ * obtained (exact tokenizer + encoding when available, or heuristic otherwise).
15
21
  */
16
- declare function estimateTokens(value: unknown): number;
22
+ declare function estimateTokensWithMeta(value: unknown, options?: TokenEstimateOptions): TokenEstimateResult;
17
23
 
18
- export { estimateTokens, serializeForTokenEstimate };
24
+ 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-U433WUUT.js";
5
6
  export {
6
7
  estimateTokens,
8
+ estimateTokensWithMeta,
7
9
  serializeForTokenEstimate
8
10
  };
package/dist/index.cjs CHANGED
@@ -148,17 +148,63 @@ 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 estimateTokens(value, options = {}) {
163
+ return estimateTokensWithMeta(value, options).count;
164
+ }
165
+ function estimateTokensWithMeta(value, options = {}) {
166
+ const { exact = false, model = "gpt-4o-mini", fallbackToHeuristic = true } = options;
167
+ const heuristic = estimateTokensHeuristic(value);
168
+ const heuristicEstimator = "heuristic: 1 token \u2248 4 characters";
169
+ if (!exact) {
170
+ return { count: heuristic, estimator: heuristicEstimator };
171
+ }
172
+ try {
173
+ const tiktoken = require2("tiktoken");
174
+ const encoder = tiktoken.encoding_for_model(model);
175
+ const text = serializeForTokenEstimate(value);
176
+ const count = encoder.encode(text).length;
177
+ let encodingName = null;
178
+ if (encoder.name) encodingName = encoder.name;
179
+ if (!encodingName && typeof tiktoken.model_to_encoding === "function") {
180
+ try {
181
+ encodingName = tiktoken.model_to_encoding(model);
182
+ } catch (e) {
183
+ encodingName = null;
184
+ }
185
+ }
186
+ if (!encodingName) {
187
+ const m = String(model || "").toLowerCase();
188
+ if (m.includes("davinci") || m.startsWith("text-")) encodingName = "r50k_base";
189
+ else encodingName = "cl100k_base";
190
+ }
191
+ const estimator = `exact tokenizer: model=${model} encoding=${encodingName}`;
192
+ return { count, estimator };
193
+ } catch (e) {
194
+ if (!fallbackToHeuristic) {
195
+ return { count: heuristic, estimator: "exact requested but tokenizer unavailable" };
196
+ }
197
+ return { count: heuristic, estimator: heuristicEstimator };
198
+ }
199
+ }
159
200
 
160
201
  // src/core/analyze.ts
161
202
  function analyze(input, options = {}) {
203
+ const {
204
+ exact = false,
205
+ model = "gpt-4o-mini",
206
+ fallbackToHeuristic = true
207
+ } = options;
162
208
  if (!input || typeof input === "object" && input !== null && Object.keys(input).length === 0) {
163
209
  return {
164
210
  originalTokens: 0,
@@ -169,10 +215,15 @@ function analyze(input, options = {}) {
169
215
  reductionRatio: 1,
170
216
  originalCharacters: 0,
171
217
  optimizedCharacters: 0,
172
- estimator: "heuristic: 1 token \u2248 4 characters"
218
+ estimator: exact ? `exact tokenizer: model=${model}` : "heuristic: 1 token \u2248 4 characters"
173
219
  };
174
220
  }
175
- const originalTokens = estimateTokens(input);
221
+ const originalMeta = estimateTokensWithMeta(input, {
222
+ exact,
223
+ model,
224
+ fallbackToHeuristic
225
+ });
226
+ const originalTokens = originalMeta.count;
176
227
  let optimizedData = input;
177
228
  if (options.prune && Array.isArray(options.prune)) {
178
229
  optimizedData = prune(optimizedData, options.prune);
@@ -186,10 +237,16 @@ function analyze(input, options = {}) {
186
237
  if (options.toTOON === true || options.toon === true) {
187
238
  optimizedData = toTOON(optimizedData);
188
239
  }
189
- const optimizedTokens = estimateTokens(optimizedData);
240
+ const optimizedMeta = estimateTokensWithMeta(optimizedData, {
241
+ exact,
242
+ model,
243
+ fallbackToHeuristic
244
+ });
245
+ const optimizedTokens = optimizedMeta.count;
190
246
  const savings = Math.max(0, originalTokens - optimizedTokens);
191
247
  const savingsPercent = originalTokens > 0 ? Math.round(savings / originalTokens * 100) : 0;
192
248
  const reductionRatio = originalTokens > 0 ? optimizedTokens / originalTokens : 1;
249
+ const estimatorLabel = originalMeta && originalMeta.estimator ? originalMeta.estimator : optimizedMeta.estimator;
193
250
  return {
194
251
  originalTokens,
195
252
  optimizedTokens,
@@ -199,7 +256,7 @@ function analyze(input, options = {}) {
199
256
  reductionRatio,
200
257
  originalCharacters: serializeForTokenEstimate(input).length,
201
258
  optimizedCharacters: serializeForTokenEstimate(optimizedData).length,
202
- estimator: "heuristic: 1 token \u2248 4 characters"
259
+ estimator: estimatorLabel
203
260
  };
204
261
  }
205
262
 
@@ -329,8 +386,8 @@ var AIChain = class {
329
386
  toNatural() {
330
387
  return toNatural(this.data);
331
388
  }
332
- analyze() {
333
- return analyze(this.data);
389
+ analyze(options) {
390
+ return analyze(this.data, options);
334
391
  }
335
392
  value() {
336
393
  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-P6KXISNT.js";
4
4
  import {
5
5
  analyze
6
- } from "./chunk-ZVQRYJU5.js";
6
+ } from "./chunk-IOMOVRC7.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-U433WUUT.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.7",
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
- };