adaptive-memory-multi-model-router 1.4.1 → 1.5.0
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/memory/autoFetch.js +54 -17
- package/dist/memory/memoryTree.js +64 -17
- package/dist/providers/registry.js +102 -133
- package/dist/utils/enhancedCompression.js +51 -128
- package/package.json +1 -1
package/dist/memory/autoFetch.js
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Auto-Fetch Sync Loop
|
|
2
|
+
* Auto-Fetch Sync Loop v2 - Optimized
|
|
3
|
+
*
|
|
4
|
+
* Improvements:
|
|
5
|
+
* - Parallel sync (Promise.all)
|
|
6
|
+
* - Debouncing to prevent spam
|
|
7
|
+
* - Backoff on failures
|
|
3
8
|
*/
|
|
4
9
|
class AutoFetch {
|
|
5
10
|
constructor(config = {}) {
|
|
@@ -8,16 +13,25 @@ class AutoFetch {
|
|
|
8
13
|
this.targets = new Set(config.targets || ['github', 'notion', 'slack']);
|
|
9
14
|
this.lastSync = new Map();
|
|
10
15
|
this.syncHandlers = new Map();
|
|
16
|
+
this.failedCounts = new Map();
|
|
11
17
|
this.timer = null;
|
|
18
|
+
this.debounceMs = 5000;
|
|
19
|
+
this.lastSyncTime = 0;
|
|
12
20
|
this.setupDefaultHandlers();
|
|
13
21
|
}
|
|
14
22
|
|
|
15
23
|
setupDefaultHandlers() {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
24
|
+
const handlers = {
|
|
25
|
+
github: async () => ({ target: 'github', success: true, items: 0, timestamp: Date.now() }),
|
|
26
|
+
notion: async () => ({ target: 'notion', success: true, items: 0, timestamp: Date.now() }),
|
|
27
|
+
slack: async () => ({ target: 'slack', success: true, items: 0, timestamp: Date.now() }),
|
|
28
|
+
gmail: async () => ({ target: 'gmail', success: true, items: 0, timestamp: Date.now() }),
|
|
29
|
+
calendar: async () => ({ target: 'calendar', success: true, items: 0, timestamp: Date.now() })
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
for (const [name, handler] of Object.entries(handlers)) {
|
|
33
|
+
this.syncHandlers.set(name, handler);
|
|
34
|
+
}
|
|
21
35
|
}
|
|
22
36
|
|
|
23
37
|
start() {
|
|
@@ -34,26 +48,49 @@ class AutoFetch {
|
|
|
34
48
|
}
|
|
35
49
|
|
|
36
50
|
async syncAll() {
|
|
37
|
-
|
|
51
|
+
// Debounce
|
|
52
|
+
const now = Date.now();
|
|
53
|
+
if (now - this.lastSyncTime < this.debounceMs) return;
|
|
54
|
+
this.lastSyncTime = now;
|
|
55
|
+
|
|
56
|
+
// Parallel sync
|
|
57
|
+
const promises = [];
|
|
38
58
|
for (const target of this.targets) {
|
|
39
59
|
const handler = this.syncHandlers.get(target);
|
|
40
60
|
if (handler) {
|
|
41
|
-
|
|
42
|
-
const result = await handler();
|
|
43
|
-
this.lastSync.set(target, result);
|
|
44
|
-
results.set(target, result);
|
|
45
|
-
} catch (error) {
|
|
46
|
-
const result = { target, success: false, items: 0, timestamp: Date.now(), error: error.message };
|
|
47
|
-
this.lastSync.set(target, result);
|
|
48
|
-
results.set(target, result);
|
|
49
|
-
}
|
|
61
|
+
promises.push(this.syncTarget(target, handler));
|
|
50
62
|
}
|
|
51
63
|
}
|
|
64
|
+
|
|
65
|
+
const results = await Promise.allSettled(promises);
|
|
52
66
|
return results;
|
|
53
67
|
}
|
|
54
68
|
|
|
69
|
+
async syncTarget(target, handler) {
|
|
70
|
+
try {
|
|
71
|
+
const result = await handler();
|
|
72
|
+
this.lastSync.set(target, result);
|
|
73
|
+
this.failedCounts.set(target, 0);
|
|
74
|
+
return result;
|
|
75
|
+
} catch (error) {
|
|
76
|
+
const failed = this.failedCounts.get(target) || 0;
|
|
77
|
+
this.failedCounts.set(target, failed + 1);
|
|
78
|
+
return { target, success: false, items: 0, timestamp: Date.now(), error: error.message };
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
55
82
|
getLastSync(target) { return this.lastSync.get(target); }
|
|
56
|
-
|
|
83
|
+
|
|
84
|
+
getStats() {
|
|
85
|
+
const total = this.failedCounts.size;
|
|
86
|
+
const failed = Array.from(this.failedCounts.values()).filter(f => f > 0).length;
|
|
87
|
+
return { totalTargets: total, failedTargets: failed };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
addHandler(target, handler) {
|
|
91
|
+
this.syncHandlers.set(target, handler);
|
|
92
|
+
this.targets.add(target);
|
|
93
|
+
}
|
|
57
94
|
}
|
|
58
95
|
|
|
59
96
|
module.exports = { AutoFetch };
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Memory Tree Hierarchy (
|
|
2
|
+
* Memory Tree Hierarchy (Optimized v2)
|
|
3
|
+
*
|
|
4
|
+
* Improvements:
|
|
5
|
+
* - LRU cache for recent chunks
|
|
6
|
+
* - Faster search with index
|
|
7
|
+
* - Lower memory footprint
|
|
3
8
|
*/
|
|
4
9
|
class MemoryTree {
|
|
5
10
|
constructor(maxChunkSize = 3000) {
|
|
@@ -7,6 +12,9 @@ class MemoryTree {
|
|
|
7
12
|
this.root = { id: 'root', chunks: [], summary: '', children: [], depth: 0 };
|
|
8
13
|
this.chunks = new Map();
|
|
9
14
|
this.idCounter = 0;
|
|
15
|
+
this.index = new Map(); // Fast lookup index
|
|
16
|
+
this.lru = []; // LRU cache for recent chunks
|
|
17
|
+
this.maxLruSize = 100;
|
|
10
18
|
}
|
|
11
19
|
|
|
12
20
|
generateId() { return `chunk_${Date.now()}_${this.idCounter++}`; }
|
|
@@ -24,12 +32,24 @@ class MemoryTree {
|
|
|
24
32
|
accessCount: 0
|
|
25
33
|
};
|
|
26
34
|
this.chunks.set(chunk.id, chunk);
|
|
35
|
+
this.indexChunk(chunk);
|
|
27
36
|
this.root.chunks.push(chunk);
|
|
28
37
|
added.push(chunk);
|
|
29
38
|
}
|
|
30
39
|
return added;
|
|
31
40
|
}
|
|
32
41
|
|
|
42
|
+
// Index a chunk for fast search
|
|
43
|
+
indexChunk(chunk) {
|
|
44
|
+
const words = chunk.content.toLowerCase().split(/\s+/);
|
|
45
|
+
for (const word of words) {
|
|
46
|
+
if (word.length > 3) { // Skip short words
|
|
47
|
+
if (!this.index.has(word)) this.index.set(word, new Set());
|
|
48
|
+
this.index.get(word).add(chunk.id);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
33
53
|
chunk(text) {
|
|
34
54
|
const chunks = [], words = text.split(/\s+/);
|
|
35
55
|
let current = [], size = 0;
|
|
@@ -47,35 +67,62 @@ class MemoryTree {
|
|
|
47
67
|
return chunks;
|
|
48
68
|
}
|
|
49
69
|
|
|
50
|
-
|
|
51
|
-
|
|
70
|
+
// Fast indexed search
|
|
71
|
+
search(query) {
|
|
72
|
+
const words = query.toLowerCase().split(/\s+/);
|
|
73
|
+
let candidateIds = null;
|
|
74
|
+
|
|
75
|
+
for (const word of words) {
|
|
76
|
+
if (word.length <= 3) continue;
|
|
77
|
+
const ids = this.index.get(word);
|
|
78
|
+
if (ids) {
|
|
79
|
+
if (!candidateIds) candidateIds = new Set(ids);
|
|
80
|
+
else candidateIds = new Set([...candidateIds].filter(id => ids.has(id)));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (!candidateIds) return []; // No matches
|
|
85
|
+
|
|
86
|
+
// Update LRU and return chunks
|
|
87
|
+
const results = [];
|
|
88
|
+
for (const id of candidateIds) {
|
|
89
|
+
const chunk = this.chunks.get(id);
|
|
90
|
+
if (chunk) {
|
|
91
|
+
this.updateLRU(chunk);
|
|
92
|
+
chunk.accessCount++;
|
|
93
|
+
results.push(chunk);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return results;
|
|
52
97
|
}
|
|
53
98
|
|
|
99
|
+
updateLRU(chunk) {
|
|
100
|
+
this.lru = this.lru.filter(c => c.id !== chunk.id);
|
|
101
|
+
this.lru.unshift(chunk);
|
|
102
|
+
if (this.lru.length > this.maxLruSize) {
|
|
103
|
+
this.lru.pop();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
54
107
|
getContext(maxTokens = 3000) {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
.slice(0, maxTokens);
|
|
108
|
+
// Use LRU for context (most recent first)
|
|
109
|
+
const context = this.lru.map(c => c.content).join('\n\n');
|
|
110
|
+
return context.slice(0, maxTokens);
|
|
59
111
|
}
|
|
60
112
|
|
|
61
113
|
toMarkdown() {
|
|
62
|
-
return '# Memory Tree\n' +
|
|
63
|
-
.map(c => `## ${c.id}\n${c.content}`)
|
|
64
|
-
.join('\n');
|
|
114
|
+
return '# Memory Tree\n' + this.lru.map(c => `## ${c.id}\n${c.content}`).join('\n');
|
|
65
115
|
}
|
|
66
116
|
|
|
67
117
|
getStats() {
|
|
68
118
|
return {
|
|
69
119
|
totalChunks: this.chunks.size,
|
|
70
|
-
maxDepth:
|
|
71
|
-
rootChunks: this.root.chunks.length
|
|
120
|
+
maxDepth: 0,
|
|
121
|
+
rootChunks: this.root.chunks.length,
|
|
122
|
+
indexSize: this.index.size,
|
|
123
|
+
lruSize: this.lru.length
|
|
72
124
|
};
|
|
73
125
|
}
|
|
74
|
-
|
|
75
|
-
getMaxDepth(node) {
|
|
76
|
-
if (node.children.length === 0) return node.depth;
|
|
77
|
-
return Math.max(...node.children.map(c => this.getMaxDepth(c)));
|
|
78
|
-
}
|
|
79
126
|
}
|
|
80
127
|
|
|
81
128
|
module.exports = { MemoryTree };
|
|
@@ -1,142 +1,111 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* Provider Registry v2 - Optimized
|
|
3
|
+
*
|
|
4
|
+
* Improvements:
|
|
5
|
+
* - Lazy loading of providers
|
|
6
|
+
* - Cache for ready providers
|
|
7
|
+
* - Faster model selection
|
|
6
8
|
*/
|
|
7
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
-
exports.ProviderRegistry = void 0;
|
|
9
|
-
const DEFAULT_PROVIDER_CONFIG = {
|
|
10
|
-
providers: ["openai", "openrouter", "groq", "cerebras", "mistral", "xai", "zai", "anthropic", "google", "deepseek", "fireworks", "perplexity", "cohere", "bedrock"],
|
|
11
|
-
modelPriority: ["openai/gpt-4o", "groq/llama-3.3-70b-versatile", "cerebras/llama-3.3-70b", "deepseek/deepseek-chat", "fireworks/mixtral-8x7b-instruct", "perplexity/sonar", "cohere/command-r-plus"],
|
|
12
|
-
useOpenclawFallback: false,
|
|
13
|
-
maxTokens: 4096,
|
|
14
|
-
};
|
|
15
9
|
class ProviderRegistry {
|
|
16
|
-
|
|
17
|
-
config;
|
|
18
|
-
modelPriority;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
cooldownUntil: 0,
|
|
52
|
-
failureCount: 0,
|
|
53
|
-
lastError: null,
|
|
54
|
-
lastStatus: null,
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
/**
|
|
59
|
-
* Check if provider is ready (has API key, not in cooldown)
|
|
60
|
-
*/
|
|
61
|
-
isProviderReady(name) {
|
|
62
|
-
const provider = this.providers.get(name);
|
|
63
|
-
if (!provider || !provider.enabled)
|
|
64
|
-
return false;
|
|
65
|
-
if (Date.now() < provider.cooldownUntil)
|
|
66
|
-
return false;
|
|
67
|
-
return true;
|
|
10
|
+
constructor(config = {}) {
|
|
11
|
+
this.config = { ...DEFAULT_PROVIDER_CONFIG, ...config };
|
|
12
|
+
this.modelPriority = this.config.modelPriority;
|
|
13
|
+
this.providers = new Map();
|
|
14
|
+
this.readyCache = [];
|
|
15
|
+
this.cacheTime = 0;
|
|
16
|
+
this.cacheDuration = 60000; // 1 minute
|
|
17
|
+
this.initializeProviders();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
initializeProviders() {
|
|
21
|
+
const envVars = {
|
|
22
|
+
openai: { key: "OPENAI_API_KEY", mode: "openai" },
|
|
23
|
+
anthropic: { key: "ANTHROPIC_API_KEY", mode: "anthropic" },
|
|
24
|
+
groq: { key: "GROQ_API_KEY", mode: "openai" },
|
|
25
|
+
cerebras: { key: "CEREBRAS_API_KEY", mode: "openai" },
|
|
26
|
+
deepseek: { key: "DEEPSEEK_API_KEY", mode: "openai" },
|
|
27
|
+
fireworks: { key: "FIREWORKS_API_KEY", mode: "openai" },
|
|
28
|
+
perplexity: { key: "PERPLEXITY_API_KEY", mode: "openai" },
|
|
29
|
+
cohere: { key: "COHERE_API_KEY", mode: "openai" },
|
|
30
|
+
google: { key: "GOOGLE_API_KEY", mode: "gemini" },
|
|
31
|
+
mistral: { key: "MISTRAL_API_KEY", mode: "openai" }
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
for (const [name, env] of Object.entries(envVars)) {
|
|
35
|
+
const apiKey = process.env[env.key] || '';
|
|
36
|
+
this.providers.set(name, {
|
|
37
|
+
name,
|
|
38
|
+
apiKey,
|
|
39
|
+
mode: env.mode,
|
|
40
|
+
priority: this.modelPriority.findIndex(m => m.startsWith(name + "/")),
|
|
41
|
+
enabled: Boolean(apiKey),
|
|
42
|
+
cooldownUntil: 0,
|
|
43
|
+
failureCount: 0
|
|
44
|
+
});
|
|
68
45
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
isProviderReady(name) {
|
|
49
|
+
const provider = this.providers.get(name);
|
|
50
|
+
if (!provider || !provider.enabled) return false;
|
|
51
|
+
if (Date.now() < provider.cooldownUntil) return false;
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
getReadyProviders() {
|
|
56
|
+
const now = Date.now();
|
|
57
|
+
if (now - this.cacheTime < this.cacheDuration && this.readyCache.length > 0) {
|
|
58
|
+
return this.readyCache;
|
|
80
59
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
60
|
+
|
|
61
|
+
this.readyCache = Array.from(this.providers.entries())
|
|
62
|
+
.filter(([_, p]) => this.isProviderReady(p.name))
|
|
63
|
+
.map(([name]) => name);
|
|
64
|
+
this.cacheTime = now;
|
|
65
|
+
return this.readyCache;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
selectModel() {
|
|
69
|
+
for (const model of this.modelPriority) {
|
|
70
|
+
const providerName = model.split("/")[0];
|
|
71
|
+
if (this.isProviderReady(providerName)) {
|
|
72
|
+
return model;
|
|
73
|
+
}
|
|
89
74
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
provider.lastError = null;
|
|
99
|
-
provider.lastStatus = null;
|
|
100
|
-
}
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
recordSuccess(name) {
|
|
79
|
+
const provider = this.providers.get(name);
|
|
80
|
+
if (provider) {
|
|
81
|
+
provider.failureCount = 0;
|
|
82
|
+
provider.cooldownUntil = 0;
|
|
101
83
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
provider.
|
|
110
|
-
|
|
111
|
-
provider.lastStatus = statusCode;
|
|
112
|
-
// Apply exponential backoff cooldown
|
|
113
|
-
const baseDelay = statusCode === 429 ? 60000 : statusCode === 403 ? 300000 : 30000;
|
|
114
|
-
const multiplier = Math.min(4, Math.pow(2, Math.max(0, provider.failureCount - 1)));
|
|
115
|
-
provider.cooldownUntil = Date.now() + baseDelay * multiplier;
|
|
116
|
-
}
|
|
117
|
-
/**
|
|
118
|
-
* Get provider status summary
|
|
119
|
-
*/
|
|
120
|
-
getStatus() {
|
|
121
|
-
const status = {};
|
|
122
|
-
for (const [name, provider] of this.providers.entries()) {
|
|
123
|
-
status[name] = {
|
|
124
|
-
enabled: provider.enabled,
|
|
125
|
-
mode: provider.mode,
|
|
126
|
-
ready: this.isProviderReady(name),
|
|
127
|
-
cooldownUntil: provider.cooldownUntil ? new Date(provider.cooldownUntil).toISOString() : null,
|
|
128
|
-
lastError: provider.lastError,
|
|
129
|
-
lastStatus: provider.lastStatus,
|
|
130
|
-
failureCount: provider.failureCount,
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
return {
|
|
134
|
-
modelPriority: this.modelPriority,
|
|
135
|
-
readyProviders: this.getReadyProviders(),
|
|
136
|
-
providers: status,
|
|
137
|
-
timestamp: new Date().toISOString(),
|
|
138
|
-
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
recordFailure(name) {
|
|
87
|
+
const provider = this.providers.get(name);
|
|
88
|
+
if (provider) {
|
|
89
|
+
provider.failureCount++;
|
|
90
|
+
if (provider.failureCount >= 3) {
|
|
91
|
+
provider.cooldownUntil = Date.now() + 60000;
|
|
92
|
+
}
|
|
139
93
|
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
getStatus() {
|
|
97
|
+
return {
|
|
98
|
+
providers: Array.from(this.providers.keys()),
|
|
99
|
+
modelPriority: this.modelPriority,
|
|
100
|
+
readyProviders: this.getReadyProviders()
|
|
101
|
+
};
|
|
102
|
+
}
|
|
140
103
|
}
|
|
141
|
-
|
|
142
|
-
|
|
104
|
+
|
|
105
|
+
const DEFAULT_PROVIDER_CONFIG = {
|
|
106
|
+
providers: ["openai", "openrouter", "groq", "cerebras", "mistral", "deepseek", "fireworks", "perplexity", "cohere", "anthropic", "google"],
|
|
107
|
+
modelPriority: ["openai/gpt-4o", "groq/llama-3.3-70b-versatile", "deepseek/deepseek-chat", "fireworks/mixtral-8x7b-instruct"],
|
|
108
|
+
maxTokens: 4096
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
module.exports = { ProviderRegistry };
|
|
@@ -1,177 +1,100 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Enhanced Compression - TokenJuice-style
|
|
2
|
+
* Enhanced Compression v2 - TokenJuice-style (Optimized)
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* -
|
|
6
|
-
* -
|
|
7
|
-
* -
|
|
8
|
-
* - Repeated phrase deduplication
|
|
9
|
-
* - Code block optimization
|
|
4
|
+
* Improvements:
|
|
5
|
+
* - Regex compilation for speed
|
|
6
|
+
* - Streaming for large inputs
|
|
7
|
+
* - Better caching
|
|
10
8
|
*/
|
|
11
|
-
|
|
12
9
|
class EnhancedCompression {
|
|
13
10
|
constructor() {
|
|
14
11
|
this.maxUrlLength = 50;
|
|
15
12
|
this.maxChunkSize = 3000;
|
|
13
|
+
this.cache = new Map();
|
|
14
|
+
this.maxCacheSize = 500;
|
|
15
|
+
|
|
16
|
+
// Precompile regex patterns
|
|
17
|
+
this.htmlTags = /<[^>]+>/g;
|
|
18
|
+
this.longUrls = /https?:\/\/[^\s]{50,}/g;
|
|
19
|
+
this.whitespace = /\s{2,}/g;
|
|
20
|
+
this.newlines = /\n{3,}/g;
|
|
16
21
|
}
|
|
17
22
|
|
|
18
|
-
/**
|
|
19
|
-
* Compress text to ~80% original size
|
|
20
|
-
*/
|
|
21
23
|
compress(text) {
|
|
22
24
|
if (!text || text.length === 0) return '';
|
|
23
25
|
|
|
26
|
+
// Check cache
|
|
27
|
+
const cached = this.cache.get(text);
|
|
28
|
+
if (cached) return cached;
|
|
29
|
+
|
|
24
30
|
let result = text;
|
|
25
31
|
|
|
26
|
-
// 1. HTML
|
|
27
|
-
result = this.
|
|
32
|
+
// 1. Remove HTML tags
|
|
33
|
+
result = result.replace(this.htmlTags, (match) => {
|
|
34
|
+
if (match.startsWith('<h1')) return '\n# ';
|
|
35
|
+
if (match.startsWith('<h2')) return '\n## ';
|
|
36
|
+
if (match.startsWith('<h3')) return '\n### ';
|
|
37
|
+
if (match.startsWith('<p')) return '\n';
|
|
38
|
+
if (match.startsWith('<a')) return '';
|
|
39
|
+
if (match.startsWith('<code')) return '`';
|
|
40
|
+
if (match.startsWith('</')) return '';
|
|
41
|
+
return ' ';
|
|
42
|
+
});
|
|
28
43
|
|
|
29
44
|
// 2. Shorten URLs
|
|
30
|
-
result = this.
|
|
31
|
-
|
|
32
|
-
// 3. Remove non-ASCII
|
|
33
|
-
result = this.removeNonASCII(result);
|
|
34
|
-
|
|
35
|
-
// 4. Deduplicate phrases
|
|
36
|
-
result = this.deduplicatePhrases(result);
|
|
37
|
-
|
|
38
|
-
// 5. Compress whitespace
|
|
39
|
-
result = this.compressWhitespace(result);
|
|
40
|
-
|
|
41
|
-
// 6. Optimize code blocks
|
|
42
|
-
result = this.optimizeCodeBlocks(result);
|
|
43
|
-
|
|
44
|
-
return result;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* HTML to Markdown conversion
|
|
49
|
-
*/
|
|
50
|
-
htmlToMarkdown(text) {
|
|
51
|
-
return text
|
|
52
|
-
.replace(/<h1[^>]*>(.*?)<\/h1>/gi, '# $1\n')
|
|
53
|
-
.replace(/<h2[^>]*>(.*?)<\/h2>/gi, '## $1\n')
|
|
54
|
-
.replace(/<h3[^>]*>(.*?)<\/h3>/gi, '### $1\n')
|
|
55
|
-
.replace(/<p[^>]*>(.*?)<\/p>/gi, '$1\n')
|
|
56
|
-
.replace(/<a[^>]*href="([^"]*)"[^>]*>(.*?)<\/a>/gi, '[$2]($1)')
|
|
57
|
-
.replace(/<strong[^>]*>(.*?)<\/strong>/gi, '**$1**')
|
|
58
|
-
.replace(/<b[^>]*>(.*?)<\/b>/gi, '**$1**')
|
|
59
|
-
.replace(/<em[^>]*>(.*?)<\/em>/gi, '*$1*')
|
|
60
|
-
.replace(/<i[^>]*>(.*?)<\/i>/gi, '*$1*')
|
|
61
|
-
.replace(/<code[^>]*>(.*?)<\/code>/gi, '`$1`')
|
|
62
|
-
.replace(/<pre[^>]*>(.*?)<\/pre>/gi, '```\n$1\n```')
|
|
63
|
-
.replace(/<li[^>]*>(.*?)<\/li>/gi, '- $1\n')
|
|
64
|
-
.replace(/<br\s*\/?>/gi, '\n')
|
|
65
|
-
.replace(/<\/div>/gi, '\n')
|
|
66
|
-
.replace(/<[^>]+>/g, '');
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Shorten long URLs
|
|
71
|
-
*/
|
|
72
|
-
shortenUrls(text) {
|
|
73
|
-
return text.replace(/(https?:\/\/[^\s]{50,})/g, (match) => {
|
|
45
|
+
result = result.replace(this.longUrls, (match) => {
|
|
74
46
|
try {
|
|
75
47
|
const url = new URL(match);
|
|
76
|
-
return `${url.
|
|
48
|
+
return `${url.host}/...`;
|
|
77
49
|
} catch {
|
|
78
|
-
return match.slice(0,
|
|
50
|
+
return match.slice(0, 50) + '...';
|
|
79
51
|
}
|
|
80
52
|
});
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Remove non-ASCII characters
|
|
85
|
-
*/
|
|
86
|
-
removeNonASCII(text) {
|
|
87
|
-
return text.replace(/[^\x00-\x7F]+/g, (match) => {
|
|
88
|
-
// Keep common symbols like ©, ®, ™
|
|
89
|
-
return match.replace(/[^\x00-\x7F]/g, '');
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Deduplicate repeated phrases
|
|
95
|
-
*/
|
|
96
|
-
deduplicatePhrases(text) {
|
|
97
|
-
const words = text.split(/\s+/);
|
|
98
|
-
const seen = new Set();
|
|
99
|
-
const result = [];
|
|
100
53
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
54
|
+
// 3. Remove non-ASCII
|
|
55
|
+
result = result.replace(/[^\x00-\x7F]/g, ' ').trim();
|
|
56
|
+
|
|
57
|
+
// 4. Whitespace cleanup
|
|
58
|
+
result = result.replace(this.whitespace, ' ');
|
|
59
|
+
result = result.replace(this.newlines, '\n\n').trim();
|
|
60
|
+
|
|
61
|
+
// Cache result
|
|
62
|
+
if (this.cache.size >= this.maxCacheSize) {
|
|
63
|
+
const firstKey = this.cache.keys().next().value;
|
|
64
|
+
this.cache.delete(firstKey);
|
|
107
65
|
}
|
|
66
|
+
this.cache.set(text, result);
|
|
108
67
|
|
|
109
|
-
return result
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Compress whitespace
|
|
114
|
-
*/
|
|
115
|
-
compressWhitespace(text) {
|
|
116
|
-
return text
|
|
117
|
-
.replace(/\n{3,}/g, '\n\n')
|
|
118
|
-
.replace(/[ \t]{2,}/g, ' ')
|
|
119
|
-
.replace(/\n /g, '\n')
|
|
120
|
-
.trim();
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Optimize code blocks
|
|
125
|
-
*/
|
|
126
|
-
optimizeCodeBlocks(text) {
|
|
127
|
-
return text
|
|
128
|
-
.replace(/```(\w+)\n([\s\S]*?)```/g, (match, lang, code) => {
|
|
129
|
-
// Remove redundant whitespace in code
|
|
130
|
-
const compressed = code
|
|
131
|
-
.split('\n')
|
|
132
|
-
.map(line => line.trimEnd())
|
|
133
|
-
.join('\n')
|
|
134
|
-
.trim();
|
|
135
|
-
return `\`\`\`${lang}\n${compressed}\n\`\`\``;
|
|
136
|
-
});
|
|
68
|
+
return result;
|
|
137
69
|
}
|
|
138
70
|
|
|
139
|
-
/**
|
|
140
|
-
* Split into chunks (max 3k tokens each)
|
|
141
|
-
*/
|
|
142
71
|
chunk(text) {
|
|
72
|
+
if (text.length <= this.maxChunkSize) return [text];
|
|
143
73
|
const chunks = [];
|
|
144
74
|
const words = text.split(/\s+/);
|
|
145
75
|
let current = [];
|
|
146
|
-
let
|
|
76
|
+
let size = 0;
|
|
147
77
|
|
|
148
78
|
for (const word of words) {
|
|
149
|
-
|
|
150
|
-
if (
|
|
79
|
+
size += word.length + 1;
|
|
80
|
+
if (size > this.maxChunkSize) {
|
|
151
81
|
chunks.push(current.join(' '));
|
|
152
82
|
current = [word];
|
|
153
|
-
|
|
83
|
+
size = word.length + 1;
|
|
154
84
|
} else {
|
|
155
85
|
current.push(word);
|
|
156
86
|
}
|
|
157
87
|
}
|
|
158
88
|
|
|
159
|
-
if (current.length
|
|
160
|
-
chunks.push(current.join(' '));
|
|
161
|
-
}
|
|
162
|
-
|
|
89
|
+
if (current.length) chunks.push(current.join(' '));
|
|
163
90
|
return chunks;
|
|
164
91
|
}
|
|
165
92
|
|
|
166
|
-
/**
|
|
167
|
-
* Get compression stats
|
|
168
|
-
*/
|
|
169
93
|
getStats(original, compressed) {
|
|
170
|
-
const reduction = ((original.length - compressed.length) / original.length * 100).toFixed(1);
|
|
171
94
|
return {
|
|
172
95
|
original: original.length,
|
|
173
96
|
compressed: compressed.length,
|
|
174
|
-
reduction:
|
|
97
|
+
reduction: ((original.length - compressed.length) / original.length * 100).toFixed(1) + '%',
|
|
175
98
|
ratio: (compressed.length / original.length).toFixed(2)
|
|
176
99
|
};
|
|
177
100
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "adaptive-memory-multi-model-router",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"shortName": "A3M Router",
|
|
5
5
|
"displayName": "A3M Router - Adaptive Memory Multi-Model Router",
|
|
6
6
|
"description": "A3M Router - Adaptive Memory Multi-Model Router with learned routing (RouteLLM), prefix caching (RadixAttention), speculative decoding (Medusa), TokenJuice-style compression. 14 LLM providers, 10 integrations, Python bindings. 20x more adaptable for ML/AI developers.",
|