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.
Files changed (130) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +50 -0
  2. package/.github/ISSUE_TEMPLATE/config.yml +11 -0
  3. package/.github/ISSUE_TEMPLATE/feature_request.md +37 -0
  4. package/.github/PULL_REQUEST_TEMPLATE.md +44 -0
  5. package/.github/workflows/npm-stats-validation.yml +152 -0
  6. package/.github/workflows/pages.yml +37 -0
  7. package/CHANGELOG.md +122 -0
  8. package/CODE_OF_CONDUCT.md +128 -0
  9. package/CONTRIBUTING.md +110 -0
  10. package/LAUNCH-PAIN-DRIVEN.md +339 -0
  11. package/LAUNCH.md +575 -0
  12. package/POPULARITY_BOOSTERS.md +285 -0
  13. package/README.md +231 -300
  14. package/SECURITY.md +69 -0
  15. package/articles/CONTENT_STRUCTURE.md +292 -0
  16. package/articles/DEVTO_COST_GUIDE.md +473 -0
  17. package/articles/DEVTO_FINAL.md +416 -0
  18. package/articles/DEVTO_MULTI_PROVIDER.md +542 -0
  19. package/articles/HN_10X_BETTER.md +430 -0
  20. package/articles/HN_CHINESE_STYLE.md +308 -0
  21. package/articles/HN_FINAL.md +199 -0
  22. package/articles/HN_POSTED_VERSION.md +56 -0
  23. package/articles/HN_RESEARCH.md +364 -0
  24. package/articles/PAIN-DRIVEN-devto-v2.md +308 -0
  25. package/articles/PAIN-DRIVEN-devto-v3.md +268 -0
  26. package/articles/PAIN-DRIVEN-devto.md +242 -0
  27. package/articles/PAIN-DRIVEN-hackernews-v2.md +138 -0
  28. package/articles/PAIN-DRIVEN-hackernews-v3.md +151 -0
  29. package/articles/PAIN-DRIVEN-hackernews.md +131 -0
  30. package/articles/PAIN-DRIVEN-reddit-v2.md +301 -0
  31. package/articles/PAIN-DRIVEN-reddit-v3.md +236 -0
  32. package/articles/PAIN-DRIVEN-reddit.md +218 -0
  33. package/articles/PAIN-DRIVEN-twitter-v2.md +110 -0
  34. package/articles/PAIN-DRIVEN-twitter-v3.md +121 -0
  35. package/articles/PAIN-DRIVEN-twitter.md +120 -0
  36. package/articles/PORTKEY_VS_A3M.md +147 -0
  37. package/articles/REDDIT_FINAL.md +232 -0
  38. package/articles/TWITTER_FINAL.md +167 -0
  39. package/articles/WHY_10X_BETTER.md +261 -0
  40. package/articles/WHY_CHINESE_STYLE_BETTER.md +323 -0
  41. package/articles/ai-discoverability-llm-routing.md +210 -0
  42. package/articles/devto-llm-routing.md +109 -0
  43. package/articles/hackernews-show-hn.md +65 -0
  44. package/articles/hashnode-llm-cost-optimization.md +125 -0
  45. package/articles/medium-building-llm-router.md +205 -0
  46. package/articles/reddit-ml.md +86 -0
  47. package/articles/twitter-thread-cost-savings.md +98 -0
  48. package/articles/youtube-tutorial-script.md +262 -0
  49. package/assets/banner.svg +109 -0
  50. package/assets/logo.svg +68 -0
  51. package/assets/social-preview.svg +64 -0
  52. package/demo/demo-script.md +53 -0
  53. package/dist/analytics/costAnalytics.d.ts +77 -0
  54. package/dist/analytics/costAnalytics.d.ts.map +1 -0
  55. package/dist/analytics/costAnalytics.js +219 -0
  56. package/dist/analytics/costAnalytics.js.map +1 -0
  57. package/dist/cache/semanticCache.d.ts +62 -0
  58. package/dist/cache/semanticCache.d.ts.map +1 -0
  59. package/dist/cache/semanticCache.js +176 -0
  60. package/dist/cache/semanticCache.js.map +1 -0
  61. package/dist/cli.js +35 -0
  62. package/dist/geo/generativeEngineOptimization.js +321 -0
  63. package/dist/geo/geoRouter.js +387 -0
  64. package/dist/index.d.ts +4 -723
  65. package/dist/index.js +11 -344
  66. package/dist/index.js.map +1 -1
  67. package/dist/integrations/langchainAdapter.d.ts +146 -0
  68. package/dist/integrations/langchainAdapter.d.ts.map +1 -0
  69. package/dist/integrations/langchainAdapter.js +731 -0
  70. package/dist/integrations/langchainAdapter.js.map +1 -0
  71. package/dist/integrations/oauth.d.ts +69 -0
  72. package/dist/integrations/oauth.d.ts.map +1 -0
  73. package/dist/integrations/oauth.js +225 -21
  74. package/dist/integrations/oauth.js.map +1 -0
  75. package/dist/memory/autoFetch.d.ts +39 -0
  76. package/dist/memory/autoFetch.d.ts.map +1 -0
  77. package/dist/memory/autoFetch.js +80 -88
  78. package/dist/memory/autoFetch.js.map +1 -0
  79. package/dist/memory/memoryTree.d.ts +76 -0
  80. package/dist/memory/memoryTree.d.ts.map +1 -0
  81. package/dist/memory/memoryTree.js +185 -130
  82. package/dist/memory/memoryTree.js.map +1 -0
  83. package/dist/memory/obsidianVault.d.ts +71 -0
  84. package/dist/memory/obsidianVault.d.ts.map +1 -0
  85. package/dist/memory/obsidianVault.js +207 -22
  86. package/dist/memory/obsidianVault.js.map +1 -0
  87. package/dist/providers/providerConfig.d.ts +49 -0
  88. package/dist/providers/providerConfig.d.ts.map +1 -0
  89. package/dist/providers/providerConfig.js +806 -401
  90. package/dist/providers/providerConfig.js.map +1 -0
  91. package/dist/security/guardrails.d.ts +76 -0
  92. package/dist/security/guardrails.d.ts.map +1 -0
  93. package/dist/security/guardrails.js +479 -0
  94. package/dist/security/guardrails.js.map +1 -0
  95. package/dist/security/inputValidation.js +351 -0
  96. package/dist/server/dashboard.d.ts +58 -0
  97. package/dist/server/dashboard.d.ts.map +1 -0
  98. package/dist/server/dashboard.js +553 -0
  99. package/dist/server/dashboard.js.map +1 -0
  100. package/dist/server/modelMapper.d.ts +43 -0
  101. package/dist/server/modelMapper.d.ts.map +1 -0
  102. package/dist/server/modelMapper.js +154 -0
  103. package/dist/server/modelMapper.js.map +1 -0
  104. package/dist/server/proxyServer.d.ts +41 -0
  105. package/dist/server/proxyServer.d.ts.map +1 -0
  106. package/dist/server/proxyServer.js +932 -0
  107. package/dist/server/proxyServer.js.map +1 -0
  108. package/dist/skills/__tests__/skill_manager.test.d.ts +2 -0
  109. package/dist/skills/__tests__/skill_manager.test.d.ts.map +1 -0
  110. package/dist/skills/__tests__/skill_manager.test.js +268 -0
  111. package/dist/skills/__tests__/skill_manager.test.js.map +1 -0
  112. package/docs/geo/GENERATIVE_ENGINE_OPTIMIZATION.md +232 -0
  113. package/docs-site/index.html +347 -0
  114. package/llms.txt +138 -0
  115. package/package.json +72 -7
  116. package/playground/README.md +51 -0
  117. package/playground/codesandbox.json +12 -0
  118. package/playground/index.js +39 -0
  119. package/scripts/update-npm-badges.js +158 -0
  120. package/src/analytics/costAnalytics.ts +304 -0
  121. package/src/cache/semanticCache.ts +221 -0
  122. package/src/index.ts +6 -0
  123. package/src/integrations/langchainAdapter.ts +955 -0
  124. package/src/providers/providerConfig.ts +923 -0
  125. package/src/security/guardrails.ts +585 -0
  126. package/src/server/dashboard.ts +610 -0
  127. package/src/server/modelMapper.ts +182 -0
  128. package/src/server/proxyServer.ts +1105 -0
  129. package/src/types/langchain.d.ts +83 -0
  130. 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"}