agentnet 0.0.1 → 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 +317 -364
- package/_OLD_README.md +554 -0
- package/assets/network01.png +0 -0
- package/examples/customer-support/README.md +66 -0
- package/examples/customer-support/agents.yaml +457 -0
- package/examples/customer-support/index.js +408 -0
- package/examples/event-planner/README.md +69 -0
- package/examples/event-planner/agents.yaml +318 -0
- package/examples/event-planner/index.js +547 -0
- package/{src/examples → examples/simple}/simple.js +2 -2
- package/{src/examples → examples/smartness}/agents-smartness.yaml +8 -17
- package/{src/examples/def3.js → examples/smartness/index.js} +9 -9
- package/jest.config.js +1 -0
- package/package.json +6 -3
- package/src/agent/agent-loader.js +75 -12
- package/src/agent/agent.js +13 -3
- package/src/agent/runtime.js +9 -6
- package/src/llm/base.js +131 -0
- package/src/llm/gemini.js +137 -117
- package/src/llm/gpt.js +131 -104
- package/src/store/store.js +82 -48
- 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 -379
- package/src/examples/agents.yaml +0 -394
- package/src/examples/def.js +0 -74
- package/src/examples/def2.js +0 -65
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { logger } from './logger.js';
|
|
2
|
+
import { ConfigurationError } from '../errors/index.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Map of API versions with their compatibility information
|
|
6
|
+
*/
|
|
7
|
+
export const API_VERSIONS = {
|
|
8
|
+
// Smartagent API versions
|
|
9
|
+
'smartagent.io/v1alpha1': {
|
|
10
|
+
isSupported: true,
|
|
11
|
+
isStable: false,
|
|
12
|
+
minEngineVersion: '0.5.0',
|
|
13
|
+
features: ['basic', 'discovery', 'handoffs']
|
|
14
|
+
},
|
|
15
|
+
// Agentnet API versions
|
|
16
|
+
'agentnet.io/v1alpha1': {
|
|
17
|
+
isSupported: true,
|
|
18
|
+
isStable: false,
|
|
19
|
+
minEngineVersion: '0.8.0',
|
|
20
|
+
features: ['basic', 'discovery', 'handoffs', 'advanced-routing']
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Default API version to use when none is specified
|
|
26
|
+
*/
|
|
27
|
+
export const DEFAULT_API_VERSION = 'smartagent.io/v1alpha1';
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Latest stable API version
|
|
31
|
+
*/
|
|
32
|
+
export const LATEST_STABLE_VERSION = 'agentnet.io/v1alpha1';
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Version upgrade paths map
|
|
36
|
+
* Defines valid upgrade paths between versions
|
|
37
|
+
*/
|
|
38
|
+
export const VERSION_UPGRADE_PATHS = {
|
|
39
|
+
'smartagent.io/v1alpha1': ['agentnet.io/v1alpha1']
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Check if an API version is supported
|
|
44
|
+
* @param {string} version - The API version to check
|
|
45
|
+
* @returns {boolean} True if the version is supported
|
|
46
|
+
*/
|
|
47
|
+
export function isVersionSupported(version) {
|
|
48
|
+
return API_VERSIONS[version]?.isSupported === true;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Check if an API version is stable (not subject to breaking changes)
|
|
53
|
+
* @param {string} version - The API version to check
|
|
54
|
+
* @returns {boolean} True if the version is stable
|
|
55
|
+
*/
|
|
56
|
+
export function isVersionStable(version) {
|
|
57
|
+
return API_VERSIONS[version]?.isStable === true;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Checks if a feature is supported in given API version
|
|
62
|
+
* @param {string} version - The API version to check
|
|
63
|
+
* @param {string} feature - The feature to check
|
|
64
|
+
* @returns {boolean} True if the feature is supported in this version
|
|
65
|
+
*/
|
|
66
|
+
export function isFeatureSupported(version, feature) {
|
|
67
|
+
return API_VERSIONS[version]?.features?.includes(feature) === true;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Validates an agent definition's API version
|
|
72
|
+
* @param {object} definition - Agent definition with apiVersion
|
|
73
|
+
* @param {string} currentEngineVersion - Current version of the agent engine
|
|
74
|
+
* @returns {object} Validated version information
|
|
75
|
+
* @throws {ConfigurationError} If version is unsupported
|
|
76
|
+
*/
|
|
77
|
+
export function validateApiVersion(definition, currentEngineVersion = '1.0.0') {
|
|
78
|
+
const apiVersion = definition.apiVersion || DEFAULT_API_VERSION;
|
|
79
|
+
const agentName = definition.metadata?.name || 'Unnamed Agent';
|
|
80
|
+
|
|
81
|
+
// Check if version exists
|
|
82
|
+
const versionInfo = API_VERSIONS[apiVersion];
|
|
83
|
+
if (!versionInfo) {
|
|
84
|
+
const supportedVersions = Object.keys(API_VERSIONS).join(', ');
|
|
85
|
+
throw new ConfigurationError(
|
|
86
|
+
`Unsupported apiVersion '${apiVersion}' for agent '${agentName}'. Supported versions: ${supportedVersions}`,
|
|
87
|
+
{ apiVersion, agentName }
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Check if version is supported
|
|
92
|
+
if (!versionInfo.isSupported) {
|
|
93
|
+
throw new ConfigurationError(
|
|
94
|
+
`API version '${apiVersion}' is deprecated for agent '${agentName}'. Please update to a supported version.`,
|
|
95
|
+
{ apiVersion, agentName }
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Check engine compatibility (simple version check for now)
|
|
100
|
+
if (versionInfo.minEngineVersion && versionInfo.minEngineVersion > currentEngineVersion) {
|
|
101
|
+
logger.warn(
|
|
102
|
+
`Agent '${agentName}' uses apiVersion '${apiVersion}' which requires engine version ${versionInfo.minEngineVersion} or higher. Current: ${currentEngineVersion}`,
|
|
103
|
+
{ apiVersion, agentName, minEngineVersion: versionInfo.minEngineVersion, currentEngineVersion }
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Log stability warning for non-stable versions
|
|
108
|
+
if (!versionInfo.isStable) {
|
|
109
|
+
logger.warn(
|
|
110
|
+
`Agent '${agentName}' uses apiVersion '${apiVersion}' which is not marked as stable and may have breaking changes in future releases.`,
|
|
111
|
+
{ apiVersion, agentName }
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
version: apiVersion,
|
|
117
|
+
info: versionInfo
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Get normalized version of API version
|
|
123
|
+
* This extracts just the version part, removing the domain prefix
|
|
124
|
+
* @param {string} apiVersion - Full API version string (e.g., 'agentnet.io/v1alpha1')
|
|
125
|
+
* @returns {string} Normalized version (e.g., 'v1alpha1')
|
|
126
|
+
*/
|
|
127
|
+
export function getNormalizedVersion(apiVersion) {
|
|
128
|
+
const parts = apiVersion.split('/');
|
|
129
|
+
return parts.length > 1 ? parts[1] : apiVersion;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Checks if an upgrade path exists between versions
|
|
134
|
+
* @param {string} fromVersion - Source version
|
|
135
|
+
* @param {string} toVersion - Target version
|
|
136
|
+
* @returns {boolean} Whether a direct upgrade path exists
|
|
137
|
+
*/
|
|
138
|
+
export function canUpgradeVersion(fromVersion, toVersion) {
|
|
139
|
+
if (fromVersion === toVersion) return true;
|
|
140
|
+
return VERSION_UPGRADE_PATHS[fromVersion]?.includes(toVersion) === true;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Get possible upgrade paths for a version
|
|
145
|
+
* @param {string} fromVersion - Current version
|
|
146
|
+
* @returns {string[]} Available upgrade targets
|
|
147
|
+
*/
|
|
148
|
+
export function getUpgradeOptions(fromVersion) {
|
|
149
|
+
return VERSION_UPGRADE_PATHS[fromVersion] || [];
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Migration helpers for each version transformation
|
|
154
|
+
*/
|
|
155
|
+
const VERSION_MIGRATIONS = {
|
|
156
|
+
// Migration from smartagent.io/v1alpha1 to agentnet.io/v1alpha1
|
|
157
|
+
'smartagent.io/v1alpha1->agentnet.io/v1alpha1': (definition) => {
|
|
158
|
+
// Deep clone the definition to avoid modifying the original
|
|
159
|
+
const newDef = JSON.parse(JSON.stringify(definition));
|
|
160
|
+
|
|
161
|
+
// Update the apiVersion
|
|
162
|
+
newDef.apiVersion = 'agentnet.io/v1alpha1';
|
|
163
|
+
|
|
164
|
+
// Handle specific field migrations
|
|
165
|
+
// Example: rename or restructure fields as needed
|
|
166
|
+
|
|
167
|
+
// Log the migration
|
|
168
|
+
logger.info(`Migrated agent definition from smartagent.io/v1alpha1 to agentnet.io/v1alpha1`, {
|
|
169
|
+
agentName: newDef.metadata?.name
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
return newDef;
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Migrate an agent definition from one version to another
|
|
178
|
+
* @param {object} definition - Agent definition to migrate
|
|
179
|
+
* @param {string} targetVersion - Target API version
|
|
180
|
+
* @returns {object} Migrated definition
|
|
181
|
+
* @throws {ConfigurationError} If migration path doesn't exist
|
|
182
|
+
*/
|
|
183
|
+
export function migrateDefinition(definition, targetVersion) {
|
|
184
|
+
const sourceVersion = definition.apiVersion || DEFAULT_API_VERSION;
|
|
185
|
+
|
|
186
|
+
// If already at target version, return as is
|
|
187
|
+
if (sourceVersion === targetVersion) {
|
|
188
|
+
return definition;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Check if direct upgrade path exists
|
|
192
|
+
if (!canUpgradeVersion(sourceVersion, targetVersion)) {
|
|
193
|
+
throw new ConfigurationError(
|
|
194
|
+
`No direct migration path from ${sourceVersion} to ${targetVersion}`,
|
|
195
|
+
{ sourceVersion, targetVersion }
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Get the migration function
|
|
200
|
+
const migrationKey = `${sourceVersion}->${targetVersion}`;
|
|
201
|
+
const migrationFn = VERSION_MIGRATIONS[migrationKey];
|
|
202
|
+
|
|
203
|
+
if (!migrationFn) {
|
|
204
|
+
throw new ConfigurationError(
|
|
205
|
+
`Migration path exists but no implementation found for ${migrationKey}`,
|
|
206
|
+
{ sourceVersion, targetVersion }
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Apply the migration
|
|
211
|
+
return migrationFn(definition);
|
|
212
|
+
}
|
|
@@ -1,379 +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
|
-
* Sets up discovery subscription to find other agents
|
|
21
|
-
*/
|
|
22
|
-
async function setupDiscoverySubscription(nc, discoveryTopic, agentName, discoveredAgents) {
|
|
23
|
-
let discoverySub;
|
|
24
|
-
|
|
25
|
-
try {
|
|
26
|
-
discoverySub = nc.subscribe(discoveryTopic);
|
|
27
|
-
logger.info(`Agent ${agentName} subscribed to discovery topic ${discoveryTopic}`);
|
|
28
|
-
} catch (error) {
|
|
29
|
-
throw new DiscoveryError(
|
|
30
|
-
`Failed to subscribe to discovery topic ${discoveryTopic}`,
|
|
31
|
-
{ agentName, topic: discoveryTopic },
|
|
32
|
-
error
|
|
33
|
-
);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const handleDiscovery = async () => {
|
|
37
|
-
try {
|
|
38
|
-
for await (const m of discoverySub) {
|
|
39
|
-
try {
|
|
40
|
-
const payloadSetup = JSON.parse(m.string());
|
|
41
|
-
|
|
42
|
-
// Validate the payload structure
|
|
43
|
-
if (!payloadSetup.agentName || !Array.isArray(payloadSetup.schemas)) {
|
|
44
|
-
logger.warn('Invalid discovery payload received', { payload: payloadSetup });
|
|
45
|
-
continue;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
for (const schema of payloadSetup.schemas) {
|
|
49
|
-
// Skip invalid schemas
|
|
50
|
-
if (!schema || !schema.name) {
|
|
51
|
-
logger.warn('Invalid schema in discovery payload', { schema });
|
|
52
|
-
continue;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const agentKey = `${payloadSetup.agentName}-${schema.name}`;
|
|
56
|
-
|
|
57
|
-
if (payloadSetup.agentName !== agentName && !discoveredAgents[agentKey]) {
|
|
58
|
-
logger.info(`${agentName} discovered agent capability: ${payloadSetup.agentName} with capability ${schema.name}`);
|
|
59
|
-
|
|
60
|
-
const handoffFunction = async (conversation, state, input) => {
|
|
61
|
-
try {
|
|
62
|
-
// Use withTimeout to ensure handoffs don't hang
|
|
63
|
-
return await withTimeout(
|
|
64
|
-
async () => {
|
|
65
|
-
try {
|
|
66
|
-
const response = await withRetry(
|
|
67
|
-
async () => {
|
|
68
|
-
const message = new Message({
|
|
69
|
-
session: state,
|
|
70
|
-
content: input
|
|
71
|
-
})
|
|
72
|
-
const req = await nc.request(
|
|
73
|
-
payloadSetup.agentName,
|
|
74
|
-
message.serialize(),
|
|
75
|
-
{ timeout: TIMEOUT_TASK_REQUEST }
|
|
76
|
-
);
|
|
77
|
-
return req.string();
|
|
78
|
-
},
|
|
79
|
-
{
|
|
80
|
-
maxRetries: 2,
|
|
81
|
-
onRetry: ({ attempt }) => {
|
|
82
|
-
logger.warn(`Retrying handoff attempt ${attempt} to ${payloadSetup.agentName}`, {
|
|
83
|
-
schema: schema.name
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
);
|
|
88
|
-
return response;
|
|
89
|
-
} catch (error) {
|
|
90
|
-
throw new HandoffError(
|
|
91
|
-
`Handoff to agent ${payloadSetup.agentName} failed: ${error.message}`,
|
|
92
|
-
agentName,
|
|
93
|
-
payloadSetup.agentName,
|
|
94
|
-
{ schemaName: schema.name }
|
|
95
|
-
);
|
|
96
|
-
}
|
|
97
|
-
},
|
|
98
|
-
TIMEOUT_TASK_REQUEST,
|
|
99
|
-
`handoff to ${payloadSetup.agentName}`
|
|
100
|
-
);
|
|
101
|
-
} catch (error) {
|
|
102
|
-
logger.error(`Handoff error to ${payloadSetup.agentName}`, {
|
|
103
|
-
error,
|
|
104
|
-
schema: schema.name
|
|
105
|
-
});
|
|
106
|
-
throw error;
|
|
107
|
-
}
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
discoveredAgents[agentKey] = {
|
|
111
|
-
name: schema.name,
|
|
112
|
-
schema: schema,
|
|
113
|
-
function: handoffFunction
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
} catch (error) {
|
|
118
|
-
logger.error('Error processing discovery message', { error });
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
} catch (error) {
|
|
122
|
-
logger.error("Discovery subscription error", { error });
|
|
123
|
-
|
|
124
|
-
// Attempt to resubscribe if the connection is still active
|
|
125
|
-
if (nc.isConnected()) {
|
|
126
|
-
logger.info('Attempting to resubscribe to discovery topic');
|
|
127
|
-
try {
|
|
128
|
-
discoverySub = nc.subscribe(discoveryTopic);
|
|
129
|
-
handleDiscovery(); // Restart the handling process
|
|
130
|
-
} catch (resubError) {
|
|
131
|
-
logger.error('Failed to resubscribe to discovery topic', { error: resubError });
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
// Start processing discovery messages
|
|
138
|
-
handleDiscovery();
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Sets up a heartbeat to announce this agent's capabilities
|
|
143
|
-
*/
|
|
144
|
-
function setupDiscoveryHeartbeat(nc, discoveryTopic, agentName, discoverySchemas) {
|
|
145
|
-
let consecutiveErrors = 0;
|
|
146
|
-
|
|
147
|
-
return setInterval(async () => {
|
|
148
|
-
try {
|
|
149
|
-
const heartbeatPayload = {
|
|
150
|
-
type: 'discovery',
|
|
151
|
-
agentName: agentName,
|
|
152
|
-
schemas: discoverySchemas
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
await nc.publish(discoveryTopic, JSON.stringify(heartbeatPayload));
|
|
156
|
-
|
|
157
|
-
// Reset error counter on success
|
|
158
|
-
if (consecutiveErrors > 0) {
|
|
159
|
-
logger.info(`Discovery heartbeat resumed for ${agentName}`);
|
|
160
|
-
consecutiveErrors = 0;
|
|
161
|
-
}
|
|
162
|
-
} catch (error) {
|
|
163
|
-
consecutiveErrors++;
|
|
164
|
-
|
|
165
|
-
// Log with increasing severity based on consecutive failures
|
|
166
|
-
if (consecutiveErrors > 5) {
|
|
167
|
-
logger.error(`Failed to publish discovery heartbeat (${consecutiveErrors} consecutive failures)`, {
|
|
168
|
-
error,
|
|
169
|
-
agentName,
|
|
170
|
-
topic: discoveryTopic
|
|
171
|
-
});
|
|
172
|
-
} else {
|
|
173
|
-
logger.warn(`Error publishing discovery heartbeat (attempt ${consecutiveErrors})`, {
|
|
174
|
-
error: error.message,
|
|
175
|
-
agentName
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
}, HEARTBEAT_INTERVAL);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* Creates a task handler for incoming requests
|
|
184
|
-
*/
|
|
185
|
-
async function createTaskHandler(nc, agentName, processingFunction) {
|
|
186
|
-
let taskSub;
|
|
187
|
-
|
|
188
|
-
try {
|
|
189
|
-
taskSub = nc.subscribe(agentName, { queue: agentName });
|
|
190
|
-
logger.info(`Agent ${agentName} subscribed for task handling`);
|
|
191
|
-
} catch (error) {
|
|
192
|
-
throw new TransportError(
|
|
193
|
-
`Failed to subscribe for task handling: ${error.message}`,
|
|
194
|
-
'NATS',
|
|
195
|
-
{ agentName }
|
|
196
|
-
);
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
try {
|
|
200
|
-
for await (const m of taskSub) {
|
|
201
|
-
let payload;
|
|
202
|
-
|
|
203
|
-
try {
|
|
204
|
-
// Parse and validate the payload
|
|
205
|
-
payload = m.json();
|
|
206
|
-
if (!payload || typeof payload !== 'object') {
|
|
207
|
-
throw new Error('Invalid payload: not a JSON object');
|
|
208
|
-
}
|
|
209
|
-
const message = new Message(payload)
|
|
210
|
-
const input = message.getContent()
|
|
211
|
-
const session = message.getSession()
|
|
212
|
-
|
|
213
|
-
logger.debug(`Received task request for ${agentName}`, {
|
|
214
|
-
inputPreview: typeof input === 'string'
|
|
215
|
-
? input.substring(0, 100)
|
|
216
|
-
: 'Non-string input'
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
// Process the task with timeout
|
|
220
|
-
const response = await withTimeout(
|
|
221
|
-
async () => processingFunction(message),
|
|
222
|
-
TIMEOUT_TASK_REQUEST * 2, // Double the timeout for processing
|
|
223
|
-
`task processing for ${agentName}`
|
|
224
|
-
);
|
|
225
|
-
|
|
226
|
-
// Respond with the result
|
|
227
|
-
await m.respond(response.serialize());
|
|
228
|
-
|
|
229
|
-
logger.debug(`Completed task request for ${agentName}`);
|
|
230
|
-
} catch (error) {
|
|
231
|
-
logger.error("Error processing task", {
|
|
232
|
-
error,
|
|
233
|
-
agentName,
|
|
234
|
-
inputPreview: payload && input
|
|
235
|
-
? (typeof input === 'string'
|
|
236
|
-
? input.substring(0, 100)
|
|
237
|
-
: 'Non-string input')
|
|
238
|
-
: 'No input'
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
// Send error response back
|
|
242
|
-
try {
|
|
243
|
-
await m.respond(JSON.stringify({
|
|
244
|
-
error: true,
|
|
245
|
-
message: error.message,
|
|
246
|
-
type: error.name || 'Error'
|
|
247
|
-
}));
|
|
248
|
-
} catch (respondError) {
|
|
249
|
-
logger.error("Failed to send error response", { error: respondError });
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
} catch (error) {
|
|
254
|
-
logger.error("Task subscription error", { error, agentName });
|
|
255
|
-
|
|
256
|
-
// Attempt to resubscribe if the connection is still active
|
|
257
|
-
if (nc.isConnected()) {
|
|
258
|
-
logger.info('Attempting to resubscribe for task handling');
|
|
259
|
-
try {
|
|
260
|
-
const newTaskSub = nc.subscribe(agentName, { queue: agentName });
|
|
261
|
-
// Start a new processing loop
|
|
262
|
-
createTaskHandler(nc, agentName, processingFunction);
|
|
263
|
-
} catch (resubError) {
|
|
264
|
-
logger.error('Failed to resubscribe for task handling', { error: resubError });
|
|
265
|
-
throw new TransportError(
|
|
266
|
-
"Failed to resubscribe for task handling",
|
|
267
|
-
'NATS',
|
|
268
|
-
{ agentName, originalError: error.message, resubError: resubError.message }
|
|
269
|
-
);
|
|
270
|
-
}
|
|
271
|
-
} else {
|
|
272
|
-
throw new TransportError(
|
|
273
|
-
"NATS connection lost during task handling",
|
|
274
|
-
'NATS',
|
|
275
|
-
{ agentName }
|
|
276
|
-
);
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
/**
|
|
282
|
-
* Safely connects to NATS with retry logic
|
|
283
|
-
*/
|
|
284
|
-
async function safeConnect(instance, options = {}) {
|
|
285
|
-
const { maxRetries = MAX_RECONNECT_ATTEMPTS } = options;
|
|
286
|
-
|
|
287
|
-
return withRetry(
|
|
288
|
-
async () => {
|
|
289
|
-
try {
|
|
290
|
-
return await instance.connect();
|
|
291
|
-
} catch (error) {
|
|
292
|
-
throw new TransportError(
|
|
293
|
-
`Failed to connect to NATS: ${error.message}`,
|
|
294
|
-
'NATS',
|
|
295
|
-
{ details: error.message }
|
|
296
|
-
);
|
|
297
|
-
}
|
|
298
|
-
},
|
|
299
|
-
{
|
|
300
|
-
maxRetries,
|
|
301
|
-
baseDelayMs: 500,
|
|
302
|
-
onRetry: ({ attempt }) => {
|
|
303
|
-
logger.warn(`NATS connect attempt ${attempt}/${maxRetries} failed, retrying...`);
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
);
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
/**
|
|
310
|
-
* Creates a NATS-based runtime for agent communication
|
|
311
|
-
*/
|
|
312
|
-
export async function NatsIOAgentRuntime(agentName, ioInterfaces, discoverySchemas) {
|
|
313
|
-
if (ioInterfaces.length > 1) {
|
|
314
|
-
throw new TransportError(
|
|
315
|
-
'Only one IO Nats interface is supported',
|
|
316
|
-
'NATS',
|
|
317
|
-
{ agentName, interfacesCount: ioInterfaces.length }
|
|
318
|
-
);
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
if (ioInterfaces.length === 0) {
|
|
322
|
-
logger.warn(`No NATS interfaces provided for agent ${agentName}, creating passive runtime`);
|
|
323
|
-
return { handleTask: async () => {}, discoveredAgents: [] };
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
const io = ioInterfaces[0];
|
|
327
|
-
const intervals = [];
|
|
328
|
-
const discoveredAgents = {};
|
|
329
|
-
|
|
330
|
-
try {
|
|
331
|
-
// Connect to NATS with retry logic
|
|
332
|
-
logger.info(`Connecting to NATS for agent ${agentName}`);
|
|
333
|
-
const nc = await safeConnect(io.instance);
|
|
334
|
-
|
|
335
|
-
// Verify configuration
|
|
336
|
-
if (!io.config || !io.config.bindings || !io.config.bindings.discoveryTopic) {
|
|
337
|
-
throw new TransportError(
|
|
338
|
-
'Missing required NATS configuration: discoveryTopic',
|
|
339
|
-
'NATS',
|
|
340
|
-
{ agentName }
|
|
341
|
-
);
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
const discoveryTopic = io.config.bindings.discoveryTopic;
|
|
345
|
-
logger.info(`Agent ${agentName} initialized with discovery topic ${discoveryTopic}`);
|
|
346
|
-
|
|
347
|
-
// Step 1. Subscribe to discovery topic
|
|
348
|
-
await setupDiscoverySubscription(nc, discoveryTopic, agentName, discoveredAgents);
|
|
349
|
-
|
|
350
|
-
// Step 2. Publish discovery heartbeat
|
|
351
|
-
const interval = setupDiscoveryHeartbeat(nc, discoveryTopic, agentName, discoverySchemas);
|
|
352
|
-
intervals.push(interval);
|
|
353
|
-
|
|
354
|
-
// Step 3. Create task handler
|
|
355
|
-
const handleTask = async (fn) => {
|
|
356
|
-
if (typeof fn !== 'function') {
|
|
357
|
-
throw new Error('Task handler must be a function');
|
|
358
|
-
}
|
|
359
|
-
await createTaskHandler(nc, agentName, fn);
|
|
360
|
-
};
|
|
361
|
-
|
|
362
|
-
return { handleTask, discoveredAgents };
|
|
363
|
-
} catch (error) {
|
|
364
|
-
// Clean up intervals if connection fails
|
|
365
|
-
intervals.forEach(clearInterval);
|
|
366
|
-
|
|
367
|
-
// Enhance the error with context if it's not already a TransportError
|
|
368
|
-
if (!(error instanceof TransportError)) {
|
|
369
|
-
error = new TransportError(
|
|
370
|
-
`Failed to initialize NATS runtime: ${error.message}`,
|
|
371
|
-
'NATS',
|
|
372
|
-
{ agentName }
|
|
373
|
-
);
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
logger.error("NATS runtime initialization failed", { error, agentName });
|
|
377
|
-
throw error;
|
|
378
|
-
}
|
|
379
|
-
}
|