@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,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
+ });