adaptive-memory-multi-model-router 1.9.4 → 2.0.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/.github/ISSUE_TEMPLATE/bug_report.md +50 -0
- package/.github/ISSUE_TEMPLATE/config.yml +11 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +37 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +44 -0
- package/.github/workflows/npm-stats-validation.yml +152 -0
- package/.github/workflows/pages.yml +37 -0
- package/CHANGELOG.md +122 -0
- package/CODE_OF_CONDUCT.md +128 -0
- package/CONTRIBUTING.md +110 -0
- package/LAUNCH-PAIN-DRIVEN.md +339 -0
- package/LAUNCH.md +575 -0
- package/POPULARITY_BOOSTERS.md +285 -0
- package/README.md +231 -300
- package/SECURITY.md +69 -0
- package/articles/CONTENT_STRUCTURE.md +292 -0
- package/articles/DEVTO_COST_GUIDE.md +473 -0
- package/articles/DEVTO_FINAL.md +416 -0
- package/articles/DEVTO_MULTI_PROVIDER.md +542 -0
- package/articles/HN_10X_BETTER.md +430 -0
- package/articles/HN_CHINESE_STYLE.md +308 -0
- package/articles/HN_FINAL.md +199 -0
- package/articles/HN_POSTED_VERSION.md +56 -0
- package/articles/HN_RESEARCH.md +364 -0
- package/articles/PAIN-DRIVEN-devto-v2.md +308 -0
- package/articles/PAIN-DRIVEN-devto-v3.md +268 -0
- package/articles/PAIN-DRIVEN-devto.md +242 -0
- package/articles/PAIN-DRIVEN-hackernews-v2.md +138 -0
- package/articles/PAIN-DRIVEN-hackernews-v3.md +151 -0
- package/articles/PAIN-DRIVEN-hackernews.md +131 -0
- package/articles/PAIN-DRIVEN-reddit-v2.md +301 -0
- package/articles/PAIN-DRIVEN-reddit-v3.md +236 -0
- package/articles/PAIN-DRIVEN-reddit.md +218 -0
- package/articles/PAIN-DRIVEN-twitter-v2.md +110 -0
- package/articles/PAIN-DRIVEN-twitter-v3.md +121 -0
- package/articles/PAIN-DRIVEN-twitter.md +120 -0
- package/articles/PORTKEY_VS_A3M.md +147 -0
- package/articles/REDDIT_FINAL.md +232 -0
- package/articles/TWITTER_FINAL.md +167 -0
- package/articles/WHY_10X_BETTER.md +261 -0
- package/articles/WHY_CHINESE_STYLE_BETTER.md +323 -0
- package/articles/ai-discoverability-llm-routing.md +210 -0
- package/articles/devto-llm-routing.md +109 -0
- package/articles/hackernews-show-hn.md +65 -0
- package/articles/hashnode-llm-cost-optimization.md +125 -0
- package/articles/medium-building-llm-router.md +205 -0
- package/articles/reddit-ml.md +86 -0
- package/articles/twitter-thread-cost-savings.md +98 -0
- package/articles/youtube-tutorial-script.md +262 -0
- package/assets/banner.svg +109 -0
- package/assets/logo.svg +68 -0
- package/assets/social-preview.svg +64 -0
- package/demo/demo-script.md +53 -0
- package/dist/analytics/costAnalytics.d.ts +77 -0
- package/dist/analytics/costAnalytics.d.ts.map +1 -0
- package/dist/analytics/costAnalytics.js +219 -0
- package/dist/analytics/costAnalytics.js.map +1 -0
- package/dist/cache/semanticCache.d.ts +62 -0
- package/dist/cache/semanticCache.d.ts.map +1 -0
- package/dist/cache/semanticCache.js +176 -0
- package/dist/cache/semanticCache.js.map +1 -0
- package/dist/cli.js +35 -0
- package/dist/geo/generativeEngineOptimization.js +321 -0
- package/dist/geo/geoRouter.js +387 -0
- package/dist/index.d.ts +4 -723
- package/dist/index.js +11 -344
- package/dist/index.js.map +1 -1
- package/dist/integrations/langchainAdapter.d.ts +146 -0
- package/dist/integrations/langchainAdapter.d.ts.map +1 -0
- package/dist/integrations/langchainAdapter.js +731 -0
- package/dist/integrations/langchainAdapter.js.map +1 -0
- package/dist/integrations/oauth.d.ts +69 -0
- package/dist/integrations/oauth.d.ts.map +1 -0
- package/dist/integrations/oauth.js +225 -21
- package/dist/integrations/oauth.js.map +1 -0
- package/dist/memory/autoFetch.d.ts +39 -0
- package/dist/memory/autoFetch.d.ts.map +1 -0
- package/dist/memory/autoFetch.js +80 -88
- package/dist/memory/autoFetch.js.map +1 -0
- package/dist/memory/memoryTree.d.ts +76 -0
- package/dist/memory/memoryTree.d.ts.map +1 -0
- package/dist/memory/memoryTree.js +185 -130
- package/dist/memory/memoryTree.js.map +1 -0
- package/dist/memory/obsidianVault.d.ts +71 -0
- package/dist/memory/obsidianVault.d.ts.map +1 -0
- package/dist/memory/obsidianVault.js +207 -22
- package/dist/memory/obsidianVault.js.map +1 -0
- package/dist/providers/providerConfig.d.ts +49 -0
- package/dist/providers/providerConfig.d.ts.map +1 -0
- package/dist/providers/providerConfig.js +806 -401
- package/dist/providers/providerConfig.js.map +1 -0
- package/dist/security/guardrails.d.ts +76 -0
- package/dist/security/guardrails.d.ts.map +1 -0
- package/dist/security/guardrails.js +479 -0
- package/dist/security/guardrails.js.map +1 -0
- package/dist/security/inputValidation.js +351 -0
- package/dist/server/dashboard.d.ts +58 -0
- package/dist/server/dashboard.d.ts.map +1 -0
- package/dist/server/dashboard.js +553 -0
- package/dist/server/dashboard.js.map +1 -0
- package/dist/server/modelMapper.d.ts +43 -0
- package/dist/server/modelMapper.d.ts.map +1 -0
- package/dist/server/modelMapper.js +154 -0
- package/dist/server/modelMapper.js.map +1 -0
- package/dist/server/proxyServer.d.ts +41 -0
- package/dist/server/proxyServer.d.ts.map +1 -0
- package/dist/server/proxyServer.js +932 -0
- package/dist/server/proxyServer.js.map +1 -0
- package/dist/skills/__tests__/skill_manager.test.d.ts +2 -0
- package/dist/skills/__tests__/skill_manager.test.d.ts.map +1 -0
- package/dist/skills/__tests__/skill_manager.test.js +268 -0
- package/dist/skills/__tests__/skill_manager.test.js.map +1 -0
- package/docs/geo/GENERATIVE_ENGINE_OPTIMIZATION.md +232 -0
- package/docs-site/index.html +347 -0
- package/llms.txt +138 -0
- package/package.json +72 -7
- package/playground/README.md +51 -0
- package/playground/codesandbox.json +12 -0
- package/playground/index.js +39 -0
- package/scripts/update-npm-badges.js +158 -0
- package/src/analytics/costAnalytics.ts +304 -0
- package/src/cache/semanticCache.ts +221 -0
- package/src/index.ts +6 -0
- package/src/integrations/langchainAdapter.ts +955 -0
- package/src/providers/providerConfig.ts +923 -0
- package/src/security/guardrails.ts +585 -0
- package/src/server/dashboard.ts +610 -0
- package/src/server/modelMapper.ts +182 -0
- package/src/server/proxyServer.ts +1105 -0
- package/src/types/langchain.d.ts +83 -0
- package/tsconfig.build.json +20 -0
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* A3M Router - Input Validation & Security
|
|
4
|
+
*
|
|
5
|
+
* Security features for production LLM routing:
|
|
6
|
+
* - Input sanitization
|
|
7
|
+
* - Prompt injection detection
|
|
8
|
+
* - Rate limiting
|
|
9
|
+
* - Content filtering
|
|
10
|
+
* - PII detection
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
// Common prompt injection patterns
|
|
14
|
+
const PROMPT_INJECTION_PATTERNS = [
|
|
15
|
+
/ignore\s+(?:previous|above|earlier)/i,
|
|
16
|
+
/disregard\s+(?:previous|above|earlier)/i,
|
|
17
|
+
/forget\s+(?:previous|above|earlier)/i,
|
|
18
|
+
/system\s*:\s*/i,
|
|
19
|
+
/you\s+are\s+now/i,
|
|
20
|
+
/new\s+instruction/i,
|
|
21
|
+
/override\s+(?:previous|settings)/i,
|
|
22
|
+
/bypass\s+(?:filter|restriction)/i,
|
|
23
|
+
/DAN\s*\(/i, // Do Anything Now
|
|
24
|
+
/jailbreak/i,
|
|
25
|
+
/\[\s*system\s*\]/i,
|
|
26
|
+
/<\s*system\s*>/i,
|
|
27
|
+
/\{\s*system\s*\}/i,
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
// PII patterns
|
|
31
|
+
const PII_PATTERNS = {
|
|
32
|
+
email: /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g,
|
|
33
|
+
phone: /\b\d{3}[-.]?\d{3}[-.]?\d{4}\b/g,
|
|
34
|
+
ssn: /\b\d{3}-\d{2}-\d{4}\b/g,
|
|
35
|
+
creditCard: /\b(?:\d{4}[- ]?){3}\d{4}\b/g,
|
|
36
|
+
apiKey: /(?:api[_-]?key|apikey|token)\s*[:=]\s*["']?[a-zA-Z0-9_-]{20,}["']?/gi,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// Content filter categories
|
|
40
|
+
const CONTENT_CATEGORIES = {
|
|
41
|
+
hate: /\b(hate|kill|die|attack|violence)\b/i,
|
|
42
|
+
selfHarm: /\b(suicide|self[- ]?harm|kill\s+myself)\b/i,
|
|
43
|
+
illegal: /\b(hack|exploit|steal|fraud|illegal)\b/i,
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
class InputValidator {
|
|
47
|
+
constructor(options = {}) {
|
|
48
|
+
this.options = {
|
|
49
|
+
maxLength: options.maxLength || 10000,
|
|
50
|
+
maxTokens: options.maxTokens || 4000,
|
|
51
|
+
enableInjectionDetection: options.enableInjectionDetection !== false,
|
|
52
|
+
enablePIIDetection: options.enablePIIDetection || false,
|
|
53
|
+
enableContentFilter: options.enableContentFilter !== false,
|
|
54
|
+
allowedDomains: options.allowedDomains || [],
|
|
55
|
+
blockedPatterns: options.blockedPatterns || [],
|
|
56
|
+
...options,
|
|
57
|
+
};
|
|
58
|
+
this.rateLimiter = new Map();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Sanitize input text
|
|
63
|
+
*/
|
|
64
|
+
sanitize(text) {
|
|
65
|
+
if (typeof text !== 'string') {
|
|
66
|
+
throw new Error('Input must be a string');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
let sanitized = text;
|
|
70
|
+
|
|
71
|
+
// Trim whitespace
|
|
72
|
+
sanitized = sanitized.trim();
|
|
73
|
+
|
|
74
|
+
// Limit length
|
|
75
|
+
if (sanitized.length > this.options.maxLength) {
|
|
76
|
+
sanitized = sanitized.substring(0, this.options.maxLength);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Remove null bytes
|
|
80
|
+
sanitized = sanitized.replace(/\x00/g, '');
|
|
81
|
+
|
|
82
|
+
// Normalize unicode
|
|
83
|
+
sanitized = sanitized.normalize('NFC');
|
|
84
|
+
|
|
85
|
+
return sanitized;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Detect prompt injection attempts
|
|
90
|
+
*/
|
|
91
|
+
detectInjection(text) {
|
|
92
|
+
if (!this.options.enableInjectionDetection) {
|
|
93
|
+
return { detected: false, matches: [] };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const matches = [];
|
|
97
|
+
|
|
98
|
+
for (const pattern of PROMPT_INJECTION_PATTERNS) {
|
|
99
|
+
if (pattern.test(text)) {
|
|
100
|
+
matches.push(pattern.toString());
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Check custom blocked patterns
|
|
105
|
+
for (const pattern of this.options.blockedPatterns) {
|
|
106
|
+
const regex = new RegExp(pattern, 'i');
|
|
107
|
+
if (regex.test(text)) {
|
|
108
|
+
matches.push(`custom:${pattern}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
detected: matches.length > 0,
|
|
114
|
+
matches,
|
|
115
|
+
risk: matches.length > 2 ? 'high' : matches.length > 0 ? 'medium' : 'low',
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Detect PII in text
|
|
121
|
+
*/
|
|
122
|
+
detectPII(text) {
|
|
123
|
+
if (!this.options.enablePIIDetection) {
|
|
124
|
+
return { detected: false, matches: {} };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const matches = {};
|
|
128
|
+
|
|
129
|
+
for (const [type, pattern] of Object.entries(PII_PATTERNS)) {
|
|
130
|
+
const found = text.match(pattern);
|
|
131
|
+
if (found) {
|
|
132
|
+
matches[type] = found;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
detected: Object.keys(matches).length > 0,
|
|
138
|
+
matches,
|
|
139
|
+
types: Object.keys(matches),
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Content filtering
|
|
145
|
+
*/
|
|
146
|
+
filterContent(text) {
|
|
147
|
+
if (!this.options.enableContentFilter) {
|
|
148
|
+
return { flagged: false, categories: [] };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const categories = [];
|
|
152
|
+
|
|
153
|
+
for (const [category, pattern] of Object.entries(CONTENT_CATEGORIES)) {
|
|
154
|
+
if (pattern.test(text)) {
|
|
155
|
+
categories.push(category);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
flagged: categories.length > 0,
|
|
161
|
+
categories,
|
|
162
|
+
action: categories.length > 1 ? 'block' : 'warn',
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Rate limiting check
|
|
168
|
+
*/
|
|
169
|
+
checkRateLimit(identifier, maxRequests = 100, windowMs = 60000) {
|
|
170
|
+
const now = Date.now();
|
|
171
|
+
const windowStart = now - windowMs;
|
|
172
|
+
|
|
173
|
+
if (!this.rateLimiter.has(identifier)) {
|
|
174
|
+
this.rateLimiter.set(identifier, []);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const requests = this.rateLimiter.get(identifier);
|
|
178
|
+
|
|
179
|
+
// Remove old requests
|
|
180
|
+
const validRequests = requests.filter(time => time > windowStart);
|
|
181
|
+
|
|
182
|
+
if (validRequests.length >= maxRequests) {
|
|
183
|
+
const oldestRequest = validRequests[0];
|
|
184
|
+
const retryAfter = Math.ceil((oldestRequest + windowMs - now) / 1000);
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
allowed: false,
|
|
188
|
+
retryAfter,
|
|
189
|
+
remaining: 0,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Add current request
|
|
194
|
+
validRequests.push(now);
|
|
195
|
+
this.rateLimiter.set(identifier, validRequests);
|
|
196
|
+
|
|
197
|
+
return {
|
|
198
|
+
allowed: true,
|
|
199
|
+
remaining: maxRequests - validRequests.length,
|
|
200
|
+
resetTime: now + windowMs,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Full validation pipeline
|
|
206
|
+
*/
|
|
207
|
+
validate(text, options = {}) {
|
|
208
|
+
const identifier = options.identifier || 'anonymous';
|
|
209
|
+
const results = {
|
|
210
|
+
valid: true,
|
|
211
|
+
sanitized: null,
|
|
212
|
+
errors: [],
|
|
213
|
+
warnings: [],
|
|
214
|
+
metadata: {},
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
try {
|
|
218
|
+
// Step 1: Sanitize
|
|
219
|
+
results.sanitized = this.sanitize(text);
|
|
220
|
+
|
|
221
|
+
// Step 2: Rate limiting
|
|
222
|
+
const rateLimit = this.checkRateLimit(
|
|
223
|
+
identifier,
|
|
224
|
+
options.maxRequests,
|
|
225
|
+
options.windowMs
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
if (!rateLimit.allowed) {
|
|
229
|
+
results.valid = false;
|
|
230
|
+
results.errors.push({
|
|
231
|
+
type: 'rate_limit',
|
|
232
|
+
message: `Rate limit exceeded. Retry after ${rateLimit.retryAfter}s`,
|
|
233
|
+
});
|
|
234
|
+
return results;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
results.metadata.rateLimit = rateLimit;
|
|
238
|
+
|
|
239
|
+
// Step 3: Injection detection
|
|
240
|
+
const injection = this.detectInjection(results.sanitized);
|
|
241
|
+
if (injection.detected) {
|
|
242
|
+
if (injection.risk === 'high') {
|
|
243
|
+
results.valid = false;
|
|
244
|
+
results.errors.push({
|
|
245
|
+
type: 'injection',
|
|
246
|
+
message: 'Potential prompt injection detected',
|
|
247
|
+
details: injection.matches,
|
|
248
|
+
});
|
|
249
|
+
} else {
|
|
250
|
+
results.warnings.push({
|
|
251
|
+
type: 'injection',
|
|
252
|
+
message: 'Suspicious patterns detected',
|
|
253
|
+
details: injection.matches,
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Step 4: PII detection
|
|
259
|
+
const pii = this.detectPII(results.sanitized);
|
|
260
|
+
if (pii.detected) {
|
|
261
|
+
results.warnings.push({
|
|
262
|
+
type: 'pii',
|
|
263
|
+
message: `PII detected: ${pii.types.join(', ')}`,
|
|
264
|
+
details: pii.types,
|
|
265
|
+
});
|
|
266
|
+
results.metadata.piiTypes = pii.types;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Step 5: Content filtering
|
|
270
|
+
const content = this.filterContent(results.sanitized);
|
|
271
|
+
if (content.flagged) {
|
|
272
|
+
if (content.action === 'block') {
|
|
273
|
+
results.valid = false;
|
|
274
|
+
results.errors.push({
|
|
275
|
+
type: 'content',
|
|
276
|
+
message: `Content flagged: ${content.categories.join(', ')}`,
|
|
277
|
+
details: content.categories,
|
|
278
|
+
});
|
|
279
|
+
} else {
|
|
280
|
+
results.warnings.push({
|
|
281
|
+
type: 'content',
|
|
282
|
+
message: `Content warning: ${content.categories.join(', ')}`,
|
|
283
|
+
details: content.categories,
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return results;
|
|
289
|
+
|
|
290
|
+
} catch (error) {
|
|
291
|
+
results.valid = false;
|
|
292
|
+
results.errors.push({
|
|
293
|
+
type: 'validation_error',
|
|
294
|
+
message: error.message,
|
|
295
|
+
});
|
|
296
|
+
return results;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Clean up old rate limit entries
|
|
302
|
+
*/
|
|
303
|
+
cleanupRateLimiter(maxAgeMs = 3600000) {
|
|
304
|
+
const now = Date.now();
|
|
305
|
+
let cleaned = 0;
|
|
306
|
+
|
|
307
|
+
for (const [identifier, requests] of this.rateLimiter) {
|
|
308
|
+
const validRequests = requests.filter(time => now - time < maxAgeMs);
|
|
309
|
+
if (validRequests.length === 0) {
|
|
310
|
+
this.rateLimiter.delete(identifier);
|
|
311
|
+
cleaned++;
|
|
312
|
+
} else {
|
|
313
|
+
this.rateLimiter.set(identifier, validRequests);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
return { cleaned, remaining: this.rateLimiter.size };
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Convenience functions
|
|
322
|
+
function sanitizeInput(text, options = {}) {
|
|
323
|
+
const validator = new InputValidator(options);
|
|
324
|
+
return validator.sanitize(text);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function validateInput(text, options = {}) {
|
|
328
|
+
const validator = new InputValidator(options);
|
|
329
|
+
return validator.validate(text, options);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function detectInjection(text) {
|
|
333
|
+
const validator = new InputValidator({ enableInjectionDetection: true });
|
|
334
|
+
return validator.detectInjection(text);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function detectPII(text) {
|
|
338
|
+
const validator = new InputValidator({ enablePIIDetection: true });
|
|
339
|
+
return validator.detectPII(text);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
module.exports = {
|
|
343
|
+
InputValidator,
|
|
344
|
+
sanitizeInput,
|
|
345
|
+
validateInput,
|
|
346
|
+
detectInjection,
|
|
347
|
+
detectPII,
|
|
348
|
+
PROMPT_INJECTION_PATTERNS,
|
|
349
|
+
PII_PATTERNS,
|
|
350
|
+
CONTENT_CATEGORIES,
|
|
351
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A3M Router - Dashboard UI + API Endpoints
|
|
3
|
+
*
|
|
4
|
+
* Single HTML dashboard served at GET / when the proxy server runs.
|
|
5
|
+
* Dark theme, hacker/terminal aesthetic, auto-refreshes every 5s.
|
|
6
|
+
* API endpoints: /api/stats, /api/providers, /api/requests, /api/clear
|
|
7
|
+
*
|
|
8
|
+
* No React, no build step — vanilla HTML/CSS/JS served inline.
|
|
9
|
+
*/
|
|
10
|
+
import * as http from "http";
|
|
11
|
+
export interface DashboardRequest {
|
|
12
|
+
timestamp: number;
|
|
13
|
+
query: string;
|
|
14
|
+
provider: string;
|
|
15
|
+
model: string;
|
|
16
|
+
latency: number;
|
|
17
|
+
cost: number;
|
|
18
|
+
status: "success" | "error";
|
|
19
|
+
error?: string;
|
|
20
|
+
tokens?: {
|
|
21
|
+
input: number;
|
|
22
|
+
output: number;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export interface DashboardProvider {
|
|
26
|
+
id: string;
|
|
27
|
+
name: string;
|
|
28
|
+
status: "online" | "offline";
|
|
29
|
+
requestsToday: number;
|
|
30
|
+
costToday: number;
|
|
31
|
+
avgLatency: number;
|
|
32
|
+
lastError: string | null;
|
|
33
|
+
lastErrorTime: number | null;
|
|
34
|
+
}
|
|
35
|
+
export interface DashboardStats {
|
|
36
|
+
totalRequestsToday: number;
|
|
37
|
+
totalCostToday: number;
|
|
38
|
+
avgLatency: number;
|
|
39
|
+
activeProviders: number;
|
|
40
|
+
totalProviders: number;
|
|
41
|
+
providers: DashboardProvider[];
|
|
42
|
+
recentRequests: DashboardRequest[];
|
|
43
|
+
costByProvider: Record<string, number>;
|
|
44
|
+
uptime: number;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Record a request for the dashboard.
|
|
48
|
+
*/
|
|
49
|
+
export declare function recordRequest(req: Omit<DashboardRequest, "timestamp">): void;
|
|
50
|
+
/**
|
|
51
|
+
* Register a provider as available (called on startup / health check).
|
|
52
|
+
*/
|
|
53
|
+
export declare function registerProvider(id: string, name: string): void;
|
|
54
|
+
/**
|
|
55
|
+
* Handle dashboard API requests. Returns true if the request was handled.
|
|
56
|
+
*/
|
|
57
|
+
export declare function handleDashboardRequest(req: http.IncomingMessage, res: http.ServerResponse): boolean;
|
|
58
|
+
export declare function getDashboardHTML(): string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dashboard.d.ts","sourceRoot":"","sources":["../../src/server/dashboard.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAM7B,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;CAC5C;AAED,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,QAAQ,GAAG,SAAS,CAAC;IAC7B,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAED,MAAM,WAAW,cAAc;IAC7B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,iBAAiB,EAAE,CAAC;IAC/B,cAAc,EAAE,gBAAgB,EAAE,CAAC;IACnC,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,MAAM,EAAE,MAAM,CAAC;CAChB;AAWD;;GAEG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,IAAI,CAAC,gBAAgB,EAAE,WAAW,CAAC,GAAG,IAAI,CAoC5E;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAa/D;AA4CD;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,GAAG,EAAE,IAAI,CAAC,eAAe,EACzB,GAAG,EAAE,IAAI,CAAC,cAAc,GACvB,OAAO,CA0DT;AAMD,wBAAgB,gBAAgB,IAAI,MAAM,CA0XzC"}
|