@snap-agent/core 0.1.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/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 ViloTech
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
package/README.md ADDED
@@ -0,0 +1,625 @@
1
+ # SnapAgent
2
+
3
+ **The AI Agent SDK that runs everywhere.** A TypeScript-first SDK for building stateful AI agents with multi-provider support (OpenAI, Anthropic, Google). Extensible via plugins. Edge-runtime compatible.
4
+
5
+ ## Why SnapAgent?
6
+
7
+ | | SnapAgent | OpenAI Agents SDK | LangChain |
8
+ |--|-----------|-------------------|-----------|
9
+ | **Edge Compatible** | ✅ | ❌ | ❌ |
10
+ | **Bundle Size** | ~63 KB | ~150 KB | ~2 MB+ |
11
+ | **Multi-Provider** | OpenAI, Anthropic, Google | OpenAI only | ✅ |
12
+ | **Plugin Architecture** | RAG, Tools, Middleware, Analytics | Tools only | Chains |
13
+ | **Persistent Storage** | Upstash, MongoDB, Memory | In-memory only | Via integrations |
14
+ | **Zero-Config RAG** | Built-in | Manual | Manual |
15
+
16
+ ## Features
17
+
18
+ - **Multi-Provider** — Switch between OpenAI, Anthropic, and Google seamlessly
19
+ - **Edge Runtime** — Deploy to Cloudflare Workers, Vercel Edge, Deno Deploy
20
+ - **Plugin Architecture** — Extend with RAG, tools, middleware, and analytics plugins
21
+ - **Persistent Storage** — Upstash Redis (edge), MongoDB (server), or bring your own
22
+ - **Zero-Config RAG** — Add semantic search with one line of config
23
+ - **Stateful Threads** — Automatic conversation history management
24
+ - **TypeScript First** — Full type safety and excellent IDE support
25
+ - **Streaming** — Real-time response streaming built-in
26
+
27
+ ## Installation
28
+
29
+ ```bash
30
+ npm install @snap-agent/core ai @ai-sdk/openai
31
+
32
+ # Optional: Additional providers
33
+ npm install @ai-sdk/anthropic @ai-sdk/google
34
+
35
+ # Optional: Persistent storage
36
+ npm install mongodb # For server environments
37
+ # Upstash works out of the box (REST API, no package needed)
38
+ ```
39
+
40
+ ## Quick Start
41
+
42
+ ```typescript
43
+ import { createClient, MongoDBStorage } from '@snap-agent/core';
44
+
45
+ // Initialize the SDK
46
+ const client = createClient({
47
+ storage: new MongoDBStorage('mongodb://localhost:27017/agents'),
48
+ providers: {
49
+ openai: { apiKey: process.env.OPENAI_API_KEY! },
50
+ anthropic: { apiKey: process.env.ANTHROPIC_API_KEY! },
51
+ google: { apiKey: process.env.GOOGLE_API_KEY! },
52
+ },
53
+ });
54
+
55
+ // Create an agent
56
+ const agent = await client.createAgent({
57
+ name: 'Customer Support Bot',
58
+ instructions: 'You are a helpful customer support agent.',
59
+ model: 'gpt-4o',
60
+ userId: 'user-123',
61
+ provider: 'openai', // or 'anthropic', 'google'
62
+ });
63
+
64
+ // Create a conversation thread
65
+ const thread = await client.createThread({
66
+ agentId: agent.id,
67
+ userId: 'user-123',
68
+ name: 'Support Conversation',
69
+ });
70
+
71
+ // Chat!
72
+ const response = await client.chat({
73
+ threadId: thread.id,
74
+ message: 'Hello! I need help with my account.',
75
+ });
76
+
77
+ console.log(response.reply);
78
+ ```
79
+
80
+ ## Core Concepts
81
+
82
+ ### Agents
83
+
84
+ Agents are AI assistants with specific instructions, using a specific LLM provider and model.
85
+ Snap Agents are extendable via plugins and support middlewares to intercept requests or enriching responses
86
+
87
+ ```typescript
88
+ // Create an agent
89
+ const agent = await client.createAgent({
90
+ name: 'Code Reviewer',
91
+ instructions: 'You are an expert code reviewer. Provide constructive feedback.',
92
+ provider: 'anthropic',
93
+ model: 'claude-3-5-sonnet-20241022',
94
+ userId: 'user-123',
95
+ });
96
+
97
+ // Update agent
98
+ await agent.update({
99
+ instructions: 'You are a senior code reviewer with 10 years of experience.',
100
+ });
101
+
102
+ // List all agents for a user
103
+ const agents = await client.listAgents('user-123');
104
+
105
+ // Delete agent
106
+ await client.deleteAgent(agent.id);
107
+ ```
108
+
109
+ ### Threads
110
+
111
+ Threads represent conversation sessions with persistent message history.
112
+
113
+ ```typescript
114
+ // Create a thread
115
+ const thread = await client.createThread({
116
+ agentId: agent.id,
117
+ userId: 'user-123',
118
+ name: 'Code Review Session',
119
+ });
120
+
121
+ // Get thread
122
+ const loadedThread = await client.getThread(thread.id);
123
+
124
+ // List threads for an agent
125
+ const threads = await client.listThreads({ agentId: agent.id });
126
+
127
+ // Delete thread
128
+ await client.deleteThread(thread.id);
129
+ ```
130
+
131
+ ### Messages & Chat
132
+
133
+ Send messages and get AI responses with automatic history management.
134
+
135
+ ```typescript
136
+ // Simple chat
137
+ const response = await client.chat({
138
+ threadId: thread.id,
139
+ message: 'Review this code: const x = 1;',
140
+ });
141
+
142
+ // Streaming chat
143
+ await client.chatStream(
144
+ {
145
+ threadId: thread.id,
146
+ message: 'Tell me a story',
147
+ },
148
+ {
149
+ onChunk: (chunk) => process.stdout.write(chunk),
150
+ onComplete: (fullResponse) => console.log('\nDone'),
151
+ onError: (error) => console.error('Error:', error),
152
+ }
153
+ );
154
+ ```
155
+
156
+ ## Multi-Provider Support
157
+
158
+ Switch between OpenAI, Anthropic, and Google models easily:
159
+
160
+ ```typescript
161
+ import { Models } from '@snap-agent/core';
162
+
163
+ // OpenAI
164
+ const gptAgent = await client.createAgent({
165
+ name: 'GPT Agent',
166
+ provider: 'openai',
167
+ model: Models.OpenAI.GPT4O,
168
+ instructions: 'You are helpful.',
169
+ userId: 'user-123',
170
+ });
171
+
172
+ // Anthropic (Claude)
173
+ const claudeAgent = await client.createAgent({
174
+ name: 'Claude Agent',
175
+ provider: 'anthropic',
176
+ model: Models.Anthropic.CLAUDE_35_SONNET,
177
+ instructions: 'You are helpful.',
178
+ userId: 'user-123',
179
+ });
180
+
181
+ // Google (Gemini)
182
+ const geminiAgent = await client.createAgent({
183
+ name: 'Gemini Agent',
184
+ provider: 'google',
185
+ model: Models.Google.GEMINI_2_FLASH,
186
+ instructions: 'You are helpful.',
187
+ userId: 'user-123',
188
+ });
189
+ ```
190
+
191
+ ## Plugin Architecture
192
+
193
+ SnapAgent is built around a powerful plugin system. Extend your agents with any combination of plugins:
194
+
195
+ ### Plugin Types
196
+
197
+ | Type | Purpose | Example Use Cases |
198
+ |------|---------|-------------------|
199
+ | **RAG Plugins** | Semantic search & document retrieval | Knowledge bases, product catalogs, support docs |
200
+ | **Tool Plugins** | Give agents executable capabilities | API calls, calculations, data lookups |
201
+ | **Middleware Plugins** | Intercept and transform requests/responses | Rate limiting, content moderation, logging |
202
+ | **Analytics Plugins** | Track usage and performance | Monitoring, billing, optimization |
203
+
204
+ ### Combining Multiple Plugins
205
+
206
+ ```typescript
207
+ import { createClient, MemoryStorage } from '@snap-agent/core';
208
+ import { EcommerceRAGPlugin } from '@snap-agent/rag-ecommerce';
209
+ import { RateLimiter } from '@snap-agent/middleware-ratelimit';
210
+ import { SlackNotifications } from '@snap-agent/middleware-slack';
211
+ import { ConsoleAnalytics } from '@snap-agent/analytics-console';
212
+
213
+ const agent = await client.createAgent({
214
+ name: 'Production Agent',
215
+ instructions: 'You are a helpful shopping assistant.',
216
+ provider: 'openai',
217
+ model: 'gpt-4o',
218
+ userId: 'user-123',
219
+ plugins: [
220
+ // RAG: Search product catalog
221
+ new EcommerceRAGPlugin({
222
+ mongoUri: process.env.MONGODB_URI!,
223
+ openaiApiKey: process.env.OPENAI_API_KEY!,
224
+ tenantId: 'my-store',
225
+ }),
226
+
227
+ // Middleware: Rate limiting
228
+ new RateLimiter({
229
+ maxRequests: 100,
230
+ windowMs: 60000,
231
+ }),
232
+
233
+ // Middleware: Slack alerts on errors
234
+ new SlackNotifications({
235
+ webhookUrl: process.env.SLACK_WEBHOOK!,
236
+ onError: true,
237
+ }),
238
+
239
+ // Analytics: Log everything
240
+ new ConsoleAnalytics(),
241
+ ],
242
+ });
243
+ ```
244
+
245
+ ### Building Custom Plugins
246
+
247
+ ```typescript
248
+ import { MiddlewarePlugin, AnalyticsPlugin } from '@snap-agent/core';
249
+
250
+ // Custom middleware
251
+ class LoggingMiddleware implements MiddlewarePlugin {
252
+ type = 'middleware' as const;
253
+ name = 'logging';
254
+
255
+ async beforeRequest(context: any) {
256
+ console.log('Request:', context.message);
257
+ return context;
258
+ }
259
+
260
+ async afterResponse(context: any, response: any) {
261
+ console.log('Response:', response.reply.substring(0, 100));
262
+ return response;
263
+ }
264
+ }
265
+
266
+ // Custom analytics
267
+ class CustomAnalytics implements AnalyticsPlugin {
268
+ type = 'analytics' as const;
269
+ name = 'custom-analytics';
270
+
271
+ async trackRequest(data: RequestTrackingData) {
272
+ await myAnalyticsService.track('agent_request', data);
273
+ }
274
+
275
+ async trackResponse(data: ResponseTrackingData) {
276
+ await myAnalyticsService.track('agent_response', data);
277
+ }
278
+ }
279
+ ```
280
+
281
+ ### Available Plugins
282
+
283
+ | Package | Description |
284
+ |---------|-------------|
285
+ | `@snap-agent/rag-ecommerce` | E-commerce product search with attribute extraction |
286
+ | `@snap-agent/rag-support` | Support ticket and documentation search |
287
+ | `@snap-agent/rag-docs` | General documentation search |
288
+ | `@snap-agent/middleware-ratelimit` | Request rate limiting |
289
+ | `@snap-agent/middleware-moderation` | Content moderation |
290
+ | `@snap-agent/middleware-slack` | Slack notifications |
291
+ | `@snap-agent/middleware-discord` | Discord notifications |
292
+ | `@snap-agent/middleware-webhooks` | Custom webhook notifications |
293
+ | `@snap-agent/analytics-console` | Console logging analytics |
294
+
295
+ ## Zero-Config RAG
296
+
297
+ Add semantic search and retrieval-augmented generation to your agents with zero configuration:
298
+
299
+ ```typescript
300
+ // Just add rag: { enabled: true }
301
+ const agent = await client.createAgent({
302
+ name: 'Knowledge Assistant',
303
+ instructions: 'You are a helpful assistant with access to a knowledge base.',
304
+ model: 'gpt-4o',
305
+ userId: 'user-123',
306
+ rag: {
307
+ enabled: true // That's it! Uses DefaultRAGPlugin automatically
308
+ }
309
+ });
310
+
311
+ // Ingest documents
312
+ await agent.ingestDocuments([
313
+ {
314
+ id: 'doc-1',
315
+ content: 'Your document content here...',
316
+ metadata: { title: 'Doc Title', category: 'general' }
317
+ }
318
+ ]);
319
+
320
+ // Chat with RAG
321
+ const response = await client.chat({
322
+ threadId: thread.id,
323
+ message: 'What does the documentation say about...?',
324
+ useRAG: true // Enable RAG for this query
325
+ });
326
+ ```
327
+
328
+ ### Advanced RAG Configuration
329
+
330
+ ```typescript
331
+ const agent = await client.createAgent({
332
+ name: 'Advanced Agent',
333
+ model: 'gpt-4o',
334
+ userId: 'user-123',
335
+ rag: {
336
+ enabled: true,
337
+ embeddingModel: 'text-embedding-3-large', // Custom model
338
+ limit: 10, // Return more results
339
+ // Optional: Use different API key for embeddings
340
+ embeddingProviderApiKey: process.env.CUSTOM_API_KEY,
341
+ }
342
+ });
343
+ ```
344
+
345
+ ### Using Specialized RAG Plugins
346
+
347
+ For production use cases with advanced features (attribute extraction, rescoring, reranking, caching), use specialized plugins:
348
+
349
+ ```typescript
350
+ import { EcommerceRAGPlugin } from '@snap-agent/rag-ecommerce';
351
+
352
+ const agent = await client.createAgent({
353
+ name: 'Shopping Assistant',
354
+ model: 'gpt-4o',
355
+ userId: 'user-123',
356
+ plugins: [
357
+ new EcommerceRAGPlugin({
358
+ mongoUri: process.env.MONGODB_URI!,
359
+ openaiApiKey: process.env.OPENAI_API_KEY!,
360
+ voyageApiKey: process.env.VOYAGE_API_KEY!,
361
+ tenantId: 'my-store',
362
+ cache: {
363
+ embeddings: { enabled: true },
364
+ attributes: { enabled: true }
365
+ }
366
+ })
367
+ ]
368
+ });
369
+ ```
370
+
371
+ ## Storage Adapters
372
+
373
+ ### Upstash Redis (Edge + Server)
374
+
375
+ **Recommended for edge deployments.** Uses REST API, works everywhere.
376
+
377
+ ```typescript
378
+ import { UpstashStorage } from '@snap-agent/core';
379
+
380
+ const storage = new UpstashStorage({
381
+ url: process.env.UPSTASH_REDIS_REST_URL!,
382
+ token: process.env.UPSTASH_REDIS_REST_TOKEN!,
383
+ prefix: 'myapp', // Optional: key prefix for multi-tenancy
384
+ });
385
+
386
+ const client = createClient({
387
+ storage,
388
+ providers: { openai: { apiKey: process.env.OPENAI_API_KEY! } },
389
+ });
390
+ ```
391
+
392
+ ### MongoDB Storage (Server)
393
+
394
+ ```typescript
395
+ import { MongoDBStorage } from '@snap-agent/core';
396
+
397
+ const storage = new MongoDBStorage({
398
+ uri: 'mongodb://localhost:27017',
399
+ dbName: 'myapp',
400
+ agentsCollection: 'agents',
401
+ threadsCollection: 'threads',
402
+ });
403
+
404
+ // Or use simple string URI
405
+ const storage = new MongoDBStorage('mongodb://localhost:27017/myapp');
406
+ ```
407
+
408
+ ### Memory Storage (Development/Testing)
409
+
410
+ ```typescript
411
+ import { MemoryStorage } from '@snap-agent/core';
412
+
413
+ const storage = new MemoryStorage();
414
+
415
+ // Useful methods
416
+ storage.clear(); // Clear all data
417
+ console.log(storage.getStats()); // Get stats
418
+ ```
419
+
420
+ ### Custom Storage Adapter
421
+
422
+ Implement your own storage adapter for any database:
423
+
424
+ ```typescript
425
+ import { StorageAdapter } from '@snap-agent/core';
426
+
427
+ class PostgresStorage implements StorageAdapter {
428
+ async createAgent(config: AgentConfig): Promise<string> {
429
+ // Your implementation
430
+ }
431
+
432
+ async getAgent(agentId: string): Promise<AgentData | null> {
433
+ // Your implementation
434
+ }
435
+
436
+ // ... implement all required methods
437
+ }
438
+ ```
439
+
440
+ ## Advanced Usage
441
+
442
+ ### Working with Agent and Thread Objects
443
+
444
+ ```typescript
445
+ // Load and use agent directly
446
+ const agent = await client.getAgent('agent-id');
447
+ console.log(agent.name);
448
+ console.log(agent.provider);
449
+ console.log(agent.model);
450
+
451
+ // Generate response directly
452
+ const messages = [
453
+ { role: 'user', content: 'Hello!' }
454
+ ];
455
+ const reply = await agent.generateResponse(messages);
456
+
457
+ // Load and use thread directly
458
+ const thread = await client.getThread('thread-id');
459
+ await thread.addMessage('user', 'Hello!');
460
+ const messages = await thread.getMessages(10);
461
+ ```
462
+
463
+ ### Auto-Generate Thread Names
464
+
465
+ ```typescript
466
+ const thread = await client.createThread({
467
+ agentId: agent.id,
468
+ userId: 'user-123',
469
+ });
470
+
471
+ // Generate a descriptive name based on first message
472
+ const name = await client.generateThreadName('Help me debug this error');
473
+ await thread.updateName(name);
474
+ ```
475
+
476
+ ### Message Attachments
477
+
478
+ ```typescript
479
+ await client.chat({
480
+ threadId: thread.id,
481
+ message: 'Can you review this document?',
482
+ attachments: [
483
+ {
484
+ fileId: 'file-123',
485
+ filename: 'document.pdf',
486
+ contentType: 'application/pdf',
487
+ size: 1024000,
488
+ },
489
+ ],
490
+ });
491
+ ```
492
+
493
+ ### Organization Support (Multi-Tenancy)
494
+
495
+ ```typescript
496
+ const agent = await client.createAgent({
497
+ name: 'Org Agent',
498
+ userId: 'user-123',
499
+ organizationId: 'org-456',
500
+ // ... other config
501
+ });
502
+
503
+ // List agents for an organization
504
+ const orgAgents = await client.listAgents('user-123', 'org-456');
505
+ ```
506
+
507
+ ## Error Handling
508
+
509
+ ```typescript
510
+ import {
511
+ AgentNotFoundError,
512
+ ThreadNotFoundError,
513
+ ProviderNotFoundError,
514
+ InvalidConfigError,
515
+ } from '@snap-agent/core';
516
+
517
+ try {
518
+ const agent = await client.getAgent('invalid-id');
519
+ } catch (error) {
520
+ if (error instanceof AgentNotFoundError) {
521
+ console.error('Agent not found:', error.message);
522
+ } else if (error instanceof ProviderNotFoundError) {
523
+ console.error('Provider not configured:', error.message);
524
+ }
525
+ }
526
+ ```
527
+
528
+ ## Environment Variables
529
+
530
+ ```bash
531
+ # .env
532
+
533
+ # LLM Providers
534
+ OPENAI_API_KEY=sk-...
535
+ ANTHROPIC_API_KEY=sk-ant-...
536
+ GOOGLE_API_KEY=AI...
537
+
538
+ # Storage (choose one)
539
+ MONGODB_URI=mongodb://localhost:27017/agents # Server environments
540
+ UPSTASH_REDIS_REST_URL=https://your-redis.upstash.io # Edge + Server
541
+ UPSTASH_REDIS_REST_TOKEN=your-token
542
+ ```
543
+
544
+ ## Examples
545
+
546
+ **Getting Started:**
547
+ - [Basic Usage](./examples/basic.ts) - Simple agent creation and chat
548
+ - [Multi-Provider](./examples/multi-provider.ts) - Using different AI providers
549
+ - [Streaming](./examples/streaming.ts) - Real-time response streaming
550
+
551
+ **RAG & Ingestion:**
552
+ - [Zero-Config RAG](./examples/zero-config-rag.ts) - RAG with zero configuration
553
+ - [Product Ingestion](./examples/product-ingestion.ts) - Ingest product catalogs
554
+ - [URL Ingestion](./examples/url-ingestion-example.ts) - Ingest from URLs
555
+
556
+ **Deployment:**
557
+ - [Express Server](./examples/express-server.ts) - Building an API server
558
+ - [Cloudflare Workers](./examples/edge-cloudflare-worker.ts) - Edge deployment
559
+ - [Vercel Edge](./examples/edge-vercel.ts) - Vercel Edge Functions
560
+
561
+ ## Edge Runtime
562
+
563
+ Deploy AI agents to edge runtimes for low latency and global distribution:
564
+
565
+ ```typescript
566
+ // Cloudflare Workers
567
+ import { createClient, UpstashStorage } from '@snap-agent/core';
568
+
569
+ export default {
570
+ async fetch(request: Request, env: Env): Promise<Response> {
571
+ const client = createClient({
572
+ storage: new UpstashStorage({
573
+ url: env.UPSTASH_REDIS_REST_URL,
574
+ token: env.UPSTASH_REDIS_REST_TOKEN,
575
+ }),
576
+ providers: { openai: { apiKey: env.OPENAI_API_KEY } },
577
+ });
578
+
579
+ const agent = await client.createAgent({
580
+ name: 'Edge Agent',
581
+ instructions: 'You are helpful.',
582
+ provider: 'openai',
583
+ model: 'gpt-4o-mini',
584
+ userId: 'edge-user',
585
+ });
586
+
587
+ const body = await request.json() as { message: string };
588
+ const { reply } = await agent.chat(body.message);
589
+
590
+ return Response.json({ reply });
591
+ },
592
+ };
593
+ ```
594
+
595
+ **Supported runtimes:** Cloudflare Workers, Vercel Edge, Deno Deploy, AWS Lambda@Edge, any WinterCG-compliant runtime.
596
+
597
+ See [EDGE_RUNTIME.md](./EDGE_RUNTIME.md) for complete documentation.
598
+
599
+ ## Comparison
600
+
601
+ | Feature | SnapAgent | OpenAI Agents SDK | LangChain | Vercel AI SDK |
602
+ |---------|-----------|-------------------|-----------|---------------|
603
+ | Edge Compatible | ✅ | ❌ | ❌ | ✅ |
604
+ | Multi-Provider | ✅ | OpenAI only | ✅ | ✅ |
605
+ | Plugin Architecture | RAG, Tools, Middleware | Tools only | Chains | No |
606
+ | Persistent Storage | Upstash, MongoDB | In-memory | Via integrations | No |
607
+ | Zero-Config RAG | ✅ | ❌ | ❌ | ❌ |
608
+ | Agent Management | ✅ | ✅ | Complex | No |
609
+ | Thread Management | ✅ | ✅ | ❌ | ❌ |
610
+ | TypeScript First | ✅ | ✅ | Partial | ✅ |
611
+ | Bundle Size | ~63 KB | ~150 KB | ~2 MB+ | ~15 KB |
612
+ | Self-Hosted | ✅ | ❌ | ✅ | ✅ |
613
+
614
+ ## Contributing
615
+
616
+ Contributions are welcome! Please open an issue or submit a pull request on [GitHub](https://github.com/vilo-hq/snap-agent).
617
+
618
+ ## License
619
+
620
+ MIT © ViloTech
621
+
622
+ ## Support
623
+
624
+ - [GitHub Issues](https://github.com/vilo-hq/snap-agent/issues)
625
+