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