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.
@@ -0,0 +1,123 @@
1
+ # Transport Module
2
+
3
+ This module provides a common interface for different messaging transports used in the agent communication system.
4
+
5
+ ## Architecture
6
+
7
+ The transport module follows an abstract factory pattern with the following components:
8
+
9
+ - **Transport Interface**: A base class that defines common methods for all transports
10
+ - **TransportMessage**: A base class for messages exchanged between agents
11
+ - **DiscoveryMessage**: A specialized message class for agent capability discovery
12
+ - **Concrete Implementations**: Implementations of the Transport interface for different messaging systems
13
+
14
+ ## Supported Transports
15
+
16
+ - NATS
17
+ - Kafka (template implementation)
18
+ - Redis (template implementation)
19
+ - RabbitMQ (template implementation)
20
+
21
+ ## Usage
22
+
23
+ ### Creating a Transport
24
+
25
+ ```javascript
26
+ import { createTransport } from './transport/index.js';
27
+
28
+ // Create a specific transport
29
+ const natsTransport = createTransport('nats');
30
+ const kafkaTransport = createTransport('kafka');
31
+ const redisTransport = createTransport('redis');
32
+ const rabbitMqTransport = createTransport('rabbitmq');
33
+
34
+ // Connect to the messaging system
35
+ await natsTransport.connect(config);
36
+ ```
37
+
38
+ ### Creating an Agent Runtime
39
+
40
+ ```javascript
41
+ import { createAgentRuntime } from './transport/index.js';
42
+
43
+ // Create a runtime for an agent with a specific transport
44
+ const runtime = await createAgentRuntime('nats', namespace, agentName, ioInterfaces, discoverySchemas);
45
+
46
+ // Use the runtime to handle tasks
47
+ await runtime.handleTask(async (message) => {
48
+ // Process the task
49
+ return responseMessage;
50
+ });
51
+
52
+ // Access discovered agents
53
+ const discoveredAgents = runtime.discoveredAgents;
54
+ ```
55
+
56
+ ### Using the Transport Directly
57
+
58
+ ```javascript
59
+ import { createTransport } from './transport/index.js';
60
+
61
+ // Create and connect a transport
62
+ const transport = createTransport('nats');
63
+ await transport.connect(config);
64
+
65
+ // Publish a message
66
+ await transport.publish('topic', message);
67
+
68
+ // Subscribe to a topic
69
+ await transport.subscribe('topic', handleMessage);
70
+
71
+ // Send a request and wait for a response
72
+ const response = await transport.request('target', message);
73
+ ```
74
+
75
+ ## Extending with New Transports
76
+
77
+ To add a new transport:
78
+
79
+ 1. Create a new file (e.g., `myTransport.js`) that implements the Transport interface
80
+ 2. Create a factory function that returns a new instance of your transport
81
+ 3. Register the factory in the `transportFactories` map in `index.js`
82
+ 4. Create a runtime function and register it in the `runtimeFactories` map
83
+
84
+ Example:
85
+
86
+ ```javascript
87
+ // myTransport.js
88
+ import { Transport } from './base.js';
89
+
90
+ export class MyTransport extends Transport {
91
+ // Implement all required methods
92
+ }
93
+
94
+ export function createMyTransport() {
95
+ return new MyTransport();
96
+ }
97
+
98
+ export async function MyTransportIOAgentRuntime(namespace, agentName, ioInterfaces, discoverySchemas) {
99
+ // Implementation
100
+ }
101
+
102
+ // index.js - update the factory maps
103
+ const transportFactories = {
104
+ // ...existing transports
105
+ 'mytransport': createMyTransport
106
+ };
107
+
108
+ const runtimeFactories = {
109
+ // ...existing runtimes
110
+ 'mytransport': MyTransportIOAgentRuntime
111
+ };
112
+ ```
113
+
114
+ ## Common Interface
115
+
116
+ All transports must implement these key methods:
117
+
118
+ - `connect(config)`: Connect to the messaging system
119
+ - `disconnect()`: Disconnect from the messaging system
120
+ - `publish(topic, message)`: Publish a message to a topic
121
+ - `subscribe(topic, options)`: Subscribe to a topic
122
+ - `request(target, message, options)`: Send a request and wait for a response
123
+ - `createRuntime(namespace, agentName, discoverySchemas, config)`: Create a runtime for agent communication
@@ -0,0 +1,237 @@
1
+ /**
2
+ * Base Transport Interface
3
+ * Defines common functionality across different transport implementations (NATS, Kafka, RabbitMQ, Redis, etc.)
4
+ */
5
+ import { TransportError } from '../errors/index.js';
6
+ import { logger } from '../utils/logger.js';
7
+
8
+ /**
9
+ * Base TransportMessage class that can be extended by specific transport implementations
10
+ */
11
+ export class TransportMessage {
12
+ constructor(type, payload) {
13
+ this.type = type;
14
+ this.payload = payload;
15
+ }
16
+
17
+ serialize() {
18
+ return JSON.stringify({
19
+ type: this.type,
20
+ payload: this.payload
21
+ });
22
+ }
23
+
24
+ static fromString(data) {
25
+ try {
26
+ const parsed = JSON.parse(data);
27
+ return new TransportMessage(parsed.type, parsed.payload);
28
+ } catch (error) {
29
+ throw new Error(`Failed to parse message: ${error.message}`);
30
+ }
31
+ }
32
+ }
33
+
34
+ /**
35
+ * Discovery Message format for agent discovery
36
+ */
37
+ export class DiscoveryMessage extends TransportMessage {
38
+ constructor(namespace, agentName, schemas) {
39
+ if (!namespace) throw new Error('Namespace is required');
40
+ if (!agentName) throw new Error('Agent name is required');
41
+ if (!Array.isArray(schemas)) throw new Error('Schemas must be an array');
42
+
43
+ const payload = {
44
+ network: `${namespace}.${agentName}`,
45
+ agentName: agentName,
46
+ schemas: schemas
47
+ };
48
+
49
+ super('discovery', payload);
50
+ }
51
+
52
+ get network() {
53
+ return this.payload.network;
54
+ }
55
+
56
+ get agentName() {
57
+ return this.payload.agentName;
58
+ }
59
+
60
+ get schemas() {
61
+ return this.payload.schemas;
62
+ }
63
+
64
+ static fromString(data) {
65
+ const message = TransportMessage.fromString(data);
66
+
67
+ if (message.type !== 'discovery') {
68
+ throw new Error('Not a discovery message');
69
+ }
70
+
71
+ // Extract namespace from network (format: namespace.agentName)
72
+ const networkParts = message.payload.network.split('.');
73
+ if (networkParts.length !== 2) {
74
+ throw new Error('Invalid network format in discovery message');
75
+ }
76
+
77
+ const namespace = networkParts[0];
78
+ return new DiscoveryMessage(
79
+ namespace,
80
+ message.payload.agentName,
81
+ message.payload.schemas
82
+ );
83
+ }
84
+
85
+ static isValid(payload) {
86
+ return (
87
+ payload &&
88
+ typeof payload === 'object' &&
89
+ payload.type === 'discovery' &&
90
+ typeof payload.payload.network === 'string' &&
91
+ typeof payload.payload.agentName === 'string' &&
92
+ Array.isArray(payload.payload.schemas)
93
+ );
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Base Transport Interface - all transport implementations should implement this interface
99
+ */
100
+ export class Transport {
101
+ /**
102
+ * Create a new transport instance
103
+ * @param {string} transportType - The type of transport (e.g., 'NATS', 'Kafka')
104
+ */
105
+ constructor(transportType) {
106
+ this.transportType = transportType;
107
+ this.connected = false;
108
+ this.intervals = [];
109
+ }
110
+
111
+ /**
112
+ * Connect to the transport
113
+ * @param {Object} config - Configuration options
114
+ * @returns {Promise<any>} - The connection instance
115
+ */
116
+ async connect(config) {
117
+ throw new Error('Method connect() must be implemented by subclass');
118
+ }
119
+
120
+ /**
121
+ * Disconnect from the transport
122
+ * @returns {Promise<void>}
123
+ */
124
+ async disconnect() {
125
+ // Clean up intervals
126
+ this.intervals.forEach(clearInterval);
127
+ this.intervals = [];
128
+
129
+ this.connected = false;
130
+ }
131
+
132
+ /**
133
+ * Publish a message to a topic/channel
134
+ * @param {string} topic - The topic/channel to publish to
135
+ * @param {string|Buffer|Object} message - The message to publish
136
+ * @returns {Promise<void>}
137
+ */
138
+ async publish(topic, message) {
139
+ throw new Error('Method publish() must be implemented by subclass');
140
+ }
141
+
142
+ /**
143
+ * Subscribe to a topic/channel
144
+ * @param {string} topic - The topic/channel to subscribe to
145
+ * @param {Object} options - Subscription options
146
+ * @returns {Promise<any>} - The subscription instance
147
+ */
148
+ async subscribe(topic, options = {}) {
149
+ throw new Error('Method subscribe() must be implemented by subclass');
150
+ }
151
+
152
+ /**
153
+ * Send a request and wait for a response
154
+ * @param {string} target - The target to send the request to
155
+ * @param {string|Buffer|Object} message - The message to send
156
+ * @param {Object} options - Request options
157
+ * @returns {Promise<any>} - The response
158
+ */
159
+ async request(target, message, options = {}) {
160
+ throw new Error('Method request() must be implemented by subclass');
161
+ }
162
+
163
+ /**
164
+ * Set up heartbeat to announce agent capabilities
165
+ * @param {string} topic - The topic to publish heartbeats to
166
+ * @param {string} namespace - The agent namespace
167
+ * @param {string} agentName - The agent name
168
+ * @param {Array} schemas - The agent capability schemas
169
+ * @param {number} interval - The heartbeat interval in milliseconds
170
+ * @returns {number} - The interval ID
171
+ */
172
+ setupHeartbeat(topic, namespace, agentName, schemas, interval = 1000) {
173
+ const heartbeatInterval = setInterval(async () => {
174
+ try {
175
+ const discoveryMessage = new DiscoveryMessage(namespace, agentName, schemas);
176
+ await this.publish(topic, discoveryMessage.serialize());
177
+ } catch (error) {
178
+ logger.error(`Failed to publish heartbeat for ${agentName}`, {
179
+ error,
180
+ transportType: this.transportType
181
+ });
182
+ }
183
+ }, interval);
184
+
185
+ this.intervals.push(heartbeatInterval);
186
+ return heartbeatInterval;
187
+ }
188
+
189
+ /**
190
+ * Creates a runtime for agent communication
191
+ * @param {string} namespace - The agent namespace
192
+ * @param {string} agentName - The agent name
193
+ * @param {Array} discoverySchemas - The agent capability schemas for discovery
194
+ * @param {Object} config - Additional configuration
195
+ * @returns {Promise<Object>} - The runtime { handleTask, discoveredAgents }
196
+ */
197
+ async createRuntime(namespace, agentName, discoverySchemas, config = {}) {
198
+ throw new Error('Method createRuntime() must be implemented by subclass');
199
+ }
200
+ }
201
+
202
+ /**
203
+ * Safely connect to a transport with retry logic
204
+ * @param {Transport} transport - The transport instance
205
+ * @param {Object} config - Connection configuration
206
+ * @param {Object} options - Retry options
207
+ * @returns {Promise<any>} - The connection instance
208
+ */
209
+ export async function safeConnect(transport, config, options = {}) {
210
+ const { maxRetries = 5 } = options;
211
+
212
+ let attempt = 0;
213
+ let lastError = null;
214
+
215
+ while (attempt < maxRetries) {
216
+ try {
217
+ attempt++;
218
+ return await transport.connect(config);
219
+ } catch (error) {
220
+ lastError = error;
221
+ logger.warn(`Transport connect attempt ${attempt}/${maxRetries} failed, retrying...`, {
222
+ transportType: transport.transportType,
223
+ error: error.message
224
+ });
225
+
226
+ // Exponential backoff
227
+ const delay = Math.min(1000 * Math.pow(2, attempt - 1), 10000);
228
+ await new Promise(resolve => setTimeout(resolve, delay));
229
+ }
230
+ }
231
+
232
+ throw new TransportError(
233
+ `Failed to connect after ${maxRetries} attempts: ${lastError?.message}`,
234
+ transport.transportType,
235
+ { details: lastError?.message }
236
+ );
237
+ }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Transport Factory Module
3
+ * Provides a unified interface to create transport instances
4
+ */
5
+ import { Transport } from './base.js';
6
+ import { createNatsTransport, NatsIOAgentRuntime } from './nats.js';
7
+ import { createKafkaTransport, KafkaIOAgentRuntime } from './kafka.js';
8
+ import { createRedisTransport, RedisIOAgentRuntime } from './redis.js';
9
+ import { createRabbitMQTransport, RabbitMQIOAgentRuntime } from './rabbitmq.js';
10
+ import { TransportError } from '../errors/index.js';
11
+ import { logger } from '../utils/logger.js';
12
+
13
+ // Map of transport types to their factory functions
14
+ const transportFactories = {
15
+ 'nats': createNatsTransport,
16
+ 'kafka': createKafkaTransport,
17
+ 'redis': createRedisTransport,
18
+ 'rabbitmq': createRabbitMQTransport,
19
+ };
20
+
21
+ // Map of transport types to their runtime functions
22
+ const runtimeFactories = {
23
+ 'nats': NatsIOAgentRuntime,
24
+ 'kafka': KafkaIOAgentRuntime,
25
+ 'redis': RedisIOAgentRuntime,
26
+ 'rabbitmq': RabbitMQIOAgentRuntime,
27
+ };
28
+
29
+ /**
30
+ * Create a transport instance based on the specified type
31
+ * @param {string} type - The transport type (e.g., 'nats', 'kafka', 'redis', 'rabbitmq')
32
+ * @returns {Transport} - A transport instance
33
+ * @throws {TransportError} - If the transport type is not supported
34
+ */
35
+ export function createTransport(type) {
36
+ const factoryFn = transportFactories[type.toLowerCase()];
37
+
38
+ if (!factoryFn) {
39
+ throw new TransportError(
40
+ `Unsupported transport type: ${type}`,
41
+ 'TransportFactory',
42
+ { supportedTypes: Object.keys(transportFactories) }
43
+ );
44
+ }
45
+
46
+ return factoryFn();
47
+ }
48
+
49
+ /**
50
+ * Create a transport runtime for an agent
51
+ * @param {string} type - The transport type (e.g., 'nats', 'kafka', 'redis', 'rabbitmq')
52
+ * @param {string} namespace - The agent namespace
53
+ * @param {string} agentName - The agent name
54
+ * @param {Array} ioInterfaces - The IO interfaces
55
+ * @param {Array} discoverySchemas - The agent capability schemas for discovery
56
+ * @returns {Promise<Object>} - The runtime { handleTask, discoveredAgents }
57
+ * @throws {TransportError} - If the transport type is not supported
58
+ */
59
+ export async function createAgentRuntime(type, namespace, agentName, ioInterfaces, discoverySchemas) {
60
+ const runtimeFn = runtimeFactories[type.toLowerCase()];
61
+
62
+ if (!runtimeFn) {
63
+ throw new TransportError(
64
+ `Unsupported transport runtime type: ${type}`,
65
+ 'TransportFactory',
66
+ { supportedTypes: Object.keys(runtimeFactories) }
67
+ );
68
+ }
69
+
70
+ try {
71
+ return await runtimeFn(namespace, agentName, ioInterfaces, discoverySchemas);
72
+ } catch (error) {
73
+ logger.error(`Failed to create ${type} agent runtime`, { error, agentName });
74
+ throw error;
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Export base classes and interfaces for extensibility
80
+ */
81
+ export * from './base.js';
82
+
83
+ /**
84
+ * Export specific transport implementations
85
+ */
86
+ export * from './nats.js';
87
+ export * from './kafka.js';
88
+ export * from './redis.js';
89
+ export * from './rabbitmq.js';