agentnet 0.0.1

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,212 @@
1
+ import { logger } from './logger.js';
2
+
3
+ /**
4
+ * Base store interface
5
+ */
6
+ class Store {
7
+ /**
8
+ * Gets a value by key
9
+ * @param {string} key - The key to get
10
+ * @returns {Promise<string>} The value
11
+ */
12
+ async get(key) {
13
+ throw new Error('Method not implemented');
14
+ }
15
+
16
+ /**
17
+ * Sets a key to a value
18
+ * @param {string} key - The key to set
19
+ * @param {string} value - The value to set
20
+ * @returns {Promise<void>}
21
+ */
22
+ async set(key, value) {
23
+ throw new Error('Method not implemented');
24
+ }
25
+
26
+ /**
27
+ * Deletes a key
28
+ * @param {string} key - The key to delete
29
+ * @returns {Promise<boolean>} Whether the key was deleted
30
+ */
31
+ async delete(key) {
32
+ throw new Error('Method not implemented');
33
+ }
34
+ }
35
+
36
+ /**
37
+ * In-memory store implementation
38
+ */
39
+ export class MemoryStore extends Store {
40
+ constructor() {
41
+ super();
42
+ this.store = new Map();
43
+ logger.debug('Initialized in-memory store');
44
+ }
45
+
46
+ /**
47
+ * Gets a value by key
48
+ * @param {string} key - The key to get
49
+ * @returns {Promise<string>} The value
50
+ */
51
+ async get(key) {
52
+ logger.debug(`MemoryStore: Getting key ${key}`);
53
+ return this.store.get(key);
54
+ }
55
+
56
+ /**
57
+ * Sets a key to a value
58
+ * @param {string} key - The key to set
59
+ * @param {string} value - The value to set
60
+ * @returns {Promise<void>}
61
+ */
62
+ async set(key, value) {
63
+ logger.debug(`MemoryStore: Setting key ${key}`);
64
+ this.store.set(key, value);
65
+ }
66
+
67
+ /**
68
+ * Deletes a key
69
+ * @param {string} key - The key to delete
70
+ * @returns {Promise<boolean>} Whether the key was deleted
71
+ */
72
+ async delete(key) {
73
+ logger.debug(`MemoryStore: Deleting key ${key}`);
74
+ return this.store.delete(key);
75
+ }
76
+
77
+ /**
78
+ * Gets all keys
79
+ * @returns {Promise<Array<string>>} All keys in the store
80
+ */
81
+ async keys() {
82
+ return Array.from(this.store.keys());
83
+ }
84
+
85
+ /**
86
+ * Clears the store
87
+ * @returns {Promise<void>}
88
+ */
89
+ async clear() {
90
+ logger.debug('MemoryStore: Clearing all keys');
91
+ this.store.clear();
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Redis store implementation
97
+ */
98
+ export class RedisStore extends Store {
99
+ /**
100
+ * Creates a new Redis store
101
+ * @param {Object} client - Redis client
102
+ * @param {Object} options - Store options
103
+ * @param {string} options.prefix - Key prefix
104
+ * @param {number} options.ttl - Time to live in seconds
105
+ */
106
+ constructor(client, options = {}) {
107
+ super();
108
+ this.client = client;
109
+ this.prefix = options.prefix || 'smartagent:session:';
110
+ this.ttl = options.ttl || 86400; // 24 hours default
111
+ logger.debug('Initialized Redis store', { prefix: this.prefix, ttl: this.ttl });
112
+ }
113
+
114
+ /**
115
+ * Gets the full key with prefix
116
+ * @param {string} key - The base key
117
+ * @returns {string} The prefixed key
118
+ */
119
+ _getKey(key) {
120
+ return `${this.prefix}${key}`;
121
+ }
122
+
123
+ /**
124
+ * Gets a value by key
125
+ * @param {string} key - The key to get
126
+ * @returns {Promise<string>} The value
127
+ */
128
+ async get(key) {
129
+ const fullKey = this._getKey(key);
130
+ logger.debug(`RedisStore: Getting key ${fullKey}`);
131
+
132
+ try {
133
+ return await this.client.get(fullKey);
134
+ } catch (error) {
135
+ logger.error(`RedisStore: Failed to get key ${fullKey}`, { error });
136
+ throw error;
137
+ }
138
+ }
139
+
140
+ /**
141
+ * Sets a key to a value
142
+ * @param {string} key - The key to set
143
+ * @param {string} value - The value to set
144
+ * @returns {Promise<void>}
145
+ */
146
+ async set(key, value) {
147
+ const fullKey = this._getKey(key);
148
+ logger.debug(`RedisStore: Setting key ${fullKey}`);
149
+
150
+ try {
151
+ await this.client.set(fullKey, value, 'EX', this.ttl);
152
+ } catch (error) {
153
+ logger.error(`RedisStore: Failed to set key ${fullKey}`, { error });
154
+ throw error;
155
+ }
156
+ }
157
+
158
+ /**
159
+ * Deletes a key
160
+ * @param {string} key - The key to delete
161
+ * @returns {Promise<boolean>} Whether the key was deleted
162
+ */
163
+ async delete(key) {
164
+ const fullKey = this._getKey(key);
165
+ logger.debug(`RedisStore: Deleting key ${fullKey}`);
166
+
167
+ try {
168
+ const result = await this.client.del(fullKey);
169
+ return result > 0;
170
+ } catch (error) {
171
+ logger.error(`RedisStore: Failed to delete key ${fullKey}`, { error });
172
+ throw error;
173
+ }
174
+ }
175
+
176
+ /**
177
+ * Gets all keys matching the prefix
178
+ * @returns {Promise<Array<string>>} All keys in the store
179
+ */
180
+ async keys() {
181
+ try {
182
+ const keys = await this.client.keys(`${this.prefix}*`);
183
+ return keys.map(key => key.substring(this.prefix.length));
184
+ } catch (error) {
185
+ logger.error(`RedisStore: Failed to get keys`, { error });
186
+ throw error;
187
+ }
188
+ }
189
+ }
190
+
191
+ /**
192
+ * Creates an appropriate store based on configuration
193
+ * @param {Object} config - Store configuration
194
+ * @returns {Store} The configured store
195
+ */
196
+ export function createStore(config = {}) {
197
+ const { type = 'memory', ...options } = config;
198
+
199
+ switch (type.toLowerCase()) {
200
+ case 'memory':
201
+ return new MemoryStore();
202
+
203
+ case 'redis':
204
+ if (!options.client) {
205
+ throw new Error('Redis client required for Redis store');
206
+ }
207
+ return new RedisStore(options.client, options);
208
+
209
+ default:
210
+ throw new Error(`Unknown store type: ${type}`);
211
+ }
212
+ }
@@ -0,0 +1,287 @@
1
+ import { ValidationError } from '../errors/index.js';
2
+ import { logger } from './logger.js';
3
+
4
+ /**
5
+ * Validates that a value is not null or undefined
6
+ * @param {any} value - Value to check
7
+ * @param {string} name - Name of the value for error messages
8
+ * @param {string} context - Context for logging
9
+ * @throws {ValidationError} If validation fails
10
+ */
11
+ export function validateRequired(value, name, context = '') {
12
+ if (value === undefined || value === null) {
13
+ const errorMsg = `${name} is required`;
14
+ logger.error(errorMsg, { context });
15
+ throw new ValidationError(errorMsg, [{ field: name, message: 'Required field is missing' }]);
16
+ }
17
+ }
18
+
19
+ /**
20
+ * Validates that a value is of a specific type
21
+ * @param {any} value - Value to check
22
+ * @param {string} expectedType - Expected type ('string', 'number', 'boolean', 'object', 'function', 'array')
23
+ * @param {string} name - Name of the value for error messages
24
+ * @param {string} context - Context for logging
25
+ * @throws {ValidationError} If validation fails
26
+ */
27
+ export function validateType(value, expectedType, name, context = '') {
28
+ if (value === undefined || value === null) {
29
+ return; // Skip type validation for null/undefined
30
+ }
31
+
32
+ let valid = false;
33
+
34
+ if (expectedType === 'array') {
35
+ valid = Array.isArray(value);
36
+ } else if (expectedType === 'object' && Array.isArray(value)) {
37
+ valid = false; // Arrays are not objects for this validation
38
+ } else {
39
+ valid = typeof value === expectedType;
40
+ }
41
+
42
+ if (!valid) {
43
+ const actualType = Array.isArray(value) ? 'array' : typeof value;
44
+ const errorMsg = `${name} must be of type ${expectedType}, got ${actualType}`;
45
+ logger.error(errorMsg, { context, value: String(value).substring(0, 100) });
46
+ throw new ValidationError(errorMsg, [{
47
+ field: name,
48
+ message: `Expected ${expectedType}, got ${actualType}`,
49
+ actual: actualType,
50
+ expected: expectedType
51
+ }]);
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Validates that a string matches a regex pattern
57
+ * @param {string} value - String to validate
58
+ * @param {RegExp} pattern - Regex pattern to match
59
+ * @param {string} name - Name of the value for error messages
60
+ * @param {string} context - Context for logging
61
+ * @throws {ValidationError} If validation fails
62
+ */
63
+ export function validatePattern(value, pattern, name, context = '') {
64
+ if (value === undefined || value === null) {
65
+ return; // Skip pattern validation for null/undefined
66
+ }
67
+
68
+ if (typeof value !== 'string') {
69
+ const errorMsg = `${name} must be a string for pattern validation`;
70
+ logger.error(errorMsg, { context });
71
+ throw new ValidationError(errorMsg, [{
72
+ field: name,
73
+ message: 'Must be a string for pattern validation'
74
+ }]);
75
+ }
76
+
77
+ if (!pattern.test(value)) {
78
+ const errorMsg = `${name} does not match required pattern`;
79
+ logger.error(errorMsg, { context, pattern: pattern.toString() });
80
+ throw new ValidationError(errorMsg, [{
81
+ field: name,
82
+ message: 'Value does not match required pattern',
83
+ pattern: pattern.toString()
84
+ }]);
85
+ }
86
+ }
87
+
88
+ /**
89
+ * Validates an object against a schema
90
+ * @param {Object} obj - Object to validate
91
+ * @param {Object} schema - Validation schema with field definitions
92
+ * @param {string} context - Context for logging
93
+ * @throws {ValidationError} If validation fails
94
+ */
95
+ export function validateObject(obj, schema, context = '') {
96
+ validateType(obj, 'object', 'object', context);
97
+ validateType(schema, 'object', 'schema', context);
98
+
99
+ const errors = [];
100
+
101
+ // Check required fields
102
+ if (schema.required && Array.isArray(schema.required)) {
103
+ for (const field of schema.required) {
104
+ if (obj[field] === undefined || obj[field] === null) {
105
+ errors.push({
106
+ field,
107
+ message: 'Required field is missing'
108
+ });
109
+ }
110
+ }
111
+ }
112
+
113
+ // Check field types and constraints
114
+ if (schema.properties && typeof schema.properties === 'object') {
115
+ for (const [field, fieldSchema] of Object.entries(schema.properties)) {
116
+ // Skip if field is not present and not required
117
+ if ((obj[field] === undefined || obj[field] === null) &&
118
+ (!schema.required || !schema.required.includes(field))) {
119
+ continue;
120
+ }
121
+
122
+ // Validate field if present
123
+ if (obj[field] !== undefined && obj[field] !== null) {
124
+ // Check type
125
+ if (fieldSchema.type) {
126
+ try {
127
+ validateType(obj[field], fieldSchema.type, field, context);
128
+ } catch (error) {
129
+ if (error instanceof ValidationError) {
130
+ errors.push(...error.errors);
131
+ } else {
132
+ errors.push({ field, message: error.message });
133
+ }
134
+ }
135
+ }
136
+
137
+ // Check pattern for strings
138
+ if (fieldSchema.type === 'string' && fieldSchema.pattern) {
139
+ try {
140
+ validatePattern(obj[field], new RegExp(fieldSchema.pattern), field, context);
141
+ } catch (error) {
142
+ if (error instanceof ValidationError) {
143
+ errors.push(...error.errors);
144
+ } else {
145
+ errors.push({ field, message: error.message });
146
+ }
147
+ }
148
+ }
149
+
150
+ // Check enum values
151
+ if (fieldSchema.enum && Array.isArray(fieldSchema.enum) &&
152
+ !fieldSchema.enum.includes(obj[field])) {
153
+ errors.push({
154
+ field,
155
+ message: `Value must be one of: ${fieldSchema.enum.join(', ')}`,
156
+ enum: fieldSchema.enum,
157
+ actual: obj[field]
158
+ });
159
+ }
160
+
161
+ // Check nested objects recursively
162
+ if (fieldSchema.type === 'object' && fieldSchema.properties &&
163
+ typeof obj[field] === 'object' && obj[field] !== null) {
164
+ try {
165
+ validateObject(obj[field], fieldSchema, `${context}.${field}`);
166
+ } catch (error) {
167
+ if (error instanceof ValidationError) {
168
+ // Add parent field to nested errors
169
+ const nestedErrors = error.errors.map(err => ({
170
+ ...err,
171
+ field: `${field}.${err.field}`
172
+ }));
173
+ errors.push(...nestedErrors);
174
+ } else {
175
+ errors.push({ field, message: error.message });
176
+ }
177
+ }
178
+ }
179
+
180
+ // Check array items
181
+ if (fieldSchema.type === 'array' && fieldSchema.items &&
182
+ Array.isArray(obj[field])) {
183
+ for (let i = 0; i < obj[field].length; i++) {
184
+ const item = obj[field][i];
185
+
186
+ // Validate item type
187
+ if (typeof fieldSchema.items === 'string') {
188
+ try {
189
+ validateType(item, fieldSchema.items, `${field}[${i}]`, context);
190
+ } catch (error) {
191
+ if (error instanceof ValidationError) {
192
+ errors.push(...error.errors);
193
+ } else {
194
+ errors.push({ field: `${field}[${i}]`, message: error.message });
195
+ }
196
+ }
197
+ }
198
+ // Validate item against schema
199
+ else if (typeof fieldSchema.items === 'object') {
200
+ try {
201
+ validateObject(item, fieldSchema.items, `${context}.${field}[${i}]`);
202
+ } catch (error) {
203
+ if (error instanceof ValidationError) {
204
+ // Add parent field to nested errors
205
+ const nestedErrors = error.errors.map(err => ({
206
+ ...err,
207
+ field: `${field}[${i}].${err.field}`
208
+ }));
209
+ errors.push(...nestedErrors);
210
+ } else {
211
+ errors.push({ field: `${field}[${i}]`, message: error.message });
212
+ }
213
+ }
214
+ }
215
+ }
216
+ }
217
+ }
218
+ }
219
+ }
220
+
221
+ // If errors were found, throw ValidationError
222
+ if (errors.length > 0) {
223
+ const errorMsg = `Validation failed for ${context || 'object'}: ${errors.map(e => e.message).join(', ')}`;
224
+ logger.error(errorMsg, { errors });
225
+ throw new ValidationError(errorMsg, errors);
226
+ }
227
+
228
+ return true;
229
+ }
230
+
231
+ /**
232
+ * Validates tool input against its schema
233
+ * @param {Object} toolSchema - Tool schema with parameters definition
234
+ * @param {Object} input - Input data to validate
235
+ * @returns {Object} Validation result with success boolean and optional errors
236
+ */
237
+ export function validateToolInput(toolSchema, input) {
238
+ if (!toolSchema || !toolSchema.parameters) {
239
+ return { valid: true };
240
+ }
241
+
242
+ try {
243
+ validateObject(input, toolSchema.parameters, `tool.${toolSchema.name || 'unknown'}`);
244
+ return { valid: true };
245
+ } catch (error) {
246
+ if (error instanceof ValidationError) {
247
+ return {
248
+ valid: false,
249
+ errors: error.errors
250
+ };
251
+ }
252
+
253
+ return {
254
+ valid: false,
255
+ errors: [{ message: error.message }]
256
+ };
257
+ }
258
+ }
259
+
260
+ /**
261
+ * Validates that a value is one of the allowed values
262
+ * @param {any} value - Value to check
263
+ * @param {Array} allowedValues - List of allowed values
264
+ * @param {string} name - Name of the value for error messages
265
+ * @param {string} context - Context for logging
266
+ * @throws {ValidationError} If validation fails
267
+ */
268
+ export function validateEnum(value, allowedValues, name, context = '') {
269
+ if (value === undefined || value === null) {
270
+ return; // Skip enum validation for null/undefined
271
+ }
272
+
273
+ if (!Array.isArray(allowedValues)) {
274
+ throw new Error('allowedValues must be an array');
275
+ }
276
+
277
+ if (!allowedValues.includes(value)) {
278
+ const errorMsg = `${name} must be one of: ${allowedValues.join(', ')}`;
279
+ logger.error(errorMsg, { context, value });
280
+ throw new ValidationError(errorMsg, [{
281
+ field: name,
282
+ message: 'Value is not in allowed values list',
283
+ allowedValues,
284
+ actual: value
285
+ }]);
286
+ }
287
+ }