adaptive-memory-multi-model-router 2.6.0 → 2.7.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/README.md +149 -22
- package/dist/cache/semanticCache.d.ts +54 -22
- package/dist/cache/semanticCache.js +230 -86
- package/dist/cache/semanticCache.js.map +1 -1
- package/dist/cost/budgetEnforcer.d.ts +108 -0
- package/dist/cost/budgetEnforcer.js +295 -0
- package/dist/cost/budgetEnforcer.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +5 -1
- package/dist/routing/providerHealth.d.ts +154 -0
- package/dist/routing/providerHealth.js +371 -0
- package/dist/routing/providerHealth.js.map +1 -0
- package/dist/sdk.d.ts +124 -0
- package/dist/sdk.js +109 -100
- package/package.json +3 -2
- package/src/cache/semanticCache.ts +293 -103
- package/src/cost/budgetEnforcer.ts +358 -0
- package/src/index.ts +2 -0
- package/src/routing/providerHealth.ts +483 -0
- package/test/test_budgetEnforcer.ts +310 -0
- package/test/test_providerHealth.ts +523 -0
- package/test/test_semanticCache.ts +507 -0
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A3M Router - BudgetEnforcer Tests
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { BudgetEnforcer, BudgetConfig, SpendRecord } from '../src/cost/budgetEnforcer';
|
|
6
|
+
|
|
7
|
+
interface TestResult {
|
|
8
|
+
passed: boolean;
|
|
9
|
+
name: string;
|
|
10
|
+
error?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const results: TestResult[] = [];
|
|
14
|
+
|
|
15
|
+
function assertEqual<T>(name: string, actual: T, expected: T): void {
|
|
16
|
+
const passed = JSON.stringify(actual) === JSON.stringify(expected);
|
|
17
|
+
results.push({
|
|
18
|
+
passed,
|
|
19
|
+
name,
|
|
20
|
+
error: passed ? undefined : `Expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function assertTrue(name: string, actual: boolean): void {
|
|
25
|
+
assertEqual(name, actual, true);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function assertFalse(name: string, actual: boolean): void {
|
|
29
|
+
assertEqual(name, actual, false);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function assertThrows(name: string, fn: () => void): void {
|
|
33
|
+
try {
|
|
34
|
+
fn();
|
|
35
|
+
results.push({ passed: false, name, error: 'Expected function to throw, but it did not' });
|
|
36
|
+
} catch {
|
|
37
|
+
results.push({ passed: true, name });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
// Test Suite
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
|
|
45
|
+
async function runTests() {
|
|
46
|
+
console.log('Running BudgetEnforcer tests...\n');
|
|
47
|
+
|
|
48
|
+
// Test: Basic budget checking
|
|
49
|
+
{
|
|
50
|
+
const enforcer = new BudgetEnforcer();
|
|
51
|
+
enforcer.setBudget('test-key', 10000, { hardCap: true }); // $100.00 = 10000 cents
|
|
52
|
+
|
|
53
|
+
const result = enforcer.checkBudget('test-key', 5000); // $50
|
|
54
|
+
assertTrue('checkBudget allows request under budget', result.allowed);
|
|
55
|
+
assertEqual('remaining after $50 of $100 budget', result.remaining, 5000);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Test: Spend recording
|
|
59
|
+
{
|
|
60
|
+
const enforcer = new BudgetEnforcer();
|
|
61
|
+
enforcer.setBudget('test-key', 10000);
|
|
62
|
+
|
|
63
|
+
enforcer.recordSpend('test-key', 3000);
|
|
64
|
+
const spend = enforcer.getSpend('test-key');
|
|
65
|
+
|
|
66
|
+
assertEqual('spent after recording $30', spend?.spent, 3000);
|
|
67
|
+
assertEqual('remaining after recording $30 of $100', spend?.remaining, 7000);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Test: Cumulative spend tracking
|
|
71
|
+
{
|
|
72
|
+
const enforcer = new BudgetEnforcer();
|
|
73
|
+
enforcer.setBudget('test-key', 10000);
|
|
74
|
+
|
|
75
|
+
enforcer.recordSpend('test-key', 3000);
|
|
76
|
+
enforcer.recordSpend('test-key', 2000);
|
|
77
|
+
const spend = enforcer.getSpend('test-key');
|
|
78
|
+
|
|
79
|
+
assertEqual('total spent after two recordings', spend?.spent, 5000);
|
|
80
|
+
assertEqual('remaining after two recordings', spend?.remaining, 5000);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Test: Threshold alerts
|
|
84
|
+
{
|
|
85
|
+
const enforcer = new BudgetEnforcer();
|
|
86
|
+
let alertFired = false;
|
|
87
|
+
let alertData: any = null;
|
|
88
|
+
|
|
89
|
+
enforcer.on('budget:warning', (data: any) => {
|
|
90
|
+
alertFired = true;
|
|
91
|
+
alertData = data;
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
enforcer.setBudget('test-key', 10000, {
|
|
95
|
+
alertThresholds: [0.5, 0.8],
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// Spend to hit 50% threshold
|
|
99
|
+
enforcer.recordSpend('test-key', 5000);
|
|
100
|
+
|
|
101
|
+
assertTrue('alert fired at 50% threshold', alertFired);
|
|
102
|
+
assertEqual('alert threshold value', alertData?.threshold, 0.5);
|
|
103
|
+
assertEqual('alert spent value', alertData?.spent, 5000);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Test: Threshold only fires once
|
|
107
|
+
{
|
|
108
|
+
const enforcer = new BudgetEnforcer();
|
|
109
|
+
let alertCount = 0;
|
|
110
|
+
|
|
111
|
+
enforcer.on('budget:warning', () => {
|
|
112
|
+
alertCount++;
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
enforcer.setBudget('test-key', 10000, {
|
|
116
|
+
alertThresholds: [0.5],
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
enforcer.recordSpend('test-key', 5000);
|
|
120
|
+
enforcer.recordSpend('test-key', 1000); // More spend, still above 50%
|
|
121
|
+
enforcer.recordSpend('test-key', 1000);
|
|
122
|
+
|
|
123
|
+
assertEqual('alert only fires once per threshold', alertCount, 1);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Test: Hard cap rejection
|
|
127
|
+
{
|
|
128
|
+
const enforcer = new BudgetEnforcer();
|
|
129
|
+
enforcer.setBudget('test-key', 10000, { hardCap: true });
|
|
130
|
+
|
|
131
|
+
// Spend most of budget
|
|
132
|
+
enforcer.recordSpend('test-key', 9000);
|
|
133
|
+
|
|
134
|
+
const result = enforcer.checkBudget('test-key', 2000); // Would exceed
|
|
135
|
+
assertFalse('checkBudget rejects request when hard cap would be exceeded', result.allowed);
|
|
136
|
+
assertTrue('reason provided for rejection', result.reason?.includes('Budget exceeded') ?? false);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Test: Hard cap allows if under budget
|
|
140
|
+
{
|
|
141
|
+
const enforcer = new BudgetEnforcer();
|
|
142
|
+
enforcer.setBudget('test-key', 10000, { hardCap: true });
|
|
143
|
+
|
|
144
|
+
enforcer.recordSpend('test-key', 5000);
|
|
145
|
+
|
|
146
|
+
const result = enforcer.checkBudget('test-key', 3000);
|
|
147
|
+
assertTrue('checkBudget allows request under hard cap', result.allowed);
|
|
148
|
+
assertEqual('correct remaining', result.remaining, 2000);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Test: Budget reset
|
|
152
|
+
{
|
|
153
|
+
const enforcer = new BudgetEnforcer();
|
|
154
|
+
enforcer.setBudget('test-key', 10000);
|
|
155
|
+
|
|
156
|
+
enforcer.recordSpend('test-key', 8000);
|
|
157
|
+
enforcer.resetBudget('test-key');
|
|
158
|
+
|
|
159
|
+
const spend = enforcer.getSpend('test-key');
|
|
160
|
+
assertEqual('spent reset to 0', spend?.spent, 0);
|
|
161
|
+
assertEqual('remaining reset to full budget', spend?.remaining, 10000);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Test: Set budget on unknown key (permissive)
|
|
165
|
+
{
|
|
166
|
+
const enforcer = new BudgetEnforcer();
|
|
167
|
+
const result = enforcer.checkBudget('unknown-key', 5000);
|
|
168
|
+
assertTrue('unknown key allowed by default', result.allowed);
|
|
169
|
+
assertEqual('unknown key has infinite remaining', result.remaining, Infinity);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Test: Update limit
|
|
173
|
+
{
|
|
174
|
+
const enforcer = new BudgetEnforcer();
|
|
175
|
+
enforcer.setBudget('test-key', 10000);
|
|
176
|
+
enforcer.recordSpend('test-key', 5000);
|
|
177
|
+
|
|
178
|
+
enforcer.updateLimit('test-key', 20000);
|
|
179
|
+
|
|
180
|
+
const spend = enforcer.getSpend('test-key');
|
|
181
|
+
assertEqual('budget increased', spend?.budget, 20000);
|
|
182
|
+
assertEqual('remaining recalculated', spend?.remaining, 15000);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Test: Get all spend records
|
|
186
|
+
{
|
|
187
|
+
const enforcer = new BudgetEnforcer();
|
|
188
|
+
enforcer.setBudget('key1', 10000);
|
|
189
|
+
enforcer.setBudget('key2', 20000);
|
|
190
|
+
enforcer.recordSpend('key1', 1000);
|
|
191
|
+
enforcer.recordSpend('key2', 2000);
|
|
192
|
+
|
|
193
|
+
const all = enforcer.getAllSpend();
|
|
194
|
+
assertEqual('getAllSpend returns 2 records', all.length, 2);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Test: Remove budget
|
|
198
|
+
{
|
|
199
|
+
const enforcer = new BudgetEnforcer();
|
|
200
|
+
enforcer.setBudget('test-key', 10000);
|
|
201
|
+
enforcer.removeBudget('test-key');
|
|
202
|
+
|
|
203
|
+
const config = enforcer.getBudgetConfig('test-key');
|
|
204
|
+
assertEqual('config removed', config, undefined);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Test: 100% threshold event
|
|
208
|
+
{
|
|
209
|
+
const enforcer = new BudgetEnforcer();
|
|
210
|
+
let exceededEvent = false;
|
|
211
|
+
|
|
212
|
+
enforcer.on('budget:exceeded', () => {
|
|
213
|
+
exceededEvent = true;
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
enforcer.setBudget('test-key', 10000, { hardCap: true });
|
|
217
|
+
enforcer.recordSpend('test-key', 10000);
|
|
218
|
+
|
|
219
|
+
assertTrue('budget:exceeded event fired', exceededEvent);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Test: Soft cap allows request but warns
|
|
223
|
+
{
|
|
224
|
+
const enforcer = new BudgetEnforcer();
|
|
225
|
+
let warningCount = 0;
|
|
226
|
+
|
|
227
|
+
enforcer.on('budget:warning', () => {
|
|
228
|
+
warningCount++;
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
enforcer.setBudget('test-key', 10000, { hardCap: false });
|
|
232
|
+
|
|
233
|
+
// Spend to exceed
|
|
234
|
+
enforcer.recordSpend('test-key', 11000);
|
|
235
|
+
|
|
236
|
+
assertTrue('soft cap allows over-budget spend', warningCount >= 1);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Test: Days until reset
|
|
240
|
+
{
|
|
241
|
+
const enforcer = new BudgetEnforcer();
|
|
242
|
+
enforcer.setBudget('test-key', 10000);
|
|
243
|
+
|
|
244
|
+
const days = enforcer.getDaysUntilReset('test-key');
|
|
245
|
+
assertTrue('days until reset is positive', (days ?? 0) > 0);
|
|
246
|
+
assertTrue('days until reset is <= 31', (days ?? 0) <= 31);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Test: getSpend returns undefined for unknown key
|
|
250
|
+
{
|
|
251
|
+
const enforcer = new BudgetEnforcer();
|
|
252
|
+
const spend = enforcer.getSpend('unknown-key');
|
|
253
|
+
assertEqual('getSpend returns undefined for unknown key', spend, undefined);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Test: Multiple threshold progression
|
|
257
|
+
{
|
|
258
|
+
const enforcer = new BudgetEnforcer();
|
|
259
|
+
const alerts: number[] = [];
|
|
260
|
+
|
|
261
|
+
enforcer.on('budget:warning', (data: any) => {
|
|
262
|
+
alerts.push(data.threshold);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
enforcer.setBudget('test-key', 10000, {
|
|
266
|
+
alertThresholds: [0.5, 0.8, 1.0],
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
enforcer.recordSpend('test-key', 5000); // 50%
|
|
270
|
+
enforcer.recordSpend('test-key', 3000); // 80%
|
|
271
|
+
enforcer.recordSpend('test-key', 2000); // 100%
|
|
272
|
+
|
|
273
|
+
assertEqual('alerts at 50%, 80%, 100%', JSON.stringify(alerts), JSON.stringify([0.5, 0.8, 1.0]));
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Test: Factory function
|
|
277
|
+
{
|
|
278
|
+
const enforcer = createBudgetEnforcer();
|
|
279
|
+
enforcer.setBudget('test-key', 10000);
|
|
280
|
+
const result = enforcer.checkBudget('test-key', 1000);
|
|
281
|
+
assertTrue('factory created enforcer works', result.allowed);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Print results
|
|
285
|
+
console.log('\n--- Test Results ---');
|
|
286
|
+
let passed = 0;
|
|
287
|
+
let failed = 0;
|
|
288
|
+
|
|
289
|
+
for (const r of results) {
|
|
290
|
+
if (r.passed) {
|
|
291
|
+
console.log(` PASS: ${r.name}`);
|
|
292
|
+
passed++;
|
|
293
|
+
} else {
|
|
294
|
+
console.log(` FAIL: ${r.name}`);
|
|
295
|
+
if (r.error) console.log(` ${r.error}`);
|
|
296
|
+
failed++;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
console.log(`\n${passed} passed, ${failed} failed out of ${results.length} tests`);
|
|
301
|
+
|
|
302
|
+
if (failed > 0) {
|
|
303
|
+
process.exit(1);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
runTests().catch((err) => {
|
|
308
|
+
console.error('Test runner error:', err);
|
|
309
|
+
process.exit(1);
|
|
310
|
+
});
|