codeaura-embedded-runtime-agent 0.1.0

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.
@@ -0,0 +1,261 @@
1
+
2
+ const DEFAULT_ENDPOINT = "https://public-api.codeaura.ai/api/v1/runtime-agent/events",
3
+ defaultConfig = {
4
+ enabled: true,
5
+ endpoint: DEFAULT_ENDPOINT,
6
+ apiKey: process.env.API_KEY,
7
+ projectId: null,
8
+ framework: null,
9
+ buffer: {
10
+ maxSize: 100,
11
+ flushInterval: 5000,
12
+ maxRetrySize: 500
13
+ },
14
+ transport: {
15
+ timeout: 5000,
16
+ retries: 0
17
+ },
18
+ instrumentation: {
19
+ controllers: true,
20
+ helpers: true
21
+ },
22
+ logging: {
23
+ enabled: true,
24
+ level: "info"
25
+ }
26
+ },
27
+
28
+ VALID_LOG_LEVELS = ["debug", "info", "warn", "error"];
29
+
30
+ class Config {
31
+
32
+ constructor(userConfig) {
33
+ this.config = null;
34
+
35
+ userConfig = userConfig || {};
36
+
37
+ try {
38
+ this.config = this.mergeDeep(defaultConfig, userConfig);
39
+ this.validate();
40
+ } catch (error) {
41
+ throw error;
42
+ }
43
+ }
44
+
45
+ mergeDeep(defaults, userConfig) {
46
+ let merged = {},
47
+ key = null,
48
+ defaultValue = null,
49
+ userValue = null,
50
+ isDefaultObject = false,
51
+ isUserObject = false;
52
+
53
+ for (key in defaults) {
54
+ if (Object.prototype.hasOwnProperty.call(defaults, key)) {
55
+ defaultValue = defaults[key];
56
+ userValue = userConfig[key];
57
+ isDefaultObject = defaultValue !== null && typeof defaultValue === "object" && !Array.isArray(defaultValue);
58
+ isUserObject = userValue !== null && typeof userValue === "object" && !Array.isArray(userValue);
59
+
60
+ if (isDefaultObject && isUserObject) {
61
+ merged[key] = this.mergeDeep(defaultValue, userValue);
62
+ } else if (userValue !== undefined && userValue !== null) {
63
+ merged[key] = userValue;
64
+ } else if (userValue === null && key === "framework") {
65
+ merged[key] = null;
66
+ } else {
67
+ merged[key] = defaultValue;
68
+ }
69
+ } else {
70
+
71
+ }
72
+ }
73
+
74
+ return merged;
75
+ }
76
+
77
+ validate() {
78
+ let frameworkType = null,
79
+ logLevel = null;
80
+
81
+ try {
82
+ if (!this.config.apiKey || typeof this.config.apiKey !== "string") {
83
+ throw new Error("[CodeAura Agent] Configuration error: apiKey is required and must be a string");
84
+ } else {
85
+
86
+ }
87
+
88
+ if (this.config.framework !== null && typeof this.config.framework !== "object") {
89
+ throw new Error("[CodeAura Agent] Configuration error: framework must be null or an object");
90
+ } else {
91
+
92
+ }
93
+
94
+ if (this.config.framework !== null) {
95
+ frameworkType = this.config.framework.type;
96
+
97
+ if (frameworkType !== null && typeof frameworkType !== "string") {
98
+ throw new Error("[CodeAura Agent] Configuration error: framework.type must be null or a string");
99
+ } else {
100
+
101
+ }
102
+ } else {
103
+
104
+ }
105
+
106
+ this.validateBuffer();
107
+ this.validateTransport();
108
+ this.validateInstrumentation();
109
+
110
+ if (typeof this.config.logging.enabled !== "boolean") {
111
+ throw new Error("[CodeAura Agent] Configuration error: logging.enabled must be a boolean");
112
+ } else {
113
+
114
+ }
115
+
116
+ logLevel = this.config.logging.level;
117
+
118
+ if (VALID_LOG_LEVELS.indexOf(logLevel) === -1) {
119
+ throw new Error("[CodeAura Agent] Configuration error: logging.level must be one of: " + VALID_LOG_LEVELS.join(", "));
120
+ } else {
121
+
122
+ }
123
+ } catch (error) {
124
+ throw error;
125
+ }
126
+ }
127
+
128
+ validateBuffer() {
129
+ let maxSize = null,
130
+ flushInterval = null;
131
+
132
+ maxSize = this.config.buffer.maxSize;
133
+ flushInterval = this.config.buffer.flushInterval;
134
+
135
+ if (typeof maxSize !== "number" || !isFinite(maxSize) || maxSize < 1) {
136
+ throw new Error("[CodeAura Agent] Configuration error: buffer.maxSize must be a number >= 1");
137
+ } else {
138
+
139
+ }
140
+
141
+ if (typeof flushInterval !== "number" || !isFinite(flushInterval) || flushInterval < 100) {
142
+ throw new Error("[CodeAura Agent] Configuration error: buffer.flushInterval must be a number >= 100ms");
143
+ } else {
144
+
145
+ }
146
+
147
+ let maxRetrySize = null;
148
+
149
+ maxRetrySize = this.config.buffer.maxRetrySize;
150
+
151
+ if (typeof maxRetrySize !== "number" || !isFinite(maxRetrySize) || maxRetrySize < 1) {
152
+ throw new Error("[CodeAura Agent] Configuration error: buffer.maxRetrySize must be a number >= 1");
153
+ } else {
154
+
155
+ }
156
+ }
157
+
158
+ validateTransport() {
159
+ let timeout = null,
160
+ retries = null;
161
+
162
+ timeout = this.config.transport.timeout;
163
+ retries = this.config.transport.retries;
164
+
165
+ if (typeof timeout !== "number" || !isFinite(timeout) || timeout <= 0) {
166
+ throw new Error("[CodeAura Agent] Configuration error: transport.timeout must be a number > 0");
167
+ } else {
168
+
169
+ }
170
+
171
+ if (typeof retries !== "number" || !isFinite(retries) || retries < 0) {
172
+ throw new Error("[CodeAura Agent] Configuration error: transport.retries must be a number >= 0");
173
+ } else {
174
+
175
+ }
176
+ }
177
+
178
+ validateInstrumentation() {
179
+ let controllers = null,
180
+ helpers = null;
181
+
182
+ controllers = this.config.instrumentation.controllers;
183
+ helpers = this.config.instrumentation.helpers;
184
+
185
+ if (typeof controllers !== "boolean") {
186
+ throw new Error("[CodeAura Agent] Configuration error: instrumentation.controllers must be a boolean");
187
+ } else {
188
+
189
+ }
190
+
191
+ if (typeof helpers !== "boolean") {
192
+ throw new Error("[CodeAura Agent] Configuration error: instrumentation.helpers must be a boolean");
193
+ } else {
194
+
195
+ }
196
+ }
197
+
198
+ get(key) {
199
+ return this.config[key];
200
+ }
201
+
202
+ isEnabled() {
203
+ return this.config.enabled === true;
204
+ }
205
+
206
+ getAll() {
207
+ let configCopy = null;
208
+
209
+ configCopy = this.deepCopy(this.config);
210
+
211
+ return configCopy;
212
+ }
213
+
214
+ deepCopy(obj, seen = new WeakSet()) {
215
+ let copy = null,
216
+ key = null,
217
+ value = null,
218
+ isObject = false;
219
+
220
+ if (obj === null || typeof obj !== "object") {
221
+ return obj;
222
+ } else {
223
+
224
+ }
225
+
226
+ if (seen.has(obj)) {
227
+ return undefined;
228
+ }
229
+
230
+ if (Array.isArray(obj)) {
231
+ seen.add(obj);
232
+
233
+ return obj.map(item => this.deepCopy(item, seen));
234
+ } else {
235
+
236
+ }
237
+
238
+ copy = {};
239
+ seen.add(obj);
240
+
241
+ for (key in obj) {
242
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
243
+ value = obj[key];
244
+ isObject = value !== null && typeof value === "object" && !Array.isArray(value);
245
+
246
+ if (isObject) {
247
+ copy[key] = this.deepCopy(value, seen);
248
+ } else {
249
+ copy[key] = value;
250
+ }
251
+ } else {
252
+
253
+ }
254
+ }
255
+
256
+ return copy;
257
+ }
258
+
259
+ }
260
+
261
+ module.exports = Config;
@@ -0,0 +1,164 @@
1
+
2
+ class MemoryBuffer {
3
+
4
+ constructor(config, transport, logger, agentConfig) {
5
+ this.config = config || {};
6
+ this.transport = transport;
7
+ this.logger = logger;
8
+ this.agentConfig = agentConfig || {};
9
+ this.projectId = this.agentConfig.projectId || null;
10
+ this.buffer = [];
11
+ this.retryBuffer = [];
12
+ this.maxSize = this.config.maxSize || 100;
13
+ this.maxRetrySize = this.config.maxRetrySize || 500;
14
+ this.flushInterval = this.config.flushInterval || 5000;
15
+ this.flushTimer = null;
16
+ this.flushing = false;
17
+
18
+ this.startFlushTimer();
19
+ }
20
+
21
+ add(event) {
22
+ try {
23
+ if (event) {
24
+ if (this.projectId) {
25
+ event.projectId = this.projectId;
26
+ }
27
+
28
+ this.buffer.push(event);
29
+ this.logger.debug(`Event added to buffer. Current size: ${this.buffer.length}`);
30
+
31
+ if (this.buffer.length >= this.maxSize) {
32
+ this.flush();
33
+ }
34
+ }
35
+ } catch (error) {
36
+ this.logger.errorWithStack("Error adding event to buffer", error);
37
+ }
38
+ }
39
+
40
+ moveToRetryBuffer(events) {
41
+ let available = 0,
42
+ eventsToRetry = null,
43
+ dropped = 0;
44
+
45
+ try {
46
+ available = this.maxRetrySize - this.retryBuffer.length;
47
+
48
+ if (available <= 0) {
49
+ this.logger.warn(`Retry buffer full (${this.maxRetrySize}). Discarding ${events.length} event(s).`);
50
+ } else if (events.length > available) {
51
+ dropped = events.length - available;
52
+ eventsToRetry = events.slice(0, available);
53
+ this.retryBuffer = this.retryBuffer.concat(eventsToRetry);
54
+ this.logger.warn(`Retry buffer nearly full. Discarding ${dropped} event(s). Retry buffer size: ${this.retryBuffer.length}`);
55
+ } else {
56
+ eventsToRetry = events;
57
+ this.retryBuffer = this.retryBuffer.concat(eventsToRetry);
58
+ this.logger.debug(`Moved ${eventsToRetry.length} event(s) to retry buffer. Retry buffer size: ${this.retryBuffer.length}`);
59
+ }
60
+ } catch (error) {
61
+ this.logger.errorWithStack("Error moving events to retry buffer", error);
62
+ }
63
+ }
64
+
65
+ async flush() {
66
+ if (this.flushing) {
67
+ this.logger.debug("Flush already in progress, skipping");
68
+
69
+ return;
70
+ }
71
+
72
+ if (this.buffer.length === 0 && this.retryBuffer.length === 0) {
73
+ this.logger.debug("Buffer is empty, skipping flush");
74
+
75
+ return;
76
+ }
77
+
78
+ this.flushing = true;
79
+
80
+ let eventsToSend = null,
81
+ currentEvents = null,
82
+ retryEvents = null,
83
+ eventCount = 0;
84
+
85
+ try {
86
+ retryEvents = this.retryBuffer.slice();
87
+ currentEvents = this.buffer.slice();
88
+ eventsToSend = retryEvents.concat(currentEvents);
89
+ eventCount = eventsToSend.length;
90
+
91
+ this.buffer = [];
92
+ this.retryBuffer = [];
93
+
94
+ this.logger.debug(`Flushing ${eventCount} event(s) (${retryEvents.length} retry, ${currentEvents.length} new)`);
95
+
96
+ if (this.transport) {
97
+ try {
98
+ await this.transport.send(eventsToSend);
99
+ } catch (transportError) {
100
+ this.logger.errorWithStack(`Transport error while sending ${eventCount} event(s)`, transportError);
101
+ this.moveToRetryBuffer(eventsToSend);
102
+ }
103
+ } else {
104
+ this.logger.warn(`No transport available, discarding ${eventCount} event(s)`);
105
+ }
106
+ } catch (error) {
107
+ this.logger.errorWithStack("Error flushing buffer", error);
108
+ } finally {
109
+ this.flushing = false;
110
+ }
111
+ }
112
+
113
+ startFlushTimer() {
114
+ if (this.flushTimer) {
115
+ clearInterval(this.flushTimer);
116
+ }
117
+
118
+ this.flushTimer = setInterval(() => {
119
+ this.flush();
120
+ }, this.flushInterval);
121
+
122
+ this.logger.debug(`Flush timer started with interval: ${this.flushInterval}ms`);
123
+ }
124
+
125
+ stopFlushTimer() {
126
+ if (this.flushTimer) {
127
+ clearInterval(this.flushTimer);
128
+ this.flushTimer = null;
129
+ this.logger.debug("Flush timer stopped");
130
+ }
131
+ }
132
+
133
+ getSize() {
134
+ return this.buffer.length;
135
+ }
136
+
137
+ getRetrySize() {
138
+ return this.retryBuffer.length;
139
+ }
140
+
141
+ clear() {
142
+ const previousSize = this.buffer.length,
143
+ previousRetrySize = this.retryBuffer.length;
144
+
145
+ this.buffer = [];
146
+ this.retryBuffer = [];
147
+ this.logger.debug(`Buffer cleared. Discarded ${previousSize} event(s) and ${previousRetrySize} retry event(s).`);
148
+ }
149
+
150
+ async shutdown() {
151
+ try {
152
+ this.logger.debug("Shutting down memory buffer");
153
+
154
+ this.stopFlushTimer();
155
+ await this.flush();
156
+
157
+ this.logger.debug("Memory buffer shutdown complete");
158
+ } catch (error) {
159
+ this.logger.errorWithStack("Error during buffer shutdown", error);
160
+ }
161
+ }
162
+ }
163
+
164
+ module.exports = MemoryBuffer;
@@ -0,0 +1,171 @@
1
+
2
+ class EventSchema {
3
+
4
+ static create(params) {
5
+ let type = null,
6
+ name = null,
7
+ durationMs = null,
8
+ meta = null,
9
+ error = null,
10
+ event = null,
11
+ inputs = null,
12
+ output = null,
13
+ path = "",
14
+ errorInfo = null;
15
+
16
+ type = params.type;
17
+ name = params.name;
18
+ durationMs = params.durationMs;
19
+ meta = params.meta || {};
20
+ inputs = params.inputs || null;
21
+ output = params.output || null;
22
+ error = params.error || null;
23
+ path = params.path || "";
24
+
25
+ event = {
26
+ id: EventSchema.generateId(),
27
+ timestamp: new Date().toISOString(),
28
+ type: type,
29
+ name: name,
30
+ framework: "sailsjs",
31
+ language: "nodejs",
32
+ durationMs: Math.round(durationMs * 100) / 100,
33
+ controllerExecutionId: params.controllerExecutionId || null,
34
+ parentExecutionId: params.parentExecutionId || null,
35
+ meta: meta,
36
+ inputs: inputs,
37
+ output: output,
38
+ path: path
39
+ };
40
+
41
+ if (error) {
42
+ errorInfo = {
43
+ message: error.message,
44
+ type: error.name || "Error",
45
+ stack: error.stack
46
+ };
47
+ event.error = errorInfo;
48
+ } else {
49
+
50
+ }
51
+
52
+ return event;
53
+ }
54
+
55
+ static createControllerEvent(params) {
56
+ let name = null,
57
+ durationMs = null,
58
+ meta = null,
59
+ error = null,
60
+ inputs = null,
61
+ output = null,
62
+ path = "",
63
+ event = null;
64
+
65
+ name = params.name;
66
+ durationMs = params.durationMs;
67
+ meta = params.meta || {};
68
+ error = params.error || null;
69
+ inputs = params.inputs || null;
70
+ output = params.output || null;
71
+ path = params.path || "";
72
+
73
+ event = EventSchema.create({
74
+ type: "controller",
75
+ name: name,
76
+ durationMs: durationMs,
77
+ meta: meta,
78
+ inputs: inputs,
79
+ output: output,
80
+ path: path,
81
+ error: error
82
+ });
83
+
84
+ return event;
85
+ }
86
+
87
+ static createHelperEvent(params) {
88
+ let name = null,
89
+ durationMs = null,
90
+ meta = null,
91
+ error = null,
92
+ inputs = null,
93
+ output = null,
94
+ path = "",
95
+ event = null;
96
+
97
+ name = params.name;
98
+ durationMs = params.durationMs;
99
+ meta = params.meta || {};
100
+ error = params.error || null;
101
+ inputs = params.inputs || null;
102
+ output = params.output || null;
103
+ path = params.path || "";
104
+
105
+ event = EventSchema.create({
106
+ type: "helper",
107
+ name: name,
108
+ durationMs: durationMs,
109
+ meta: meta,
110
+ inputs: inputs,
111
+ output: output,
112
+ path: path,
113
+ error: error,
114
+ controllerExecutionId: params.controllerExecutionId || null,
115
+ parentExecutionId: params.parentExecutionId || null
116
+ });
117
+
118
+ return event;
119
+ }
120
+
121
+ static validate(event) {
122
+ let requiredFields = null,
123
+ i = 0,
124
+ field = null,
125
+ isValidType = false,
126
+ isValidDuration = false;
127
+
128
+ requiredFields = ["id", "timestamp", "type", "name", "framework", "language", "durationMs"];
129
+
130
+ for (i = 0; i < requiredFields.length; i++) {
131
+ field = requiredFields[i];
132
+
133
+ if (event[field] === undefined || event[field] === null) {
134
+ return false;
135
+ } else {
136
+
137
+ }
138
+ }
139
+
140
+ isValidType = (event.type === "controller" || event.type === "helper");
141
+ if (!isValidType) {
142
+ return false;
143
+ } else {
144
+
145
+ }
146
+
147
+ isValidDuration = (typeof event.durationMs === "number" && event.durationMs >= 0);
148
+ if (!isValidDuration) {
149
+ return false;
150
+ } else {
151
+
152
+ }
153
+
154
+ return true;
155
+ }
156
+
157
+ static generateId() {
158
+ let timestamp = null,
159
+ random = null,
160
+ id = null;
161
+
162
+ timestamp = Date.now().toString(36);
163
+ random = Math.random().toString(36).substring(2, 9);
164
+ id = timestamp + "-" + random;
165
+
166
+ return id;
167
+ }
168
+
169
+ }
170
+
171
+ module.exports = EventSchema;