llm-chat-msg-compressor 1.0.0 → 1.0.2
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/LICENSE +1 -1
- package/README.md +21 -0
- package/dist/analyzer.js +77 -17
- package/dist/index.js +20 -6
- package/dist/optimizer.d.ts +4 -0
- package/dist/optimizer.js +17 -15
- package/dist/strategies.d.ts +4 -0
- package/dist/strategies.js +124 -69
- package/package.json +8 -2
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
# llm-chat-msg-compressor 🚀
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/llm-chat-msg-compressor)
|
|
4
|
+
[](https://github.com/Sridharvn/llm-chat-msg-compressor/blob/main/LICENSE)
|
|
5
|
+
[](https://github.com/Sridharvn/llm-chat-msg-compressor/actions)
|
|
6
|
+
|
|
3
7
|
Intelligent JSON optimizer for LLM APIs. Automatically reduces token usage by selecting the best compression strategy for your data payload.
|
|
4
8
|
|
|
5
9
|
## Features
|
|
6
10
|
|
|
7
11
|
- **🧠 Intelligent**: Analyzes payload structure to pick the best strategy
|
|
12
|
+
- **⚡ High Performance**: Optimized for low-latency with single-pass analysis and zero production dependencies
|
|
8
13
|
- **📉 Efficient**: Saves 10-40% input tokens on average
|
|
9
14
|
- **✅ Safe**: Full restoration of original data (semantic equality)
|
|
10
15
|
- **🔌 Easy**: Simple `optimize()` and `restore()` API
|
|
@@ -66,3 +71,19 @@ optimize(data, {
|
|
|
66
71
|
By default, the library is **Safe-by-Default**. It preserves all data types (including booleans), ensuring that downstream code (e.g., in your backend or strictly typed clients) works without modification.
|
|
67
72
|
|
|
68
73
|
If you need maximum compression and your LLM can handle `1`/`0` instead of `true`/`false`, you can enable `unsafe: true`.
|
|
74
|
+
|
|
75
|
+
## Performance
|
|
76
|
+
|
|
77
|
+
The library is designed for high-throughput environments:
|
|
78
|
+
|
|
79
|
+
- **Zero-Stringify Analysis**: Estimates payload size during traversal to avoid memory spikes.
|
|
80
|
+
- **Lazy Detection**: Decompression auto-detects strategies using targeted marker searches instead of full-string scans.
|
|
81
|
+
- **Memory Efficient**: Uses optimized loops and reuses strategy instances to minimize garbage collection.
|
|
82
|
+
|
|
83
|
+
## Contributing
|
|
84
|
+
|
|
85
|
+
Contributions are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines and [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) for our code of conduct.
|
|
86
|
+
|
|
87
|
+
## License
|
|
88
|
+
|
|
89
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
package/dist/analyzer.js
CHANGED
|
@@ -3,8 +3,19 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.Analyzer = void 0;
|
|
4
4
|
class Analyzer {
|
|
5
5
|
static analyze(data) {
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
// Pre-flight check for primitives or very small objects
|
|
7
|
+
if (data === null || typeof data !== 'object') {
|
|
8
|
+
return {
|
|
9
|
+
totalBytes: data === undefined ? 0 : Buffer.byteLength(JSON.stringify(data), 'utf8'),
|
|
10
|
+
arrayDensity: 0,
|
|
11
|
+
maxExampleArrayLength: 0,
|
|
12
|
+
nestingDepth: 0,
|
|
13
|
+
repeatedKeysEstimate: 0,
|
|
14
|
+
estimatedAbbrevSavings: 0,
|
|
15
|
+
estimatedSchemaSavings: 0
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
let totalBytes = 0;
|
|
8
19
|
let arrayCount = 0;
|
|
9
20
|
let objectCount = 0;
|
|
10
21
|
let maxArrLen = 0;
|
|
@@ -18,17 +29,40 @@ class Analyzer {
|
|
|
18
29
|
if (arr.length < 3 || typeof arr[0] !== 'object' || arr[0] === null)
|
|
19
30
|
return 0;
|
|
20
31
|
// Check consistency of first few items
|
|
21
|
-
const
|
|
22
|
-
const
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
32
|
+
const firstItem = arr[0];
|
|
33
|
+
const keys = Object.keys(firstItem);
|
|
34
|
+
const keyCount = keys.length;
|
|
35
|
+
if (keyCount === 0)
|
|
36
|
+
return 0;
|
|
37
|
+
const sampleSize = Math.min(arr.length, 5);
|
|
38
|
+
let isConsistent = true;
|
|
39
|
+
for (let i = 1; i < sampleSize; i++) {
|
|
40
|
+
const item = arr[i];
|
|
41
|
+
if (!item || typeof item !== 'object' || Array.isArray(item)) {
|
|
42
|
+
isConsistent = false;
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
const itemKeys = Object.keys(item);
|
|
46
|
+
if (itemKeys.length !== keyCount) {
|
|
47
|
+
isConsistent = false;
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
// Check if all keys from first item exist in this item
|
|
51
|
+
for (const key of keys) {
|
|
52
|
+
if (!(key in item)) {
|
|
53
|
+
isConsistent = false;
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (!isConsistent)
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
26
60
|
if (isConsistent) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const perItemOverhead = keysLen + (
|
|
61
|
+
let keysLen = 0;
|
|
62
|
+
for (const key of keys) {
|
|
63
|
+
keysLen += key.length;
|
|
64
|
+
}
|
|
65
|
+
const perItemOverhead = keysLen + (keyCount * 2); // quotes + colon approx
|
|
32
66
|
return (arr.length - 1) * perItemOverhead;
|
|
33
67
|
}
|
|
34
68
|
return 0;
|
|
@@ -38,16 +72,42 @@ class Analyzer {
|
|
|
38
72
|
if (Array.isArray(obj)) {
|
|
39
73
|
arrayCount++;
|
|
40
74
|
maxArrLen = Math.max(maxArrLen, obj.length);
|
|
75
|
+
totalBytes += 2; // []
|
|
76
|
+
if (obj.length > 1)
|
|
77
|
+
totalBytes += obj.length - 1; // commas
|
|
41
78
|
// Check if this specific array offers schema savings
|
|
42
79
|
schemaSavings += calculateArraySchemaSavings(obj);
|
|
43
|
-
|
|
80
|
+
for (let i = 0; i < obj.length; i++) {
|
|
81
|
+
traverse(obj[i], currentDepth + 1);
|
|
82
|
+
}
|
|
44
83
|
}
|
|
45
84
|
else if (obj && typeof obj === 'object') {
|
|
46
85
|
objectCount++;
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
86
|
+
totalBytes += 2; // {}
|
|
87
|
+
let first = true;
|
|
88
|
+
for (const key in obj) {
|
|
89
|
+
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
90
|
+
if (!first)
|
|
91
|
+
totalBytes += 1; // comma
|
|
92
|
+
first = false;
|
|
93
|
+
totalKeysCount++;
|
|
94
|
+
totalKeyLength += key.length;
|
|
95
|
+
totalBytes += key.length + 3; // "key":
|
|
96
|
+
traverse(obj[key], currentDepth + 1);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
// Primitive
|
|
102
|
+
if (typeof obj === 'string') {
|
|
103
|
+
totalBytes += Buffer.byteLength(obj, 'utf8') + 2; // quotes
|
|
104
|
+
}
|
|
105
|
+
else if (typeof obj === 'number' || typeof obj === 'boolean') {
|
|
106
|
+
totalBytes += String(obj).length;
|
|
107
|
+
}
|
|
108
|
+
else if (obj === null) {
|
|
109
|
+
totalBytes += 4;
|
|
110
|
+
}
|
|
51
111
|
}
|
|
52
112
|
};
|
|
53
113
|
traverse(data, 0);
|
package/dist/index.js
CHANGED
|
@@ -33,18 +33,32 @@ function restore(data) {
|
|
|
33
33
|
return strat.decompress(data);
|
|
34
34
|
}
|
|
35
35
|
// Detect Schema Separation format anywhere in the structure
|
|
36
|
-
|
|
37
|
-
if (JSON.stringify(data).includes('"$s"') && JSON.stringify(data).includes('"$d"')) {
|
|
36
|
+
if (hasSchemaMarker(data)) {
|
|
38
37
|
const strat = new strategies_1.SchemaDataSeparationStrategy();
|
|
39
38
|
return strat.decompress(data);
|
|
40
39
|
}
|
|
41
40
|
// Default: return as is
|
|
42
41
|
return data;
|
|
43
42
|
}
|
|
44
|
-
function
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
43
|
+
function hasSchemaMarker(obj) {
|
|
44
|
+
if (!obj || typeof obj !== 'object')
|
|
45
|
+
return false;
|
|
46
|
+
if (Array.isArray(obj)) {
|
|
47
|
+
for (let i = 0; i < obj.length; i++) {
|
|
48
|
+
if (hasSchemaMarker(obj[i]))
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
if ('$s' in obj && '$d' in obj)
|
|
54
|
+
return true;
|
|
55
|
+
for (const k in obj) {
|
|
56
|
+
if (Object.prototype.hasOwnProperty.call(obj, k)) {
|
|
57
|
+
if (hasSchemaMarker(obj[k]))
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return false;
|
|
48
62
|
}
|
|
49
63
|
var optimizer_2 = require("./optimizer");
|
|
50
64
|
Object.defineProperty(exports, "Optimizer", { enumerable: true, get: function () { return optimizer_2.Optimizer; } });
|
package/dist/optimizer.d.ts
CHANGED
|
@@ -5,6 +5,10 @@ export interface OptimizerOptions {
|
|
|
5
5
|
unsafe?: boolean;
|
|
6
6
|
}
|
|
7
7
|
export declare class Optimizer {
|
|
8
|
+
private schemaStrat;
|
|
9
|
+
private abbrevStrat;
|
|
10
|
+
private ultraStratSafe;
|
|
11
|
+
private ultraStratUnsafe;
|
|
8
12
|
private strategies;
|
|
9
13
|
/**
|
|
10
14
|
* Automatically selects and applies the best compression strategy
|
package/dist/optimizer.js
CHANGED
|
@@ -5,10 +5,14 @@ const strategies_1 = require("./strategies");
|
|
|
5
5
|
const analyzer_1 = require("./analyzer");
|
|
6
6
|
class Optimizer {
|
|
7
7
|
constructor() {
|
|
8
|
+
this.schemaStrat = new strategies_1.SchemaDataSeparationStrategy();
|
|
9
|
+
this.abbrevStrat = new strategies_1.AbbreviatedKeysStrategy();
|
|
10
|
+
this.ultraStratSafe = new strategies_1.UltraCompactStrategy({ unsafe: false });
|
|
11
|
+
this.ultraStratUnsafe = new strategies_1.UltraCompactStrategy({ unsafe: true });
|
|
8
12
|
this.strategies = [
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
13
|
+
this.schemaStrat,
|
|
14
|
+
this.ultraStratSafe,
|
|
15
|
+
this.abbrevStrat
|
|
12
16
|
];
|
|
13
17
|
}
|
|
14
18
|
/**
|
|
@@ -20,31 +24,23 @@ class Optimizer {
|
|
|
20
24
|
const metrics = analyzer_1.Analyzer.analyze(data);
|
|
21
25
|
// 1. If too small, just minify
|
|
22
26
|
if (metrics.totalBytes < thresholdBytes) {
|
|
23
|
-
console.log(`[Optimizer] Selected: minify (Size ${metrics.totalBytes} < ${thresholdBytes})`);
|
|
24
27
|
return strategies_1.minify.compress(data);
|
|
25
28
|
}
|
|
26
29
|
// 2. Smart Strategy Selection
|
|
27
30
|
// Compare estimated savings to pick the winner.
|
|
28
|
-
console.log(`[Optimizer] Analysis: SchemaSavings=${Math.round(metrics.estimatedSchemaSavings)} bytes, AbbrevSavings=${Math.round(metrics.estimatedAbbrevSavings)} bytes`);
|
|
29
31
|
// Prefer SchemaSeparation if it saves MORE than AbbreviatedKeys (with a slight buffer for safety)
|
|
30
32
|
// Schema Separation is "riskier" structure-wise (arrays vs maps), so we want it to be worth it.
|
|
31
33
|
if (metrics.estimatedSchemaSavings > metrics.estimatedAbbrevSavings * 1.1) {
|
|
32
|
-
|
|
33
|
-
const schemaStrat = new strategies_1.SchemaDataSeparationStrategy();
|
|
34
|
-
return schemaStrat.compress(data);
|
|
34
|
+
return this.schemaStrat.compress(data);
|
|
35
35
|
}
|
|
36
36
|
// 3. Fallback to UltraCompact if aggressive is set
|
|
37
37
|
if (aggressive) {
|
|
38
|
-
|
|
39
|
-
const ultra = new strategies_1.UltraCompactStrategy({ unsafe });
|
|
40
|
-
return ultra.compress(data);
|
|
38
|
+
return unsafe ? this.ultraStratUnsafe.compress(data) : this.ultraStratSafe.compress(data);
|
|
41
39
|
}
|
|
42
40
|
// 4. Default: Abbreviated Keys
|
|
43
41
|
// If Schema Separation isn't significantly better, we default to this.
|
|
44
42
|
// It handles mixed/nested payloads better and is "safer" structure-wise.
|
|
45
|
-
|
|
46
|
-
const abbr = new strategies_1.AbbreviatedKeysStrategy();
|
|
47
|
-
return abbr.compress(data);
|
|
43
|
+
return this.abbrevStrat.compress(data);
|
|
48
44
|
}
|
|
49
45
|
/**
|
|
50
46
|
* Helper to get a specific strategy
|
|
@@ -52,7 +48,13 @@ class Optimizer {
|
|
|
52
48
|
getStrategy(name) {
|
|
53
49
|
if (name === 'minify')
|
|
54
50
|
return strategies_1.minify;
|
|
55
|
-
|
|
51
|
+
if (name === 'schema-data-separation')
|
|
52
|
+
return this.schemaStrat;
|
|
53
|
+
if (name === 'abbreviated-keys')
|
|
54
|
+
return this.abbrevStrat;
|
|
55
|
+
if (name === 'ultra-compact')
|
|
56
|
+
return this.ultraStratSafe; // Default to safe
|
|
57
|
+
return undefined;
|
|
56
58
|
}
|
|
57
59
|
}
|
|
58
60
|
exports.Optimizer = Optimizer;
|
package/dist/strategies.d.ts
CHANGED
|
@@ -6,6 +6,10 @@ export interface CompressionStrategy {
|
|
|
6
6
|
compress(data: any): any;
|
|
7
7
|
decompress(data: any): any;
|
|
8
8
|
}
|
|
9
|
+
/**
|
|
10
|
+
* Shared utility for generating short keys (a, b, ... z, aa, ab ...)
|
|
11
|
+
*/
|
|
12
|
+
export declare const generateShortKey: (index: number) => string;
|
|
9
13
|
/**
|
|
10
14
|
* Strategy 1: Minify (Baseline)
|
|
11
15
|
* Just standard JSON serialization (handled by default JSON.stringify)
|
package/dist/strategies.js
CHANGED
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.UltraCompactStrategy = exports.SchemaDataSeparationStrategy = exports.AbbreviatedKeysStrategy = exports.minify = void 0;
|
|
3
|
+
exports.UltraCompactStrategy = exports.SchemaDataSeparationStrategy = exports.AbbreviatedKeysStrategy = exports.minify = exports.generateShortKey = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Shared utility for generating short keys (a, b, ... z, aa, ab ...)
|
|
6
|
+
*/
|
|
7
|
+
const generateShortKey = (index) => {
|
|
8
|
+
let shortKey = '';
|
|
9
|
+
let temp = index;
|
|
10
|
+
do {
|
|
11
|
+
shortKey = String.fromCharCode(97 + (temp % 26)) + shortKey;
|
|
12
|
+
temp = Math.floor(temp / 26) - 1;
|
|
13
|
+
} while (temp >= 0);
|
|
14
|
+
return shortKey;
|
|
15
|
+
};
|
|
16
|
+
exports.generateShortKey = generateShortKey;
|
|
4
17
|
/**
|
|
5
18
|
* Strategy 1: Minify (Baseline)
|
|
6
19
|
* Just standard JSON serialization (handled by default JSON.stringify)
|
|
@@ -22,30 +35,30 @@ class AbbreviatedKeysStrategy {
|
|
|
22
35
|
this.name = 'abbreviated-keys';
|
|
23
36
|
}
|
|
24
37
|
compress(data) {
|
|
25
|
-
// Implementation that returns { m: map, d: data }
|
|
26
38
|
const keyMap = new Map();
|
|
27
|
-
const reverseMap = new Map();
|
|
28
39
|
let counter = 0;
|
|
29
40
|
const getShortKey = (key) => {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
do {
|
|
34
|
-
shortKey = String.fromCharCode(97 + (temp % 26)) + shortKey;
|
|
35
|
-
temp = Math.floor(temp / 26) - 1;
|
|
36
|
-
} while (temp >= 0);
|
|
41
|
+
let shortKey = keyMap.get(key);
|
|
42
|
+
if (shortKey === undefined) {
|
|
43
|
+
shortKey = (0, exports.generateShortKey)(counter++);
|
|
37
44
|
keyMap.set(key, shortKey);
|
|
38
|
-
reverseMap.set(shortKey, key);
|
|
39
45
|
}
|
|
40
|
-
return
|
|
46
|
+
return shortKey;
|
|
41
47
|
};
|
|
42
48
|
const traverse = (obj) => {
|
|
43
|
-
if (Array.isArray(obj))
|
|
44
|
-
|
|
49
|
+
if (Array.isArray(obj)) {
|
|
50
|
+
const newArr = new Array(obj.length);
|
|
51
|
+
for (let i = 0; i < obj.length; i++) {
|
|
52
|
+
newArr[i] = traverse(obj[i]);
|
|
53
|
+
}
|
|
54
|
+
return newArr;
|
|
55
|
+
}
|
|
45
56
|
if (obj && typeof obj === 'object') {
|
|
46
57
|
const newObj = {};
|
|
47
58
|
for (const k in obj) {
|
|
48
|
-
|
|
59
|
+
if (Object.prototype.hasOwnProperty.call(obj, k)) {
|
|
60
|
+
newObj[getShortKey(k)] = traverse(obj[k]);
|
|
61
|
+
}
|
|
49
62
|
}
|
|
50
63
|
return newObj;
|
|
51
64
|
}
|
|
@@ -61,17 +74,26 @@ class AbbreviatedKeysStrategy {
|
|
|
61
74
|
if (!pkg || !pkg.m || pkg.d === undefined)
|
|
62
75
|
return pkg;
|
|
63
76
|
const reverseMap = new Map();
|
|
64
|
-
for (const
|
|
65
|
-
|
|
77
|
+
for (const k in pkg.m) {
|
|
78
|
+
if (Object.prototype.hasOwnProperty.call(pkg.m, k)) {
|
|
79
|
+
reverseMap.set(pkg.m[k], k);
|
|
80
|
+
}
|
|
66
81
|
}
|
|
67
82
|
const traverse = (obj) => {
|
|
68
|
-
if (Array.isArray(obj))
|
|
69
|
-
|
|
83
|
+
if (Array.isArray(obj)) {
|
|
84
|
+
const newArr = new Array(obj.length);
|
|
85
|
+
for (let i = 0; i < obj.length; i++) {
|
|
86
|
+
newArr[i] = traverse(obj[i]);
|
|
87
|
+
}
|
|
88
|
+
return newArr;
|
|
89
|
+
}
|
|
70
90
|
if (obj && typeof obj === 'object') {
|
|
71
91
|
const newObj = {};
|
|
72
92
|
for (const k in obj) {
|
|
73
|
-
|
|
74
|
-
|
|
93
|
+
if (Object.prototype.hasOwnProperty.call(obj, k)) {
|
|
94
|
+
const originalKey = reverseMap.get(k) || k;
|
|
95
|
+
newObj[originalKey] = traverse(obj[k]);
|
|
96
|
+
}
|
|
75
97
|
}
|
|
76
98
|
return newObj;
|
|
77
99
|
}
|
|
@@ -94,11 +116,30 @@ class SchemaDataSeparationStrategy {
|
|
|
94
116
|
if (Array.isArray(obj)) {
|
|
95
117
|
// Check if it's an array of objects
|
|
96
118
|
if (obj.length > 0 && typeof obj[0] === 'object' && obj[0] !== null && !Array.isArray(obj[0])) {
|
|
97
|
-
const
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
119
|
+
const firstItem = obj[0];
|
|
120
|
+
const keys = Object.keys(firstItem);
|
|
121
|
+
const keyCount = keys.length;
|
|
122
|
+
let allMatch = true;
|
|
123
|
+
for (let i = 1; i < obj.length; i++) {
|
|
124
|
+
const item = obj[i];
|
|
125
|
+
if (typeof item !== 'object' || item === null || Array.isArray(item)) {
|
|
126
|
+
allMatch = false;
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
const itemKeys = Object.keys(item);
|
|
130
|
+
if (itemKeys.length !== keyCount) {
|
|
131
|
+
allMatch = false;
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
for (const key of keys) {
|
|
135
|
+
if (!(key in item)) {
|
|
136
|
+
allMatch = false;
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (!allMatch)
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
102
143
|
if (allMatch) {
|
|
103
144
|
return {
|
|
104
145
|
$s: keys, // Schema
|
|
@@ -106,12 +147,18 @@ class SchemaDataSeparationStrategy {
|
|
|
106
147
|
};
|
|
107
148
|
}
|
|
108
149
|
}
|
|
109
|
-
|
|
150
|
+
const newArr = new Array(obj.length);
|
|
151
|
+
for (let i = 0; i < obj.length; i++) {
|
|
152
|
+
newArr[i] = traverse(obj[i]);
|
|
153
|
+
}
|
|
154
|
+
return newArr;
|
|
110
155
|
}
|
|
111
156
|
if (obj && typeof obj === 'object') {
|
|
112
157
|
const newObj = {};
|
|
113
158
|
for (const k in obj) {
|
|
114
|
-
|
|
159
|
+
if (Object.prototype.hasOwnProperty.call(obj, k)) {
|
|
160
|
+
newObj[k] = traverse(obj[k]);
|
|
161
|
+
}
|
|
115
162
|
}
|
|
116
163
|
return newObj;
|
|
117
164
|
}
|
|
@@ -124,19 +171,30 @@ class SchemaDataSeparationStrategy {
|
|
|
124
171
|
if (obj && typeof obj === 'object') {
|
|
125
172
|
if (obj.$s && obj.$d && Array.isArray(obj.$s) && Array.isArray(obj.$d)) {
|
|
126
173
|
const keys = obj.$s;
|
|
127
|
-
|
|
174
|
+
const dataArr = obj.$d;
|
|
175
|
+
const result = new Array(dataArr.length);
|
|
176
|
+
for (let i = 0; i < dataArr.length; i++) {
|
|
177
|
+
const values = dataArr[i];
|
|
128
178
|
const item = {};
|
|
129
|
-
keys.
|
|
130
|
-
item[
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
}
|
|
179
|
+
for (let j = 0; j < keys.length; j++) {
|
|
180
|
+
item[keys[j]] = traverse(values[j]);
|
|
181
|
+
}
|
|
182
|
+
result[i] = item;
|
|
183
|
+
}
|
|
184
|
+
return result;
|
|
185
|
+
}
|
|
186
|
+
if (Array.isArray(obj)) {
|
|
187
|
+
const newArr = new Array(obj.length);
|
|
188
|
+
for (let i = 0; i < obj.length; i++) {
|
|
189
|
+
newArr[i] = traverse(obj[i]);
|
|
190
|
+
}
|
|
191
|
+
return newArr;
|
|
134
192
|
}
|
|
135
|
-
if (Array.isArray(obj))
|
|
136
|
-
return obj.map(traverse);
|
|
137
193
|
const newObj = {};
|
|
138
194
|
for (const k in obj) {
|
|
139
|
-
|
|
195
|
+
if (Object.prototype.hasOwnProperty.call(obj, k)) {
|
|
196
|
+
newObj[k] = traverse(obj[k]);
|
|
197
|
+
}
|
|
140
198
|
}
|
|
141
199
|
return newObj;
|
|
142
200
|
}
|
|
@@ -159,16 +217,12 @@ class UltraCompactStrategy {
|
|
|
159
217
|
const keyMap = new Map();
|
|
160
218
|
let counter = 0;
|
|
161
219
|
const getShortKey = (key) => {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
do {
|
|
166
|
-
shortKey = String.fromCharCode(97 + (temp % 26)) + shortKey;
|
|
167
|
-
temp = Math.floor(temp / 26) - 1;
|
|
168
|
-
} while (temp >= 0);
|
|
220
|
+
let shortKey = keyMap.get(key);
|
|
221
|
+
if (shortKey === undefined) {
|
|
222
|
+
shortKey = (0, exports.generateShortKey)(counter++);
|
|
169
223
|
keyMap.set(key, shortKey);
|
|
170
224
|
}
|
|
171
|
-
return
|
|
225
|
+
return shortKey;
|
|
172
226
|
};
|
|
173
227
|
const traverse = (obj) => {
|
|
174
228
|
// Bool optimization: Only if unsafe mode is enabled
|
|
@@ -178,12 +232,19 @@ class UltraCompactStrategy {
|
|
|
178
232
|
if (obj === false)
|
|
179
233
|
return 0;
|
|
180
234
|
}
|
|
181
|
-
if (Array.isArray(obj))
|
|
182
|
-
|
|
235
|
+
if (Array.isArray(obj)) {
|
|
236
|
+
const newArr = new Array(obj.length);
|
|
237
|
+
for (let i = 0; i < obj.length; i++) {
|
|
238
|
+
newArr[i] = traverse(obj[i]);
|
|
239
|
+
}
|
|
240
|
+
return newArr;
|
|
241
|
+
}
|
|
183
242
|
if (obj && typeof obj === 'object') {
|
|
184
243
|
const newObj = {};
|
|
185
244
|
for (const k in obj) {
|
|
186
|
-
|
|
245
|
+
if (Object.prototype.hasOwnProperty.call(obj, k)) {
|
|
246
|
+
newObj[getShortKey(k)] = traverse(obj[k]);
|
|
247
|
+
}
|
|
187
248
|
}
|
|
188
249
|
return newObj;
|
|
189
250
|
}
|
|
@@ -199,32 +260,26 @@ class UltraCompactStrategy {
|
|
|
199
260
|
if (!pkg || !pkg.m || pkg.d === undefined)
|
|
200
261
|
return pkg;
|
|
201
262
|
const reverseMap = new Map();
|
|
202
|
-
for (const
|
|
203
|
-
|
|
263
|
+
for (const k in pkg.m) {
|
|
264
|
+
if (Object.prototype.hasOwnProperty.call(pkg.m, k)) {
|
|
265
|
+
reverseMap.set(pkg.m[k], k);
|
|
266
|
+
}
|
|
204
267
|
}
|
|
205
268
|
const traverse = (obj) => {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
// unless we store type info effectively.
|
|
214
|
-
// Actually, for LLM optimization, sending 1/0 is enough.
|
|
215
|
-
// If we want exact restoration, we need a schema.
|
|
216
|
-
// Let's stick to key restoration for now, and leave values as is (1/0) or maybe keep booleans as is if size diff is minimal?
|
|
217
|
-
// "true" is 4 bytes, "1" is 1 byte.
|
|
218
|
-
// If we want to support full roundtrip without schema, we might skip bool optimization OR live with the type change.
|
|
219
|
-
// Let's keep 1/0 and NOT restore to boolean automatically to avoid corrupting actual numbers.
|
|
220
|
-
// The user will receive 1/0 instead of true/false.
|
|
221
|
-
if (Array.isArray(obj))
|
|
222
|
-
return obj.map(traverse);
|
|
269
|
+
if (Array.isArray(obj)) {
|
|
270
|
+
const newArr = new Array(obj.length);
|
|
271
|
+
for (let i = 0; i < obj.length; i++) {
|
|
272
|
+
newArr[i] = traverse(obj[i]);
|
|
273
|
+
}
|
|
274
|
+
return newArr;
|
|
275
|
+
}
|
|
223
276
|
if (obj && typeof obj === 'object') {
|
|
224
277
|
const newObj = {};
|
|
225
278
|
for (const k in obj) {
|
|
226
|
-
|
|
227
|
-
|
|
279
|
+
if (Object.prototype.hasOwnProperty.call(obj, k)) {
|
|
280
|
+
const originalKey = reverseMap.get(k) || k;
|
|
281
|
+
newObj[originalKey] = traverse(obj[k]);
|
|
282
|
+
}
|
|
228
283
|
}
|
|
229
284
|
return newObj;
|
|
230
285
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "llm-chat-msg-compressor",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Intelligent JSON compression for LLM API optimization",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -35,9 +35,15 @@
|
|
|
35
35
|
"type": "git",
|
|
36
36
|
"url": "git+https://github.com/Sridharvn/llm-chat-msg-compressor.git"
|
|
37
37
|
},
|
|
38
|
+
"bugs": {
|
|
39
|
+
"url": "https://github.com/Sridharvn/llm-chat-msg-compressor/issues"
|
|
40
|
+
},
|
|
41
|
+
"homepage": "https://github.com/Sridharvn/llm-chat-msg-compressor#readme",
|
|
42
|
+
"engines": {
|
|
43
|
+
"node": ">=18.0.0"
|
|
44
|
+
},
|
|
38
45
|
"devDependencies": {
|
|
39
46
|
"@types/jest": "^30.0.0",
|
|
40
|
-
"gpt-3-encoder": "^1.1.4",
|
|
41
47
|
"jest": "^30.2.0",
|
|
42
48
|
"ts-jest": "^29.4.6",
|
|
43
49
|
"ts-node": "^10.9.2",
|