@backendkit-labs/agent-web 0.4.0 → 0.4.2

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 CHANGED
@@ -2,14 +2,49 @@
2
2
 
3
3
  HTTP/WebSocket server layer for [@backendkit-labs/agent-core](https://www.npmjs.com/package/@backendkit-labs/agent-core).
4
4
 
5
- Wraps `AgentEngine` in a Fastify 5 server and exposes it to browsers via WebSocket with streaming, multi-session management, and a real-time monitor bus.
5
+ Wraps `AgentEngine` in a Fastify 5 server with streaming WebSocket sessions, JWT authentication, Prometheus metrics, rate limiting, a real-time monitor bus, and a flexible trigger system (cron jobs, webhooks, BullMQ queues).
6
6
 
7
- ## Install
7
+ [![npm](https://img.shields.io/npm/v/@backendkit-labs/agent-web)](https://www.npmjs.com/package/@backendkit-labs/agent-web)
8
+
9
+ ## Table of contents
10
+
11
+ - [Installation](#installation)
12
+ - [Quick start](#quick-start)
13
+ - [AgentServer options](#agentserver-options)
14
+ - [HTTP and WebSocket routes](#http-and-websocket-routes)
15
+ - [WebSocket protocol](#websocket-protocol)
16
+ - [EngineFactory pattern](#enginefactory-pattern)
17
+ - [SessionManager](#sessionmanager)
18
+ - [JWT authentication](#jwt-authentication)
19
+ - [Prometheus metrics](#prometheus-metrics)
20
+ - [Rate limiting](#rate-limiting)
21
+ - [Session metadata store](#session-metadata-store)
22
+ - [MonitorBus](#monitorbus)
23
+ - [TriggerBus — cron, webhooks, queues](#triggerbus--cron-webhooks-queues)
24
+ - [HeadlessTransport](#headlesstransport)
25
+ - [Full production example](#full-production-example)
26
+
27
+ ---
28
+
29
+ ## Installation
8
30
 
9
31
  ```bash
10
32
  npm install @backendkit-labs/agent-web @backendkit-labs/agent-core
11
33
  ```
12
34
 
35
+ Optional integrations (install only what you need):
36
+
37
+ ```bash
38
+ npm install jsonwebtoken # JWT auth
39
+ npm install prom-client # Prometheus metrics
40
+ npm install @fastify/rate-limit # rate limiting
41
+ npm install node-cron # cron triggers
42
+ npm install bullmq # queue triggers
43
+ npm install ioredis # Redis session store
44
+ ```
45
+
46
+ ---
47
+
13
48
  ## Quick start
14
49
 
15
50
  ```typescript
@@ -18,45 +53,654 @@ import type { EngineFactory } from '@backendkit-labs/agent-web';
18
53
  import { AgentEngine, AgentRegistry, ToolRegistry, ProviderRegistry } from '@backendkit-labs/agent-core';
19
54
 
20
55
  const engineFactory: EngineFactory = (sessionId, transport) =>
21
- new AgentEngine({ transport, /* ...your config */ });
56
+ new AgentEngine({
57
+ transport,
58
+ sessionId,
59
+ // ...your engine config
60
+ });
22
61
 
23
62
  const server = new AgentServer({
24
- port: 3000,
63
+ port: 3000,
25
64
  engineFactory,
26
- cors: '*',
65
+ cors: '*',
27
66
  });
28
67
 
29
68
  await server.start();
30
- // WS /chat — chat sessions
31
- // WS /monitor — real-time observer (read-only)
69
+ // WS /chat — streaming chat sessions
70
+ // WS /monitor — real-time read-only observer
32
71
  // GET /health — { status, sessions, monitors }
33
72
  // GET /sessions — active session list
73
+ // DELETE /sessions/:id — close a session
74
+ ```
75
+
76
+ ---
77
+
78
+ ## AgentServer options
79
+
80
+ ```typescript
81
+ interface AgentServerOptions {
82
+ // Required
83
+ engineFactory: EngineFactory; // creates a new AgentEngine per session
84
+
85
+ // Network
86
+ port?: number; // default: 3000
87
+ host?: string; // default: '0.0.0.0'
88
+ cors?: string | string[]; // default: '*'
89
+ logger?: boolean | object; // Fastify logger, default: false
90
+
91
+ // Auth
92
+ auth?: AuthHook; // called on every request before handlers
93
+
94
+ // Rate limiting (requires @fastify/rate-limit)
95
+ rateLimiting?: {
96
+ max?: number; // max requests per IP per window, default: 100
97
+ timeWindow?: string; // e.g. '1 minute', '30 seconds'
98
+ };
99
+
100
+ // Prometheus metrics (requires prom-client)
101
+ metrics?: {
102
+ enabled: boolean; // must be true to activate
103
+ path?: string; // default: '/metrics'
104
+ prefix?: string; // metric name prefix
105
+ collectNodeMetrics?: boolean; // include Node.js process metrics
106
+ };
107
+
108
+ // Session management
109
+ maxIdleMs?: number; // idle timeout before session is closed
110
+ maxSessions?: number; // max concurrent sessions
111
+ store?: SessionMetaStore; // durable metadata store (default: in-memory)
112
+
113
+ // Triggers
114
+ triggers?: TriggerBus; // wires webhook routes and starts cron jobs
115
+ }
34
116
  ```
35
117
 
118
+ ---
119
+
120
+ ## HTTP and WebSocket routes
121
+
122
+ | Method | Path | Description |
123
+ |--------|------|-------------|
124
+ | `GET` | `/health` | Server health: `{ status, sessions, monitors, uptime }` |
125
+ | `GET` | `/sessions` | Array of active session metadata |
126
+ | `DELETE` | `/sessions/:id` | Force-close a session |
127
+ | `GET` | `/metrics` | Prometheus metrics (if enabled) |
128
+ | `WS` | `/chat` | Streaming chat — one session per connection |
129
+ | `WS` | `/monitor` | Read-only observer of all sessions |
130
+ | `POST` | `/webhooks/:name` | Webhook trigger (registered via TriggerBus) |
131
+
132
+ ---
133
+
36
134
  ## WebSocket protocol
37
135
 
38
- **Client → server** (`/chat`):
136
+ ### `/chat` — chat sessions
137
+
138
+ **Client → server:**
139
+
39
140
  ```json
40
- { "type": "input", "content": "Your message" }
141
+ { "type": "input", "content": "Your message here" }
41
142
  { "type": "abort" }
42
143
  ```
43
144
 
44
- **Server → client** — standard `AgentEvent` JSON frames: `token`, `tool_call`, `tool_result`, `agent_switch`, `block_start`, `done`, `error`, etc.
145
+ **Server → client** (streamed `AgentEvent` frames):
146
+
147
+ ```json
148
+ { "type": "run_start", "sessionId": "abc-123" }
149
+ { "type": "token", "content": "Hello" }
150
+ { "type": "tool_call", "name": "search", "args": { "query": "..." } }
151
+ { "type": "tool_result", "name": "search", "result": "..." }
152
+ { "type": "agent_switch", "from": "general", "to": "security-reviewer" }
153
+ { "type": "done", "final": "Full assistant response" }
154
+ { "type": "error", "message": "Something went wrong" }
155
+ ```
156
+
157
+ ### `/monitor` — real-time observer
158
+
159
+ Connect to observe all sessions without sending any messages:
160
+
161
+ ```json
162
+ { "type": "monitor_hello", "sessionIds": ["abc-123", "def-456"] }
163
+ { "type": "monitor_session_open", "sessionId": "ghi-789" }
164
+ { "type": "monitor_session_close", "sessionId": "ghi-789" }
165
+ { "sessionId": "abc-123", "type": "run_input", "content": "..." }
166
+ { "sessionId": "abc-123", "type": "token", "content": "..." }
167
+ { "sessionId": "abc-123", "type": "done", "final": "..." }
168
+ ```
169
+
170
+ ---
171
+
172
+ ## EngineFactory pattern
173
+
174
+ `EngineFactory` is a function `(sessionId: string, transport: Transport) => AgentEngine`. Each incoming WebSocket connection gets its own isolated engine instance.
175
+
176
+ ### Minimal factory
177
+
178
+ ```typescript
179
+ import type { EngineFactory } from '@backendkit-labs/agent-web';
180
+ import { AgentEngine } from '@backendkit-labs/agent-core';
181
+
182
+ const engineFactory: EngineFactory = (sessionId, transport) =>
183
+ new AgentEngine({
184
+ transport,
185
+ sessionId,
186
+ model: { provider: 'openai', id: 'gpt-4o' },
187
+ agents,
188
+ tools,
189
+ providers,
190
+ defaultProvider: 'openai',
191
+ defaultAgentId: 'general',
192
+ maxIterations: 50,
193
+ iterationMode: 'auto',
194
+ });
195
+ ```
196
+
197
+ ### Factory with per-session memory
198
+
199
+ ```typescript
200
+ import { createMemorySystem } from '@backendkit-labs/agent-core';
201
+ import * as path from 'path';
202
+ import * as os from 'os';
203
+
204
+ const engineFactory: EngineFactory = (sessionId, transport) => {
205
+ // Isolated memory per session
206
+ const sessDir = path.join(os.homedir(), '.bk-agent', 'sessions', sessionId);
207
+ const memory = createMemorySystem({
208
+ episodic: { provider: 'json', path: path.join(sessDir, 'episodes.json') },
209
+ semantic: { provider: 'json', path: path.join(sessDir, 'knowledge.json') },
210
+ procedural:{ provider: 'json', path: path.join(sessDir, 'patterns.json') },
211
+ });
212
+
213
+ return new AgentEngine({
214
+ transport,
215
+ sessionId,
216
+ memorySystem: memory,
217
+ ...baseConfig,
218
+ });
219
+ };
220
+ ```
221
+
222
+ ### Factory with userId from JWT
223
+
224
+ When JWT auth is enabled, the `sessionId` carries the authenticated user's context. You can also inject user-specific data:
225
+
226
+ ```typescript
227
+ const engineFactory: EngineFactory = (sessionId, transport) => {
228
+ // sessionId is set to the JWT 'sub' claim when JwtAuth is used
229
+ const userConfig = getUserConfig(sessionId); // look up per-user settings
230
+
231
+ return new AgentEngine({
232
+ transport,
233
+ sessionId,
234
+ projectContext: `User: ${userConfig.name}\nRole: ${userConfig.role}`,
235
+ ...baseConfig,
236
+ });
237
+ };
238
+ ```
239
+
240
+ ---
241
+
242
+ ## SessionManager
243
+
244
+ `SessionManager` creates, tracks, and cleans up sessions. It is created internally by `AgentServer`, but can also be used standalone.
245
+
246
+ ```typescript
247
+ import { SessionManager } from '@backendkit-labs/agent-web';
248
+
249
+ const sessions = new SessionManager({
250
+ engineFactory,
251
+ maxIdleMs: 30 * 60 * 1000, // close after 30 min of inactivity
252
+ maxSessions: 100,
253
+ monitorBus: myMonitorBus,
254
+ metrics: myMetricsCollector,
255
+ store: myMetaStore,
256
+ });
257
+
258
+ // Lifecycle
259
+ const session = await sessions.getOrCreate('session-id', myTransport);
260
+ await sessions.close('session-id');
261
+ const all = sessions.list(); // SessionMeta[]
262
+ ```
263
+
264
+ ---
265
+
266
+ ## JWT authentication
267
+
268
+ `JwtAuth` validates Bearer tokens in the `Authorization` header or `?token=` query parameter (useful for browser WebSocket connections that can't set custom headers).
269
+
270
+ ### Setup
271
+
272
+ ```typescript
273
+ import { AgentServer, JwtAuth } from '@backendkit-labs/agent-web';
274
+
275
+ const jwt = new JwtAuth({
276
+ secret: process.env.JWT_SECRET!, // or use publicKey for RS256
277
+ algorithm: 'HS256', // default
278
+ sessionIdClaim:'sub', // JWT claim to use as sessionId
279
+ expiresIn: '24h',
280
+ });
281
+
282
+ const server = new AgentServer({
283
+ port: 3000,
284
+ engineFactory,
285
+ auth: jwt.hook(), // AuthHook applied to all routes
286
+ });
287
+ ```
288
+
289
+ ### Issuing tokens (your login endpoint)
290
+
291
+ ```typescript
292
+ import { JwtAuth } from '@backendkit-labs/agent-web';
293
+
294
+ const jwt = new JwtAuth({ secret: process.env.JWT_SECRET! });
295
+
296
+ // Issue a token for an authenticated user
297
+ const token = jwt.sign({
298
+ sub: 'user-123',
299
+ role: 'admin',
300
+ name: 'Alice',
301
+ });
302
+
303
+ // Return to client — they send it as:
304
+ // Authorization: Bearer <token>
305
+ // or ws://host/chat?token=<token>
306
+ ```
307
+
308
+ ### Per-user session isolation
309
+
310
+ When `sessionIdClaim` is `'sub'`, every user gets their own session automatically. Two requests with the same token always hit the same session:
311
+
312
+ ```typescript
313
+ const jwt = new JwtAuth({
314
+ secret: process.env.JWT_SECRET!,
315
+ sessionIdClaim: 'sub', // user ID as session key
316
+ });
317
+ ```
318
+
319
+ ### Custom auth hook
320
+
321
+ If you use a different auth system (API keys, OAuth tokens), implement `AuthHook` directly:
322
+
323
+ ```typescript
324
+ import type { AuthHook } from '@backendkit-labs/agent-web';
325
+
326
+ const apiKeyAuth: AuthHook = async (req, reply) => {
327
+ const key = req.headers['x-api-key'];
328
+ if (!key || !validApiKeys.has(key as string)) {
329
+ reply.code(401).send({ error: 'Unauthorized' });
330
+ }
331
+ };
332
+
333
+ const server = new AgentServer({ auth: apiKeyAuth, ...opts });
334
+ ```
335
+
336
+ ---
337
+
338
+ ## Prometheus metrics
339
+
340
+ Requires `prom-client` to be installed. Exposes a `/metrics` endpoint with standard Prometheus counters and histograms.
341
+
342
+ ```typescript
343
+ const server = new AgentServer({
344
+ ...opts,
345
+ metrics: {
346
+ enabled: true,
347
+ path: '/metrics', // default
348
+ prefix: 'bk_agent_', // prepended to all metric names
349
+ collectNodeMetrics: true, // include Node.js process metrics
350
+ },
351
+ });
352
+ ```
353
+
354
+ ### Exposed metrics
45
355
 
46
- ## Monitor bus
356
+ | Metric | Type | Description |
357
+ |--------|------|-------------|
358
+ | `bk_agent_sessions_active` | Gauge | Currently open sessions |
359
+ | `bk_agent_runs_total{status}` | Counter | Total runs by status (`success`, `error`) |
360
+ | `bk_agent_run_duration_seconds` | Histogram | Run latency distribution |
361
+ | Node.js metrics | Various | Memory, CPU, GC (when `collectNodeMetrics: true`) |
47
362
 
48
- Connect to `WS /monitor` to observe all sessions in real time (read-only):
363
+ ### Using MetricsCollector directly
49
364
 
365
+ ```typescript
366
+ import { MetricsCollector } from '@backendkit-labs/agent-web';
367
+
368
+ const collector = new MetricsCollector({ prefix: 'my_agent_' });
369
+
370
+ // Record a completed run
371
+ const endRun = collector.startRun('session-abc');
372
+ // ... after run completes:
373
+ endRun('success'); // or endRun('error')
374
+
375
+ // Get Prometheus text format
376
+ const text = await collector.getMetrics();
377
+ // # HELP my_agent_runs_total ...
378
+ // my_agent_runs_total{status="success"} 42
379
+ ```
380
+
381
+ ---
382
+
383
+ ## Rate limiting
384
+
385
+ Requires `@fastify/rate-limit`. Applied globally to all routes:
386
+
387
+ ```typescript
388
+ const server = new AgentServer({
389
+ ...opts,
390
+ rateLimiting: {
391
+ max: 60, // max 60 requests per IP
392
+ timeWindow: '1 minute', // sliding window
393
+ },
394
+ });
395
+ ```
396
+
397
+ When the limit is exceeded, the server responds with:
50
398
  ```json
51
- { "type": "monitor_hello", "sessionIds": ["..."] }
52
- { "type": "monitor_session_open", "sessionId": "..." }
53
- { "type": "monitor_session_close", "sessionId": "..." }
54
- { "sessionId": "...", "type": "run_input", "content": "..." }
55
- { "sessionId": "...", "type": "token", "content": "..." }
399
+ { "statusCode": 429, "error": "Too Many Requests", "message": "Rate limit exceeded" }
56
400
  ```
57
401
 
58
- Use with [`@backendkit-labs/agent-web-ui`](https://www.npmjs.com/package/@backendkit-labs/agent-web-ui) for a ready-made React chat + kanban monitor UI.
402
+ ---
59
403
 
60
- ## License
404
+ ## Session metadata store
61
405
 
62
- MIT
406
+ By default, session metadata lives in memory and is lost on restart. Use `RedisSessionMetaStore` for durable metadata across restarts and horizontal scaling.
407
+
408
+ ### In-memory (default)
409
+
410
+ ```typescript
411
+ import { InMemorySessionMetaStore } from '@backendkit-labs/agent-web';
412
+
413
+ const server = new AgentServer({
414
+ ...opts,
415
+ store: new InMemorySessionMetaStore(),
416
+ });
417
+ ```
418
+
419
+ ### Redis store (requires ioredis or node-redis)
420
+
421
+ `RedisSessionMetaStore` is duck-typed — it accepts any Redis client that implements `.get()`, `.set()`, and `.del()`:
422
+
423
+ ```typescript
424
+ import { RedisSessionMetaStore } from '@backendkit-labs/agent-web';
425
+ import Redis from 'ioredis';
426
+
427
+ const redis = new Redis(process.env.REDIS_URL!);
428
+
429
+ const server = new AgentServer({
430
+ ...opts,
431
+ store: new RedisSessionMetaStore(redis, {
432
+ ttlSeconds: 86400, // keep session metadata for 24h
433
+ prefix: 'bk:sess:', // key prefix
434
+ }),
435
+ });
436
+ ```
437
+
438
+ ---
439
+
440
+ ## MonitorBus
441
+
442
+ `MonitorBus` broadcasts all session events to connected `/monitor` WebSocket observers. Used by the Kanban board in `@backendkit-labs/agent-web-ui`.
443
+
444
+ ### Standalone usage
445
+
446
+ ```typescript
447
+ import { MonitorBus } from '@backendkit-labs/agent-web';
448
+
449
+ const monitor = new MonitorBus();
450
+
451
+ // Subscribe to all events (e.g. for logging or alerting)
452
+ const unsub = monitor.subscribe((frame) => {
453
+ if (frame.type === 'done') {
454
+ console.log(`[${frame.sessionId}] run completed`);
455
+ }
456
+ if (frame.type === 'error') {
457
+ alerting.notify(`Agent error in session ${frame.sessionId}: ${frame.message}`);
458
+ }
459
+ });
460
+
461
+ // Clean up
462
+ unsub();
463
+ ```
464
+
465
+ ---
466
+
467
+ ## TriggerBus — cron, webhooks, queues
468
+
469
+ `TriggerBus` lets autonomous agent runs happen without a WebSocket client. Runs are visible in the Kanban monitor.
470
+
471
+ ### Cron trigger (requires `node-cron`)
472
+
473
+ ```typescript
474
+ import { TriggerBus } from '@backendkit-labs/agent-web';
475
+
476
+ const triggers = new TriggerBus(engineFactory)
477
+ .addCron({
478
+ name: 'daily-report',
479
+ schedule: '0 8 * * 1-5', // weekdays at 8 AM
480
+ buildPrompt: () => 'Generate the daily engineering standup summary from recent commits.',
481
+ runOnStart: false, // set true to fire immediately on server start
482
+ onResult: (out) => slack.send('#daily', out),
483
+ onError: (err) => logger.error('[daily-report]', err),
484
+ })
485
+ .addCron({
486
+ name: 'health-check',
487
+ schedule: '*/15 * * * *', // every 15 minutes
488
+ buildPrompt: () => 'Check all API endpoints and report any that are down.',
489
+ onResult: (out) => { if (out.includes('DOWN')) pagerduty.alert(out); },
490
+ });
491
+
492
+ const server = new AgentServer({ triggers, ...opts });
493
+ await server.start();
494
+ // Cron jobs start automatically with the server
495
+ ```
496
+
497
+ ### Webhook trigger
498
+
499
+ ```typescript
500
+ const triggers = new TriggerBus(engineFactory)
501
+ .addWebhook({
502
+ name: 'github-pr',
503
+ path: '/webhooks/github', // registered as POST /webhooks/github
504
+ secret: process.env.GITHUB_SECRET, // HMAC X-Hub-Signature-256 validation (optional)
505
+ buildPrompt: (body) => {
506
+ const pr = body.pull_request;
507
+ return `Review PR #${pr.number}: "${pr.title}"\n${pr.diff_url}`;
508
+ },
509
+ onResult: (out, body) => github.postReview(body.pull_request.number, out),
510
+ onError: (err) => logger.error('[github-pr]', err),
511
+ })
512
+ .addWebhook({
513
+ name: 'jira-issue',
514
+ path: '/webhooks/jira',
515
+ buildPrompt: (body) => `Analyze Jira issue ${body.issue.key}: ${body.issue.fields.summary}`,
516
+ onResult: (out) => jira.addComment(body.issue.key, out),
517
+ });
518
+ ```
519
+
520
+ **Testing a webhook:**
521
+
522
+ ```bash
523
+ curl -X POST http://localhost:3000/webhooks/github \
524
+ -H "Content-Type: application/json" \
525
+ -H "X-Hub-Signature-256: sha256=<hmac>" \
526
+ -d '{ "pull_request": { "number": 42, "title": "Add auth module" } }'
527
+ ```
528
+
529
+ ### Queue trigger (BullMQ — requires `bullmq`)
530
+
531
+ ```typescript
532
+ import { TriggerBus } from '@backendkit-labs/agent-web';
533
+
534
+ const triggers = new TriggerBus(engineFactory)
535
+ .addQueue({
536
+ name: 'document-processor',
537
+ queueName: 'documents', // BullMQ queue name
538
+ connection: process.env.REDIS_URL!, // Redis URL or connection object
539
+ concurrency: 3, // process 3 jobs in parallel
540
+ buildPrompt: (job) => {
541
+ return `Summarize and extract key points from this document:\n\n${job.data.content}`;
542
+ },
543
+ onResult: (out, job) => {
544
+ db.documents.update(job.data.id, { summary: out });
545
+ },
546
+ onError: (err, job) => {
547
+ logger.error(`[queue] Failed to process document ${job?.data?.id}:`, err);
548
+ // The error is re-thrown so BullMQ marks the job as failed and applies retry policy
549
+ },
550
+ });
551
+ ```
552
+
553
+ **Publishing a job (from anywhere in your system):**
554
+
555
+ ```typescript
556
+ import { Queue } from 'bullmq';
557
+
558
+ const queue = new Queue('documents', { connection: process.env.REDIS_URL! });
559
+
560
+ await queue.add('process-doc', {
561
+ id: 'doc-456',
562
+ content: 'Full document text...',
563
+ }, {
564
+ attempts: 3,
565
+ backoff: { type: 'exponential', delay: 2000 },
566
+ removeOnComplete: 100,
567
+ removeOnFail: 50,
568
+ });
569
+ ```
570
+
571
+ ### Queue connection options
572
+
573
+ ```typescript
574
+ // URL string
575
+ connection: 'redis://localhost:6379'
576
+ connection: 'redis://:password@redis-host:6380/1'
577
+
578
+ // Connection object
579
+ connection: {
580
+ host: 'redis-host',
581
+ port: 6380,
582
+ password: 'mypassword',
583
+ username: 'default',
584
+ db: 1,
585
+ tls: {}, // enable TLS
586
+ }
587
+ ```
588
+
589
+ ### CronTriggerDef with all options
590
+
591
+ ```typescript
592
+ interface CronTriggerDef {
593
+ name: string; // unique name for the trigger
594
+ schedule: string; // standard cron expression
595
+ buildPrompt: (ts: Date) => string; // returns the prompt to run
596
+ agentId?: string; // override default agent
597
+ runOnStart?: boolean; // fire immediately on server start
598
+ onResult?: (output: string) => void;
599
+ onError?: (err: Error) => void;
600
+ }
601
+ ```
602
+
603
+ ---
604
+
605
+ ## HeadlessTransport
606
+
607
+ `HeadlessTransport` is a `Transport` implementation that buffers all `AgentEvent` objects in memory instead of streaming them over a socket. Used internally by `TriggerBus` for autonomous runs.
608
+
609
+ ```typescript
610
+ import { HeadlessTransport } from '@backendkit-labs/agent-web';
611
+
612
+ const transport = new HeadlessTransport();
613
+
614
+ const engine = new AgentEngine({ ...opts, transport });
615
+ await engine.run('Generate a daily report');
616
+
617
+ // All events are buffered
618
+ const events = transport.getEvents();
619
+ const tokens = events
620
+ .filter(e => e.type === 'chunk')
621
+ .map(e => e.content)
622
+ .join('');
623
+
624
+ console.log('Final output:', tokens);
625
+ ```
626
+
627
+ ---
628
+
629
+ ## Full production example
630
+
631
+ A complete production server with JWT auth, Redis sessions, Prometheus metrics, cron jobs, webhook triggers, and BullMQ queue processing:
632
+
633
+ ```typescript
634
+ import { AgentServer, JwtAuth, TriggerBus, RedisSessionMetaStore } from '@backendkit-labs/agent-web';
635
+ import { createEnterpriseSetup } from '@backendkit-labs/agent-enterprise';
636
+ import { ProviderRegistry, DeepSeekProvider } from '@backendkit-labs/agent-core';
637
+ import Redis from 'ioredis';
638
+
639
+ // LLM providers
640
+ const providers = new ProviderRegistry();
641
+ providers.register('deepseek', new DeepSeekProvider({
642
+ apiKey: process.env.DEEPSEEK_API_KEY!,
643
+ model: 'deepseek-chat',
644
+ }));
645
+
646
+ // Enterprise setup (RAG + 6 agent profiles)
647
+ const enterprise = createEnterpriseSetup({
648
+ vaultPath: process.env.VAULT_PATH!,
649
+ providers,
650
+ defaultProvider: 'deepseek',
651
+ });
652
+ await enterprise.indexAll();
653
+
654
+ // Redis
655
+ const redis = new Redis(process.env.REDIS_URL!);
656
+
657
+ // JWT auth
658
+ const jwt = new JwtAuth({
659
+ secret: process.env.JWT_SECRET!,
660
+ sessionIdClaim: 'sub',
661
+ });
662
+
663
+ // Autonomous triggers
664
+ const triggers = new TriggerBus(enterprise.engineFactory)
665
+ .addCron({
666
+ name: 'weekly-digest',
667
+ schedule: '0 9 * * 1', // Mondays at 9 AM
668
+ buildPrompt: () => 'Generate a weekly digest of all knowledge base changes.',
669
+ onResult: (out) => slack.send('#general', out),
670
+ })
671
+ .addWebhook({
672
+ name: 'support-ticket',
673
+ path: '/webhooks/support',
674
+ secret: process.env.WEBHOOK_SECRET,
675
+ buildPrompt: (body) => `Handle support ticket #${body.id}: ${body.subject}\n${body.description}`,
676
+ onResult: (out, body) => helpdesk.reply(body.id, out),
677
+ })
678
+ .addQueue({
679
+ name: 'email-processor',
680
+ queueName: 'emails',
681
+ connection: process.env.REDIS_URL!,
682
+ concurrency: 5,
683
+ buildPrompt: (job) => `Draft a professional reply to this email:\n\n${job.data.body}`,
684
+ onResult: (out, job) => emailService.send({ to: job.data.from, body: out }),
685
+ });
686
+
687
+ // Server
688
+ const server = new AgentServer({
689
+ port: 3000,
690
+ engineFactory: enterprise.engineFactory,
691
+ auth: jwt.hook(),
692
+ store: new RedisSessionMetaStore(redis),
693
+ cors: ['https://app.company.com'],
694
+ logger: { level: 'info' },
695
+ rateLimiting: { max: 60, timeWindow: '1 minute' },
696
+ metrics: { enabled: true, prefix: 'bk_' },
697
+ maxIdleMs: 30 * 60 * 1000,
698
+ triggers,
699
+ });
700
+
701
+ await server.start();
702
+ console.log('Agent server running on :3000');
703
+
704
+ // Graceful shutdown
705
+ process.on('SIGTERM', () => server.stop());
706
+ ```