@robota-sdk/agent-plugin 3.0.0-beta.64

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 (82) hide show
  1. package/LICENSE +21 -0
  2. package/dist/node/index.cjs +1 -0
  3. package/dist/node/index.d.ts +1724 -0
  4. package/dist/node/index.d.ts.map +1 -0
  5. package/dist/node/index.js +2 -0
  6. package/dist/node/index.js.map +1 -0
  7. package/package.json +48 -0
  8. package/src/conversation-history/__tests__/conversation-history-plugin.test.ts +221 -0
  9. package/src/conversation-history/__tests__/history-storages.test.ts +115 -0
  10. package/src/conversation-history/conversation-history-helpers.ts +120 -0
  11. package/src/conversation-history/conversation-history-plugin.ts +294 -0
  12. package/src/conversation-history/index.ts +11 -0
  13. package/src/conversation-history/storages/database-storage.ts +96 -0
  14. package/src/conversation-history/storages/file-storage.ts +95 -0
  15. package/src/conversation-history/storages/index.ts +3 -0
  16. package/src/conversation-history/storages/memory-storage.ts +44 -0
  17. package/src/conversation-history/types.ts +64 -0
  18. package/src/error-handling/__tests__/error-handling-plugin.test.ts +201 -0
  19. package/src/error-handling/context-adapter.ts +48 -0
  20. package/src/error-handling/error-handling-helpers.ts +53 -0
  21. package/src/error-handling/error-handling-plugin.ts +293 -0
  22. package/src/error-handling/index.ts +9 -0
  23. package/src/error-handling/types.ts +82 -0
  24. package/src/execution-analytics/__tests__/execution-analytics-plugin.test.ts +224 -0
  25. package/src/execution-analytics/analytics-aggregation.ts +88 -0
  26. package/src/execution-analytics/execution-analytics-helpers.ts +83 -0
  27. package/src/execution-analytics/execution-analytics-plugin.ts +315 -0
  28. package/src/execution-analytics/index.ts +9 -0
  29. package/src/execution-analytics/types.ts +97 -0
  30. package/src/index.ts +8 -0
  31. package/src/limits/__tests__/limits-plugin.test.ts +712 -0
  32. package/src/limits/index.ts +9 -0
  33. package/src/limits/limits-helpers.ts +185 -0
  34. package/src/limits/limits-plugin.ts +196 -0
  35. package/src/limits/types.ts +73 -0
  36. package/src/limits/validation.ts +81 -0
  37. package/src/logging/__tests__/formatters.test.ts +48 -0
  38. package/src/logging/__tests__/logging-plugin.test.ts +464 -0
  39. package/src/logging/__tests__/logging-storages.test.ts +95 -0
  40. package/src/logging/formatters.ts +28 -0
  41. package/src/logging/index.ts +15 -0
  42. package/src/logging/logging-helpers.ts +223 -0
  43. package/src/logging/logging-plugin.ts +288 -0
  44. package/src/logging/storages/console-storage.ts +44 -0
  45. package/src/logging/storages/file-storage.ts +44 -0
  46. package/src/logging/storages/index.ts +4 -0
  47. package/src/logging/storages/remote-storage.ts +78 -0
  48. package/src/logging/storages/silent-storage.ts +18 -0
  49. package/src/logging/types.ts +106 -0
  50. package/src/performance/__tests__/memory-storage.test.ts +86 -0
  51. package/src/performance/__tests__/performance-plugin.test.ts +208 -0
  52. package/src/performance/__tests__/system-metrics-collector.test.ts +33 -0
  53. package/src/performance/collectors/system-metrics-collector.ts +69 -0
  54. package/src/performance/index.ts +12 -0
  55. package/src/performance/performance-helpers.ts +86 -0
  56. package/src/performance/performance-plugin.ts +274 -0
  57. package/src/performance/storages/index.ts +1 -0
  58. package/src/performance/storages/memory-storage.ts +88 -0
  59. package/src/performance/types.ts +160 -0
  60. package/src/usage/__tests__/aggregate-usage-stats.test.ts +136 -0
  61. package/src/usage/__tests__/memory-storage.test.ts +83 -0
  62. package/src/usage/__tests__/silent-storage.test.ts +44 -0
  63. package/src/usage/__tests__/usage-plugin-helpers.test.ts +155 -0
  64. package/src/usage/__tests__/usage-plugin.test.ts +358 -0
  65. package/src/usage/aggregate-usage-stats.ts +142 -0
  66. package/src/usage/index.ts +14 -0
  67. package/src/usage/storages/file-storage.ts +115 -0
  68. package/src/usage/storages/index.ts +4 -0
  69. package/src/usage/storages/memory-storage.ts +61 -0
  70. package/src/usage/storages/remote-storage.ts +143 -0
  71. package/src/usage/storages/silent-storage.ts +38 -0
  72. package/src/usage/types.ts +132 -0
  73. package/src/usage/usage-plugin-helpers.ts +116 -0
  74. package/src/usage/usage-plugin.ts +296 -0
  75. package/src/webhook/__tests__/webhook-plugin.test.ts +560 -0
  76. package/src/webhook/http-client.ts +141 -0
  77. package/src/webhook/index.ts +9 -0
  78. package/src/webhook/transformer.ts +209 -0
  79. package/src/webhook/types.ts +201 -0
  80. package/src/webhook/webhook-helpers.ts +60 -0
  81. package/src/webhook/webhook-plugin.ts +298 -0
  82. package/src/webhook/webhook-queue.ts +148 -0
