@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.
@@ -45,7 +45,7 @@ function prune(obj, options = {}) {
45
45
  return arr;
46
46
  }
47
47
  const result = {};
48
- for (const key in obj) {
48
+ for (const key of Object.keys(obj)) {
49
49
  if (removeKeys.includes(key)) continue;
50
50
  const value = prune(obj[key], normalizedOptions);
51
51
  if (value === void 0) continue;
@@ -67,20 +67,28 @@ function compact(obj) {
67
67
  function flatten(obj, prefix = "", res = {}) {
68
68
  if (obj === null || obj === void 0) return res;
69
69
  if (typeof obj !== "object") {
70
- res[prefix] = obj;
70
+ setValue(res, prefix, obj);
71
71
  return res;
72
72
  }
73
- for (const key in obj) {
73
+ for (const key of Object.keys(obj)) {
74
74
  const value = obj[key];
75
75
  const newKey = prefix ? `${prefix}.${key}` : key;
76
76
  if (typeof value === "object" && value !== null) {
77
77
  flatten(value, newKey, res);
78
78
  } else {
79
- res[newKey] = value;
79
+ setValue(res, newKey, value);
80
80
  }
81
81
  }
82
82
  return res;
83
83
  }
84
+ function setValue(res, key, value) {
85
+ if (Object.prototype.hasOwnProperty.call(res, key)) {
86
+ throw new Error(
87
+ `Cannot flatten input: the path "${key}" collides with an existing key. Use keys without dots or rename one of the conflicting properties.`
88
+ );
89
+ }
90
+ res[key] = value;
91
+ }
84
92
 
85
93
  // src/core/toon.ts
86
94
  function toTOON(data, indent = 0) {
@@ -98,11 +106,11 @@ function toTOON(data, indent = 0) {
98
106
  }
99
107
  return result;
100
108
  }
101
- return `${space}[${data.length}]: ${data.join(",")}`;
109
+ return `${space}[${data.length}]: ${data.map(formatValue).join(",")}`;
102
110
  }
103
111
  if (typeof data === "object" && data !== null) {
104
112
  let result = "";
105
- for (const key in data) {
113
+ for (const key of Object.keys(data)) {
106
114
  const value = data[key];
107
115
  if (typeof value === "object" && value !== null) {
108
116
  result += `${space}${key}:
@@ -121,7 +129,9 @@ ${toTOON(value, indent + 1)}
121
129
  }
122
130
  function formatValue(val) {
123
131
  if (val === null || val === void 0) return "";
124
- if (typeof val === "string") return val;
132
+ if (typeof val === "string") {
133
+ return val === "" || /[,\n\r]/.test(val) ? JSON.stringify(val) : val;
134
+ }
125
135
  return String(val);
126
136
  }
127
137
  function isPlainObject(value) {
@@ -129,13 +139,60 @@ function isPlainObject(value) {
129
139
  }
130
140
 
131
141
  // src/core/token.ts
132
- function estimateTokens(text) {
133
- if (!text) return 0;
134
- return Math.ceil(text.length / 4);
142
+ var import_node_module = require("module");
143
+ var import_meta = {};
144
+ var require2 = (0, import_node_module.createRequire)(import_meta.url);
145
+ function serializeForTokenEstimate(value) {
146
+ if (typeof value === "string") return value;
147
+ const serialized = JSON.stringify(value);
148
+ return serialized ?? "";
149
+ }
150
+ function estimateTokensHeuristic(value) {
151
+ return Math.ceil(serializeForTokenEstimate(value).length / 4);
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
+ }
135
187
  }
136
188
 
137
189
  // src/core/analyze.ts
138
190
  function analyze(input, options = {}) {
191
+ const {
192
+ exact = false,
193
+ model = "gpt-4o-mini",
194
+ fallbackToHeuristic = true
195
+ } = options;
139
196
  if (!input || typeof input === "object" && input !== null && Object.keys(input).length === 0) {
140
197
  return {
141
198
  originalTokens: 0,
@@ -143,11 +200,18 @@ function analyze(input, options = {}) {
143
200
  savings: 0,
144
201
  savingsPercent: 0,
145
202
  optimizedData: null,
146
- reductionRatio: 1
203
+ reductionRatio: 1,
204
+ originalCharacters: 0,
205
+ optimizedCharacters: 0,
206
+ estimator: exact ? `exact tokenizer: model=${model}` : "heuristic: 1 token \u2248 4 characters"
147
207
  };
148
208
  }
149
- let originalTokens = estimateTokens(input);
150
- if (isNaN(originalTokens) || !isFinite(originalTokens)) originalTokens = 0;
209
+ const originalMeta = estimateTokensWithMeta(input, {
210
+ exact,
211
+ model,
212
+ fallbackToHeuristic
213
+ });
214
+ const originalTokens = originalMeta.count;
151
215
  let optimizedData = input;
152
216
  if (options.prune && Array.isArray(options.prune)) {
153
217
  optimizedData = prune(optimizedData, options.prune);
@@ -161,18 +225,26 @@ function analyze(input, options = {}) {
161
225
  if (options.toTOON === true || options.toon === true) {
162
226
  optimizedData = toTOON(optimizedData);
163
227
  }
164
- let optimizedTokens = estimateTokens(optimizedData);
165
- if (isNaN(optimizedTokens) || !isFinite(optimizedTokens)) optimizedTokens = 0;
228
+ const optimizedMeta = estimateTokensWithMeta(optimizedData, {
229
+ exact,
230
+ model,
231
+ fallbackToHeuristic
232
+ });
233
+ const optimizedTokens = optimizedMeta.count;
166
234
  const savings = Math.max(0, originalTokens - optimizedTokens);
167
235
  const savingsPercent = originalTokens > 0 ? Math.round(savings / originalTokens * 100) : 0;
168
236
  const reductionRatio = originalTokens > 0 ? optimizedTokens / originalTokens : 1;
237
+ const estimatorLabel = originalMeta && originalMeta.estimator ? originalMeta.estimator : optimizedMeta.estimator;
169
238
  return {
170
239
  originalTokens,
171
240
  optimizedTokens,
172
241
  savings,
173
242
  savingsPercent,
174
243
  optimizedData,
175
- reductionRatio
244
+ reductionRatio,
245
+ originalCharacters: serializeForTokenEstimate(input).length,
246
+ optimizedCharacters: serializeForTokenEstimate(optimizedData).length,
247
+ estimator: estimatorLabel
176
248
  };
177
249
  }
178
250
  // Annotate the CommonJS export names for ESM import in node:
@@ -1,13 +1,26 @@
1
1
  /**
2
2
  * Safe version of analyze() - Prevents NaN values
3
3
  */
4
- declare function analyze(input: any, options?: any): {
4
+ type AnalyzeOptions = {
5
+ prune?: string[];
6
+ compact?: boolean;
7
+ flatten?: boolean;
8
+ toTOON?: boolean;
9
+ toon?: boolean;
10
+ exact?: boolean;
11
+ model?: string;
12
+ fallbackToHeuristic?: boolean;
13
+ };
14
+ declare function analyze(input: unknown, options?: AnalyzeOptions): {
5
15
  originalTokens: number;
6
16
  optimizedTokens: number;
7
17
  savings: number;
8
18
  savingsPercent: number;
9
- optimizedData: any;
19
+ optimizedData: unknown;
10
20
  reductionRatio: number;
21
+ originalCharacters: number;
22
+ optimizedCharacters: number;
23
+ estimator: string;
11
24
  };
12
25
 
13
- export { analyze };
26
+ export { type AnalyzeOptions, analyze };
@@ -1,13 +1,26 @@
1
1
  /**
2
2
  * Safe version of analyze() - Prevents NaN values
3
3
  */
4
- declare function analyze(input: any, options?: any): {
4
+ type AnalyzeOptions = {
5
+ prune?: string[];
6
+ compact?: boolean;
7
+ flatten?: boolean;
8
+ toTOON?: boolean;
9
+ toon?: boolean;
10
+ exact?: boolean;
11
+ model?: string;
12
+ fallbackToHeuristic?: boolean;
13
+ };
14
+ declare function analyze(input: unknown, options?: AnalyzeOptions): {
5
15
  originalTokens: number;
6
16
  optimizedTokens: number;
7
17
  savings: number;
8
18
  savingsPercent: number;
9
- optimizedData: any;
19
+ optimizedData: unknown;
10
20
  reductionRatio: number;
21
+ originalCharacters: number;
22
+ optimizedCharacters: number;
23
+ estimator: string;
11
24
  };
12
25
 
13
- export { analyze };
26
+ export { type AnalyzeOptions, analyze };
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  analyze
3
- } from "../chunk-GQ62V6ZK.js";
4
- import "../chunk-3ECVBELU.js";
5
- import "../chunk-EUFLW42L.js";
6
- import "../chunk-YMOSWQGE.js";
7
- import "../chunk-IAV75SZA.js";
8
- import "../chunk-TM6L7YF2.js";
3
+ } from "../chunk-IOMOVRC7.js";
4
+ import "../chunk-6VRLDRJK.js";
5
+ import "../chunk-XE36GLJP.js";
6
+ import "../chunk-L7BC62MT.js";
7
+ import "../chunk-ZD536GZF.js";
8
+ import "../chunk-U433WUUT.js";
9
9
  export {
10
10
  analyze
11
11
  };
@@ -45,7 +45,7 @@ function prune(obj, options = {}) {
45
45
  return arr;
46
46
  }
47
47
  const result = {};
48
- for (const key in obj) {
48
+ for (const key of Object.keys(obj)) {
49
49
  if (removeKeys.includes(key)) continue;
50
50
  const value = prune(obj[key], normalizedOptions);
51
51
  if (value === void 0) continue;
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  compact
3
- } from "../chunk-EUFLW42L.js";
4
- import "../chunk-IAV75SZA.js";
3
+ } from "../chunk-XE36GLJP.js";
4
+ import "../chunk-ZD536GZF.js";
5
5
  export {
6
6
  compact
7
7
  };
@@ -26,20 +26,28 @@ module.exports = __toCommonJS(flatten_exports);
26
26
  function flatten(obj, prefix = "", res = {}) {
27
27
  if (obj === null || obj === void 0) return res;
28
28
  if (typeof obj !== "object") {
29
- res[prefix] = obj;
29
+ setValue(res, prefix, obj);
30
30
  return res;
31
31
  }
32
- for (const key in obj) {
32
+ for (const key of Object.keys(obj)) {
33
33
  const value = obj[key];
34
34
  const newKey = prefix ? `${prefix}.${key}` : key;
35
35
  if (typeof value === "object" && value !== null) {
36
36
  flatten(value, newKey, res);
37
37
  } else {
38
- res[newKey] = value;
38
+ setValue(res, newKey, value);
39
39
  }
40
40
  }
41
41
  return res;
42
42
  }
43
+ function setValue(res, key, value) {
44
+ if (Object.prototype.hasOwnProperty.call(res, key)) {
45
+ throw new Error(
46
+ `Cannot flatten input: the path "${key}" collides with an existing key. Use keys without dots or rename one of the conflicting properties.`
47
+ );
48
+ }
49
+ res[key] = value;
50
+ }
43
51
  // Annotate the CommonJS export names for ESM import in node:
44
52
  0 && (module.exports = {
45
53
  flatten
@@ -13,6 +13,6 @@
13
13
  * flatten({ user: { name: "Ali" } })
14
14
  * -> { "user.name": "Ali" }
15
15
  */
16
- declare function flatten(obj: any, prefix?: string, res?: any): any;
16
+ declare function flatten(obj: any, prefix?: string, res?: Record<string, unknown>): Record<string, unknown>;
17
17
 
18
18
  export { flatten };
@@ -13,6 +13,6 @@
13
13
  * flatten({ user: { name: "Ali" } })
14
14
  * -> { "user.name": "Ali" }
15
15
  */
16
- declare function flatten(obj: any, prefix?: string, res?: any): any;
16
+ declare function flatten(obj: any, prefix?: string, res?: Record<string, unknown>): Record<string, unknown>;
17
17
 
18
18
  export { flatten };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  flatten
3
- } from "../chunk-YMOSWQGE.js";
3
+ } from "../chunk-L7BC62MT.js";
4
4
  export {
5
5
  flatten
6
6
  };
@@ -43,7 +43,7 @@ function prune(obj, options = {}) {
43
43
  return arr;
44
44
  }
45
45
  const result = {};
46
- for (const key in obj) {
46
+ for (const key of Object.keys(obj)) {
47
47
  if (removeKeys.includes(key)) continue;
48
48
  const value = prune(obj[key], normalizedOptions);
49
49
  if (value === void 0) continue;
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  prune
3
- } from "../chunk-IAV75SZA.js";
3
+ } from "../chunk-ZD536GZF.js";
4
4
  export {
5
5
  prune
6
6
  };
@@ -20,14 +20,63 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/core/token.ts
21
21
  var token_exports = {};
22
22
  __export(token_exports, {
23
- estimateTokens: () => estimateTokens
23
+ estimateTokens: () => estimateTokens,
24
+ estimateTokensWithMeta: () => estimateTokensWithMeta,
25
+ serializeForTokenEstimate: () => serializeForTokenEstimate
24
26
  });
25
27
  module.exports = __toCommonJS(token_exports);
26
- function estimateTokens(text) {
27
- if (!text) return 0;
28
- return Math.ceil(text.length / 4);
28
+ var import_node_module = require("module");
29
+ var import_meta = {};
30
+ var require2 = (0, import_node_module.createRequire)(import_meta.url);
31
+ function serializeForTokenEstimate(value) {
32
+ if (typeof value === "string") return value;
33
+ const serialized = JSON.stringify(value);
34
+ return serialized ?? "";
35
+ }
36
+ function estimateTokensHeuristic(value) {
37
+ return Math.ceil(serializeForTokenEstimate(value).length / 4);
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
+ }
29
76
  }
30
77
  // Annotate the CommonJS export names for ESM import in node:
31
78
  0 && (module.exports = {
32
- estimateTokens
79
+ estimateTokens,
80
+ estimateTokensWithMeta,
81
+ serializeForTokenEstimate
33
82
  });
@@ -1,12 +1,24 @@
1
+ interface TokenEstimateOptions {
2
+ exact?: boolean;
3
+ model?: string;
4
+ fallbackToHeuristic?: boolean;
5
+ }
6
+ declare function serializeForTokenEstimate(value: unknown): string;
1
7
  /**
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
8
+ * Estimates tokens using a transparent four-characters-per-token heuristic.
9
+ * JSON values are serialized first, matching the compact form normally sent
10
+ * to an API. Set exact=true to attempt a model-specific tokenizer count when
11
+ * the runtime has the tokenizer installed.
9
12
  */
10
- declare function estimateTokens(text: string): number;
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).
21
+ */
22
+ declare function estimateTokensWithMeta(value: unknown, options?: TokenEstimateOptions): TokenEstimateResult;
11
23
 
12
- export { estimateTokens };
24
+ export { type TokenEstimateOptions, type TokenEstimateResult, estimateTokens, estimateTokensWithMeta, serializeForTokenEstimate };
@@ -1,12 +1,24 @@
1
+ interface TokenEstimateOptions {
2
+ exact?: boolean;
3
+ model?: string;
4
+ fallbackToHeuristic?: boolean;
5
+ }
6
+ declare function serializeForTokenEstimate(value: unknown): string;
1
7
  /**
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
8
+ * Estimates tokens using a transparent four-characters-per-token heuristic.
9
+ * JSON values are serialized first, matching the compact form normally sent
10
+ * to an API. Set exact=true to attempt a model-specific tokenizer count when
11
+ * the runtime has the tokenizer installed.
9
12
  */
10
- declare function estimateTokens(text: string): number;
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).
21
+ */
22
+ declare function estimateTokensWithMeta(value: unknown, options?: TokenEstimateOptions): TokenEstimateResult;
11
23
 
12
- export { estimateTokens };
24
+ export { type TokenEstimateOptions, type TokenEstimateResult, estimateTokens, estimateTokensWithMeta, serializeForTokenEstimate };
@@ -1,6 +1,10 @@
1
1
  import {
2
- estimateTokens
3
- } from "../chunk-TM6L7YF2.js";
2
+ estimateTokens,
3
+ estimateTokensWithMeta,
4
+ serializeForTokenEstimate
5
+ } from "../chunk-U433WUUT.js";
4
6
  export {
5
- estimateTokens
7
+ estimateTokens,
8
+ estimateTokensWithMeta,
9
+ serializeForTokenEstimate
6
10
  };
@@ -38,11 +38,11 @@ function toTOON(data, indent = 0) {
38
38
  }
39
39
  return result;
40
40
  }
41
- return `${space}[${data.length}]: ${data.join(",")}`;
41
+ return `${space}[${data.length}]: ${data.map(formatValue).join(",")}`;
42
42
  }
43
43
  if (typeof data === "object" && data !== null) {
44
44
  let result = "";
45
- for (const key in data) {
45
+ for (const key of Object.keys(data)) {
46
46
  const value = data[key];
47
47
  if (typeof value === "object" && value !== null) {
48
48
  result += `${space}${key}:
@@ -61,7 +61,9 @@ ${toTOON(value, indent + 1)}
61
61
  }
62
62
  function formatValue(val) {
63
63
  if (val === null || val === void 0) return "";
64
- if (typeof val === "string") return val;
64
+ if (typeof val === "string") {
65
+ return val === "" || /[,\n\r]/.test(val) ? JSON.stringify(val) : val;
66
+ }
65
67
  return String(val);
66
68
  }
67
69
  function isPlainObject(value) {
package/dist/core/toon.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  toTOON
3
- } from "../chunk-3ECVBELU.js";
3
+ } from "../chunk-6VRLDRJK.js";
4
4
  export {
5
5
  toTOON
6
6
  };