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
package/src/store/store.js
CHANGED
|
@@ -1,31 +1,6 @@
|
|
|
1
1
|
import { v4 as uuid } from 'uuid'
|
|
2
2
|
import { createClient } from 'redis'
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
let config = {
|
|
6
|
-
user: process.env.PG_USER || 'postgres',
|
|
7
|
-
host: process.env.PG_HOST || 'localhost',
|
|
8
|
-
database: process.env.PG_DATABASE || 'postgres',
|
|
9
|
-
password: process.env.PG_PASSWORD || 'password',
|
|
10
|
-
port: process.env.PG_PORT || 5433
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
if (process.env.PG_USE_SSL == 'true' || process.env.PG_USE_SSL == true) {
|
|
14
|
-
config.ssl = {
|
|
15
|
-
rejectUnauthorized: false
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const pool = new pg.Pool(config)
|
|
20
|
-
|
|
21
|
-
pool.on('error', err => {
|
|
22
|
-
console.log(new Date(), 'Lost Postgres connection', err)
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
export async function getClient() {
|
|
26
|
-
const client = await pool.connect()
|
|
27
|
-
return client
|
|
28
|
-
}
|
|
3
|
+
import pgp from 'pg-promise'
|
|
29
4
|
|
|
30
5
|
export function session (id) {
|
|
31
6
|
let state = {}
|
|
@@ -117,37 +92,96 @@ export function redisStore (_config = null) {
|
|
|
117
92
|
}
|
|
118
93
|
}
|
|
119
94
|
|
|
120
|
-
export function postgresStore
|
|
121
|
-
let
|
|
122
|
-
|
|
95
|
+
export function postgresStore(config = null) {
|
|
96
|
+
let db = null;
|
|
97
|
+
const pgPromise = pgp(); // Initialize pg-promise
|
|
98
|
+
// Default connection config
|
|
99
|
+
const defaultConfig = {
|
|
100
|
+
host: process.env.PG_HOST || 'localhost',
|
|
101
|
+
port: process.env.PG_PORT || 5433,
|
|
102
|
+
database: process.env.PG_DATABASE || 'postgres',
|
|
103
|
+
user: process.env.PG_USER || 'postgres',
|
|
104
|
+
password: process.env.PG_PASSWORD || 'password',
|
|
105
|
+
max: 30, // max number of clients in the pool
|
|
106
|
+
ssl: process.env.PG_USE_SSL === 'true' ? { rejectUnauthorized: false } : null,
|
|
107
|
+
table: 'conversation_state',
|
|
108
|
+
schema: 'smartchat_agent'
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const connectionConfig = config || defaultConfig;
|
|
123
112
|
|
|
124
113
|
return {
|
|
125
|
-
connect: async function
|
|
126
|
-
if (!
|
|
127
|
-
|
|
114
|
+
connect: async function() {
|
|
115
|
+
if (!db) {
|
|
116
|
+
// For URL-style connection string
|
|
117
|
+
if (typeof connectionConfig === 'string') {
|
|
118
|
+
db = pgPromise(connectionConfig);
|
|
119
|
+
} else {
|
|
120
|
+
db = pgPromise(connectionConfig);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Test connection
|
|
124
|
+
try {
|
|
125
|
+
await db.connect();
|
|
126
|
+
|
|
127
|
+
// Create schema if not exists
|
|
128
|
+
await db.none('CREATE SCHEMA IF NOT EXISTS $1:name', [connectionConfig.schema]);
|
|
129
|
+
|
|
130
|
+
// Create table if not exists
|
|
131
|
+
await db.none(`
|
|
132
|
+
CREATE TABLE IF NOT EXISTS $1:name.$2:name (
|
|
133
|
+
id UUID PRIMARY KEY,
|
|
134
|
+
state_id TEXT,
|
|
135
|
+
state TEXT,
|
|
136
|
+
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
|
137
|
+
)
|
|
138
|
+
`, [connectionConfig.schema, connectionConfig.table]);
|
|
139
|
+
|
|
140
|
+
// Create unique index if not exists
|
|
141
|
+
await db.none(`
|
|
142
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_acc_agent_chat_id
|
|
143
|
+
ON $1:name.$2:name(state_id)
|
|
144
|
+
`, [connectionConfig.schema, connectionConfig.table]);
|
|
145
|
+
|
|
146
|
+
} catch (error) {
|
|
147
|
+
throw error;
|
|
148
|
+
}
|
|
128
149
|
}
|
|
129
150
|
},
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
151
|
+
|
|
152
|
+
disconnect: async function() {
|
|
153
|
+
if (db) {
|
|
154
|
+
await pgPromise.end();
|
|
155
|
+
db = null;
|
|
134
156
|
}
|
|
135
157
|
},
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
158
|
+
|
|
159
|
+
set: async function(key, value) {
|
|
160
|
+
const id = uuid();
|
|
161
|
+
try {
|
|
162
|
+
return await db.one(
|
|
163
|
+
'INSERT INTO $1:name.$2:name (state_id, state, id) VALUES ($3, $4, $5) ON CONFLICT (state_id) DO UPDATE SET state=$4, updated_at=CURRENT_TIMESTAMP RETURNING id',
|
|
164
|
+
[connectionConfig.schema, connectionConfig.table, key, value, id]
|
|
165
|
+
);
|
|
166
|
+
} catch (error) {
|
|
167
|
+
console.error('Error storing state:', error);
|
|
168
|
+
throw error;
|
|
144
169
|
}
|
|
145
|
-
|
|
146
|
-
|
|
170
|
+
},
|
|
171
|
+
|
|
172
|
+
get: async function(key) {
|
|
173
|
+
try {
|
|
174
|
+
const result = await db.oneOrNone(
|
|
175
|
+
'SELECT state FROM $1:name.$2:name WHERE state_id = $3',
|
|
176
|
+
[connectionConfig.schema, connectionConfig.table, key]
|
|
177
|
+
);
|
|
178
|
+
return result ? result.state : null;
|
|
179
|
+
} catch (error) {
|
|
180
|
+
console.error('Error retrieving state:', error);
|
|
181
|
+
throw error;
|
|
147
182
|
}
|
|
148
|
-
return null
|
|
149
183
|
}
|
|
150
|
-
}
|
|
184
|
+
};
|
|
151
185
|
}
|
|
152
186
|
|
|
153
187
|
export function memoryStore () {
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
import { jest } from '@jest/globals'
|
|
2
|
+
|
|
3
|
+
import { Agent } from '../agent/agent';
|
|
4
|
+
import { ConfigurationError, CompilationError } from '../errors';
|
|
5
|
+
|
|
6
|
+
// Mock the AgentRuntime module
|
|
7
|
+
jest.mock('../agent/runtime.js', () => ({
|
|
8
|
+
AgentRuntime: jest.fn(),
|
|
9
|
+
}));
|
|
10
|
+
// Import the mocked AgentRuntime to allow inspection (e.g. toHaveBeenCalledWith)
|
|
11
|
+
import { AgentRuntime } from '../agent/runtime.js';
|
|
12
|
+
|
|
13
|
+
describe('Agent Core Functionality', () => {
|
|
14
|
+
let agentBuilder;
|
|
15
|
+
let mockLlmApi;
|
|
16
|
+
let mockStoreInstance;
|
|
17
|
+
let mockIoInstance;
|
|
18
|
+
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
// Reset mocks before each test
|
|
21
|
+
jest.clearAllMocks();
|
|
22
|
+
|
|
23
|
+
agentBuilder = Agent();
|
|
24
|
+
mockLlmApi = {
|
|
25
|
+
getClient: jest.fn().mockResolvedValue({}),
|
|
26
|
+
callModel: jest.fn().mockResolvedValue('llm_response'),
|
|
27
|
+
// Adding mock prompt and onResponse as AgentRuntime might expect them if not deeply mocked
|
|
28
|
+
prompt: jest.fn(),
|
|
29
|
+
onResponse: jest.fn(),
|
|
30
|
+
};
|
|
31
|
+
mockStoreInstance = {
|
|
32
|
+
connect: jest.fn().mockResolvedValue(true),
|
|
33
|
+
// Add other methods if your store interactions become more complex in tests
|
|
34
|
+
};
|
|
35
|
+
mockIoInstance = {
|
|
36
|
+
type: 'TestIO',
|
|
37
|
+
// Mock other IO methods if needed by AgentRuntime or specific IO logic
|
|
38
|
+
};
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe('Agent Factory & Defaults', () => {
|
|
42
|
+
it('should create a new agent builder with default metadata', () => {
|
|
43
|
+
expect(agentBuilder._config.metadata).toEqual({
|
|
44
|
+
name: 'default',
|
|
45
|
+
namespace: 'default',
|
|
46
|
+
description: 'A default agent',
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should create a new agent builder with default runner config', () => {
|
|
51
|
+
expect(agentBuilder._config.runner).toEqual({
|
|
52
|
+
maxRuns: 10,
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('should create a new agent builder with default hooks', () => {
|
|
57
|
+
expect(typeof agentBuilder._config.on.prompt).toBe('function');
|
|
58
|
+
expect(typeof agentBuilder._config.on.response).toBe('function');
|
|
59
|
+
// Test the default behavior of hooks
|
|
60
|
+
const testInput = "test input";
|
|
61
|
+
expect(agentBuilder._config.on.prompt({}, testInput)).resolves.toBe(testInput);
|
|
62
|
+
const testResult = "test result";
|
|
63
|
+
expect(agentBuilder._config.on.response({}, [], testResult)).resolves.toBe(testResult);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe('setMetadata(metadata)', () => {
|
|
68
|
+
it('should allow setting valid metadata', () => {
|
|
69
|
+
const metadata = { name: 'testAgent', namespace: 'testSpace', description: 'A test agent' };
|
|
70
|
+
agentBuilder.setMetadata(metadata);
|
|
71
|
+
expect(agentBuilder._config.metadata).toEqual(metadata);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('should merge with existing metadata, overwriting common fields', () => {
|
|
75
|
+
agentBuilder.setMetadata({ name: 'initialName', customField: 'initialValue' });
|
|
76
|
+
agentBuilder.setMetadata({ name: 'newName', description: 'newDescription' });
|
|
77
|
+
expect(agentBuilder._config.metadata).toEqual({
|
|
78
|
+
name: 'newName',
|
|
79
|
+
namespace: 'default', // From initial defaults if not overridden
|
|
80
|
+
description: 'newDescription',
|
|
81
|
+
customField: 'initialValue'
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('should throw ConfigurationError if metadata is null', () => {
|
|
86
|
+
expect(() => agentBuilder.setMetadata(null)).toThrow(ConfigurationError);
|
|
87
|
+
expect(() => agentBuilder.setMetadata(null)).toThrow('Metadata is required');
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Validations for name and namespace during compile are more prominent,
|
|
91
|
+
// but direct setters could also enforce this, though current code doesn't.
|
|
92
|
+
// If direct enforcement in setMetadata is desired, add tests here.
|
|
93
|
+
// For now, these are primarily tested via compile's validation.
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
describe('withLLM(llmApi, llmConfig)', () => {
|
|
97
|
+
it('should configure LLM with valid API and config', () => {
|
|
98
|
+
const llmConfig = { model: 'test-model', temperature: 0.7 };
|
|
99
|
+
agentBuilder.withLLM(mockLlmApi, llmConfig);
|
|
100
|
+
expect(agentBuilder._config.llm.api).toBe(mockLlmApi);
|
|
101
|
+
expect(agentBuilder._config.llm.config).toEqual(llmConfig);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('should throw ConfigurationError if llmApi is null', () => {
|
|
105
|
+
expect(() => agentBuilder.withLLM(null, {})).toThrow(ConfigurationError);
|
|
106
|
+
expect(() => agentBuilder.withLLM(null, {})).toThrow('LLM API is required');
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('should throw ConfigurationError if llmApi is not an object (during compile)', async () => {
|
|
110
|
+
agentBuilder.withLLM("notAnObject", {});
|
|
111
|
+
await expect(agentBuilder.compile()).rejects.toThrow(new ConfigurationError("LLM API must be a valid object"));
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('should throw ConfigurationError if llmApi is missing getClient (during compile)', async () => {
|
|
115
|
+
const invalidApi = { ...mockLlmApi };
|
|
116
|
+
delete invalidApi.getClient;
|
|
117
|
+
agentBuilder.withLLM(invalidApi, {});
|
|
118
|
+
await expect(agentBuilder.compile()).rejects.toThrow(new ConfigurationError("LLM API must have a getClient method"));
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('should throw ConfigurationError if llmApi is missing callModel (during compile)', async () => {
|
|
122
|
+
const invalidApi = { ...mockLlmApi };
|
|
123
|
+
delete invalidApi.callModel;
|
|
124
|
+
agentBuilder.withLLM(invalidApi, {});
|
|
125
|
+
await expect(agentBuilder.compile()).rejects.toThrow(new ConfigurationError("LLM API must have a callModel method"));
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
describe('withStore(storeInstance, storeConfig)', () => {
|
|
130
|
+
it('should configure store with valid instance and config', () => {
|
|
131
|
+
const storeConfig = { type: 'test-store' };
|
|
132
|
+
agentBuilder.withStore(mockStoreInstance, storeConfig);
|
|
133
|
+
expect(agentBuilder._config.store.instance).toBe(mockStoreInstance);
|
|
134
|
+
expect(agentBuilder._config.store.config).toEqual(storeConfig);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('should throw ConfigurationError if storeInstance is null', () => {
|
|
138
|
+
expect(() => agentBuilder.withStore(null, {})).toThrow(ConfigurationError);
|
|
139
|
+
expect(() => agentBuilder.withStore(null, {})).toThrow('Store instance is required');
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('should throw ConfigurationError if storeInstance is not an object (during compile)', async () => {
|
|
143
|
+
agentBuilder.withLLM(mockLlmApi, {}); // Need LLM for compilation
|
|
144
|
+
agentBuilder.withStore("notAnObject", {});
|
|
145
|
+
await expect(agentBuilder.compile()).rejects.toThrow(new ConfigurationError("Store instance must be a valid object"));
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('should throw ConfigurationError if storeInstance is missing connect (during compile)', async () => {
|
|
149
|
+
const invalidStore = {}; // Missing connect
|
|
150
|
+
agentBuilder.withLLM(mockLlmApi, {});
|
|
151
|
+
agentBuilder.withStore(invalidStore, {});
|
|
152
|
+
await expect(agentBuilder.compile()).rejects.toThrow(new ConfigurationError("Store instance must have a connect method"));
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
describe('addIO(instance, ioConfig)', () => {
|
|
157
|
+
it('should add IO interface with valid instance and config', () => {
|
|
158
|
+
const ioConfig = { setting: 'test-setting' };
|
|
159
|
+
agentBuilder.addIO(mockIoInstance, ioConfig);
|
|
160
|
+
expect(agentBuilder._config.io[0].type).toBe(mockIoInstance.type);
|
|
161
|
+
expect(agentBuilder._config.io[0].instance).toBe(mockIoInstance);
|
|
162
|
+
expect(agentBuilder._config.io[0].config).toEqual(ioConfig);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('should throw ConfigurationError if instance is null', () => {
|
|
166
|
+
expect(() => agentBuilder.addIO(null, {})).toThrow(ConfigurationError);
|
|
167
|
+
expect(() => agentBuilder.addIO(null, {})).toThrow('IO instance must have a type');
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('should throw ConfigurationError if instance.type is missing', () => {
|
|
171
|
+
expect(() => agentBuilder.addIO({}, {})).toThrow(ConfigurationError);
|
|
172
|
+
expect(() => agentBuilder.addIO({}, {})).toThrow('IO instance must have a type');
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// Further IO validation (missing instance/config on compile) is tested in compile section
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
describe('addToolSchema(schema)', () => {
|
|
179
|
+
it('should add a valid tool schema', () => {
|
|
180
|
+
const toolSchema = { name: 'testTool', description: 'A tool for testing' };
|
|
181
|
+
agentBuilder.addToolSchema(toolSchema);
|
|
182
|
+
expect(agentBuilder._config.toolsSchemas['testTool']).toEqual(toolSchema);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('should throw ConfigurationError if schema is null', () => {
|
|
186
|
+
expect(() => agentBuilder.addToolSchema(null)).toThrow(ConfigurationError);
|
|
187
|
+
expect(() => agentBuilder.addToolSchema(null)).toThrow('Tool schema must have a name');
|
|
188
|
+
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it('should throw ConfigurationError if schema.name is missing', () => {
|
|
192
|
+
expect(() => agentBuilder.addToolSchema({ description: ' nameless tool' })).toThrow(ConfigurationError);
|
|
193
|
+
expect(() => agentBuilder.addToolSchema({ description: ' nameless tool' })).toThrow('Tool schema must have a name');
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
describe('addDiscoverySchema(schema)', () => {
|
|
198
|
+
it('should add a valid discovery schema', () => {
|
|
199
|
+
const discoverySchema = { name: 'testDiscovery', description: 'For discovering things' };
|
|
200
|
+
agentBuilder.addDiscoverySchema(discoverySchema);
|
|
201
|
+
expect(agentBuilder._config.discoverySchemas).toContainEqual(discoverySchema);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('should throw ConfigurationError if schema is null', () => {
|
|
205
|
+
expect(() => agentBuilder.addDiscoverySchema(null)).toThrow(ConfigurationError);
|
|
206
|
+
expect(() => agentBuilder.addDiscoverySchema(null)).toThrow('Discovery schema is required');
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
describe('on(eventName, handler)', () => {
|
|
211
|
+
it('should register custom prompt and response handlers', () => {
|
|
212
|
+
const mockPromptFn = jest.fn();
|
|
213
|
+
const mockResponseFn = jest.fn();
|
|
214
|
+
agentBuilder.on('prompt', mockPromptFn);
|
|
215
|
+
agentBuilder.on('response', mockResponseFn);
|
|
216
|
+
expect(agentBuilder._config.on.prompt).toBe(mockPromptFn);
|
|
217
|
+
expect(agentBuilder._config.on.response).toBe(mockResponseFn);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('should throw ConfigurationError if handler is not a function', () => {
|
|
221
|
+
expect(() => agentBuilder.on('prompt', 'not-a-function')).toThrow(ConfigurationError);
|
|
222
|
+
expect(() => agentBuilder.on('prompt', 'not-a-function')).toThrow('Event handler for prompt must be a function');
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
describe('getToolsSchemas()', () => {
|
|
227
|
+
it('should return a copy of tool schemas', () => {
|
|
228
|
+
const toolSchema1 = { name: 'tool1', description: 'Tool one' };
|
|
229
|
+
const toolSchema2 = { name: 'tool2', description: 'Tool two' };
|
|
230
|
+
agentBuilder.addToolSchema(toolSchema1);
|
|
231
|
+
agentBuilder.addToolSchema(toolSchema2);
|
|
232
|
+
|
|
233
|
+
const retrievedSchemas = agentBuilder.getToolsSchemas();
|
|
234
|
+
expect(retrievedSchemas).toEqual({ tool1: toolSchema1, tool2: toolSchema2 });
|
|
235
|
+
// Ensure it's a copy
|
|
236
|
+
retrievedSchemas.tool1.description = "modified";
|
|
237
|
+
expect(agentBuilder._config.toolsSchemas.tool1.description).toBe("Tool one");
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it('should return an empty object if no tools are added', () => {
|
|
241
|
+
expect(agentBuilder.getToolsSchemas()).toEqual({});
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
describe('compile()', () => {
|
|
246
|
+
beforeEach(() => {
|
|
247
|
+
// Minimum valid config for most compile tests
|
|
248
|
+
agentBuilder.withLLM(mockLlmApi, { model: 'test-model' });
|
|
249
|
+
agentBuilder.setMetadata({ name: 'compileAgent', namespace: 'compileSpace' });
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it('should successfully compile with minimal valid configuration', async () => {
|
|
253
|
+
AgentRuntime.mockResolvedValue({ query: jest.fn() });
|
|
254
|
+
const compiledAgent = await agentBuilder.compile();
|
|
255
|
+
expect(AgentRuntime).toHaveBeenCalledTimes(1);
|
|
256
|
+
expect(AgentRuntime).toHaveBeenCalledWith(agentBuilder._config); // Check if called with the correct config
|
|
257
|
+
expect(compiledAgent).toBeDefined();
|
|
258
|
+
expect(typeof compiledAgent.query).toBe('function');
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it('should throw ConfigurationError if metadata.name is empty during compile', async () => {
|
|
262
|
+
agentBuilder.setMetadata({ name: '', namespace: 'test' });
|
|
263
|
+
await expect(agentBuilder.compile()).rejects.toThrow(new ConfigurationError("Agent name cannot be empty"));
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it('should throw ConfigurationError if metadata.namespace is empty during compile', async () => {
|
|
267
|
+
agentBuilder.setMetadata({ name: 'test', namespace: ' ' }); // Whitespace
|
|
268
|
+
await expect(agentBuilder.compile()).rejects.toThrow(new ConfigurationError("Agent namespace cannot be empty"));
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
it('should throw ConfigurationError if LLM is not configured', async () => {
|
|
272
|
+
const freshAgent = Agent(); // No LLM
|
|
273
|
+
freshAgent.setMetadata({ name: 'noLlmAgent', namespace: 'test' });
|
|
274
|
+
await expect(freshAgent.compile()).rejects.toThrow(ConfigurationError);
|
|
275
|
+
// The schema validation might throw a generic "is required" or a more specific one based on schema order
|
|
276
|
+
// For AGENT_CONFIG_SCHEMA, 'llm' is required.
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it('should throw ConfigurationError if an added IO interface has no instance (during compile)', async () => {
|
|
280
|
+
agentBuilder._config.io.push({ type: 'BadIO', config: {} /* no instance */});
|
|
281
|
+
await expect(agentBuilder.compile()).rejects.toThrow(new ConfigurationError("IO interface BadIO at index 0 has no instance"));
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it('should throw ConfigurationError if an added IO interface has no config (during compile)', async () => {
|
|
285
|
+
agentBuilder._config.io.push({ type: 'BadIO', instance: mockIoInstance /* no config */});
|
|
286
|
+
await expect(agentBuilder.compile()).rejects.toThrow(new ConfigurationError("IO interface BadIO at index 0 has no configuration"));
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
it('should throw ConfigurationError if a tool schema is invalid (e.g., name missing, checked during compile)', async () => {
|
|
290
|
+
// Note: addToolSchema checks this, but validateConfiguration re-checks.
|
|
291
|
+
// This test ensures validateConfiguration's check works.
|
|
292
|
+
agentBuilder._config.toolsSchemas['badTool'] = { description: "I am bad" }; // No name in schema value
|
|
293
|
+
await expect(agentBuilder.compile()).rejects.toThrow(new ConfigurationError("Tool schema must have a name"));
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
it('should throw ConfigurationError if runner.maxRuns is not a positive number', async () => {
|
|
297
|
+
agentBuilder._config.runner.maxRuns = 0;
|
|
298
|
+
await expect(agentBuilder.compile()).rejects.toThrow(new ConfigurationError("runner.maxRuns must be greater than 0"));
|
|
299
|
+
agentBuilder._config.runner.maxRuns = -1;
|
|
300
|
+
await expect(agentBuilder.compile()).rejects.toThrow(new ConfigurationError("runner.maxRuns must be greater than 0"));
|
|
301
|
+
agentBuilder._config.runner.maxRuns = 'not a number';
|
|
302
|
+
await expect(agentBuilder.compile()).rejects.toThrow(new ConfigurationError("Invalid type for runner.maxRuns in agent_config: expected number, got string"));
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
it('should throw ConfigurationError if an event handler is not a function', async () => {
|
|
306
|
+
agentBuilder._config.on.prompt = "not a function";
|
|
307
|
+
await expect(agentBuilder.compile()).rejects.toThrow(new ConfigurationError("Event handler for 'prompt' must be a function"));
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
it('should throw CompilationError if AgentRuntime throws an error', async () => {
|
|
311
|
+
const runtimeError = new Error('Runtime failed!');
|
|
312
|
+
AgentRuntime.mockRejectedValue(runtimeError);
|
|
313
|
+
await expect(agentBuilder.compile()).rejects.toThrow(CompilationError);
|
|
314
|
+
await expect(agentBuilder.compile()).rejects.toThrow(`Failed to compile agent ${agentBuilder._config.metadata.name}: ${runtimeError.message}`);
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
it('should pass full configuration to AgentRuntime', async () => {
|
|
318
|
+
const toolSchema = { name: 'myTool', parameters: {} };
|
|
319
|
+
const discoverySchema = { name: 'myDiscovery' };
|
|
320
|
+
const ioConfig = { network: 'testNet' };
|
|
321
|
+
const storeConfig = { db: 'testDb' };
|
|
322
|
+
const promptHook = jest.fn();
|
|
323
|
+
const responseHook = jest.fn();
|
|
324
|
+
|
|
325
|
+
agentBuilder
|
|
326
|
+
.addToolSchema(toolSchema)
|
|
327
|
+
.addDiscoverySchema(discoverySchema)
|
|
328
|
+
.addIO(mockIoInstance, ioConfig)
|
|
329
|
+
.withStore(mockStoreInstance, storeConfig)
|
|
330
|
+
.on('prompt', promptHook)
|
|
331
|
+
.on('response', responseHook);
|
|
332
|
+
|
|
333
|
+
AgentRuntime.mockResolvedValue({ query: jest.fn() });
|
|
334
|
+
await agentBuilder.compile();
|
|
335
|
+
|
|
336
|
+
expect(AgentRuntime).toHaveBeenCalledWith(
|
|
337
|
+
expect.objectContaining({
|
|
338
|
+
metadata: agentBuilder._config.metadata,
|
|
339
|
+
llm: agentBuilder._config.llm,
|
|
340
|
+
store: agentBuilder._config.store,
|
|
341
|
+
io: expect.arrayContaining([expect.objectContaining({ type: mockIoInstance.type, instance: mockIoInstance, config: ioConfig })]),
|
|
342
|
+
toolsSchemas: expect.objectContaining({ 'myTool': toolSchema }),
|
|
343
|
+
discoverySchemas: expect.arrayContaining([discoverySchema]),
|
|
344
|
+
on: expect.objectContaining({ prompt: promptHook, response: responseHook }),
|
|
345
|
+
runner: agentBuilder._config.runner
|
|
346
|
+
})
|
|
347
|
+
);
|
|
348
|
+
});
|
|
349
|
+
});
|
|
350
|
+
});
|