@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,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logging strategy types
|
|
3
|
+
*/
|
|
4
|
+
export type TLoggingStrategy = 'console' | 'file' | 'remote' | 'silent';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Log levels
|
|
8
|
+
*/
|
|
9
|
+
export type TLogLevel = 'debug' | 'info' | 'warn' | 'error';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Log entry structure
|
|
13
|
+
*/
|
|
14
|
+
export interface ILogEntry {
|
|
15
|
+
timestamp: Date;
|
|
16
|
+
level: TLogLevel;
|
|
17
|
+
message: string;
|
|
18
|
+
context?: Record<string, string | number | boolean | Date | undefined>;
|
|
19
|
+
metadata?: {
|
|
20
|
+
executionId?: string;
|
|
21
|
+
conversationId?: string;
|
|
22
|
+
userId?: string;
|
|
23
|
+
sessionId?: string;
|
|
24
|
+
operation?: string;
|
|
25
|
+
duration?: number;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
import type { IPluginOptions, IPluginStats, ILogger } from '@robota-sdk/agent-core';
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Configuration options for logging plugin
|
|
33
|
+
*/
|
|
34
|
+
export interface ILoggingPluginOptions extends IPluginOptions {
|
|
35
|
+
/** Logging strategy to use */
|
|
36
|
+
strategy: TLoggingStrategy;
|
|
37
|
+
/** Minimum log level to capture */
|
|
38
|
+
level?: TLogLevel;
|
|
39
|
+
/** File path for file strategy */
|
|
40
|
+
filePath?: string;
|
|
41
|
+
/** Remote endpoint for remote strategy */
|
|
42
|
+
remoteEndpoint?: string;
|
|
43
|
+
/** Headers for remote logging */
|
|
44
|
+
remoteHeaders?: Record<string, string>;
|
|
45
|
+
/** Maximum number of logs to keep in memory */
|
|
46
|
+
maxLogs?: number;
|
|
47
|
+
/** Whether to include stack traces in error logs */
|
|
48
|
+
includeStackTrace?: boolean;
|
|
49
|
+
/** Custom log formatter */
|
|
50
|
+
formatter?: ILogFormatter;
|
|
51
|
+
/** Logger instance for internal plugin logging */
|
|
52
|
+
logger?: ILogger;
|
|
53
|
+
/** Batch size for remote logging */
|
|
54
|
+
batchSize?: number;
|
|
55
|
+
/** Flush interval for batched logging in milliseconds */
|
|
56
|
+
flushInterval?: number;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Log formatter interface
|
|
61
|
+
*/
|
|
62
|
+
export interface ILogFormatter {
|
|
63
|
+
format(entry: ILogEntry): string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Log storage interface
|
|
68
|
+
*/
|
|
69
|
+
export interface ILogStorage {
|
|
70
|
+
write(entry: ILogEntry): Promise<void>;
|
|
71
|
+
flush(): Promise<void>;
|
|
72
|
+
close(): Promise<void>;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Structured context data for log entries */
|
|
76
|
+
export interface ILoggingContextData
|
|
77
|
+
extends Record<string, string | number | boolean | Date | undefined> {
|
|
78
|
+
userInput?: string;
|
|
79
|
+
duration?: number;
|
|
80
|
+
toolName?: string;
|
|
81
|
+
success?: boolean;
|
|
82
|
+
executionId?: string;
|
|
83
|
+
operation?: string;
|
|
84
|
+
errorMessage?: string;
|
|
85
|
+
errorStack?: string;
|
|
86
|
+
inputLength?: number;
|
|
87
|
+
responseLength?: number;
|
|
88
|
+
hasOptions?: boolean;
|
|
89
|
+
modelName?: string;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Logging plugin statistics
|
|
94
|
+
*/
|
|
95
|
+
export interface ILoggingPluginStats extends IPluginStats {
|
|
96
|
+
/** Total number of logs written */
|
|
97
|
+
logsWritten: number;
|
|
98
|
+
/** Number of failed log writes */
|
|
99
|
+
failedWrites: number;
|
|
100
|
+
/** Current log level */
|
|
101
|
+
currentLevel: TLogLevel;
|
|
102
|
+
/** Storage strategy in use */
|
|
103
|
+
strategy: TLoggingStrategy;
|
|
104
|
+
/** Last flush timestamp */
|
|
105
|
+
lastFlushTime?: Date;
|
|
106
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
+
import { MemoryPerformanceStorage } from '../storages/memory-storage';
|
|
3
|
+
import type { IPerformanceMetrics } from '../types';
|
|
4
|
+
|
|
5
|
+
function makeMetric(overrides: Partial<IPerformanceMetrics> = {}): IPerformanceMetrics {
|
|
6
|
+
return {
|
|
7
|
+
operation: 'test-op',
|
|
8
|
+
duration: 100,
|
|
9
|
+
success: true,
|
|
10
|
+
errorCount: 0,
|
|
11
|
+
timestamp: new Date(),
|
|
12
|
+
...overrides,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
describe('MemoryPerformanceStorage', () => {
|
|
17
|
+
let storage: MemoryPerformanceStorage;
|
|
18
|
+
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
storage = new MemoryPerformanceStorage(100);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('saves and retrieves entries', async () => {
|
|
24
|
+
await storage.save(makeMetric());
|
|
25
|
+
const metrics = await storage.getMetrics();
|
|
26
|
+
expect(metrics).toHaveLength(1);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('evicts oldest when maxEntries exceeded', async () => {
|
|
30
|
+
storage = new MemoryPerformanceStorage(2);
|
|
31
|
+
await storage.save(makeMetric({ operation: 'first' }));
|
|
32
|
+
await storage.save(makeMetric({ operation: 'second' }));
|
|
33
|
+
await storage.save(makeMetric({ operation: 'third' }));
|
|
34
|
+
const metrics = await storage.getMetrics();
|
|
35
|
+
expect(metrics).toHaveLength(2);
|
|
36
|
+
expect(metrics.some((m) => m.operation === 'first')).toBe(false);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('filters by operation', async () => {
|
|
40
|
+
await storage.save(makeMetric({ operation: 'op-a' }));
|
|
41
|
+
await storage.save(makeMetric({ operation: 'op-b' }));
|
|
42
|
+
const metrics = await storage.getMetrics('op-a');
|
|
43
|
+
expect(metrics).toHaveLength(1);
|
|
44
|
+
expect(metrics[0].operation).toBe('op-a');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('filters by time range', async () => {
|
|
48
|
+
await storage.save(makeMetric({ timestamp: new Date('2024-01-01') }));
|
|
49
|
+
await storage.save(makeMetric({ timestamp: new Date('2025-06-01') }));
|
|
50
|
+
const metrics = await storage.getMetrics(undefined, {
|
|
51
|
+
start: new Date('2025-01-01'),
|
|
52
|
+
end: new Date('2026-01-01'),
|
|
53
|
+
});
|
|
54
|
+
expect(metrics).toHaveLength(1);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('returns aggregated stats', async () => {
|
|
58
|
+
await storage.save(makeMetric({ duration: 100, success: true, errorCount: 0 }));
|
|
59
|
+
await storage.save(makeMetric({ duration: 300, success: false, errorCount: 2 }));
|
|
60
|
+
const stats = await storage.getAggregatedStats();
|
|
61
|
+
expect(stats.totalOperations).toBe(2);
|
|
62
|
+
expect(stats.averageDuration).toBe(200);
|
|
63
|
+
expect(stats.minDuration).toBe(100);
|
|
64
|
+
expect(stats.maxDuration).toBe(300);
|
|
65
|
+
expect(stats.successRate).toBe(0.5);
|
|
66
|
+
expect(stats.errorRate).toBe(1);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('returns zeroed aggregated stats for empty data', async () => {
|
|
70
|
+
const stats = await storage.getAggregatedStats();
|
|
71
|
+
expect(stats.totalOperations).toBe(0);
|
|
72
|
+
expect(stats.averageDuration).toBe(0);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('clear removes all entries', async () => {
|
|
76
|
+
await storage.save(makeMetric());
|
|
77
|
+
await storage.clear();
|
|
78
|
+
const metrics = await storage.getMetrics();
|
|
79
|
+
expect(metrics).toHaveLength(0);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('flush and close are no-ops', async () => {
|
|
83
|
+
await expect(storage.flush()).resolves.toBeUndefined();
|
|
84
|
+
await expect(storage.close()).resolves.toBeUndefined();
|
|
85
|
+
});
|
|
86
|
+
});
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { PerformancePlugin } from '../performance-plugin';
|
|
3
|
+
import { ConfigurationError, PluginError } from '@robota-sdk/agent-core';
|
|
4
|
+
|
|
5
|
+
describe('PerformancePlugin', () => {
|
|
6
|
+
let plugin: PerformancePlugin;
|
|
7
|
+
|
|
8
|
+
afterEach(async () => {
|
|
9
|
+
if (plugin) {
|
|
10
|
+
await plugin.destroy();
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
describe('constructor', () => {
|
|
15
|
+
it('creates plugin with memory strategy', () => {
|
|
16
|
+
plugin = new PerformancePlugin({ strategy: 'memory' });
|
|
17
|
+
expect(plugin.name).toBe('PerformancePlugin');
|
|
18
|
+
expect(plugin.version).toBe('1.0.0');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('throws ConfigurationError for missing strategy', () => {
|
|
22
|
+
expect(() => new PerformancePlugin({ strategy: '' as any })).toThrow(ConfigurationError);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('throws ConfigurationError for invalid strategy', () => {
|
|
26
|
+
expect(() => new PerformancePlugin({ strategy: 'invalid' as any })).toThrow(
|
|
27
|
+
ConfigurationError,
|
|
28
|
+
);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('throws ConfigurationError for unimplemented strategy', () => {
|
|
32
|
+
// file, prometheus, remote, silent are valid but not implemented
|
|
33
|
+
expect(() => new PerformancePlugin({ strategy: 'file' })).toThrow(ConfigurationError);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe('recordMetrics', () => {
|
|
38
|
+
beforeEach(() => {
|
|
39
|
+
plugin = new PerformancePlugin({
|
|
40
|
+
strategy: 'memory',
|
|
41
|
+
monitorMemory: false,
|
|
42
|
+
monitorCPU: false,
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('records a metric entry', async () => {
|
|
47
|
+
await plugin.recordMetrics({
|
|
48
|
+
operation: 'test-op',
|
|
49
|
+
duration: 100,
|
|
50
|
+
success: true,
|
|
51
|
+
errorCount: 0,
|
|
52
|
+
});
|
|
53
|
+
const metrics = await plugin.getMetrics();
|
|
54
|
+
expect(metrics).toHaveLength(1);
|
|
55
|
+
expect(metrics[0].operation).toBe('test-op');
|
|
56
|
+
expect(metrics[0].duration).toBe(100);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('records with system metrics when monitoring enabled', async () => {
|
|
60
|
+
plugin = new PerformancePlugin({
|
|
61
|
+
strategy: 'memory',
|
|
62
|
+
monitorMemory: true,
|
|
63
|
+
monitorCPU: true,
|
|
64
|
+
monitorNetwork: true,
|
|
65
|
+
});
|
|
66
|
+
await plugin.recordMetrics({
|
|
67
|
+
operation: 'test-op',
|
|
68
|
+
duration: 50,
|
|
69
|
+
success: true,
|
|
70
|
+
errorCount: 0,
|
|
71
|
+
});
|
|
72
|
+
const metrics = await plugin.getMetrics();
|
|
73
|
+
expect(metrics).toHaveLength(1);
|
|
74
|
+
// Memory and CPU should be populated from NodeSystemMetricsCollector
|
|
75
|
+
// Network will be undefined as it is not fully implemented
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
describe('getMetrics', () => {
|
|
80
|
+
beforeEach(async () => {
|
|
81
|
+
plugin = new PerformancePlugin({
|
|
82
|
+
strategy: 'memory',
|
|
83
|
+
monitorMemory: false,
|
|
84
|
+
monitorCPU: false,
|
|
85
|
+
});
|
|
86
|
+
await plugin.recordMetrics({
|
|
87
|
+
operation: 'op-a',
|
|
88
|
+
duration: 100,
|
|
89
|
+
success: true,
|
|
90
|
+
errorCount: 0,
|
|
91
|
+
});
|
|
92
|
+
await plugin.recordMetrics({
|
|
93
|
+
operation: 'op-b',
|
|
94
|
+
duration: 200,
|
|
95
|
+
success: false,
|
|
96
|
+
errorCount: 1,
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('returns all metrics with no filter', async () => {
|
|
101
|
+
const metrics = await plugin.getMetrics();
|
|
102
|
+
expect(metrics).toHaveLength(2);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('filters by operation', async () => {
|
|
106
|
+
const metrics = await plugin.getMetrics('op-a');
|
|
107
|
+
expect(metrics).toHaveLength(1);
|
|
108
|
+
expect(metrics[0].operation).toBe('op-a');
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
describe('getAggregatedStats', () => {
|
|
113
|
+
beforeEach(async () => {
|
|
114
|
+
plugin = new PerformancePlugin({
|
|
115
|
+
strategy: 'memory',
|
|
116
|
+
monitorMemory: false,
|
|
117
|
+
monitorCPU: false,
|
|
118
|
+
});
|
|
119
|
+
await plugin.recordMetrics({ operation: 'op', duration: 100, success: true, errorCount: 0 });
|
|
120
|
+
await plugin.recordMetrics({ operation: 'op', duration: 300, success: false, errorCount: 2 });
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('aggregates correctly', async () => {
|
|
124
|
+
const stats = await plugin.getAggregatedStats();
|
|
125
|
+
expect(stats.totalOperations).toBe(2);
|
|
126
|
+
expect(stats.averageDuration).toBe(200);
|
|
127
|
+
expect(stats.minDuration).toBe(100);
|
|
128
|
+
expect(stats.maxDuration).toBe(300);
|
|
129
|
+
expect(stats.successRate).toBe(0.5);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('returns zeroed stats for empty data', async () => {
|
|
133
|
+
await plugin.clearMetrics();
|
|
134
|
+
const stats = await plugin.getAggregatedStats();
|
|
135
|
+
expect(stats.totalOperations).toBe(0);
|
|
136
|
+
expect(stats.averageDuration).toBe(0);
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
describe('clearMetrics', () => {
|
|
141
|
+
it('clears all metrics', async () => {
|
|
142
|
+
plugin = new PerformancePlugin({
|
|
143
|
+
strategy: 'memory',
|
|
144
|
+
monitorMemory: false,
|
|
145
|
+
monitorCPU: false,
|
|
146
|
+
});
|
|
147
|
+
await plugin.recordMetrics({ operation: 'op', duration: 100, success: true, errorCount: 0 });
|
|
148
|
+
await plugin.clearMetrics();
|
|
149
|
+
const metrics = await plugin.getMetrics();
|
|
150
|
+
expect(metrics).toHaveLength(0);
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
describe('onModuleEvent', () => {
|
|
155
|
+
beforeEach(() => {
|
|
156
|
+
plugin = new PerformancePlugin({
|
|
157
|
+
strategy: 'memory',
|
|
158
|
+
monitorMemory: false,
|
|
159
|
+
monitorCPU: false,
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('records metrics from module execution complete event', async () => {
|
|
164
|
+
await plugin.onModuleEvent('module.execution.complete', {
|
|
165
|
+
type: 'module.execution.complete',
|
|
166
|
+
data: { moduleName: 'test-module', moduleType: 'processing', duration: 250, success: true },
|
|
167
|
+
timestamp: new Date(),
|
|
168
|
+
});
|
|
169
|
+
const metrics = await plugin.getMetrics();
|
|
170
|
+
expect(metrics).toHaveLength(1);
|
|
171
|
+
expect(metrics[0].operation).toBe('module_execution');
|
|
172
|
+
expect(metrics[0].duration).toBe(250);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('records error metrics from module error events', async () => {
|
|
176
|
+
await plugin.onModuleEvent('module.execution.error', {
|
|
177
|
+
type: 'module.execution.error',
|
|
178
|
+
data: { moduleName: 'test-module', moduleType: 'processing', duration: 50 },
|
|
179
|
+
error: new Error('module failed'),
|
|
180
|
+
timestamp: new Date(),
|
|
181
|
+
});
|
|
182
|
+
const metrics = await plugin.getMetrics();
|
|
183
|
+
expect(metrics).toHaveLength(1);
|
|
184
|
+
expect(metrics[0].errorCount).toBe(1);
|
|
185
|
+
expect(metrics[0].success).toBe(false);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('ignores unrecognized events', async () => {
|
|
189
|
+
await plugin.onModuleEvent('some.unknown.event' as any, {
|
|
190
|
+
type: 'module.execution.complete' as any,
|
|
191
|
+
data: { duration: 100 },
|
|
192
|
+
timestamp: new Date(),
|
|
193
|
+
});
|
|
194
|
+
const metrics = await plugin.getMetrics();
|
|
195
|
+
expect(metrics).toHaveLength(0);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it('ignores events without duration', async () => {
|
|
199
|
+
await plugin.onModuleEvent('module.execution.complete', {
|
|
200
|
+
type: 'module.execution.complete',
|
|
201
|
+
data: { moduleName: 'test-module', moduleType: 'processing' },
|
|
202
|
+
timestamp: new Date(),
|
|
203
|
+
});
|
|
204
|
+
const metrics = await plugin.getMetrics();
|
|
205
|
+
expect(metrics).toHaveLength(0);
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { NodeSystemMetricsCollector } from '../collectors/system-metrics-collector';
|
|
3
|
+
|
|
4
|
+
describe('NodeSystemMetricsCollector', () => {
|
|
5
|
+
const collector = new NodeSystemMetricsCollector();
|
|
6
|
+
|
|
7
|
+
describe('getMemoryUsage', () => {
|
|
8
|
+
it('returns memory usage data', async () => {
|
|
9
|
+
const usage = await collector.getMemoryUsage();
|
|
10
|
+
expect(usage).toBeDefined();
|
|
11
|
+
expect(usage!.used).toBeGreaterThan(0);
|
|
12
|
+
expect(usage!.heap.used).toBeGreaterThan(0);
|
|
13
|
+
expect(usage!.heap.total).toBeGreaterThan(0);
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
describe('getCPUUsage', () => {
|
|
18
|
+
it('returns CPU usage data', async () => {
|
|
19
|
+
const usage = await collector.getCPUUsage();
|
|
20
|
+
expect(usage).toBeDefined();
|
|
21
|
+
expect(typeof usage!.user).toBe('number');
|
|
22
|
+
expect(typeof usage!.system).toBe('number');
|
|
23
|
+
expect(usage!.percent).toBe(0); // Basic implementation returns 0
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe('getNetworkStats', () => {
|
|
28
|
+
it('returns undefined (not fully implemented)', async () => {
|
|
29
|
+
const stats = await collector.getNetworkStats();
|
|
30
|
+
expect(stats).toBeUndefined();
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
});
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { type ISystemMetricsCollector, type IPerformanceMetrics } from '../types';
|
|
2
|
+
import { createLogger, type ILogger } from '@robota-sdk/agent-core';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Node.js system metrics collector
|
|
6
|
+
*/
|
|
7
|
+
export class NodeSystemMetricsCollector implements ISystemMetricsCollector {
|
|
8
|
+
private logger: ILogger;
|
|
9
|
+
|
|
10
|
+
constructor() {
|
|
11
|
+
this.logger = createLogger('NodeSystemMetricsCollector');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async getMemoryUsage(): Promise<IPerformanceMetrics['memoryUsage']> {
|
|
15
|
+
try {
|
|
16
|
+
const memoryUsage = process.memoryUsage();
|
|
17
|
+
|
|
18
|
+
// Note: This is a basic implementation for Node.js
|
|
19
|
+
// In production, you might want to use more sophisticated system monitoring libraries
|
|
20
|
+
return {
|
|
21
|
+
used: memoryUsage.rss,
|
|
22
|
+
free: 0, // Not directly available in Node.js without additional libraries
|
|
23
|
+
total: memoryUsage.rss + memoryUsage.external,
|
|
24
|
+
heap: {
|
|
25
|
+
used: memoryUsage.heapUsed,
|
|
26
|
+
total: memoryUsage.heapTotal,
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
} catch (error) {
|
|
30
|
+
this.logger.warn('Failed to get memory usage', {
|
|
31
|
+
error: error instanceof Error ? error.message : String(error),
|
|
32
|
+
});
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async getCPUUsage(): Promise<IPerformanceMetrics['cpuUsage']> {
|
|
38
|
+
try {
|
|
39
|
+
const cpuUsage = process.cpuUsage();
|
|
40
|
+
|
|
41
|
+
// Note: This is a basic implementation for Node.js
|
|
42
|
+
// For more accurate CPU percentage, you'd need to measure over time intervals
|
|
43
|
+
return {
|
|
44
|
+
user: cpuUsage.user,
|
|
45
|
+
system: cpuUsage.system,
|
|
46
|
+
percent: 0, // Would need time-based calculation for actual percentage
|
|
47
|
+
};
|
|
48
|
+
} catch (error) {
|
|
49
|
+
this.logger.warn('Failed to get CPU usage', {
|
|
50
|
+
error: error instanceof Error ? error.message : String(error),
|
|
51
|
+
});
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async getNetworkStats(): Promise<IPerformanceMetrics['networkStats']> {
|
|
57
|
+
try {
|
|
58
|
+
// Note: Network stats are not directly available in Node.js without additional monitoring
|
|
59
|
+
// This would typically require integrating with system monitoring tools or libraries
|
|
60
|
+
this.logger.warn('Network stats monitoring not fully implemented yet');
|
|
61
|
+
return undefined;
|
|
62
|
+
} catch (error) {
|
|
63
|
+
this.logger.warn('Failed to get network stats', {
|
|
64
|
+
error: error instanceof Error ? error.message : String(error),
|
|
65
|
+
});
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export { PerformancePlugin } from './performance-plugin';
|
|
2
|
+
export { MemoryPerformanceStorage } from './storages/memory-storage';
|
|
3
|
+
export { NodeSystemMetricsCollector } from './collectors/system-metrics-collector';
|
|
4
|
+
export type {
|
|
5
|
+
TPerformanceMonitoringStrategy,
|
|
6
|
+
IPerformanceMetrics,
|
|
7
|
+
IAggregatedPerformanceStats,
|
|
8
|
+
IPerformancePluginOptions,
|
|
9
|
+
IPerformancePluginStats,
|
|
10
|
+
IPerformanceStorage,
|
|
11
|
+
ISystemMetricsCollector,
|
|
12
|
+
} from './types';
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Performance Plugin - Validation, storage factory, and module data extraction helpers.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from performance-plugin.ts to keep each file under 300 lines.
|
|
5
|
+
* @internal
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { ConfigurationError, EVENT_EMITTER_EVENTS } from '@robota-sdk/agent-core';
|
|
9
|
+
import { MemoryPerformanceStorage } from './storages/index';
|
|
10
|
+
import type { IPerformancePluginOptions, IPerformanceStorage } from './types';
|
|
11
|
+
|
|
12
|
+
/** Validate PerformancePlugin constructor options. @internal */
|
|
13
|
+
export function validatePerformanceOptions(options: IPerformancePluginOptions): void {
|
|
14
|
+
if (!options.strategy) {
|
|
15
|
+
throw new ConfigurationError('Performance monitoring strategy is required');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (!['memory', 'file', 'prometheus', 'remote', 'silent'].includes(options.strategy)) {
|
|
19
|
+
throw new ConfigurationError('Invalid performance monitoring strategy', {
|
|
20
|
+
validStrategies: ['memory', 'file', 'prometheus', 'remote', 'silent'],
|
|
21
|
+
provided: options.strategy,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Create IPerformanceStorage instance for the given strategy. @internal */
|
|
27
|
+
export function createPerformanceStorage(
|
|
28
|
+
strategy: string,
|
|
29
|
+
maxEntries: number,
|
|
30
|
+
): IPerformanceStorage {
|
|
31
|
+
switch (strategy) {
|
|
32
|
+
case 'memory':
|
|
33
|
+
return new MemoryPerformanceStorage(maxEntries);
|
|
34
|
+
default:
|
|
35
|
+
throw new ConfigurationError('Performance monitoring strategy is not implemented', {
|
|
36
|
+
provided: strategy,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Event name → performance metrics descriptor mapping. @internal */
|
|
42
|
+
export const PERFORMANCE_MODULE_EVENT_MAP: ReadonlyMap<
|
|
43
|
+
string,
|
|
44
|
+
{ operation: string; phase: string; isError: boolean }
|
|
45
|
+
> = new Map([
|
|
46
|
+
[
|
|
47
|
+
EVENT_EMITTER_EVENTS.MODULE_INITIALIZE_COMPLETE,
|
|
48
|
+
{ operation: 'module_initialization', phase: 'initialization', isError: false },
|
|
49
|
+
],
|
|
50
|
+
[
|
|
51
|
+
EVENT_EMITTER_EVENTS.MODULE_INITIALIZE_ERROR,
|
|
52
|
+
{ operation: 'module_initialization', phase: 'initialization', isError: true },
|
|
53
|
+
],
|
|
54
|
+
[
|
|
55
|
+
EVENT_EMITTER_EVENTS.MODULE_EXECUTION_COMPLETE,
|
|
56
|
+
{ operation: 'module_execution', phase: 'execution', isError: false },
|
|
57
|
+
],
|
|
58
|
+
[
|
|
59
|
+
EVENT_EMITTER_EVENTS.MODULE_EXECUTION_ERROR,
|
|
60
|
+
{ operation: 'module_execution', phase: 'execution', isError: true },
|
|
61
|
+
],
|
|
62
|
+
[
|
|
63
|
+
EVENT_EMITTER_EVENTS.MODULE_DISPOSE_COMPLETE,
|
|
64
|
+
{ operation: 'module_disposal', phase: 'disposal', isError: false },
|
|
65
|
+
],
|
|
66
|
+
[
|
|
67
|
+
EVENT_EMITTER_EVENTS.MODULE_DISPOSE_ERROR,
|
|
68
|
+
{ operation: 'module_disposal', phase: 'disposal', isError: true },
|
|
69
|
+
],
|
|
70
|
+
]);
|
|
71
|
+
|
|
72
|
+
/** Safely extract module data fields from untyped event payload. @internal */
|
|
73
|
+
export function extractPerformanceModuleData(data: unknown): {
|
|
74
|
+
moduleName: string;
|
|
75
|
+
moduleType: string;
|
|
76
|
+
duration?: number;
|
|
77
|
+
success?: boolean;
|
|
78
|
+
} {
|
|
79
|
+
const record = typeof data === 'object' && data !== null ? (data as Record<string, unknown>) : {};
|
|
80
|
+
return {
|
|
81
|
+
moduleName: typeof record['moduleName'] === 'string' ? record['moduleName'] : 'unknown',
|
|
82
|
+
moduleType: typeof record['moduleType'] === 'string' ? record['moduleType'] : 'unknown',
|
|
83
|
+
...(typeof record['duration'] === 'number' && { duration: record['duration'] }),
|
|
84
|
+
...(typeof record['success'] === 'boolean' && { success: record['success'] }),
|
|
85
|
+
};
|
|
86
|
+
}
|