agentnet 0.0.2 → 0.0.4
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,521 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NATS 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
|
+
const MAX_RECONNECT_ATTEMPTS = 5;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* NATS implementation of the Transport interface
|
|
22
|
+
*/
|
|
23
|
+
export class NatsTransport extends Transport {
|
|
24
|
+
constructor() {
|
|
25
|
+
super('NATS');
|
|
26
|
+
this.connection = null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Connect to NATS
|
|
31
|
+
* @param {Object} config - NATS connection configuration
|
|
32
|
+
* @returns {Promise<any>} - The NATS connection instance
|
|
33
|
+
*/
|
|
34
|
+
async connect(config) {
|
|
35
|
+
if (this.connected && this.connection) {
|
|
36
|
+
return this.connection;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
this.connection = await config.instance.connect();
|
|
41
|
+
this.connected = true;
|
|
42
|
+
return this.connection;
|
|
43
|
+
} catch (error) {
|
|
44
|
+
throw new TransportError(
|
|
45
|
+
`Failed to connect to NATS: ${error.message}`,
|
|
46
|
+
this.transportType,
|
|
47
|
+
{ details: error.message }
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Disconnect from NATS
|
|
54
|
+
* @returns {Promise<void>}
|
|
55
|
+
*/
|
|
56
|
+
async disconnect() {
|
|
57
|
+
await super.disconnect();
|
|
58
|
+
|
|
59
|
+
if (this.connection) {
|
|
60
|
+
try {
|
|
61
|
+
await this.connection.drain();
|
|
62
|
+
this.connection = null;
|
|
63
|
+
} catch (error) {
|
|
64
|
+
logger.warn('Error disconnecting from NATS', { error });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Publish a message to a NATS topic
|
|
71
|
+
* @param {string} topic - The topic to publish to
|
|
72
|
+
* @param {string} message - The message to publish
|
|
73
|
+
* @returns {Promise<void>}
|
|
74
|
+
*/
|
|
75
|
+
async publish(topic, message) {
|
|
76
|
+
if (!this.connected || !this.connection) {
|
|
77
|
+
throw new TransportError(
|
|
78
|
+
'Cannot publish: not connected to NATS',
|
|
79
|
+
this.transportType
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
await this.connection.publish(topic, message);
|
|
85
|
+
} catch (error) {
|
|
86
|
+
throw new TransportError(
|
|
87
|
+
`Failed to publish to topic ${topic}: ${error.message}`,
|
|
88
|
+
this.transportType,
|
|
89
|
+
{ topic }
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Subscribe to a NATS topic
|
|
96
|
+
* @param {string} topic - The topic to subscribe to
|
|
97
|
+
* @param {Object} options - Subscription options
|
|
98
|
+
* @returns {Promise<any>} - The subscription instance
|
|
99
|
+
*/
|
|
100
|
+
async subscribe(topic, options = {}) {
|
|
101
|
+
if (!this.connected || !this.connection) {
|
|
102
|
+
throw new TransportError(
|
|
103
|
+
'Cannot subscribe: not connected to NATS',
|
|
104
|
+
this.transportType
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
return this.connection.subscribe(topic, options);
|
|
110
|
+
} catch (error) {
|
|
111
|
+
throw new TransportError(
|
|
112
|
+
`Failed to subscribe to topic ${topic}: ${error.message}`,
|
|
113
|
+
this.transportType,
|
|
114
|
+
{ topic }
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Send a request and wait for a response
|
|
121
|
+
* @param {string} target - The target to send the request to
|
|
122
|
+
* @param {string} message - The message to send
|
|
123
|
+
* @param {Object} options - Request options
|
|
124
|
+
* @returns {Promise<any>} - The response
|
|
125
|
+
*/
|
|
126
|
+
async request(target, message, options = {}) {
|
|
127
|
+
if (!this.connected || !this.connection) {
|
|
128
|
+
throw new TransportError(
|
|
129
|
+
'Cannot send request: not connected to NATS',
|
|
130
|
+
this.transportType
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
return await this.connection.request(target, message, options);
|
|
136
|
+
} catch (error) {
|
|
137
|
+
throw new TransportError(
|
|
138
|
+
`Request to ${target} failed: ${error.message}`,
|
|
139
|
+
this.transportType,
|
|
140
|
+
{ target }
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Set up discovery subscription
|
|
147
|
+
* @param {string} discoveryTopic - The topic for discovery messages
|
|
148
|
+
* @param {string} namespace - The agent namespace
|
|
149
|
+
* @param {string} agentName - The agent name
|
|
150
|
+
* @param {Object} discoveredAgents - Map to store discovered agents
|
|
151
|
+
* @param {Array} acceptedNetworks - List of accepted networks
|
|
152
|
+
* @returns {Promise<void>}
|
|
153
|
+
*/
|
|
154
|
+
async setupDiscoverySubscription(discoveryTopic, namespace, agentName, discoveredAgents, acceptedNetworks) {
|
|
155
|
+
let discoverySub;
|
|
156
|
+
|
|
157
|
+
try {
|
|
158
|
+
discoverySub = await this.subscribe(discoveryTopic);
|
|
159
|
+
logger.info(`Agent ${agentName} subscribed to discovery topic ${discoveryTopic}`);
|
|
160
|
+
} catch (error) {
|
|
161
|
+
throw new DiscoveryError(
|
|
162
|
+
`Failed to subscribe to discovery topic ${discoveryTopic}`,
|
|
163
|
+
{ agentName, topic: discoveryTopic },
|
|
164
|
+
error
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const handleDiscovery = async () => {
|
|
169
|
+
try {
|
|
170
|
+
let nonAcceptedNetworks = {};
|
|
171
|
+
for await (const m of discoverySub) {
|
|
172
|
+
try {
|
|
173
|
+
// Attempt to parse and validate the discovery message
|
|
174
|
+
let discoveryMessage;
|
|
175
|
+
try {
|
|
176
|
+
discoveryMessage = DiscoveryMessage.fromString(m.string());
|
|
177
|
+
} catch (parseError) {
|
|
178
|
+
logger.warn('Invalid discovery message format', { error: parseError.message });
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const network = discoveryMessage.network;
|
|
183
|
+
const networkNamespace = network.split(".")[0];
|
|
184
|
+
const networkName = network.split(".")[1];
|
|
185
|
+
|
|
186
|
+
// Skip self
|
|
187
|
+
if (network === `${namespace}.${agentName}`) {
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Skip if already processed
|
|
192
|
+
if (nonAcceptedNetworks[network] === true) {
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
let isAccepted = false;
|
|
197
|
+
for (const acceptedNetwork of acceptedNetworks) {
|
|
198
|
+
const acceptedNetworkNamespace = acceptedNetwork.split(".")[0];
|
|
199
|
+
const acceptedNetworkName = acceptedNetwork.split(".")[1];
|
|
200
|
+
|
|
201
|
+
if (acceptedNetworkNamespace === networkNamespace && acceptedNetworkName === networkName) {
|
|
202
|
+
isAccepted = true;
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
// Check for wildcard patterns in accepted networks
|
|
206
|
+
|
|
207
|
+
if (acceptedNetworkNamespace === '*' && acceptedNetworkName === '*') {
|
|
208
|
+
// Both namespace and name are wildcards, accept any network
|
|
209
|
+
logger.debug(`Agent ${agentName} accepting network ${network} due to wildcard pattern *.*`);
|
|
210
|
+
isAccepted = true;
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (acceptedNetworkNamespace === '*' && acceptedNetworkName === networkName) {
|
|
215
|
+
// Namespace is wildcard, but name matches
|
|
216
|
+
logger.debug(`Agent ${agentName} accepting network ${network} due to wildcard pattern *.${networkName}`);
|
|
217
|
+
isAccepted = true;
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (acceptedNetworkNamespace === networkNamespace && acceptedNetworkName === '*') {
|
|
222
|
+
// Name is wildcard, but namespace matches
|
|
223
|
+
logger.debug(`Agent ${agentName} accepting network ${network} due to wildcard pattern ${networkNamespace}.*`);
|
|
224
|
+
isAccepted = true;
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Skip if not accepted
|
|
230
|
+
if (!isAccepted) {
|
|
231
|
+
logger.warn(`Agent ${agentName} does not accept network ${network}`);
|
|
232
|
+
nonAcceptedNetworks[network] = true;
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Process the schemas from the discovery message
|
|
237
|
+
for (const schema of discoveryMessage.schemas) {
|
|
238
|
+
// Skip invalid schemas
|
|
239
|
+
if (!schema || !schema.name) {
|
|
240
|
+
logger.warn('Invalid schema in discovery payload', { schema });
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const agentKey = `${network}-${schema.name}`;
|
|
245
|
+
|
|
246
|
+
if (discoveryMessage.agentName !== agentName && !discoveredAgents[agentKey]) {
|
|
247
|
+
logger.info(`${agentName} discovered agent capability: ${discoveryMessage.agentName} with capability ${schema.name}`);
|
|
248
|
+
|
|
249
|
+
const handoffFunction = async (conversation, state, input) => {
|
|
250
|
+
try {
|
|
251
|
+
// Use withTimeout to ensure handoffs don't hang
|
|
252
|
+
return await withTimeout(
|
|
253
|
+
async () => {
|
|
254
|
+
try {
|
|
255
|
+
const message = new Message({
|
|
256
|
+
session: state,
|
|
257
|
+
content: input
|
|
258
|
+
});
|
|
259
|
+
const req = await this.request(
|
|
260
|
+
discoveryMessage.agentName,
|
|
261
|
+
message.serialize(),
|
|
262
|
+
{ timeout: TIMEOUT_TASK_REQUEST }
|
|
263
|
+
);
|
|
264
|
+
return req.string();
|
|
265
|
+
} catch (error) {
|
|
266
|
+
throw new HandoffError(
|
|
267
|
+
`Handoff to agent ${discoveryMessage.agentName} failed: ${error.message}`,
|
|
268
|
+
agentName,
|
|
269
|
+
discoveryMessage.agentName,
|
|
270
|
+
{ schemaName: schema.name }
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
},
|
|
274
|
+
TIMEOUT_TASK_REQUEST,
|
|
275
|
+
`handoff to ${discoveryMessage.agentName}`
|
|
276
|
+
);
|
|
277
|
+
} catch (error) {
|
|
278
|
+
logger.error(`Handoff error to ${discoveryMessage.agentName}`, {
|
|
279
|
+
error,
|
|
280
|
+
schema: schema.name
|
|
281
|
+
});
|
|
282
|
+
throw error;
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
discoveredAgents[agentKey] = {
|
|
287
|
+
name: schema.name,
|
|
288
|
+
schema: schema,
|
|
289
|
+
function: handoffFunction
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
} catch (error) {
|
|
294
|
+
logger.error('Error processing discovery message', { error });
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
} catch (error) {
|
|
298
|
+
logger.error("Discovery subscription error", { error });
|
|
299
|
+
|
|
300
|
+
// Attempt to resubscribe if the connection is still active
|
|
301
|
+
if (this.connected) {
|
|
302
|
+
logger.info('Attempting to resubscribe to discovery topic');
|
|
303
|
+
try {
|
|
304
|
+
discoverySub = await this.subscribe(discoveryTopic);
|
|
305
|
+
handleDiscovery(); // Restart the handling process
|
|
306
|
+
} catch (resubError) {
|
|
307
|
+
logger.error('Failed to resubscribe to discovery topic', { error: resubError });
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
// Start processing discovery messages
|
|
314
|
+
handleDiscovery();
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Set up task handler for processing incoming requests
|
|
319
|
+
* @param {string} agentName - The agent name
|
|
320
|
+
* @param {Function} processingFunction - The function to process requests
|
|
321
|
+
* @returns {Promise<void>}
|
|
322
|
+
*/
|
|
323
|
+
async setupTaskHandler(agentName, processingFunction) {
|
|
324
|
+
let taskSub;
|
|
325
|
+
|
|
326
|
+
try {
|
|
327
|
+
taskSub = await this.subscribe(agentName, { queue: agentName });
|
|
328
|
+
logger.info(`Agent ${agentName} subscribed for task handling`);
|
|
329
|
+
} catch (error) {
|
|
330
|
+
throw new TransportError(
|
|
331
|
+
`Failed to subscribe for task handling: ${error.message}`,
|
|
332
|
+
this.transportType,
|
|
333
|
+
{ agentName }
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
try {
|
|
338
|
+
for await (const m of taskSub) {
|
|
339
|
+
let payload;
|
|
340
|
+
|
|
341
|
+
try {
|
|
342
|
+
// Parse and validate the payload
|
|
343
|
+
payload = m.json();
|
|
344
|
+
if (!payload || typeof payload !== 'object') {
|
|
345
|
+
throw new Error('Invalid payload: not a JSON object');
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const message = new Message(payload);
|
|
349
|
+
const input = message.getContent();
|
|
350
|
+
const session = message.getSession();
|
|
351
|
+
|
|
352
|
+
logger.debug(`Received task request for ${agentName}`, {
|
|
353
|
+
inputPreview: typeof input === 'string'
|
|
354
|
+
? input.substring(0, 100)
|
|
355
|
+
: 'Non-string input'
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
// Process the task with timeout
|
|
359
|
+
const response = await withTimeout(
|
|
360
|
+
async () => processingFunction(message),
|
|
361
|
+
TIMEOUT_TASK_REQUEST * 2, // Double the timeout for processing
|
|
362
|
+
`task processing for ${agentName}`
|
|
363
|
+
);
|
|
364
|
+
|
|
365
|
+
// Respond with the result
|
|
366
|
+
await m.respond(response.serialize());
|
|
367
|
+
|
|
368
|
+
logger.debug(`Completed task request for ${agentName}`);
|
|
369
|
+
} catch (error) {
|
|
370
|
+
logger.error("Error processing task", {
|
|
371
|
+
error,
|
|
372
|
+
agentName,
|
|
373
|
+
inputPreview: payload && payload.content
|
|
374
|
+
? (typeof payload.content === 'string'
|
|
375
|
+
? payload.content.substring(0, 100)
|
|
376
|
+
: 'Non-string input')
|
|
377
|
+
: 'No input'
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
// Send error response back
|
|
381
|
+
try {
|
|
382
|
+
await m.respond(JSON.stringify({
|
|
383
|
+
error: true,
|
|
384
|
+
message: error.message,
|
|
385
|
+
type: error.name || 'Error'
|
|
386
|
+
}));
|
|
387
|
+
} catch (respondError) {
|
|
388
|
+
logger.error("Failed to send error response", { error: respondError });
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
} catch (error) {
|
|
393
|
+
logger.error("Task subscription error", { error, agentName });
|
|
394
|
+
|
|
395
|
+
// Attempt to resubscribe if the connection is still active
|
|
396
|
+
if (this.connected) {
|
|
397
|
+
logger.info('Attempting to resubscribe for task handling');
|
|
398
|
+
try {
|
|
399
|
+
await this.setupTaskHandler(agentName, processingFunction);
|
|
400
|
+
} catch (resubError) {
|
|
401
|
+
logger.error('Failed to resubscribe for task handling', { error: resubError });
|
|
402
|
+
throw new TransportError(
|
|
403
|
+
"Failed to resubscribe for task handling",
|
|
404
|
+
this.transportType,
|
|
405
|
+
{ agentName, originalError: error.message, resubError: resubError.message }
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
} else {
|
|
409
|
+
throw new TransportError(
|
|
410
|
+
"NATS connection lost during task handling",
|
|
411
|
+
this.transportType,
|
|
412
|
+
{ agentName }
|
|
413
|
+
);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Creates a runtime for agent communication using NATS
|
|
420
|
+
* @param {string} namespace - The agent namespace
|
|
421
|
+
* @param {string} agentName - The agent name
|
|
422
|
+
* @param {Array} discoverySchemas - The agent capability schemas for discovery
|
|
423
|
+
* @param {Object} config - Additional configuration
|
|
424
|
+
* @returns {Promise<Object>} - The runtime { handleTask, discoveredAgents }
|
|
425
|
+
*/
|
|
426
|
+
async createRuntime(namespace, agentName, discoverySchemas, config) {
|
|
427
|
+
const discoveredAgents = {};
|
|
428
|
+
|
|
429
|
+
try {
|
|
430
|
+
// Verify configuration
|
|
431
|
+
if (!config || !config.bindings || !config.bindings.discoveryTopic) {
|
|
432
|
+
throw new TransportError(
|
|
433
|
+
'Missing required NATS configuration: discoveryTopic',
|
|
434
|
+
this.transportType,
|
|
435
|
+
{ agentName }
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
const discoveryTopic = config.bindings.discoveryTopic;
|
|
440
|
+
const acceptedNetworks = config.bindings.acceptedNetworks || [];
|
|
441
|
+
logger.info(`Agent ${agentName} initialized with discovery topic ${discoveryTopic}`);
|
|
442
|
+
|
|
443
|
+
// Step 1: Subscribe to discovery topic
|
|
444
|
+
await this.setupDiscoverySubscription(discoveryTopic, namespace, agentName, discoveredAgents, acceptedNetworks);
|
|
445
|
+
|
|
446
|
+
// Step 2: Set up heartbeat
|
|
447
|
+
this.setupHeartbeat(discoveryTopic, namespace, agentName, discoverySchemas, HEARTBEAT_INTERVAL);
|
|
448
|
+
|
|
449
|
+
// Step 3: Create task handler function
|
|
450
|
+
const handleTask = async (fn) => {
|
|
451
|
+
if (typeof fn !== 'function') {
|
|
452
|
+
throw new Error('Task handler must be a function');
|
|
453
|
+
}
|
|
454
|
+
await this.setupTaskHandler(agentName, fn);
|
|
455
|
+
};
|
|
456
|
+
|
|
457
|
+
return { handleTask, discoveredAgents };
|
|
458
|
+
} catch (error) {
|
|
459
|
+
// Enhance the error with context if it's not already a TransportError
|
|
460
|
+
if (!(error instanceof TransportError)) {
|
|
461
|
+
error = new TransportError(
|
|
462
|
+
`Failed to initialize NATS runtime: ${error.message}`,
|
|
463
|
+
this.transportType,
|
|
464
|
+
{ agentName }
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
logger.error("NATS runtime initialization failed", { error, agentName });
|
|
469
|
+
throw error;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* Factory function to create a NATS transport instance
|
|
476
|
+
* @param {Object} config - Configuration for the NATS transport
|
|
477
|
+
* @returns {NatsTransport} - A configured NATS transport instance
|
|
478
|
+
*/
|
|
479
|
+
export function createNatsTransport() {
|
|
480
|
+
return new NatsTransport();
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* Adapter function to create a NATS-based runtime for agent communication
|
|
485
|
+
* Maintains compatibility with the original NatsIOAgentRuntime function
|
|
486
|
+
* @param {string} namespace - The agent namespace
|
|
487
|
+
* @param {string} agentName - The agent name
|
|
488
|
+
* @param {Array} ioInterfaces - The IO interfaces (only the first one is used)
|
|
489
|
+
* @param {Array} discoverySchemas - The agent capability schemas for discovery
|
|
490
|
+
* @returns {Promise<Object>} - The runtime { handleTask, discoveredAgents }
|
|
491
|
+
*/
|
|
492
|
+
export async function NatsIOAgentRuntime(namespace, agentName, ioInterfaces, discoverySchemas) {
|
|
493
|
+
if (ioInterfaces.length > 1) {
|
|
494
|
+
throw new TransportError(
|
|
495
|
+
'Only one IO Nats interface is supported',
|
|
496
|
+
'NATS',
|
|
497
|
+
{ agentName, interfacesCount: ioInterfaces.length }
|
|
498
|
+
);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
if (ioInterfaces.length === 0) {
|
|
502
|
+
logger.warn(`No NATS interfaces provided for agent ${agentName}, creating passive runtime`);
|
|
503
|
+
return { handleTask: async () => {}, discoveredAgents: {} };
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
const io = ioInterfaces[0];
|
|
507
|
+
const transport = createNatsTransport();
|
|
508
|
+
|
|
509
|
+
try {
|
|
510
|
+
// Connect to NATS with retry logic
|
|
511
|
+
logger.info(`Connecting to NATS for agent ${agentName}`);
|
|
512
|
+
await safeConnect(transport, { instance: io.instance });
|
|
513
|
+
|
|
514
|
+
// Create runtime with the transport
|
|
515
|
+
return await transport.createRuntime(namespace, agentName, discoverySchemas, io.config);
|
|
516
|
+
} catch (error) {
|
|
517
|
+
// Make sure to clean up if initialization fails
|
|
518
|
+
await transport.disconnect();
|
|
519
|
+
throw error;
|
|
520
|
+
}
|
|
521
|
+
}
|