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.
- package/LICENSE.txt +202 -0
- package/README.md +488 -0
- package/package.json +23 -0
- package/src/agent/agent-loader.js +274 -0
- package/src/agent/agent.js +416 -0
- package/src/agent/client.js +14 -0
- package/src/agent/executor.js +320 -0
- package/src/agent/runtime.js +142 -0
- package/src/agent/runtimes/nats.js +379 -0
- package/src/errors/index.js +195 -0
- package/src/examples/agents-smartness.yaml +308 -0
- package/src/examples/agents.yaml +394 -0
- package/src/examples/def.js +74 -0
- package/src/examples/def2.js +65 -0
- package/src/examples/def3.js +67 -0
- package/src/examples/simple.js +103 -0
- package/src/index.js +115 -0
- package/src/llm/gemini.js +155 -0
- package/src/llm/gpt.js +155 -0
- package/src/store/store.js +167 -0
- package/src/utils/logger.js +209 -0
- package/src/utils/store.js +212 -0
- package/src/utils/validation.js +287 -0
|
@@ -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
|
+
}
|