@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.
- package/LICENSE +21 -0
- package/dist/node/index.cjs +1 -0
- package/dist/node/index.d.ts +1724 -0
- package/dist/node/index.d.ts.map +1 -0
- package/dist/node/index.js +2 -0
- package/dist/node/index.js.map +1 -0
- package/package.json +48 -0
- package/src/conversation-history/__tests__/conversation-history-plugin.test.ts +221 -0
- package/src/conversation-history/__tests__/history-storages.test.ts +115 -0
- package/src/conversation-history/conversation-history-helpers.ts +120 -0
- package/src/conversation-history/conversation-history-plugin.ts +294 -0
- package/src/conversation-history/index.ts +11 -0
- package/src/conversation-history/storages/database-storage.ts +96 -0
- package/src/conversation-history/storages/file-storage.ts +95 -0
- package/src/conversation-history/storages/index.ts +3 -0
- package/src/conversation-history/storages/memory-storage.ts +44 -0
- package/src/conversation-history/types.ts +64 -0
- package/src/error-handling/__tests__/error-handling-plugin.test.ts +201 -0
- package/src/error-handling/context-adapter.ts +48 -0
- package/src/error-handling/error-handling-helpers.ts +53 -0
- package/src/error-handling/error-handling-plugin.ts +293 -0
- package/src/error-handling/index.ts +9 -0
- package/src/error-handling/types.ts +82 -0
- package/src/execution-analytics/__tests__/execution-analytics-plugin.test.ts +224 -0
- package/src/execution-analytics/analytics-aggregation.ts +88 -0
- package/src/execution-analytics/execution-analytics-helpers.ts +83 -0
- package/src/execution-analytics/execution-analytics-plugin.ts +315 -0
- package/src/execution-analytics/index.ts +9 -0
- package/src/execution-analytics/types.ts +97 -0
- package/src/index.ts +8 -0
- package/src/limits/__tests__/limits-plugin.test.ts +712 -0
- package/src/limits/index.ts +9 -0
- package/src/limits/limits-helpers.ts +185 -0
- package/src/limits/limits-plugin.ts +196 -0
- package/src/limits/types.ts +73 -0
- package/src/limits/validation.ts +81 -0
- package/src/logging/__tests__/formatters.test.ts +48 -0
- package/src/logging/__tests__/logging-plugin.test.ts +464 -0
- package/src/logging/__tests__/logging-storages.test.ts +95 -0
- package/src/logging/formatters.ts +28 -0
- package/src/logging/index.ts +15 -0
- package/src/logging/logging-helpers.ts +223 -0
- package/src/logging/logging-plugin.ts +288 -0
- package/src/logging/storages/console-storage.ts +44 -0
- package/src/logging/storages/file-storage.ts +44 -0
- package/src/logging/storages/index.ts +4 -0
- package/src/logging/storages/remote-storage.ts +78 -0
- package/src/logging/storages/silent-storage.ts +18 -0
- package/src/logging/types.ts +106 -0
- package/src/performance/__tests__/memory-storage.test.ts +86 -0
- package/src/performance/__tests__/performance-plugin.test.ts +208 -0
- package/src/performance/__tests__/system-metrics-collector.test.ts +33 -0
- package/src/performance/collectors/system-metrics-collector.ts +69 -0
- package/src/performance/index.ts +12 -0
- package/src/performance/performance-helpers.ts +86 -0
- package/src/performance/performance-plugin.ts +274 -0
- package/src/performance/storages/index.ts +1 -0
- package/src/performance/storages/memory-storage.ts +88 -0
- package/src/performance/types.ts +160 -0
- package/src/usage/__tests__/aggregate-usage-stats.test.ts +136 -0
- package/src/usage/__tests__/memory-storage.test.ts +83 -0
- package/src/usage/__tests__/silent-storage.test.ts +44 -0
- package/src/usage/__tests__/usage-plugin-helpers.test.ts +155 -0
- package/src/usage/__tests__/usage-plugin.test.ts +358 -0
- package/src/usage/aggregate-usage-stats.ts +142 -0
- package/src/usage/index.ts +14 -0
- package/src/usage/storages/file-storage.ts +115 -0
- package/src/usage/storages/index.ts +4 -0
- package/src/usage/storages/memory-storage.ts +61 -0
- package/src/usage/storages/remote-storage.ts +143 -0
- package/src/usage/storages/silent-storage.ts +38 -0
- package/src/usage/types.ts +132 -0
- package/src/usage/usage-plugin-helpers.ts +116 -0
- package/src/usage/usage-plugin.ts +296 -0
- package/src/webhook/__tests__/webhook-plugin.test.ts +560 -0
- package/src/webhook/http-client.ts +141 -0
- package/src/webhook/index.ts +9 -0
- package/src/webhook/transformer.ts +209 -0
- package/src/webhook/types.ts +201 -0
- package/src/webhook/webhook-helpers.ts +60 -0
- package/src/webhook/webhook-plugin.ts +298 -0
- package/src/webhook/webhook-queue.ts +148 -0
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AbstractPlugin,
|
|
3
|
+
PluginCategory,
|
|
4
|
+
PluginPriority,
|
|
5
|
+
createLogger,
|
|
6
|
+
type ILogger,
|
|
7
|
+
PluginError,
|
|
8
|
+
type IEventEmitterEventData,
|
|
9
|
+
type TEventName,
|
|
10
|
+
} from '@robota-sdk/agent-core';
|
|
11
|
+
import {
|
|
12
|
+
IPerformanceMetrics,
|
|
13
|
+
IAggregatedPerformanceStats,
|
|
14
|
+
IPerformancePluginOptions,
|
|
15
|
+
IPerformancePluginStats,
|
|
16
|
+
IPerformanceStorage,
|
|
17
|
+
ISystemMetricsCollector,
|
|
18
|
+
} from './types';
|
|
19
|
+
import { NodeSystemMetricsCollector } from './collectors/system-metrics-collector';
|
|
20
|
+
import {
|
|
21
|
+
validatePerformanceOptions,
|
|
22
|
+
createPerformanceStorage,
|
|
23
|
+
extractPerformanceModuleData,
|
|
24
|
+
PERFORMANCE_MODULE_EVENT_MAP,
|
|
25
|
+
} from './performance-helpers';
|
|
26
|
+
|
|
27
|
+
const DEFAULT_MAX_ENTRIES = 5000;
|
|
28
|
+
const DEFAULT_BATCH_SIZE = 100;
|
|
29
|
+
const DEFAULT_FLUSH_INTERVAL_MS = 30000;
|
|
30
|
+
const DEFAULT_AGGREGATION_INTERVAL_MS = 60000;
|
|
31
|
+
const DEFAULT_PERFORMANCE_THRESHOLD_MS = 1000;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Collects application and system performance metrics during agent execution.
|
|
35
|
+
*
|
|
36
|
+
* Optionally monitors memory, CPU, and network via
|
|
37
|
+
* {@link ISystemMetricsCollector}. Logs a warning when an operation exceeds
|
|
38
|
+
* the configured {@link IPerformancePluginOptions.performanceThreshold | performanceThreshold}.
|
|
39
|
+
* Currently only the `memory` storage strategy is implemented.
|
|
40
|
+
*
|
|
41
|
+
* Lifecycle hooks used: {@link AbstractPlugin.onModuleEvent | onModuleEvent}
|
|
42
|
+
*
|
|
43
|
+
* @extends AbstractPlugin
|
|
44
|
+
* @see IPerformanceStorage - storage backend contract
|
|
45
|
+
* @see ISystemMetricsCollector - system metrics collection contract
|
|
46
|
+
* @see IPerformancePluginOptions - configuration options
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```ts
|
|
50
|
+
* const plugin = new PerformancePlugin({
|
|
51
|
+
* strategy: 'memory',
|
|
52
|
+
* monitorMemory: true,
|
|
53
|
+
* performanceThreshold: 2000,
|
|
54
|
+
* });
|
|
55
|
+
* await plugin.recordMetrics({ operation: 'run', duration: 1500, success: true, errorCount: 0 });
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
export class PerformancePlugin extends AbstractPlugin<
|
|
59
|
+
IPerformancePluginOptions,
|
|
60
|
+
IPerformancePluginStats
|
|
61
|
+
> {
|
|
62
|
+
name = 'PerformancePlugin';
|
|
63
|
+
version = '1.0.0';
|
|
64
|
+
|
|
65
|
+
private storage: IPerformanceStorage;
|
|
66
|
+
private metricsCollector: ISystemMetricsCollector;
|
|
67
|
+
private pluginOptions: Required<IPerformancePluginOptions>;
|
|
68
|
+
private logger: ILogger;
|
|
69
|
+
|
|
70
|
+
constructor(options: IPerformancePluginOptions) {
|
|
71
|
+
super();
|
|
72
|
+
this.logger = createLogger('PerformancePlugin');
|
|
73
|
+
|
|
74
|
+
// Set plugin classification
|
|
75
|
+
this.category = PluginCategory.MONITORING;
|
|
76
|
+
this.priority = PluginPriority.NORMAL;
|
|
77
|
+
|
|
78
|
+
// Validate options
|
|
79
|
+
validatePerformanceOptions(options);
|
|
80
|
+
|
|
81
|
+
// Set defaults
|
|
82
|
+
this.pluginOptions = {
|
|
83
|
+
enabled: options.enabled ?? true,
|
|
84
|
+
strategy: options.strategy,
|
|
85
|
+
filePath: options.filePath ?? './performance-metrics.json',
|
|
86
|
+
remoteEndpoint: options.remoteEndpoint ?? '',
|
|
87
|
+
prometheusEndpoint: options.prometheusEndpoint ?? '/metrics',
|
|
88
|
+
remoteHeaders: options.remoteHeaders ?? {},
|
|
89
|
+
maxEntries: options.maxEntries ?? DEFAULT_MAX_ENTRIES,
|
|
90
|
+
monitorMemory: options.monitorMemory ?? true,
|
|
91
|
+
monitorCPU: options.monitorCPU ?? true,
|
|
92
|
+
monitorNetwork: options.monitorNetwork ?? false,
|
|
93
|
+
batchSize: options.batchSize ?? DEFAULT_BATCH_SIZE,
|
|
94
|
+
flushInterval: options.flushInterval ?? DEFAULT_FLUSH_INTERVAL_MS,
|
|
95
|
+
aggregateStats: options.aggregateStats ?? true,
|
|
96
|
+
aggregationInterval: options.aggregationInterval ?? DEFAULT_AGGREGATION_INTERVAL_MS,
|
|
97
|
+
performanceThreshold: options.performanceThreshold ?? DEFAULT_PERFORMANCE_THRESHOLD_MS,
|
|
98
|
+
// Add plugin options defaults
|
|
99
|
+
category: options.category ?? PluginCategory.MONITORING,
|
|
100
|
+
priority: options.priority ?? PluginPriority.NORMAL,
|
|
101
|
+
moduleEvents: options.moduleEvents ?? [],
|
|
102
|
+
subscribeToAllModuleEvents: options.subscribeToAllModuleEvents ?? false,
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// Initialize storage and metrics collector
|
|
106
|
+
this.storage = createPerformanceStorage(
|
|
107
|
+
this.pluginOptions.strategy,
|
|
108
|
+
this.pluginOptions.maxEntries,
|
|
109
|
+
);
|
|
110
|
+
this.metricsCollector = new NodeSystemMetricsCollector();
|
|
111
|
+
|
|
112
|
+
this.logger.info('PerformancePlugin initialized', {
|
|
113
|
+
strategy: this.pluginOptions.strategy,
|
|
114
|
+
monitorMemory: this.pluginOptions.monitorMemory,
|
|
115
|
+
monitorCPU: this.pluginOptions.monitorCPU,
|
|
116
|
+
performanceThreshold: this.pluginOptions.performanceThreshold,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Records performance metrics from module lifecycle events
|
|
122
|
+
* (initialization, execution, disposal) including duration and error counts.
|
|
123
|
+
*/
|
|
124
|
+
override async onModuleEvent(
|
|
125
|
+
eventName: TEventName,
|
|
126
|
+
eventData: IEventEmitterEventData,
|
|
127
|
+
): Promise<void> {
|
|
128
|
+
try {
|
|
129
|
+
const descriptor = PERFORMANCE_MODULE_EVENT_MAP.get(eventName);
|
|
130
|
+
if (!descriptor) return;
|
|
131
|
+
|
|
132
|
+
const { moduleName, moduleType, duration, success } = extractPerformanceModuleData(
|
|
133
|
+
eventData.data,
|
|
134
|
+
);
|
|
135
|
+
if (duration === undefined) return;
|
|
136
|
+
|
|
137
|
+
await this.recordMetrics({
|
|
138
|
+
operation: descriptor.operation,
|
|
139
|
+
duration,
|
|
140
|
+
success: descriptor.isError ? false : (success ?? true),
|
|
141
|
+
errorCount: descriptor.isError ? 1 : 0,
|
|
142
|
+
...(eventData.executionId && { executionId: eventData.executionId }),
|
|
143
|
+
metadata: {
|
|
144
|
+
moduleName,
|
|
145
|
+
moduleType,
|
|
146
|
+
phase: descriptor.phase,
|
|
147
|
+
...(descriptor.isError && { error: eventData.error?.message || 'unknown error' }),
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
} catch (_error) {
|
|
151
|
+
// Swallow to avoid breaking module event processing
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Records performance metrics, enriching them with system metrics (memory,
|
|
157
|
+
* CPU, network) when the corresponding monitoring options are enabled.
|
|
158
|
+
* @throws PluginError if the storage write fails
|
|
159
|
+
*/
|
|
160
|
+
async recordMetrics(
|
|
161
|
+
metrics: Omit<IPerformanceMetrics, 'timestamp' | 'memoryUsage' | 'cpuUsage' | 'networkStats'>,
|
|
162
|
+
): Promise<void> {
|
|
163
|
+
try {
|
|
164
|
+
const memoryUsage = this.pluginOptions.monitorMemory
|
|
165
|
+
? await this.metricsCollector.getMemoryUsage()
|
|
166
|
+
: undefined;
|
|
167
|
+
const cpuUsage = this.pluginOptions.monitorCPU
|
|
168
|
+
? await this.metricsCollector.getCPUUsage()
|
|
169
|
+
: undefined;
|
|
170
|
+
const networkStats = this.pluginOptions.monitorNetwork
|
|
171
|
+
? await this.metricsCollector.getNetworkStats()
|
|
172
|
+
: undefined;
|
|
173
|
+
|
|
174
|
+
const entry: IPerformanceMetrics = {
|
|
175
|
+
...metrics,
|
|
176
|
+
timestamp: new Date(),
|
|
177
|
+
...(memoryUsage && { memoryUsage }),
|
|
178
|
+
...(cpuUsage && { cpuUsage }),
|
|
179
|
+
...(networkStats && { networkStats }),
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
await this.storage.save(entry);
|
|
183
|
+
|
|
184
|
+
// Log warning if performance threshold exceeded
|
|
185
|
+
if (entry.duration > this.pluginOptions.performanceThreshold) {
|
|
186
|
+
this.logger.warn('Performance threshold exceeded', {
|
|
187
|
+
operation: entry.operation,
|
|
188
|
+
duration: entry.duration,
|
|
189
|
+
threshold: this.pluginOptions.performanceThreshold,
|
|
190
|
+
executionId: entry.executionId,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
this.logger.debug('Performance metrics recorded', {
|
|
195
|
+
operation: entry.operation,
|
|
196
|
+
duration: entry.duration,
|
|
197
|
+
success: entry.success,
|
|
198
|
+
memoryUsed: entry.memoryUsage?.heap.used,
|
|
199
|
+
});
|
|
200
|
+
} catch (error) {
|
|
201
|
+
throw new PluginError('Failed to record performance metrics', this.name, {
|
|
202
|
+
error: error instanceof Error ? error.message : String(error),
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Retrieves recorded metrics, optionally filtered by operation name and time range.
|
|
209
|
+
* @throws PluginError if the storage read fails
|
|
210
|
+
*/
|
|
211
|
+
async getMetrics(
|
|
212
|
+
operation?: string,
|
|
213
|
+
timeRange?: { start: Date; end: Date },
|
|
214
|
+
): Promise<IPerformanceMetrics[]> {
|
|
215
|
+
try {
|
|
216
|
+
return await this.storage.getMetrics(operation, timeRange);
|
|
217
|
+
} catch (error) {
|
|
218
|
+
throw new PluginError('Failed to get performance metrics', this.name, {
|
|
219
|
+
operation: operation || 'all',
|
|
220
|
+
timeRange: timeRange
|
|
221
|
+
? `${timeRange.start.toISOString()}-${timeRange.end.toISOString()}`
|
|
222
|
+
: 'all',
|
|
223
|
+
error: error instanceof Error ? error.message : String(error),
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Get aggregated performance statistics
|
|
230
|
+
*/
|
|
231
|
+
async getAggregatedStats(timeRange?: {
|
|
232
|
+
start: Date;
|
|
233
|
+
end: Date;
|
|
234
|
+
}): Promise<IAggregatedPerformanceStats> {
|
|
235
|
+
try {
|
|
236
|
+
return await this.storage.getAggregatedStats(timeRange);
|
|
237
|
+
} catch (error) {
|
|
238
|
+
throw new PluginError('Failed to get aggregated performance stats', this.name, {
|
|
239
|
+
timeRange: timeRange
|
|
240
|
+
? `${timeRange.start.toISOString()}-${timeRange.end.toISOString()}`
|
|
241
|
+
: 'all',
|
|
242
|
+
error: error instanceof Error ? error.message : String(error),
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Clear all performance metrics
|
|
249
|
+
*/
|
|
250
|
+
async clearMetrics(): Promise<void> {
|
|
251
|
+
try {
|
|
252
|
+
await this.storage.clear();
|
|
253
|
+
this.logger.info('Performance metrics cleared');
|
|
254
|
+
} catch (error) {
|
|
255
|
+
throw new PluginError('Failed to clear performance metrics', this.name, {
|
|
256
|
+
error: error instanceof Error ? error.message : String(error),
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Closes the underlying storage and releases resources.
|
|
263
|
+
*/
|
|
264
|
+
async destroy(): Promise<void> {
|
|
265
|
+
try {
|
|
266
|
+
await this.storage.close();
|
|
267
|
+
this.logger.info('PerformancePlugin destroyed');
|
|
268
|
+
} catch (error) {
|
|
269
|
+
this.logger.error('Error during plugin cleanup', {
|
|
270
|
+
error: error instanceof Error ? error.message : String(error),
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { MemoryPerformanceStorage } from './memory-storage';
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { IPerformanceStorage, IPerformanceMetrics, IAggregatedPerformanceStats } from '../types';
|
|
2
|
+
|
|
3
|
+
export class MemoryPerformanceStorage implements IPerformanceStorage {
|
|
4
|
+
private entries: IPerformanceMetrics[] = [];
|
|
5
|
+
private maxEntries: number;
|
|
6
|
+
|
|
7
|
+
constructor(maxEntries: number = 5000) {
|
|
8
|
+
this.maxEntries = maxEntries;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async save(entry: IPerformanceMetrics): Promise<void> {
|
|
12
|
+
if (this.entries.length >= this.maxEntries) {
|
|
13
|
+
this.entries = this.entries.slice(-this.maxEntries + 1);
|
|
14
|
+
}
|
|
15
|
+
this.entries.push({ ...entry });
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async getMetrics(
|
|
19
|
+
operation?: string,
|
|
20
|
+
timeRange?: { start: Date; end: Date },
|
|
21
|
+
): Promise<IPerformanceMetrics[]> {
|
|
22
|
+
let filtered = [...this.entries];
|
|
23
|
+
if (operation) {
|
|
24
|
+
filtered = filtered.filter((entry) => entry.operation === operation);
|
|
25
|
+
}
|
|
26
|
+
if (timeRange) {
|
|
27
|
+
filtered = filtered.filter(
|
|
28
|
+
(entry) => entry.timestamp >= timeRange.start && entry.timestamp <= timeRange.end,
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
return filtered;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async getAggregatedStats(timeRange?: {
|
|
35
|
+
start: Date;
|
|
36
|
+
end: Date;
|
|
37
|
+
}): Promise<IAggregatedPerformanceStats> {
|
|
38
|
+
const metrics = await this.getMetrics(undefined, timeRange);
|
|
39
|
+
|
|
40
|
+
if (metrics.length === 0) {
|
|
41
|
+
return {
|
|
42
|
+
totalOperations: 0,
|
|
43
|
+
averageDuration: 0,
|
|
44
|
+
minDuration: 0,
|
|
45
|
+
maxDuration: 0,
|
|
46
|
+
successRate: 0,
|
|
47
|
+
errorRate: 0,
|
|
48
|
+
operationStats: {},
|
|
49
|
+
timeRangeStats: {
|
|
50
|
+
startTime: timeRange?.start || new Date(),
|
|
51
|
+
endTime: timeRange?.end || new Date(),
|
|
52
|
+
period: 'empty',
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const durations = metrics.map((m) => m.duration);
|
|
58
|
+
const successCount = metrics.filter((m) => m.success).length;
|
|
59
|
+
const errorCount = metrics.reduce((sum, m) => sum + m.errorCount, 0);
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
totalOperations: metrics.length,
|
|
63
|
+
averageDuration: durations.reduce((sum, d) => sum + d, 0) / durations.length,
|
|
64
|
+
minDuration: Math.min(...durations),
|
|
65
|
+
maxDuration: Math.max(...durations),
|
|
66
|
+
successRate: successCount / metrics.length,
|
|
67
|
+
errorRate: errorCount / metrics.length,
|
|
68
|
+
operationStats: {},
|
|
69
|
+
timeRangeStats: {
|
|
70
|
+
startTime: timeRange?.start || metrics[0]?.timestamp || new Date(),
|
|
71
|
+
endTime: timeRange?.end || metrics[metrics.length - 1]?.timestamp || new Date(),
|
|
72
|
+
period: 'memory',
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async clear(): Promise<void> {
|
|
78
|
+
this.entries = [];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async flush(): Promise<void> {
|
|
82
|
+
// Memory storage doesn't need flushing
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async close(): Promise<void> {
|
|
86
|
+
// Memory storage doesn't need closing
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Performance monitoring strategy types
|
|
3
|
+
*/
|
|
4
|
+
export type TPerformanceMonitoringStrategy = 'memory' | 'file' | 'prometheus' | 'remote' | 'silent';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Performance metrics entry
|
|
8
|
+
*/
|
|
9
|
+
export interface IPerformanceMetrics {
|
|
10
|
+
executionId?: string;
|
|
11
|
+
conversationId?: string;
|
|
12
|
+
timestamp: Date;
|
|
13
|
+
operation: string; // 'conversation', 'tool_execution', 'provider_call', etc.
|
|
14
|
+
duration: number; // in milliseconds
|
|
15
|
+
memoryUsage?: {
|
|
16
|
+
used: number;
|
|
17
|
+
free: number;
|
|
18
|
+
total: number;
|
|
19
|
+
heap: {
|
|
20
|
+
used: number;
|
|
21
|
+
total: number;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
cpuUsage?: {
|
|
25
|
+
user: number;
|
|
26
|
+
system: number;
|
|
27
|
+
percent: number;
|
|
28
|
+
};
|
|
29
|
+
networkStats?: {
|
|
30
|
+
requests: number;
|
|
31
|
+
bytesReceived: number;
|
|
32
|
+
bytesSent: number;
|
|
33
|
+
latency: number;
|
|
34
|
+
};
|
|
35
|
+
errorCount: number;
|
|
36
|
+
success: boolean;
|
|
37
|
+
metadata?: Record<string, string | number | boolean | Date>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Aggregated performance statistics
|
|
42
|
+
*/
|
|
43
|
+
export interface IAggregatedPerformanceStats {
|
|
44
|
+
totalOperations: number;
|
|
45
|
+
averageDuration: number;
|
|
46
|
+
minDuration: number;
|
|
47
|
+
maxDuration: number;
|
|
48
|
+
successRate: number;
|
|
49
|
+
errorRate: number;
|
|
50
|
+
memoryStats?: {
|
|
51
|
+
averageUsed: number;
|
|
52
|
+
maxUsed: number;
|
|
53
|
+
averageHeapUsed: number;
|
|
54
|
+
maxHeapUsed: number;
|
|
55
|
+
};
|
|
56
|
+
cpuStats?: {
|
|
57
|
+
averagePercent: number;
|
|
58
|
+
maxPercent: number;
|
|
59
|
+
};
|
|
60
|
+
networkStats?: {
|
|
61
|
+
totalRequests: number;
|
|
62
|
+
totalBytesReceived: number;
|
|
63
|
+
totalBytesSent: number;
|
|
64
|
+
averageLatency: number;
|
|
65
|
+
};
|
|
66
|
+
operationStats: Record<
|
|
67
|
+
string,
|
|
68
|
+
{
|
|
69
|
+
count: number;
|
|
70
|
+
averageDuration: number;
|
|
71
|
+
successRate: number;
|
|
72
|
+
errorCount: number;
|
|
73
|
+
}
|
|
74
|
+
>;
|
|
75
|
+
timeRangeStats: {
|
|
76
|
+
startTime: Date;
|
|
77
|
+
endTime: Date;
|
|
78
|
+
period: string;
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
import type { IPluginOptions, IPluginStats } from '@robota-sdk/agent-core';
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Configuration options for performance plugin
|
|
86
|
+
*/
|
|
87
|
+
export interface IPerformancePluginOptions extends IPluginOptions {
|
|
88
|
+
/** Performance monitoring strategy to use */
|
|
89
|
+
strategy: TPerformanceMonitoringStrategy;
|
|
90
|
+
/** File path for file strategy */
|
|
91
|
+
filePath?: string;
|
|
92
|
+
/** Remote endpoint for remote strategy */
|
|
93
|
+
remoteEndpoint?: string;
|
|
94
|
+
/** Prometheus endpoint for prometheus strategy */
|
|
95
|
+
prometheusEndpoint?: string;
|
|
96
|
+
/** Headers for remote monitoring */
|
|
97
|
+
remoteHeaders?: Record<string, string>;
|
|
98
|
+
/** Maximum number of performance entries to keep in memory */
|
|
99
|
+
maxEntries?: number;
|
|
100
|
+
/** Whether to monitor memory usage */
|
|
101
|
+
monitorMemory?: boolean;
|
|
102
|
+
/** Whether to monitor CPU usage */
|
|
103
|
+
monitorCPU?: boolean;
|
|
104
|
+
/** Whether to monitor network stats */
|
|
105
|
+
monitorNetwork?: boolean;
|
|
106
|
+
/** Batch size for remote reporting */
|
|
107
|
+
batchSize?: number;
|
|
108
|
+
/** Flush interval for batched reporting in milliseconds */
|
|
109
|
+
flushInterval?: number;
|
|
110
|
+
/** Whether to aggregate statistics */
|
|
111
|
+
aggregateStats?: boolean;
|
|
112
|
+
/** Aggregation interval in milliseconds */
|
|
113
|
+
aggregationInterval?: number;
|
|
114
|
+
/** Performance threshold in milliseconds to log warnings */
|
|
115
|
+
performanceThreshold?: number;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Performance storage interface
|
|
120
|
+
*/
|
|
121
|
+
export interface IPerformanceStorage {
|
|
122
|
+
save(entry: IPerformanceMetrics): Promise<void>;
|
|
123
|
+
getMetrics(
|
|
124
|
+
operation?: string,
|
|
125
|
+
timeRange?: { start: Date; end: Date },
|
|
126
|
+
): Promise<IPerformanceMetrics[]>;
|
|
127
|
+
getAggregatedStats(timeRange?: { start: Date; end: Date }): Promise<IAggregatedPerformanceStats>;
|
|
128
|
+
clear(): Promise<void>;
|
|
129
|
+
flush(): Promise<void>;
|
|
130
|
+
close(): Promise<void>;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* System metrics collector interface
|
|
135
|
+
*/
|
|
136
|
+
export interface ISystemMetricsCollector {
|
|
137
|
+
getMemoryUsage(): Promise<IPerformanceMetrics['memoryUsage']>;
|
|
138
|
+
getCPUUsage(): Promise<IPerformanceMetrics['cpuUsage']>;
|
|
139
|
+
getNetworkStats(): Promise<IPerformanceMetrics['networkStats']>;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Performance plugin statistics
|
|
144
|
+
*/
|
|
145
|
+
export interface IPerformancePluginStats extends IPluginStats {
|
|
146
|
+
/** Total number of metrics recorded */
|
|
147
|
+
metricsRecorded: number;
|
|
148
|
+
/** Number of performance threshold violations */
|
|
149
|
+
thresholdViolations: number;
|
|
150
|
+
/** Current monitoring strategy */
|
|
151
|
+
strategy: TPerformanceMonitoringStrategy;
|
|
152
|
+
/** Monitoring status */
|
|
153
|
+
monitoring: {
|
|
154
|
+
memory: boolean;
|
|
155
|
+
cpu: boolean;
|
|
156
|
+
network: boolean;
|
|
157
|
+
};
|
|
158
|
+
/** Last metrics collection timestamp */
|
|
159
|
+
lastCollectionTime?: Date;
|
|
160
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { aggregateUsageStats } from '../aggregate-usage-stats';
|
|
3
|
+
import type { IUsageStats } from '../types';
|
|
4
|
+
|
|
5
|
+
function makeEntry(overrides: Partial<IUsageStats> = {}): IUsageStats {
|
|
6
|
+
return {
|
|
7
|
+
provider: 'openai',
|
|
8
|
+
model: 'gpt-4',
|
|
9
|
+
tokensUsed: { input: 100, output: 50, total: 150 },
|
|
10
|
+
requestCount: 1,
|
|
11
|
+
duration: 500,
|
|
12
|
+
success: true,
|
|
13
|
+
timestamp: new Date('2025-01-01T10:00:00Z'),
|
|
14
|
+
...overrides,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
describe('aggregateUsageStats', () => {
|
|
19
|
+
it('returns empty aggregation for no entries', () => {
|
|
20
|
+
const result = aggregateUsageStats([]);
|
|
21
|
+
expect(result.totalRequests).toBe(0);
|
|
22
|
+
expect(result.totalTokens).toBe(0);
|
|
23
|
+
expect(result.totalCost).toBe(0);
|
|
24
|
+
expect(result.successRate).toBe(0);
|
|
25
|
+
expect(result.timeRangeStats.period).toBe('all');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('aggregates single entry correctly', () => {
|
|
29
|
+
const entry = makeEntry({ cost: { input: 1, output: 2, total: 3 } });
|
|
30
|
+
const result = aggregateUsageStats([entry]);
|
|
31
|
+
expect(result.totalRequests).toBe(1);
|
|
32
|
+
expect(result.totalTokens).toBe(150);
|
|
33
|
+
expect(result.totalCost).toBe(3);
|
|
34
|
+
expect(result.totalDuration).toBe(500);
|
|
35
|
+
expect(result.successRate).toBe(1);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('aggregates multiple entries', () => {
|
|
39
|
+
const entries = [
|
|
40
|
+
makeEntry({ success: true }),
|
|
41
|
+
makeEntry({
|
|
42
|
+
provider: 'anthropic',
|
|
43
|
+
model: 'claude-3',
|
|
44
|
+
success: false,
|
|
45
|
+
tokensUsed: { input: 200, output: 100, total: 300 },
|
|
46
|
+
}),
|
|
47
|
+
];
|
|
48
|
+
const result = aggregateUsageStats(entries);
|
|
49
|
+
expect(result.totalRequests).toBe(2);
|
|
50
|
+
expect(result.totalTokens).toBe(450);
|
|
51
|
+
expect(result.successRate).toBe(0.5);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('aggregates by provider', () => {
|
|
55
|
+
const entries = [
|
|
56
|
+
makeEntry({
|
|
57
|
+
provider: 'openai',
|
|
58
|
+
requestCount: 1,
|
|
59
|
+
tokensUsed: { input: 100, output: 50, total: 150 },
|
|
60
|
+
}),
|
|
61
|
+
makeEntry({
|
|
62
|
+
provider: 'openai',
|
|
63
|
+
requestCount: 2,
|
|
64
|
+
tokensUsed: { input: 200, output: 100, total: 300 },
|
|
65
|
+
}),
|
|
66
|
+
makeEntry({
|
|
67
|
+
provider: 'anthropic',
|
|
68
|
+
requestCount: 1,
|
|
69
|
+
tokensUsed: { input: 50, output: 25, total: 75 },
|
|
70
|
+
}),
|
|
71
|
+
];
|
|
72
|
+
const result = aggregateUsageStats(entries);
|
|
73
|
+
expect(result.providerStats['openai']?.requests).toBe(3);
|
|
74
|
+
expect(result.providerStats['openai']?.tokens).toBe(450);
|
|
75
|
+
expect(result.providerStats['anthropic']?.requests).toBe(1);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('aggregates by model', () => {
|
|
79
|
+
const entries = [
|
|
80
|
+
makeEntry({ model: 'gpt-4', requestCount: 1 }),
|
|
81
|
+
makeEntry({ model: 'gpt-3.5', requestCount: 2 }),
|
|
82
|
+
];
|
|
83
|
+
const result = aggregateUsageStats(entries);
|
|
84
|
+
expect(result.modelStats['gpt-4']?.requests).toBe(1);
|
|
85
|
+
expect(result.modelStats['gpt-3.5']?.requests).toBe(2);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('aggregates by tools', () => {
|
|
89
|
+
const entries = [
|
|
90
|
+
makeEntry({ toolsUsed: ['search', 'calc'], success: true, duration: 100 }),
|
|
91
|
+
makeEntry({ toolsUsed: ['search'], success: false, duration: 200 }),
|
|
92
|
+
];
|
|
93
|
+
const result = aggregateUsageStats(entries);
|
|
94
|
+
expect(result.toolStats['search']?.usageCount).toBe(2);
|
|
95
|
+
expect(result.toolStats['search']?.successCount).toBe(1);
|
|
96
|
+
expect(result.toolStats['calc']?.usageCount).toBe(1);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('determines period from time range', () => {
|
|
100
|
+
const now = new Date();
|
|
101
|
+
const halfHourAgo = new Date(now.getTime() - 30 * 60 * 1000);
|
|
102
|
+
const result = aggregateUsageStats([], { start: halfHourAgo, end: now });
|
|
103
|
+
expect(result.timeRangeStats.period).toBe('hour');
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('determines day period', () => {
|
|
107
|
+
const now = new Date();
|
|
108
|
+
const sixHoursAgo = new Date(now.getTime() - 6 * 60 * 60 * 1000);
|
|
109
|
+
const result = aggregateUsageStats([], { start: sixHoursAgo, end: now });
|
|
110
|
+
expect(result.timeRangeStats.period).toBe('day');
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('determines week period', () => {
|
|
114
|
+
const now = new Date();
|
|
115
|
+
const threeDaysAgo = new Date(now.getTime() - 3 * 24 * 60 * 60 * 1000);
|
|
116
|
+
const result = aggregateUsageStats([], { start: threeDaysAgo, end: now });
|
|
117
|
+
expect(result.timeRangeStats.period).toBe('week');
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('determines month period', () => {
|
|
121
|
+
const now = new Date();
|
|
122
|
+
const twoWeeksAgo = new Date(now.getTime() - 14 * 24 * 60 * 60 * 1000);
|
|
123
|
+
const result = aggregateUsageStats([], { start: twoWeeksAgo, end: now });
|
|
124
|
+
expect(result.timeRangeStats.period).toBe('month');
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('uses entry timestamps when no time range given', () => {
|
|
128
|
+
const entries = [
|
|
129
|
+
makeEntry({ timestamp: new Date('2025-01-01') }),
|
|
130
|
+
makeEntry({ timestamp: new Date('2025-01-05') }),
|
|
131
|
+
];
|
|
132
|
+
const result = aggregateUsageStats(entries);
|
|
133
|
+
expect(result.timeRangeStats.startTime).toEqual(new Date('2025-01-01'));
|
|
134
|
+
expect(result.timeRangeStats.endTime).toEqual(new Date('2025-01-05'));
|
|
135
|
+
});
|
|
136
|
+
});
|