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