@sohqureshi/tokenwise 1.0.3 → 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/dist/index.cjs CHANGED
@@ -27,6 +27,7 @@ __export(index_exports, {
27
27
  estimateTokens: () => estimateTokens,
28
28
  flatten: () => flatten,
29
29
  prune: () => prune,
30
+ serializeForTokenEstimate: () => serializeForTokenEstimate,
30
31
  toNatural: () => toNatural,
31
32
  toTOON: () => toTOON
32
33
  });
@@ -53,7 +54,7 @@ function prune(obj, options = {}) {
53
54
  return arr;
54
55
  }
55
56
  const result = {};
56
- for (const key in obj) {
57
+ for (const key of Object.keys(obj)) {
57
58
  if (removeKeys.includes(key)) continue;
58
59
  const value = prune(obj[key], normalizedOptions);
59
60
  if (value === void 0) continue;
@@ -69,20 +70,28 @@ function prune(obj, options = {}) {
69
70
  function flatten(obj, prefix = "", res = {}) {
70
71
  if (obj === null || obj === void 0) return res;
71
72
  if (typeof obj !== "object") {
72
- res[prefix] = obj;
73
+ setValue(res, prefix, obj);
73
74
  return res;
74
75
  }
75
- for (const key in obj) {
76
+ for (const key of Object.keys(obj)) {
76
77
  const value = obj[key];
77
78
  const newKey = prefix ? `${prefix}.${key}` : key;
78
79
  if (typeof value === "object" && value !== null) {
79
80
  flatten(value, newKey, res);
80
81
  } else {
81
- res[newKey] = value;
82
+ setValue(res, newKey, value);
82
83
  }
83
84
  }
84
85
  return res;
85
86
  }
87
+ function setValue(res, key, value) {
88
+ if (Object.prototype.hasOwnProperty.call(res, key)) {
89
+ throw new Error(
90
+ `Cannot flatten input: the path "${key}" collides with an existing key. Use keys without dots or rename one of the conflicting properties.`
91
+ );
92
+ }
93
+ res[key] = value;
94
+ }
86
95
 
87
96
  // src/core/compact.ts
88
97
  function compact(obj) {
@@ -106,11 +115,11 @@ function toTOON(data, indent = 0) {
106
115
  }
107
116
  return result;
108
117
  }
109
- return `${space}[${data.length}]: ${data.join(",")}`;
118
+ return `${space}[${data.length}]: ${data.map(formatValue).join(",")}`;
110
119
  }
111
120
  if (typeof data === "object" && data !== null) {
112
121
  let result = "";
113
- for (const key in data) {
122
+ for (const key of Object.keys(data)) {
114
123
  const value = data[key];
115
124
  if (typeof value === "object" && value !== null) {
116
125
  result += `${space}${key}:
@@ -129,7 +138,9 @@ ${toTOON(value, indent + 1)}
129
138
  }
130
139
  function formatValue(val) {
131
140
  if (val === null || val === void 0) return "";
132
- if (typeof val === "string") return val;
141
+ if (typeof val === "string") {
142
+ return val === "" || /[,\n\r]/.test(val) ? JSON.stringify(val) : val;
143
+ }
133
144
  return String(val);
134
145
  }
135
146
  function isPlainObject(value) {
@@ -137,13 +148,63 @@ function isPlainObject(value) {
137
148
  }
138
149
 
139
150
  // src/core/token.ts
140
- function estimateTokens(text) {
141
- if (!text) return 0;
142
- return Math.ceil(text.length / 4);
151
+ var import_node_module = require("module");
152
+ var import_meta = {};
153
+ var require2 = (0, import_node_module.createRequire)(import_meta.url);
154
+ function serializeForTokenEstimate(value) {
155
+ if (typeof value === "string") return value;
156
+ const serialized = JSON.stringify(value);
157
+ return serialized ?? "";
158
+ }
159
+ function estimateTokensHeuristic(value) {
160
+ return Math.ceil(serializeForTokenEstimate(value).length / 4);
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
+ }
143
199
  }
144
200
 
145
201
  // src/core/analyze.ts
146
202
  function analyze(input, options = {}) {
203
+ const {
204
+ exact = false,
205
+ model = "gpt-4o-mini",
206
+ fallbackToHeuristic = true
207
+ } = options;
147
208
  if (!input || typeof input === "object" && input !== null && Object.keys(input).length === 0) {
148
209
  return {
149
210
  originalTokens: 0,
@@ -151,11 +212,18 @@ function analyze(input, options = {}) {
151
212
  savings: 0,
152
213
  savingsPercent: 0,
153
214
  optimizedData: null,
154
- reductionRatio: 1
215
+ reductionRatio: 1,
216
+ originalCharacters: 0,
217
+ optimizedCharacters: 0,
218
+ estimator: exact ? `exact tokenizer: model=${model}` : "heuristic: 1 token \u2248 4 characters"
155
219
  };
156
220
  }
157
- let originalTokens = estimateTokens(input);
158
- if (isNaN(originalTokens) || !isFinite(originalTokens)) originalTokens = 0;
221
+ const originalMeta = estimateTokensWithMeta(input, {
222
+ exact,
223
+ model,
224
+ fallbackToHeuristic
225
+ });
226
+ const originalTokens = originalMeta.count;
159
227
  let optimizedData = input;
160
228
  if (options.prune && Array.isArray(options.prune)) {
161
229
  optimizedData = prune(optimizedData, options.prune);
@@ -169,18 +237,26 @@ function analyze(input, options = {}) {
169
237
  if (options.toTOON === true || options.toon === true) {
170
238
  optimizedData = toTOON(optimizedData);
171
239
  }
172
- let optimizedTokens = estimateTokens(optimizedData);
173
- if (isNaN(optimizedTokens) || !isFinite(optimizedTokens)) optimizedTokens = 0;
240
+ const optimizedMeta = estimateTokensWithMeta(optimizedData, {
241
+ exact,
242
+ model,
243
+ fallbackToHeuristic
244
+ });
245
+ const optimizedTokens = optimizedMeta.count;
174
246
  const savings = Math.max(0, originalTokens - optimizedTokens);
175
247
  const savingsPercent = originalTokens > 0 ? Math.round(savings / originalTokens * 100) : 0;
176
248
  const reductionRatio = originalTokens > 0 ? optimizedTokens / originalTokens : 1;
249
+ const estimatorLabel = originalMeta && originalMeta.estimator ? originalMeta.estimator : optimizedMeta.estimator;
177
250
  return {
178
251
  originalTokens,
179
252
  optimizedTokens,
180
253
  savings,
181
254
  savingsPercent,
182
255
  optimizedData,
183
- reductionRatio
256
+ reductionRatio,
257
+ originalCharacters: serializeForTokenEstimate(input).length,
258
+ optimizedCharacters: serializeForTokenEstimate(optimizedData).length,
259
+ estimator: estimatorLabel
184
260
  };
185
261
  }
186
262
 
@@ -310,8 +386,8 @@ var AIChain = class {
310
386
  toNatural() {
311
387
  return toNatural(this.data);
312
388
  }
313
- analyze() {
314
- return analyze(this.data);
389
+ analyze(options) {
390
+ return analyze(this.data, options);
315
391
  }
316
392
  value() {
317
393
  return this.data;
@@ -329,6 +405,7 @@ ai.toTOON = toTOON;
329
405
  ai.analyze = analyze;
330
406
  ai.toNatural = toNatural;
331
407
  ai.estimateTokens = estimateTokens;
408
+ ai.serializeForTokenEstimate = serializeForTokenEstimate;
332
409
  ai.AIChain = AIChain;
333
410
  var index_default = ai;
334
411
  // Annotate the CommonJS export names for ESM import in node:
@@ -339,6 +416,7 @@ var index_default = ai;
339
416
  estimateTokens,
340
417
  flatten,
341
418
  prune,
419
+ serializeForTokenEstimate,
342
420
  toNatural,
343
421
  toTOON
344
422
  });
package/dist/index.d.cts CHANGED
@@ -1,10 +1,10 @@
1
- import { estimateTokens } from './core/token.cjs';
2
- import { toNatural } from './core/natural.cjs';
3
- import { analyze } from './core/analyze.cjs';
4
- import { toTOON } from './core/toon.cjs';
5
- import { flatten } from './core/flatten.cjs';
6
- import { compact } from './core/compact.cjs';
7
1
  import { prune } from './core/prune.cjs';
2
+ import { compact } from './core/compact.cjs';
3
+ import { flatten } from './core/flatten.cjs';
4
+ import { toTOON } from './core/toon.cjs';
5
+ import { analyze } from './core/analyze.cjs';
6
+ import { toNatural } from './core/natural.cjs';
7
+ import { estimateTokens, serializeForTokenEstimate } from './core/token.cjs';
8
8
 
9
9
  declare class AIChain {
10
10
  private data;
@@ -14,13 +14,16 @@ 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;
21
21
  savingsPercent: number;
22
- optimizedData: any;
22
+ optimizedData: unknown;
23
23
  reductionRatio: number;
24
+ originalCharacters: number;
25
+ optimizedCharacters: number;
26
+ estimator: string;
24
27
  };
25
28
  value(): any;
26
29
  }
@@ -34,7 +37,8 @@ declare namespace ai {
34
37
  var analyze: typeof analyze;
35
38
  var toNatural: typeof toNatural;
36
39
  var estimateTokens: typeof estimateTokens;
40
+ var serializeForTokenEstimate: typeof serializeForTokenEstimate;
37
41
  var AIChain: typeof AIChain;
38
42
  }
39
43
 
40
- export { AIChain, analyze, compact, ai as default, estimateTokens, flatten, prune, toNatural, toTOON };
44
+ export { AIChain, analyze, compact, ai as default, estimateTokens, flatten, prune, serializeForTokenEstimate, toNatural, toTOON };
package/dist/index.d.ts CHANGED
@@ -1,10 +1,10 @@
1
- import { estimateTokens } from './core/token.js';
2
- import { toNatural } from './core/natural.js';
3
- import { analyze } from './core/analyze.js';
4
- import { toTOON } from './core/toon.js';
5
- import { flatten } from './core/flatten.js';
6
- import { compact } from './core/compact.js';
7
1
  import { prune } from './core/prune.js';
2
+ import { compact } from './core/compact.js';
3
+ import { flatten } from './core/flatten.js';
4
+ import { toTOON } from './core/toon.js';
5
+ import { analyze } from './core/analyze.js';
6
+ import { toNatural } from './core/natural.js';
7
+ import { estimateTokens, serializeForTokenEstimate } from './core/token.js';
8
8
 
9
9
  declare class AIChain {
10
10
  private data;
@@ -14,13 +14,16 @@ 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;
21
21
  savingsPercent: number;
22
- optimizedData: any;
22
+ optimizedData: unknown;
23
23
  reductionRatio: number;
24
+ originalCharacters: number;
25
+ optimizedCharacters: number;
26
+ estimator: string;
24
27
  };
25
28
  value(): any;
26
29
  }
@@ -34,7 +37,8 @@ declare namespace ai {
34
37
  var analyze: typeof analyze;
35
38
  var toNatural: typeof toNatural;
36
39
  var estimateTokens: typeof estimateTokens;
40
+ var serializeForTokenEstimate: typeof serializeForTokenEstimate;
37
41
  var AIChain: typeof AIChain;
38
42
  }
39
43
 
40
- export { AIChain, analyze, compact, ai as default, estimateTokens, flatten, prune, toNatural, toTOON };
44
+ export { AIChain, analyze, compact, ai as default, estimateTokens, flatten, prune, serializeForTokenEstimate, toNatural, toTOON };
package/dist/index.js CHANGED
@@ -1,27 +1,28 @@
1
1
  import {
2
2
  AIChain
3
- } from "./chunk-2IQAGI7Q.js";
3
+ } from "./chunk-P6KXISNT.js";
4
4
  import {
5
5
  analyze
6
- } from "./chunk-GQ62V6ZK.js";
6
+ } from "./chunk-IOMOVRC7.js";
7
7
  import {
8
8
  toTOON
9
- } from "./chunk-3ECVBELU.js";
9
+ } from "./chunk-6VRLDRJK.js";
10
10
  import {
11
11
  compact
12
- } from "./chunk-EUFLW42L.js";
12
+ } from "./chunk-XE36GLJP.js";
13
13
  import {
14
14
  flatten
15
- } from "./chunk-YMOSWQGE.js";
15
+ } from "./chunk-L7BC62MT.js";
16
16
  import {
17
17
  toNatural
18
18
  } from "./chunk-D4CFTFM3.js";
19
19
  import {
20
20
  prune
21
- } from "./chunk-IAV75SZA.js";
21
+ } from "./chunk-ZD536GZF.js";
22
22
  import {
23
- estimateTokens
24
- } from "./chunk-TM6L7YF2.js";
23
+ estimateTokens,
24
+ serializeForTokenEstimate
25
+ } from "./chunk-U433WUUT.js";
25
26
 
26
27
  // src/index.ts
27
28
  function ai(data) {
@@ -34,6 +35,7 @@ ai.toTOON = toTOON;
34
35
  ai.analyze = analyze;
35
36
  ai.toNatural = toNatural;
36
37
  ai.estimateTokens = estimateTokens;
38
+ ai.serializeForTokenEstimate = serializeForTokenEstimate;
37
39
  ai.AIChain = AIChain;
38
40
  var index_default = ai;
39
41
  export {
@@ -44,6 +46,7 @@ export {
44
46
  estimateTokens,
45
47
  flatten,
46
48
  prune,
49
+ serializeForTokenEstimate,
47
50
  toNatural,
48
51
  toTOON
49
52
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sohqureshi/tokenwise",
3
- "version": "1.0.3",
3
+ "version": "1.0.7",
4
4
  "description": "Optimize JSON data for AI by reducing token usage",
5
5
  "repository": {
6
6
  "type": "git",
@@ -60,6 +60,8 @@
60
60
  "scripts": {
61
61
  "build": "tsup src/index.ts src/cli.ts src/core/analyze.ts src/core/compact.ts src/core/flatten.ts src/core/natural.ts src/core/prune.ts src/core/token.ts src/core/toon.ts --format esm,cjs --dts",
62
62
  "test": "vitest",
63
+ "test:run": "vitest run",
64
+ "typecheck": "tsc --noEmit",
63
65
  "prepare": "npm run build"
64
66
  },
65
67
  "keywords": [
@@ -73,6 +75,9 @@
73
75
  ],
74
76
  "author": "Mohammad Sohail",
75
77
  "license": "MIT",
78
+ "dependencies": {
79
+ "tiktoken": "^1.0.22"
80
+ },
76
81
  "devDependencies": {
77
82
  "@types/node": "^25.6.0",
78
83
  "tsup": "^8.0.0",
@@ -1,9 +0,0 @@
1
- // src/core/token.ts
2
- function estimateTokens(text) {
3
- if (!text) return 0;
4
- return Math.ceil(text.length / 4);
5
- }
6
-
7
- export {
8
- estimateTokens
9
- };