agentnet 0.0.1

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,14 @@
1
+ import { Message, Response } from '../index.js'
2
+ export function AgentClient() {
3
+ return {
4
+ queryAgent: async (agent, input) => {
5
+ return await agent.query(input)
6
+ },
7
+
8
+ queryIo: async (io, target, message) => {
9
+ const transport = await io.connect()
10
+ const response = await transport.request(target, message.serialize(), { timeout: 60000 })
11
+ return new Response(JSON.parse(response.string()))
12
+ }
13
+ }
14
+ }
@@ -0,0 +1,320 @@
1
+ import { logger } from '../utils/logger.js';
2
+ import {
3
+ ToolExecutionError,
4
+ LLMError,
5
+ TimeoutError,
6
+ withTimeout,
7
+ withRetry
8
+ } from '../errors/index.js';
9
+
10
+ const DEFAULT_TOOL_TIMEOUT = 30000; // 30 seconds
11
+ const DEFAULT_LLM_TIMEOUT = 60000; // 60 seconds
12
+
13
+ /**
14
+ * Emits an event to the hooks system if hooks are available
15
+ * @param {Object} hooks - Event hooks
16
+ * @param {string} event - Event name
17
+ * @param {Object} data - Event data
18
+ */
19
+ async function emit(hooks, event, data) {
20
+ if (hooks === null) {
21
+ return;
22
+ }
23
+
24
+ try {
25
+ hooks.emit(event, data);
26
+ } catch (error) {
27
+ logger.warn(`Error emitting ${event} event`, { error, data });
28
+ }
29
+ }
30
+
31
+ /**
32
+ * Builds the tools and handoffs map for the executor
33
+ * @param {Object} toolsAndHandoffsMap - Map to populate
34
+ * @param {Array} tools - Tool definitions
35
+ * @param {Array} handoffs - Handoff definitions
36
+ */
37
+ export function makeToolsAndHandoffsMap(toolsAndHandoffsMap, tools, handoffs) {
38
+ if (!tools) {
39
+ return;
40
+ }
41
+
42
+ try {
43
+ // Process tools
44
+ for (const tool of tools) {
45
+ if (!tool) {
46
+ logger.warn('Skipping undefined tool');
47
+ continue;
48
+ }
49
+
50
+ if (!tool.schema) {
51
+ toolsAndHandoffsMap.tools.push(tool);
52
+ continue;
53
+ }
54
+
55
+ // Add tool schema to tools list
56
+ toolsAndHandoffsMap.tools.push(tool.schema);
57
+
58
+ // Map tool name to function
59
+ toolsAndHandoffsMap[tool.name] = {
60
+ function: tool.function,
61
+ type: 'tool'
62
+ };
63
+ }
64
+
65
+ // Process handoffs (which may be a nested array)
66
+ if (handoffs && Array.isArray(handoffs)) {
67
+ const flatHandoffs = handoffs.flat().filter(Boolean);
68
+
69
+ for (const handoff of flatHandoffs) {
70
+ if (!handoff || !handoff.schema || !handoff.name) {
71
+ logger.warn('Skipping invalid handoff definition', { handoff });
72
+ continue;
73
+ }
74
+
75
+ // Add handoff schema to tools list
76
+ toolsAndHandoffsMap.tools.push(handoff.schema);
77
+
78
+ // Map handoff name to function
79
+ toolsAndHandoffsMap[handoff.name] = {
80
+ function: handoff.function,
81
+ type: 'handoff'
82
+ };
83
+ }
84
+ }
85
+ } catch (error) {
86
+ logger.error('Error building tools and handoffs map', { error });
87
+ throw error;
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Safely executes a tool or handoff function with timeout and error handling
93
+ * @param {Function} func - Tool/handoff function to execute
94
+ * @param {string} name - Tool/handoff name
95
+ * @param {string} type - 'tool' or 'handoff'
96
+ * @param {Object} state - Agent state
97
+ * @param {any} input - Tool/handoff input
98
+ * @param {number} timeout - Timeout in ms
99
+ * @returns {Promise<any>} Tool execution result
100
+ */
101
+ async function safeExecute(func, name, type, state, input, timeout = DEFAULT_TOOL_TIMEOUT) {
102
+ if (typeof func !== 'function') {
103
+ throw new ToolExecutionError(
104
+ `${type} "${name}" is not a function`,
105
+ name,
106
+ input
107
+ );
108
+ }
109
+
110
+ try {
111
+ // Execute with timeout
112
+ return await withTimeout(
113
+ async () => {
114
+ try {
115
+ return await func(state, input);
116
+ } catch (error) {
117
+ throw new ToolExecutionError(
118
+ `Error executing ${type} "${name}": ${error.message}`,
119
+ name,
120
+ input,
121
+ error
122
+ );
123
+ }
124
+ },
125
+ timeout,
126
+ `${type} execution: ${name}`
127
+ );
128
+ } catch (error) {
129
+ // Log detailed error information
130
+ logger.error(`${type} execution error`, {
131
+ error,
132
+ toolName: name,
133
+ inputSample: JSON.stringify(input).substring(0, 200)
134
+ });
135
+
136
+ // Ensure we always return a structured error
137
+ if (error instanceof ToolExecutionError || error instanceof TimeoutError) {
138
+ throw error;
139
+ }
140
+
141
+ throw new ToolExecutionError(
142
+ `Error executing ${type} "${name}": ${error.message}`,
143
+ name,
144
+ input,
145
+ error
146
+ );
147
+ }
148
+ }
149
+
150
+ /**
151
+ * Builds an executor for agent execution
152
+ * @param {Object} toolsAndHandoffsMap - Map of tools and handoffs
153
+ * @param {Object} hooks - Event hooks
154
+ * @param {string} agentName - Agent name
155
+ * @param {Object} api - LLM API
156
+ * @param {Object} llmConfig - LLM configuration
157
+ * @param {Object} runner - Runner configuration
158
+ * @returns {Function} Executor function
159
+ */
160
+ export async function build(
161
+ toolsAndHandoffsMap,
162
+ hooks,
163
+ agentName,
164
+ api,
165
+ llmConfig,
166
+ runner
167
+ ) {
168
+ const maxRuns = runner?.maxRuns || 10;
169
+
170
+ try {
171
+ // Initialize LLM client
172
+ logger.info(`Initializing LLM client for agent ${agentName}`);
173
+ const client = await api.getClient();
174
+
175
+ // Add safe execution methods to toolsAndHandoffsMap
176
+ toolsAndHandoffsMap.safeExecute = async (name, type, state, input, timeout) => {
177
+ const handler = toolsAndHandoffsMap[name];
178
+ if (!handler) {
179
+ throw new ToolExecutionError(
180
+ `${type} "${name}" not found`,
181
+ name,
182
+ input
183
+ );
184
+ }
185
+
186
+ return await safeExecute(
187
+ handler.function,
188
+ name,
189
+ type,
190
+ state,
191
+ input,
192
+ timeout || DEFAULT_TOOL_TIMEOUT
193
+ );
194
+ };
195
+
196
+ // Create the executor function
197
+ const executor = async function(state, contents, run = 0) {
198
+ logger.info(`Running agent ${agentName} (run ${run}/${maxRuns}), conversation length: ${contents.length}`);
199
+
200
+ // Emit run event
201
+ await emit(hooks, 'executorRun', {
202
+ agentName: agentName,
203
+ run: run,
204
+ state: state,
205
+ contents: contents
206
+ });
207
+
208
+ // Check for max runs exceeded
209
+ if (run >= maxRuns) {
210
+ logger.warn(`Agent ${agentName} max runs reached: ${run}/${maxRuns}`);
211
+
212
+ await emit(hooks, 'executorMaxRuns', {
213
+ agentName: agentName,
214
+ run: run,
215
+ state: state,
216
+ contents: contents
217
+ });
218
+
219
+ // Return the last message as the result
220
+ return contents[contents.length - 1];
221
+ }
222
+
223
+ try {
224
+ // Prepare input for LLM
225
+ const input = {
226
+ client: client,
227
+ toolsAndHandoffsMap: toolsAndHandoffsMap,
228
+ conversation: contents
229
+ };
230
+
231
+ // Call LLM with timeout and retry
232
+ logger.debug(`Calling LLM for agent ${agentName}`);
233
+
234
+ const response = await withRetry(
235
+ async () => {
236
+ try {
237
+ return await withTimeout(
238
+ async () => api.callModel(llmConfig, input),
239
+ llmConfig.timeout || DEFAULT_LLM_TIMEOUT,
240
+ `LLM call for ${agentName}`
241
+ );
242
+ } catch (error) {
243
+ if (error instanceof TimeoutError) {
244
+ throw error; // Let the retry handler deal with timeouts
245
+ }
246
+
247
+ throw new LLMError(
248
+ `LLM API error: ${error.message}`,
249
+ api.type || 'unknown',
250
+ { modelConfig: llmConfig }
251
+ );
252
+ }
253
+ },
254
+ {
255
+ maxRetries: 2,
256
+ onRetry: ({ attempt }) => {
257
+ logger.warn(`Retrying LLM call for agent ${agentName} (attempt ${attempt})`);
258
+ }
259
+ }
260
+ );
261
+
262
+ logger.debug(`LLM response received for agent ${agentName}`);
263
+
264
+ // Process the response
265
+ const finished = await api.onResponse(state, contents, toolsAndHandoffsMap, response);
266
+
267
+ // If not finished, continue with the next run
268
+ if (finished == null) {
269
+ return await executor(state, contents, run + 1);
270
+ }
271
+
272
+ // Emit end event
273
+ await emit(hooks, 'executorEnd', {
274
+ agentName: agentName,
275
+ run: run,
276
+ state: state,
277
+ contents: contents,
278
+ response: finished
279
+ });
280
+
281
+ return finished;
282
+ } catch (error) {
283
+ logger.error(`Error in agent ${agentName} execution`, {
284
+ error,
285
+ run,
286
+ maxRuns
287
+ });
288
+
289
+ // Emit error event
290
+ await emit(hooks, 'executorError', {
291
+ agentName: agentName,
292
+ run: run,
293
+ state: state,
294
+ contents: contents,
295
+ error: error
296
+ });
297
+
298
+ // Add error message to conversation
299
+ contents.push({
300
+ role: 'system',
301
+ content: `Error: ${error.message}`
302
+ });
303
+
304
+ // If we haven't hit max runs, try again
305
+ if (run < maxRuns - 1) {
306
+ logger.info(`Continuing after error in agent ${agentName}`);
307
+ return await executor(state, contents, run + 1);
308
+ }
309
+
310
+ // We've reached max runs, return the error
311
+ throw error;
312
+ }
313
+ };
314
+
315
+ return executor;
316
+ } catch (error) {
317
+ logger.error(`Failed to build executor for agent ${agentName}`, { error });
318
+ throw error;
319
+ }
320
+ }
@@ -0,0 +1,142 @@
1
+ import { build, makeToolsAndHandoffsMap } from "./executor.js"
2
+ import { NatsIOAgentRuntime } from "./runtimes/nats.js"
3
+ import { logger } from "../utils/logger.js"
4
+ import { Response, SessionStore } from "../index.js"
5
+
6
+ export async function AgentRuntime(agentConfig) {
7
+ const {
8
+ toolsAndHandoffsMap,
9
+ hooks,
10
+ store,
11
+ metadata: { name: agentName },
12
+ llm: { api: llmApi, config: llmConfig },
13
+ runner,
14
+ toolsSchemas: tools,
15
+ handoffs,
16
+ io: ioInterfaces,
17
+ discoverySchemas,
18
+ on: { prompt, response }
19
+ } = agentConfig
20
+
21
+ // Initialize IO runtime
22
+ const natsInterfaces = ioInterfaces.filter(x => x.type === 'NatsIO')
23
+ const { handleTask, discoveredAgents } = await NatsIOAgentRuntime(
24
+ agentName,
25
+ natsInterfaces,
26
+ discoverySchemas
27
+ )
28
+
29
+ // Build executor
30
+ const executor = await build(
31
+ toolsAndHandoffsMap,
32
+ hooks,
33
+ agentName,
34
+ llmApi,
35
+ llmConfig,
36
+ runner
37
+ )
38
+
39
+ // Create task processing function
40
+ const taskFunction = async function(state, conversation, input) {
41
+ try {
42
+ // Update tools and handoffs map with discovered agents
43
+ makeToolsAndHandoffsMap(
44
+ toolsAndHandoffsMap,
45
+ Object.values(tools),
46
+ [handoffs, Object.values(discoveredAgents)]
47
+ )
48
+
49
+ // Process the input
50
+ await llmApi.prompt(conversation, typeof input === 'string' ? input : JSON.stringify(input))
51
+
52
+ // Execute and return result
53
+ return await executor(state, conversation)
54
+ } catch (error) {
55
+ console.error("Task execution error:", error)
56
+ return { error: "Failed to execute task", details: error.message }
57
+ }
58
+ }
59
+
60
+ const queryFunction = async function(message) {
61
+ try {
62
+ const content = message.getContent()
63
+ const session = message.getSession()
64
+ const sessionId = message.getSessionId()
65
+ const storeStateSessionId = agentName + "." + sessionId
66
+
67
+ // Load and merge session state and session data
68
+ let storeState = {
69
+ state: {},
70
+ conversation: []
71
+ }
72
+ if (store && sessionId) {
73
+ const sessionStore = new SessionStore(storeStateSessionId)
74
+ await store.instance.connect()
75
+ const _storeState = await sessionStore.load(store.instance)
76
+ for (const key of Object.keys(_storeState.state)) {
77
+ storeState.state[key] = _storeState.state[key]
78
+ }
79
+ for (const key of Object.keys(session)) {
80
+ storeState.state[key] = session[key]
81
+ }
82
+ storeState.conversation = _storeState.conversation
83
+ logger.info(`Loaded session state for agent ${agentName} with session id ${storeStateSessionId}, current conversation length ${storeState.conversation.length}`);
84
+ }
85
+
86
+ const formattedInput = typeof content === 'string' ? content : JSON.stringify(content);
87
+
88
+ logger.debug(`Query to agent ${agentName}`, {
89
+ inputPreview: formattedInput.substring(0, 100)
90
+ });
91
+
92
+ // Process input through prompt hook
93
+ const promptContent = await prompt(storeState.state, formattedInput);
94
+
95
+ // Execute agent runtime
96
+ const result = await taskFunction(storeState.state, storeState.conversation, promptContent);
97
+
98
+ // Process result through response hook
99
+ const responseMessage = await response(storeState.state, storeState.conversation, result);
100
+ // Save session state and session data
101
+ if (store && sessionId) {
102
+ const sessionStore = new SessionStore(storeStateSessionId)
103
+ await store.instance.connect()
104
+ sessionStore.setConversation(storeState.conversation)
105
+ sessionStore.setState(storeState.state)
106
+ sessionStore.trimConversation(10)
107
+ await sessionStore.dump(store.instance)
108
+ logger.info(`Dumped session state for agent ${agentName} with session id ${storeStateSessionId}, current conversation length ${storeState.conversation.length}`);
109
+ }
110
+
111
+ // Before returning, remove _ from the state
112
+ // Keep the removed keys and print the removed keys
113
+ const removedKeys = []
114
+ for (const key of Object.keys(storeState.state)) {
115
+ if (key.startsWith('_')) {
116
+ removedKeys.push(key)
117
+ delete storeState.state[key]
118
+ }
119
+ }
120
+ if (removedKeys.length > 0) {
121
+ logger.info(`Removed keys from state for agent ${agentName}: ${removedKeys.join(', ')}`);
122
+ }
123
+
124
+ const responseFormatted = new Response({
125
+ content: responseMessage,
126
+ session: storeState.state
127
+ })
128
+ return responseFormatted;
129
+ } catch (error) {
130
+ logger.error(`Agent query execution error: ${error.message}`, {
131
+ agentName: agentName,
132
+ error
133
+ });
134
+ throw error;
135
+ }
136
+ }
137
+
138
+ // Start handling tasks
139
+ handleTask(queryFunction) // queryFunction
140
+
141
+ return queryFunction // queryFunction
142
+ }