agentnet 0.0.2 → 0.0.3
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/README.md +69 -1
- package/examples/simple/simple.js +2 -2
- package/jest.config.js +1 -0
- package/package.json +5 -2
- package/src/agent/agent-loader.js +75 -12
- package/src/agent/agent.js +4 -2
- package/src/agent/runtime.js +7 -4
- package/src/llm/base.js +131 -0
- package/src/llm/gemini.js +137 -122
- package/src/llm/gpt.js +131 -109
- package/src/tests/agent.test.js +350 -0
- package/src/tools/migrate-version.js +250 -0
- package/src/transport/README.md +123 -0
- package/src/transport/base.js +237 -0
- package/src/transport/index.js +89 -0
- package/src/transport/kafka.js +474 -0
- package/src/transport/nats.js +521 -0
- package/src/transport/rabbitmq.js +722 -0
- package/src/transport/redis.js +532 -0
- package/src/utils/version.js +212 -0
- package/src/agent/runtimes/nats.js +0 -506
|
@@ -0,0 +1,532 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Redis Transport implementation
|
|
3
|
+
*/
|
|
4
|
+
import { Transport, DiscoveryMessage, safeConnect } from './base.js';
|
|
5
|
+
import { Message } from '../index.js';
|
|
6
|
+
import { logger } from '../utils/logger.js';
|
|
7
|
+
import {
|
|
8
|
+
TransportError,
|
|
9
|
+
DiscoveryError,
|
|
10
|
+
HandoffError,
|
|
11
|
+
TimeoutError,
|
|
12
|
+
withTimeout
|
|
13
|
+
} from '../errors/index.js';
|
|
14
|
+
|
|
15
|
+
// Constants
|
|
16
|
+
const HEARTBEAT_INTERVAL = 1000;
|
|
17
|
+
const TIMEOUT_TASK_REQUEST = 60000;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Redis implementation of the Transport interface
|
|
21
|
+
*/
|
|
22
|
+
export class RedisTransport extends Transport {
|
|
23
|
+
constructor() {
|
|
24
|
+
super('Redis');
|
|
25
|
+
this.client = null;
|
|
26
|
+
this.pubClient = null;
|
|
27
|
+
this.subClient = null;
|
|
28
|
+
this.subscriptions = new Map(); // Map of subscription patterns to handlers
|
|
29
|
+
this.requestMap = new Map(); // For request-response pattern
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Connect to Redis
|
|
34
|
+
* @param {Object} config - Redis connection configuration
|
|
35
|
+
* @returns {Promise<any>} - The Redis connection client
|
|
36
|
+
*/
|
|
37
|
+
async connect(config) {
|
|
38
|
+
if (this.connected && this.client) {
|
|
39
|
+
return this.client;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
// Implementation will depend on the Redis client library used
|
|
44
|
+
// For example, with ioredis:
|
|
45
|
+
// this.client = new Redis(config);
|
|
46
|
+
// this.pubClient = new Redis(config);
|
|
47
|
+
// this.subClient = new Redis(config);
|
|
48
|
+
|
|
49
|
+
// Set up subscription handling
|
|
50
|
+
// this.subClient.on('message', (channel, message) => {
|
|
51
|
+
// const handler = this.subscriptions.get(channel);
|
|
52
|
+
// if (handler) {
|
|
53
|
+
// handler(channel, message);
|
|
54
|
+
// }
|
|
55
|
+
// });
|
|
56
|
+
|
|
57
|
+
this.connected = true;
|
|
58
|
+
return this.client;
|
|
59
|
+
} catch (error) {
|
|
60
|
+
throw new TransportError(
|
|
61
|
+
`Failed to connect to Redis: ${error.message}`,
|
|
62
|
+
this.transportType,
|
|
63
|
+
{ details: error.message }
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Disconnect from Redis
|
|
70
|
+
* @returns {Promise<void>}
|
|
71
|
+
*/
|
|
72
|
+
async disconnect() {
|
|
73
|
+
await super.disconnect();
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
if (this.client) await this.client.quit();
|
|
77
|
+
if (this.pubClient) await this.pubClient.quit();
|
|
78
|
+
if (this.subClient) await this.subClient.quit();
|
|
79
|
+
|
|
80
|
+
this.client = null;
|
|
81
|
+
this.pubClient = null;
|
|
82
|
+
this.subClient = null;
|
|
83
|
+
this.subscriptions.clear();
|
|
84
|
+
} catch (error) {
|
|
85
|
+
logger.warn('Error disconnecting from Redis', { error });
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Publish a message to a Redis channel
|
|
91
|
+
* @param {string} channel - The channel to publish to
|
|
92
|
+
* @param {string} message - The message to publish
|
|
93
|
+
* @returns {Promise<void>}
|
|
94
|
+
*/
|
|
95
|
+
async publish(channel, message) {
|
|
96
|
+
if (!this.connected || !this.pubClient) {
|
|
97
|
+
throw new TransportError(
|
|
98
|
+
'Cannot publish: not connected to Redis',
|
|
99
|
+
this.transportType
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
// await this.pubClient.publish(channel, message);
|
|
105
|
+
} catch (error) {
|
|
106
|
+
throw new TransportError(
|
|
107
|
+
`Failed to publish to channel ${channel}: ${error.message}`,
|
|
108
|
+
this.transportType,
|
|
109
|
+
{ channel }
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Subscribe to a Redis channel
|
|
116
|
+
* @param {string} channel - The channel to subscribe to
|
|
117
|
+
* @param {Object} options - Subscription options
|
|
118
|
+
* @returns {Promise<string>} - The subscription identifier
|
|
119
|
+
*/
|
|
120
|
+
async subscribe(channel, options = {}, handler = null) {
|
|
121
|
+
if (!this.connected || !this.subClient) {
|
|
122
|
+
throw new TransportError(
|
|
123
|
+
'Cannot subscribe: not connected to Redis',
|
|
124
|
+
this.transportType
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
try {
|
|
129
|
+
// await this.subClient.subscribe(channel);
|
|
130
|
+
|
|
131
|
+
if (handler) {
|
|
132
|
+
this.subscriptions.set(channel, handler);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return channel; // Return the channel as the subscription ID
|
|
136
|
+
} catch (error) {
|
|
137
|
+
throw new TransportError(
|
|
138
|
+
`Failed to subscribe to channel ${channel}: ${error.message}`,
|
|
139
|
+
this.transportType,
|
|
140
|
+
{ channel }
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Unsubscribe from a Redis channel
|
|
147
|
+
* @param {string} channel - The channel to unsubscribe from
|
|
148
|
+
* @returns {Promise<void>}
|
|
149
|
+
*/
|
|
150
|
+
async unsubscribe(channel) {
|
|
151
|
+
if (!this.connected || !this.subClient) {
|
|
152
|
+
return; // Already disconnected
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
try {
|
|
156
|
+
// await this.subClient.unsubscribe(channel);
|
|
157
|
+
this.subscriptions.delete(channel);
|
|
158
|
+
} catch (error) {
|
|
159
|
+
logger.warn(`Error unsubscribing from channel ${channel}`, { error });
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Send a request and wait for a response
|
|
165
|
+
* Implements request-response pattern using Redis PubSub and unique reply channels
|
|
166
|
+
* @param {string} target - The target channel
|
|
167
|
+
* @param {string} message - The message to send
|
|
168
|
+
* @param {Object} options - Request options
|
|
169
|
+
* @returns {Promise<any>} - The response
|
|
170
|
+
*/
|
|
171
|
+
async request(target, message, options = {}) {
|
|
172
|
+
if (!this.connected || !this.pubClient || !this.subClient) {
|
|
173
|
+
throw new TransportError(
|
|
174
|
+
'Cannot send request: not connected to Redis',
|
|
175
|
+
this.transportType
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const correlationId = `req_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
180
|
+
const replyChannel = `${target}_reply_${correlationId}`;
|
|
181
|
+
const timeout = options.timeout || TIMEOUT_TASK_REQUEST;
|
|
182
|
+
|
|
183
|
+
try {
|
|
184
|
+
// Create a promise that will be resolved when the response is received
|
|
185
|
+
const responsePromise = new Promise((resolve, reject) => {
|
|
186
|
+
// Set a timeout
|
|
187
|
+
const timeoutId = setTimeout(() => {
|
|
188
|
+
this.unsubscribe(replyChannel).catch(() => {});
|
|
189
|
+
this.requestMap.delete(correlationId);
|
|
190
|
+
reject(new TimeoutError(`Request to ${target} timed out after ${timeout}ms`));
|
|
191
|
+
}, timeout);
|
|
192
|
+
|
|
193
|
+
// Store the resolver and timeout
|
|
194
|
+
this.requestMap.set(correlationId, {
|
|
195
|
+
resolve,
|
|
196
|
+
reject,
|
|
197
|
+
timeoutId
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// Set up a handler for the reply
|
|
201
|
+
const replyHandler = (channel, reply) => {
|
|
202
|
+
const requestData = this.requestMap.get(correlationId);
|
|
203
|
+
if (requestData) {
|
|
204
|
+
clearTimeout(requestData.timeoutId);
|
|
205
|
+
this.requestMap.delete(correlationId);
|
|
206
|
+
this.unsubscribe(replyChannel).catch(() => {});
|
|
207
|
+
resolve({ string: () => reply });
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
// Subscribe to the reply channel
|
|
212
|
+
this.subscribe(replyChannel, {}, replyHandler).catch(reject);
|
|
213
|
+
|
|
214
|
+
// Send the request as JSON with correlation ID and reply channel
|
|
215
|
+
const requestData = JSON.stringify({
|
|
216
|
+
data: message,
|
|
217
|
+
metadata: {
|
|
218
|
+
correlationId,
|
|
219
|
+
replyChannel
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
this.publish(target, requestData).catch(reject);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
// Wait for the response
|
|
227
|
+
return await responsePromise;
|
|
228
|
+
} catch (error) {
|
|
229
|
+
// Clean up
|
|
230
|
+
this.unsubscribe(replyChannel).catch(() => {});
|
|
231
|
+
const requestData = this.requestMap.get(correlationId);
|
|
232
|
+
if (requestData) {
|
|
233
|
+
clearTimeout(requestData.timeoutId);
|
|
234
|
+
this.requestMap.delete(correlationId);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
throw new TransportError(
|
|
238
|
+
`Request to ${target} failed: ${error.message}`,
|
|
239
|
+
this.transportType,
|
|
240
|
+
{ target }
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Set up discovery subscription
|
|
247
|
+
* @param {string} discoveryChannel - The channel for discovery messages
|
|
248
|
+
* @param {string} namespace - The agent namespace
|
|
249
|
+
* @param {string} agentName - The agent name
|
|
250
|
+
* @param {Object} discoveredAgents - Map to store discovered agents
|
|
251
|
+
* @param {Array} acceptedNetworks - List of accepted networks
|
|
252
|
+
* @returns {Promise<void>}
|
|
253
|
+
*/
|
|
254
|
+
async setupDiscoverySubscription(discoveryChannel, namespace, agentName, discoveredAgents, acceptedNetworks) {
|
|
255
|
+
try {
|
|
256
|
+
// Process discovery message handler
|
|
257
|
+
const discoveryHandler = async (channel, message) => {
|
|
258
|
+
try {
|
|
259
|
+
// Parse and validate the discovery message
|
|
260
|
+
let discoveryMessage;
|
|
261
|
+
try {
|
|
262
|
+
discoveryMessage = DiscoveryMessage.fromString(message);
|
|
263
|
+
} catch (parseError) {
|
|
264
|
+
logger.warn('Invalid discovery message format', { error: parseError.message });
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const network = discoveryMessage.network;
|
|
269
|
+
const networkNamespace = network.split(".")[0];
|
|
270
|
+
const networkName = network.split(".")[1];
|
|
271
|
+
|
|
272
|
+
// Skip self
|
|
273
|
+
if (network === `${namespace}.${agentName}`) {
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Check if network is accepted
|
|
278
|
+
let isAccepted = false;
|
|
279
|
+
for (const acceptedNetwork of acceptedNetworks) {
|
|
280
|
+
const acceptedNetworkNamespace = acceptedNetwork.split(".")[0];
|
|
281
|
+
const acceptedNetworkName = acceptedNetwork.split(".")[1];
|
|
282
|
+
|
|
283
|
+
if (
|
|
284
|
+
(acceptedNetworkNamespace === '*' || acceptedNetworkNamespace === networkNamespace) &&
|
|
285
|
+
(acceptedNetworkName === '*' || acceptedNetworkName === networkName)
|
|
286
|
+
) {
|
|
287
|
+
isAccepted = true;
|
|
288
|
+
break;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (!isAccepted) {
|
|
293
|
+
logger.warn(`Agent ${agentName} does not accept network ${network}`);
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Process the schemas from the discovery message
|
|
298
|
+
for (const schema of discoveryMessage.schemas) {
|
|
299
|
+
// Skip invalid schemas
|
|
300
|
+
if (!schema || !schema.name) {
|
|
301
|
+
logger.warn('Invalid schema in discovery payload', { schema });
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const agentKey = `${network}-${schema.name}`;
|
|
306
|
+
|
|
307
|
+
if (discoveryMessage.agentName !== agentName && !discoveredAgents[agentKey]) {
|
|
308
|
+
logger.info(`${agentName} discovered agent capability: ${discoveryMessage.agentName} with capability ${schema.name}`);
|
|
309
|
+
|
|
310
|
+
const handoffFunction = async (conversation, state, input) => {
|
|
311
|
+
try {
|
|
312
|
+
// Use withTimeout to ensure handoffs don't hang
|
|
313
|
+
return await withTimeout(
|
|
314
|
+
async () => {
|
|
315
|
+
try {
|
|
316
|
+
const message = new Message({
|
|
317
|
+
session: state,
|
|
318
|
+
content: input
|
|
319
|
+
});
|
|
320
|
+
const req = await this.request(
|
|
321
|
+
discoveryMessage.agentName,
|
|
322
|
+
message.serialize(),
|
|
323
|
+
{ timeout: TIMEOUT_TASK_REQUEST }
|
|
324
|
+
);
|
|
325
|
+
return req.string();
|
|
326
|
+
} catch (error) {
|
|
327
|
+
throw new HandoffError(
|
|
328
|
+
`Handoff to agent ${discoveryMessage.agentName} failed: ${error.message}`,
|
|
329
|
+
agentName,
|
|
330
|
+
discoveryMessage.agentName,
|
|
331
|
+
{ schemaName: schema.name }
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
},
|
|
335
|
+
TIMEOUT_TASK_REQUEST,
|
|
336
|
+
`handoff to ${discoveryMessage.agentName}`
|
|
337
|
+
);
|
|
338
|
+
} catch (error) {
|
|
339
|
+
logger.error(`Handoff error to ${discoveryMessage.agentName}`, {
|
|
340
|
+
error,
|
|
341
|
+
schema: schema.name
|
|
342
|
+
});
|
|
343
|
+
throw error;
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
discoveredAgents[agentKey] = {
|
|
348
|
+
name: schema.name,
|
|
349
|
+
schema: schema,
|
|
350
|
+
function: handoffFunction
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
} catch (error) {
|
|
355
|
+
logger.error('Error processing discovery message', { error });
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
// Subscribe to discovery channel
|
|
360
|
+
await this.subscribe(discoveryChannel, {}, discoveryHandler);
|
|
361
|
+
logger.info(`Agent ${agentName} subscribed to discovery channel ${discoveryChannel}`);
|
|
362
|
+
} catch (error) {
|
|
363
|
+
throw new DiscoveryError(
|
|
364
|
+
`Failed to set up discovery subscription on channel ${discoveryChannel}`,
|
|
365
|
+
{ agentName, channel: discoveryChannel },
|
|
366
|
+
error
|
|
367
|
+
);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Set up task handler for processing incoming requests
|
|
373
|
+
* @param {string} agentName - The agent name (used as the channel)
|
|
374
|
+
* @param {Function} processingFunction - The function to process requests
|
|
375
|
+
* @returns {Promise<void>}
|
|
376
|
+
*/
|
|
377
|
+
async setupTaskHandler(agentName, processingFunction) {
|
|
378
|
+
try {
|
|
379
|
+
const taskHandler = async (channel, message) => {
|
|
380
|
+
try {
|
|
381
|
+
// Parse the request message
|
|
382
|
+
const request = JSON.parse(message);
|
|
383
|
+
const payload = JSON.parse(request.data);
|
|
384
|
+
const metadata = request.metadata || {};
|
|
385
|
+
const replyChannel = metadata.replyChannel;
|
|
386
|
+
|
|
387
|
+
// Process the message using the Message class
|
|
388
|
+
const msg = new Message(payload);
|
|
389
|
+
|
|
390
|
+
// Process the task with timeout
|
|
391
|
+
const response = await withTimeout(
|
|
392
|
+
async () => processingFunction(msg),
|
|
393
|
+
TIMEOUT_TASK_REQUEST * 2,
|
|
394
|
+
`task processing for ${agentName}`
|
|
395
|
+
);
|
|
396
|
+
|
|
397
|
+
// Send response back if reply channel is available
|
|
398
|
+
if (replyChannel) {
|
|
399
|
+
await this.publish(replyChannel, response.serialize());
|
|
400
|
+
}
|
|
401
|
+
} catch (error) {
|
|
402
|
+
logger.error("Error processing task", { error, agentName, channel });
|
|
403
|
+
|
|
404
|
+
// Send error response back if reply channel is available
|
|
405
|
+
const request = JSON.parse(message);
|
|
406
|
+
const metadata = request.metadata || {};
|
|
407
|
+
const replyChannel = metadata.replyChannel;
|
|
408
|
+
|
|
409
|
+
if (replyChannel) {
|
|
410
|
+
await this.publish(replyChannel, JSON.stringify({
|
|
411
|
+
error: true,
|
|
412
|
+
message: error.message,
|
|
413
|
+
type: error.name || 'Error'
|
|
414
|
+
}));
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
// Subscribe to the agent's channel for tasks
|
|
420
|
+
await this.subscribe(agentName, {}, taskHandler);
|
|
421
|
+
logger.info(`Agent ${agentName} subscribed for task handling`);
|
|
422
|
+
} catch (error) {
|
|
423
|
+
throw new TransportError(
|
|
424
|
+
`Failed to set up task handler for ${agentName}: ${error.message}`,
|
|
425
|
+
this.transportType,
|
|
426
|
+
{ agentName }
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Creates a runtime for agent communication using Redis
|
|
433
|
+
* @param {string} namespace - The agent namespace
|
|
434
|
+
* @param {string} agentName - The agent name
|
|
435
|
+
* @param {Array} discoverySchemas - The agent capability schemas for discovery
|
|
436
|
+
* @param {Object} config - Additional configuration
|
|
437
|
+
* @returns {Promise<Object>} - The runtime { handleTask, discoveredAgents }
|
|
438
|
+
*/
|
|
439
|
+
async createRuntime(namespace, agentName, discoverySchemas, config) {
|
|
440
|
+
const discoveredAgents = {};
|
|
441
|
+
|
|
442
|
+
try {
|
|
443
|
+
// Verify configuration
|
|
444
|
+
if (!config || !config.bindings || !config.bindings.discoveryTopic) {
|
|
445
|
+
throw new TransportError(
|
|
446
|
+
'Missing required Redis configuration: discoveryTopic',
|
|
447
|
+
this.transportType,
|
|
448
|
+
{ agentName }
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
const discoveryChannel = config.bindings.discoveryTopic;
|
|
453
|
+
const acceptedNetworks = config.bindings.acceptedNetworks || [];
|
|
454
|
+
logger.info(`Agent ${agentName} initialized with discovery channel ${discoveryChannel}`);
|
|
455
|
+
|
|
456
|
+
// Step 1: Subscribe to discovery channel
|
|
457
|
+
await this.setupDiscoverySubscription(discoveryChannel, namespace, agentName, discoveredAgents, acceptedNetworks);
|
|
458
|
+
|
|
459
|
+
// Step 2: Set up heartbeat
|
|
460
|
+
this.setupHeartbeat(discoveryChannel, namespace, agentName, discoverySchemas, HEARTBEAT_INTERVAL);
|
|
461
|
+
|
|
462
|
+
// Step 3: Create task handler function
|
|
463
|
+
const handleTask = async (fn) => {
|
|
464
|
+
if (typeof fn !== 'function') {
|
|
465
|
+
throw new Error('Task handler must be a function');
|
|
466
|
+
}
|
|
467
|
+
await this.setupTaskHandler(agentName, fn);
|
|
468
|
+
};
|
|
469
|
+
|
|
470
|
+
return { handleTask, discoveredAgents };
|
|
471
|
+
} catch (error) {
|
|
472
|
+
// Enhance the error with context if it's not already a TransportError
|
|
473
|
+
if (!(error instanceof TransportError)) {
|
|
474
|
+
error = new TransportError(
|
|
475
|
+
`Failed to initialize Redis runtime: ${error.message}`,
|
|
476
|
+
this.transportType,
|
|
477
|
+
{ agentName }
|
|
478
|
+
);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
logger.error("Redis runtime initialization failed", { error, agentName });
|
|
482
|
+
throw error;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* Factory function to create a Redis transport instance
|
|
489
|
+
* @returns {RedisTransport} - A Redis transport instance
|
|
490
|
+
*/
|
|
491
|
+
export function createRedisTransport() {
|
|
492
|
+
return new RedisTransport();
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* Adapter function to create a Redis-based runtime for agent communication
|
|
497
|
+
* @param {string} namespace - The agent namespace
|
|
498
|
+
* @param {string} agentName - The agent name
|
|
499
|
+
* @param {Array} ioInterfaces - The IO interfaces (only the first one is used)
|
|
500
|
+
* @param {Array} discoverySchemas - The agent capability schemas for discovery
|
|
501
|
+
* @returns {Promise<Object>} - The runtime { handleTask, discoveredAgents }
|
|
502
|
+
*/
|
|
503
|
+
export async function RedisIOAgentRuntime(namespace, agentName, ioInterfaces, discoverySchemas) {
|
|
504
|
+
if (ioInterfaces.length > 1) {
|
|
505
|
+
throw new TransportError(
|
|
506
|
+
'Only one IO Redis interface is supported',
|
|
507
|
+
'Redis',
|
|
508
|
+
{ agentName, interfacesCount: ioInterfaces.length }
|
|
509
|
+
);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
if (ioInterfaces.length === 0) {
|
|
513
|
+
logger.warn(`No Redis interfaces provided for agent ${agentName}, creating passive runtime`);
|
|
514
|
+
return { handleTask: async () => {}, discoveredAgents: {} };
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
const io = ioInterfaces[0];
|
|
518
|
+
const transport = createRedisTransport();
|
|
519
|
+
|
|
520
|
+
try {
|
|
521
|
+
// Connect to Redis with retry logic
|
|
522
|
+
logger.info(`Connecting to Redis for agent ${agentName}`);
|
|
523
|
+
await safeConnect(transport, io.config);
|
|
524
|
+
|
|
525
|
+
// Create runtime with the transport
|
|
526
|
+
return await transport.createRuntime(namespace, agentName, discoverySchemas, io.config);
|
|
527
|
+
} catch (error) {
|
|
528
|
+
// Make sure to clean up if initialization fails
|
|
529
|
+
await transport.disconnect();
|
|
530
|
+
throw error;
|
|
531
|
+
}
|
|
532
|
+
}
|