@@ -0,0 +1,298 @@
1
+ /**
2
+ * Webhook Plugin - Facade Pattern Implementation
3
+ * Coordinates webhook functionality through clean, separated components
4
+ */
5
+
6
+ import {
7
+ AbstractPlugin,
8
+ PluginCategory,
9
+ PluginPriority,
10
+ type IPluginExecutionContext,
11
+ type IPluginExecutionResult,
12
+ type IPluginErrorContext,
13
+ createLogger,
14
+ type ILogger,
15
+ PluginError,
16
+ } from '@robota-sdk/agent-core';
17
+
18
+ import { WebhookTransformer } from './transformer';
19
+ import { WebhookHttpClient } from './http-client';
20
+ import {
21
+ validateWebhookEndpoints,
22
+ WEBHOOK_EXEC_EVENTS,
23
+ WEBHOOK_CONV_EVENTS,
24
+ WEBHOOK_TOOL_EVENTS,
25
+ WEBHOOK_ERROR_EVENTS,
26
+ } from './webhook-helpers';
27
+ import { WebhookQueueManager } from './webhook-queue';
28
+ import type {
29
+ TWebhookEventName,
30
+ IWebhookEventData,
31
+ TWebhookMetadata,
32
+ IWebhookPayload,
33
+ IWebhookEndpoint,
34
+ IWebhookPluginOptions,
35
+ IWebhookPluginStats,
36
+ } from './types';
37
+
38
+ /**
39
+ * Sends HTTP webhook notifications for agent execution lifecycle events.
40
+ *
41
+ * Routes events to configured {@link IWebhookEndpoint | endpoints} with
42
+ * optional event filtering per endpoint. Supports asynchronous delivery with
43
+ * configurable concurrency, automatic retries via {@link WebhookHttpClient},
44
+ * and optional payload batching.
45
+ *
46
+ * Lifecycle hooks used: {@link AbstractPlugin.afterExecution | afterExecution},
47
+ * {@link AbstractPlugin.afterConversation | afterConversation},
48
+ * {@link AbstractPlugin.afterToolExecution | afterToolExecution},
49
+ * {@link AbstractPlugin.onError | onError}
50
+ *
51
+ * @extends AbstractPlugin
52
+ * @see IWebhookPluginOptions - configuration options
53
+ * @see WebhookTransformer - payload transformation utilities
54
+ * @see WebhookHttpClient - HTTP delivery client
55
+ *
56
+ * @example
57
+ * ```ts
58
+ * const plugin = new WebhookPlugin({
59
+ * endpoints: [{ url: 'https://example.com/hook' }],
60
+ * async: true,
61
+ * maxConcurrency: 3,
62
+ * });
63
+ * ```
64
+ */
65
+ export class WebhookPlugin extends AbstractPlugin<IWebhookPluginOptions, IWebhookPluginStats> {
66
+ name = 'WebhookPlugin';
67
+ version = '1.0.0';
68
+
69
+ private pluginOptions: Required<IWebhookPluginOptions>;
70
+ private logger: ILogger;
71
+ private queue: WebhookQueueManager;
72
+
73
+ constructor(options: IWebhookPluginOptions) {
74
+ super();
75
+
76
+ // Set plugin classification
77
+ this.category = PluginCategory.NOTIFICATION;
78
+ this.priority = PluginPriority.LOW;
79
+
80
+ // Validate required options
81
+ if (!options.endpoints || options.endpoints.length === 0) {
82
+ throw new PluginError('At least one webhook endpoint is required', this.name);
83
+ }
84
+
85
+ // Set default options
86
+ this.pluginOptions = {
87
+ enabled: options.enabled ?? true,
88
+ events: [
89
+ WEBHOOK_EXEC_EVENTS.COMPLETE,
90
+ WEBHOOK_CONV_EVENTS.COMPLETE,
91
+ WEBHOOK_TOOL_EVENTS.EXECUTED,
92
+ WEBHOOK_ERROR_EVENTS.OCCURRED,
93
+ ],
94
+ defaultTimeout: 5000,
95
+ defaultRetries: 3,
96
+ async: true,
97
+ maxConcurrency: 3,
98
+ batching: {
99
+ enabled: false,
100
+ maxSize: 10,
101
+ flushInterval: 5000,
102
+ },
103
+ payloadTransformer: WebhookTransformer.defaultPayloadTransformer,
104
+ // Add plugin options defaults
105
+ category: options.category ?? PluginCategory.NOTIFICATION,
106
+ priority: options.priority ?? PluginPriority.LOW,
107
+ moduleEvents: options.moduleEvents ?? [],
108
+ subscribeToAllModuleEvents: options.subscribeToAllModuleEvents ?? false,
109
+ ...options,
110
+ };
111
+
112
+ this.logger = createLogger(`${this.name}`);
113
+ const httpClient = new WebhookHttpClient(this.logger);
114
+ this.queue = new WebhookQueueManager(
115
+ this.logger,
116
+ httpClient,
117
+ this.pluginOptions.maxConcurrency,
118
+ );
119
+
120
+ validateWebhookEndpoints(this.pluginOptions.endpoints, this.name, [
121
+ WEBHOOK_EXEC_EVENTS.START,
122
+ WEBHOOK_EXEC_EVENTS.COMPLETE,
123
+ WEBHOOK_EXEC_EVENTS.ERROR,
124
+ WEBHOOK_CONV_EVENTS.COMPLETE,
125
+ WEBHOOK_TOOL_EVENTS.EXECUTED,
126
+ WEBHOOK_ERROR_EVENTS.OCCURRED,
127
+ 'custom',
128
+ ]);
129
+
130
+ if (this.pluginOptions.batching.enabled) {
131
+ this.queue.setupBatching(
132
+ this.pluginOptions.batching.flushInterval,
133
+ this.getEndpointsForEvent.bind(this),
134
+ );
135
+ }
136
+
137
+ this.logger.info('WebhookPlugin initialized', {
138
+ endpointCount: this.pluginOptions.endpoints.length,
139
+ events: this.pluginOptions.events,
140
+ batching: this.pluginOptions.batching.enabled,
141
+ });
142
+ }
143
+
144
+ /**
145
+ * Sends an execution-complete webhook after the agent finishes processing.
146
+ */
147
+ override async afterExecution(
148
+ context: IPluginExecutionContext,
149
+ result: IPluginExecutionResult,
150
+ ): Promise<void> {
151
+ const webhookContext = WebhookTransformer.contextToWebhook(context);
152
+ const webhookResult = WebhookTransformer.resultToWebhook(result);
153
+ const eventData = WebhookTransformer.createExecutionData(webhookContext, webhookResult);
154
+ await this.sendWebhook(WEBHOOK_EXEC_EVENTS.COMPLETE, eventData);
155
+ }
156
+
157
+ /**
158
+ * Sends a conversation-complete webhook after a conversation round finishes.
159
+ */
160
+ override async afterConversation(
161
+ context: IPluginExecutionContext,
162
+ result: IPluginExecutionResult,
163
+ ): Promise<void> {
164
+ const webhookContext = WebhookTransformer.contextToWebhook(context);
165
+ const webhookResult = WebhookTransformer.resultToWebhook(result);
166
+ const eventData = WebhookTransformer.createConversationData(webhookContext, webhookResult);
167
+ await this.sendWebhook(WEBHOOK_CONV_EVENTS.COMPLETE, eventData);
168
+ }
169
+
170
+ /**
171
+ * Sends a tool-executed webhook for each tool call in the result set.
172
+ */
173
+ override async afterToolExecution(
174
+ context: IPluginExecutionContext,
175
+ toolResults: IPluginExecutionResult,
176
+ ): Promise<void> {
177
+ const webhookContext = WebhookTransformer.contextToWebhook(context);
178
+ if (toolResults.toolCalls && toolResults.toolCalls.length > 0) {
179
+ for (const toolCall of toolResults.toolCalls) {
180
+ const toolData = {
181
+ toolName: toolCall.name || '',
182
+ toolId: toolCall.id || '',
183
+ result: toolCall.result,
184
+ success: toolCall.result !== null,
185
+ duration: toolResults.duration,
186
+ };
187
+ const eventData = WebhookTransformer.createToolData(webhookContext, toolData);
188
+ await this.sendWebhook(WEBHOOK_TOOL_EVENTS.EXECUTED, eventData);
189
+ }
190
+ }
191
+ }
192
+
193
+ /**
194
+ * Sends both an error-occurred and execution-error webhook on failure.
195
+ */
196
+ override async onError(error: Error, context?: IPluginErrorContext): Promise<void> {
197
+ const webhookContext = context
198
+ ? { executionId: context.executionId, sessionId: context.sessionId, userId: context.userId }
199
+ : { executionId: undefined, sessionId: undefined, userId: undefined };
200
+ const errorEventData = WebhookTransformer.createErrorData(webhookContext, error);
201
+ await this.sendWebhook(WEBHOOK_ERROR_EVENTS.OCCURRED, errorEventData);
202
+ await this.sendWebhook(WEBHOOK_EXEC_EVENTS.ERROR, errorEventData);
203
+ }
204
+
205
+ /**
206
+ * Builds a webhook payload and delivers it to all matching endpoints. When
207
+ * batching is enabled, payloads are queued and flushed at the configured interval.
208
+ */
209
+ async sendWebhook(
210
+ event: TWebhookEventName,
211
+ data: IWebhookEventData,
212
+ metadata?: TWebhookMetadata,
213
+ ): Promise<void> {
214
+ if (!this.pluginOptions.events.includes(event)) return;
215
+
216
+ const payload: IWebhookPayload = {
217
+ event,
218
+ timestamp: new Date().toISOString(),
219
+ data: this.pluginOptions.payloadTransformer(event, data),
220
+ ...(metadata && { metadata }),
221
+ };
222
+ if (data.executionId) payload.executionId = data.executionId;
223
+ if (data.sessionId) payload.sessionId = data.sessionId;
224
+ if (data.userId) payload.userId = data.userId;
225
+
226
+ this.logger.debug('Sending webhook', {
227
+ event,
228
+ endpointCount: this.getEndpointsForEvent(event).length,
229
+ });
230
+
231
+ if (this.pluginOptions.batching.enabled) {
232
+ await this.queue.enqueueBatch(
233
+ payload,
234
+ this.pluginOptions.batching.maxSize,
235
+ this.getEndpointsForEvent.bind(this),
236
+ );
237
+ } else {
238
+ await this.queue.sendToEndpoints(
239
+ payload,
240
+ this.getEndpointsForEvent.bind(this),
241
+ this.pluginOptions.async,
242
+ );
243
+ }
244
+ }
245
+
246
+ /**
247
+ * Sends a webhook with the `custom` event type.
248
+ */
249
+ async sendCustomWebhook(data: IWebhookEventData, metadata?: TWebhookMetadata): Promise<void> {
250
+ await this.sendWebhook('custom', data, metadata);
251
+ }
252
+
253
+ /**
254
+ * Get webhook plugin statistics
255
+ */
256
+ override getStats(): IWebhookPluginStats {
257
+ const base = super.getStats();
258
+ return {
259
+ ...base,
260
+ endpointCount: this.pluginOptions.endpoints.length,
261
+ queueLength: this.queue.queueLength,
262
+ batchQueueLength: this.queue.batchQueueLength,
263
+ activeConcurrency: this.queue.activeConcurrency,
264
+ supportedEvents: this.pluginOptions.events,
265
+ totalSent: this.queue.totalSentCount,
266
+ totalErrors: this.queue.totalErrorCount,
267
+ averageResponseTime:
268
+ this.queue.totalSentCount > 0
269
+ ? this.queue.totalResponseTime / this.queue.totalSentCount
270
+ : 0,
271
+ };
272
+ }
273
+
274
+ /**
275
+ * Clear webhook queue
276
+ */
277
+ clearQueue(): void {
278
+ this.queue.clearQueues();
279
+ this.logger.info('Webhook queues cleared');
280
+ }
281
+
282
+ /**
283
+ * Flushes pending batches, clears request queues, and stops the batch timer.
284
+ */
285
+ async destroy(): Promise<void> {
286
+ this.queue.stopBatchTimer();
287
+ await this.queue.drainBatch(this.getEndpointsForEvent.bind(this));
288
+ this.clearQueue();
289
+ this.logger.info('WebhookPlugin destroyed');
290
+ }
291
+
292
+ private getEndpointsForEvent(event: TWebhookEventName): IWebhookEndpoint[] {
293
+ return this.pluginOptions.endpoints.filter((endpoint) => {
294
+ if (!endpoint.events || endpoint.events.length === 0) return true;
295
+ return endpoint.events.includes(event);
296
+ });
297
+ }
298
+ }
@@ -0,0 +1,148 @@
1
+ /**
2
+ * Webhook Plugin - Queue and batch management.
3
+ *
4
+ * Extracted from webhook-plugin.ts to keep each file under 300 lines.
5
+ * @internal
6
+ */
7
+
8
+ import { type ILogger, type TTimerId } from '@robota-sdk/agent-core';
9
+ import type {
10
+ TWebhookEventName,
11
+ IWebhookPayload,
12
+ IWebhookEndpoint,
13
+ IWebhookRequest,
14
+ } from './types';
15
+ import { WebhookHttpClient } from './http-client';
16
+
17
+ /** Manages the async request queue, batch queue, and concurrency for WebhookPlugin. @internal */
18
+ export class WebhookQueueManager {
19
+ private requestQueue: IWebhookRequest[] = [];
20
+ private batchQueue: IWebhookPayload[] = [];
21
+ private batchTimer?: TTimerId;
22
+ activeConcurrency = 0;
23
+ totalSentCount = 0;
24
+ totalErrorCount = 0;
25
+ totalResponseTime = 0;
26
+
27
+ constructor(
28
+ private readonly logger: ILogger,
29
+ private readonly httpClient: WebhookHttpClient,
30
+ private readonly maxConcurrency: number,
31
+ ) {}
32
+
33
+ get queueLength(): number {
34
+ return this.requestQueue.length;
35
+ }
36
+
37
+ get batchQueueLength(): number {
38
+ return this.batchQueue.length;
39
+ }
40
+
41
+ setupBatching(
42
+ flushInterval: number,
43
+ getEndpoints: (event: TWebhookEventName) => IWebhookEndpoint[],
44
+ ): void {
45
+ this.batchTimer = setInterval(() => {
46
+ this.drainBatch(getEndpoints);
47
+ }, flushInterval);
48
+ }
49
+
50
+ async enqueueBatch(
51
+ payload: IWebhookPayload,
52
+ maxSize: number,
53
+ getEndpoints: (event: TWebhookEventName) => IWebhookEndpoint[],
54
+ ): Promise<void> {
55
+ this.batchQueue.push(payload);
56
+ if (this.batchQueue.length >= maxSize) {
57
+ await this.drainBatch(getEndpoints);
58
+ }
59
+ }
60
+
61
+ async sendToEndpoints(
62
+ payload: IWebhookPayload,
63
+ getEndpoints: (event: TWebhookEventName) => IWebhookEndpoint[],
64
+ async_: boolean = true,
65
+ ): Promise<void> {
66
+ const endpoints = getEndpoints(payload.event);
67
+ if (endpoints.length === 0) return;
68
+
69
+ const requests: IWebhookRequest[] = endpoints.map((endpoint) => ({
70
+ endpoint,
71
+ payload,
72
+ attempt: 1,
73
+ timestamp: new Date(),
74
+ }));
75
+
76
+ if (async_) {
77
+ this.requestQueue.push(...requests);
78
+ this.processQueue();
79
+ } else {
80
+ await Promise.allSettled(
81
+ requests.map((req) => {
82
+ const startTime = Date.now();
83
+ return this.httpClient
84
+ .sendRequest(req)
85
+ .then(() => {
86
+ this.totalSentCount++;
87
+ this.totalResponseTime += Date.now() - startTime;
88
+ })
89
+ .catch((error: unknown) => {
90
+ this.totalErrorCount++;
91
+ throw error;
92
+ });
93
+ }),
94
+ );
95
+ }
96
+ }
97
+
98
+ private processQueue(): void {
99
+ while (this.requestQueue.length > 0 && this.activeConcurrency < this.maxConcurrency) {
100
+ const request = this.requestQueue.shift();
101
+ if (!request) break;
102
+
103
+ this.activeConcurrency++;
104
+ const startTime = Date.now();
105
+ this.httpClient
106
+ .sendRequest(request)
107
+ .then(() => {
108
+ this.totalSentCount++;
109
+ this.totalResponseTime += Date.now() - startTime;
110
+ })
111
+ .catch((error: unknown) => {
112
+ this.totalErrorCount++;
113
+ const message = error instanceof Error ? error.message : String(error);
114
+ this.logger.error('Webhook request failed', {
115
+ endpoint: request.endpoint.url,
116
+ event: request.payload.event,
117
+ error: message,
118
+ });
119
+ })
120
+ .finally(() => {
121
+ this.activeConcurrency--;
122
+ if (this.requestQueue.length > 0) this.processQueue();
123
+ });
124
+ }
125
+ }
126
+
127
+ async drainBatch(getEndpoints: (event: TWebhookEventName) => IWebhookEndpoint[]): Promise<void> {
128
+ if (this.batchQueue.length === 0) return;
129
+ const payloads = [...this.batchQueue];
130
+ this.batchQueue = [];
131
+ this.logger.debug('Flushing webhook batch', { payloadCount: payloads.length });
132
+ for (const payload of payloads) {
133
+ await this.sendToEndpoints(payload, getEndpoints);
134
+ }
135
+ }
136
+
137
+ clearQueues(): void {
138
+ this.requestQueue = [];
139
+ this.batchQueue = [];
140
+ }
141
+
142
+ stopBatchTimer(): void {
143
+ if (this.batchTimer) {
144
+ clearInterval(this.batchTimer);
145
+ this.batchTimer = undefined;
146
+ }
147
+ }
148
+ }