@elizaos/core 1.5.1 → 1.5.2
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/dist/browser/index.browser.js +120 -120
- package/dist/browser/index.browser.js.map +5 -21
- package/dist/browser/index.d.ts +3 -1
- package/dist/index.d.ts +2 -3
- package/dist/index.js +1 -5
- package/dist/node/index.d.ts +3 -1
- package/package.json +10 -4
- package/src/__tests__/action-chaining-simple.test.ts +203 -0
- package/src/__tests__/actions.test.ts +218 -0
- package/src/__tests__/buffer.test.ts +337 -0
- package/src/__tests__/character-validation.test.ts +309 -0
- package/src/__tests__/database.test.ts +750 -0
- package/src/__tests__/entities.test.ts +727 -0
- package/src/__tests__/env.test.ts +23 -0
- package/src/__tests__/environment.test.ts +285 -0
- package/src/__tests__/logger-browser-node.test.ts +716 -0
- package/src/__tests__/logger.test.ts +403 -0
- package/src/__tests__/messages.test.ts +196 -0
- package/src/__tests__/mockCharacter.ts +544 -0
- package/src/__tests__/parsing.test.ts +58 -0
- package/src/__tests__/prompts.test.ts +159 -0
- package/src/__tests__/roles.test.ts +331 -0
- package/src/__tests__/runtime-embedding.test.ts +343 -0
- package/src/__tests__/runtime.test.ts +978 -0
- package/src/__tests__/search.test.ts +15 -0
- package/src/__tests__/services-by-type.test.ts +204 -0
- package/src/__tests__/services.test.ts +136 -0
- package/src/__tests__/settings.test.ts +810 -0
- package/src/__tests__/utils.test.ts +1105 -0
- package/src/__tests__/uuid.test.ts +94 -0
- package/src/actions.ts +122 -0
- package/src/database.ts +579 -0
- package/src/entities.ts +406 -0
- package/src/index.browser.ts +48 -0
- package/src/index.node.ts +39 -0
- package/src/index.ts +50 -0
- package/src/logger.ts +527 -0
- package/src/prompts.ts +243 -0
- package/src/roles.ts +85 -0
- package/src/runtime.ts +2514 -0
- package/src/schemas/character.ts +149 -0
- package/src/search.ts +1543 -0
- package/src/sentry/instrument.browser.ts +65 -0
- package/src/sentry/instrument.node.ts +57 -0
- package/src/sentry/instrument.ts +82 -0
- package/src/services.ts +105 -0
- package/src/settings.ts +409 -0
- package/src/test_resources/constants.ts +12 -0
- package/src/test_resources/testSetup.ts +21 -0
- package/src/test_resources/types.ts +22 -0
- package/src/types/agent.ts +112 -0
- package/src/types/browser.ts +145 -0
- package/src/types/components.ts +184 -0
- package/src/types/database.ts +348 -0
- package/src/types/email.ts +162 -0
- package/src/types/environment.ts +129 -0
- package/src/types/events.ts +249 -0
- package/src/types/index.ts +29 -0
- package/src/types/knowledge.ts +65 -0
- package/src/types/lp.ts +124 -0
- package/src/types/memory.ts +228 -0
- package/src/types/message.ts +233 -0
- package/src/types/messaging.ts +57 -0
- package/src/types/model.ts +359 -0
- package/src/types/pdf.ts +77 -0
- package/src/types/plugin.ts +78 -0
- package/src/types/post.ts +271 -0
- package/src/types/primitives.ts +97 -0
- package/src/types/runtime.ts +190 -0
- package/src/types/service.ts +198 -0
- package/src/types/settings.ts +30 -0
- package/src/types/state.ts +60 -0
- package/src/types/task.ts +72 -0
- package/src/types/tee.ts +107 -0
- package/src/types/testing.ts +30 -0
- package/src/types/token.ts +96 -0
- package/src/types/transcription.ts +133 -0
- package/src/types/video.ts +108 -0
- package/src/types/wallet.ts +56 -0
- package/src/types/web-search.ts +146 -0
- package/src/utils/__tests__/buffer.test.ts +80 -0
- package/src/utils/__tests__/environment.test.ts +58 -0
- package/src/utils/__tests__/stringToUuid.test.ts +88 -0
- package/src/utils/buffer.ts +312 -0
- package/src/utils/environment.ts +316 -0
- package/src/utils/server-health.ts +117 -0
- package/src/utils.ts +1076 -0
- package/dist/tsconfig.build.tsbuildinfo +0 -1
|
@@ -0,0 +1,978 @@
|
|
|
1
|
+
import { beforeEach, afterEach, describe, expect, it } from 'bun:test';
|
|
2
|
+
import { mock, spyOn } from 'bun:test';
|
|
3
|
+
import { AgentRuntime } from '../runtime';
|
|
4
|
+
import { MemoryType, ModelType } from '../types';
|
|
5
|
+
import type {
|
|
6
|
+
Action,
|
|
7
|
+
Character,
|
|
8
|
+
IDatabaseAdapter,
|
|
9
|
+
Memory,
|
|
10
|
+
ModelTypeName,
|
|
11
|
+
Plugin,
|
|
12
|
+
Provider,
|
|
13
|
+
State,
|
|
14
|
+
UUID,
|
|
15
|
+
} from '../types';
|
|
16
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
17
|
+
const stringToUuid = (id: string): UUID => id as UUID;
|
|
18
|
+
|
|
19
|
+
// --- Mocks ---
|
|
20
|
+
|
|
21
|
+
// Use hoisted for prompts mock
|
|
22
|
+
const mockSplitChunks = mock();
|
|
23
|
+
mock.module('../src/utils', () => ({
|
|
24
|
+
splitChunks: mockSplitChunks,
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
// Use hoisted for ./index mock (safeReplacer)
|
|
28
|
+
const mockSafeReplacer = mock((_key, value) => value); // Simple replacer mock
|
|
29
|
+
// Don't mock the entire index module to avoid interfering with other tests
|
|
30
|
+
|
|
31
|
+
// Mock IDatabaseAdapter (inline style matching your example)
|
|
32
|
+
const mockDatabaseAdapter: IDatabaseAdapter = {
|
|
33
|
+
db: {},
|
|
34
|
+
init: mock().mockResolvedValue(undefined),
|
|
35
|
+
initialize: mock().mockResolvedValue(undefined),
|
|
36
|
+
runMigrations: mock().mockResolvedValue(undefined),
|
|
37
|
+
isReady: mock().mockResolvedValue(true),
|
|
38
|
+
close: mock().mockResolvedValue(undefined),
|
|
39
|
+
getConnection: mock().mockResolvedValue({}),
|
|
40
|
+
getEntityByIds: mock().mockResolvedValue([]),
|
|
41
|
+
getEntitiesByIds: mock().mockResolvedValue([]),
|
|
42
|
+
createEntities: mock().mockResolvedValue(true),
|
|
43
|
+
getMemories: mock().mockResolvedValue([]),
|
|
44
|
+
getMemoryById: mock().mockResolvedValue(null),
|
|
45
|
+
getMemoriesByRoomIds: mock().mockResolvedValue([]),
|
|
46
|
+
getMemoriesByIds: mock().mockResolvedValue([]),
|
|
47
|
+
getCachedEmbeddings: mock().mockResolvedValue([]),
|
|
48
|
+
log: mock().mockResolvedValue(undefined),
|
|
49
|
+
searchMemories: mock().mockResolvedValue([]),
|
|
50
|
+
createMemory: mock().mockResolvedValue(stringToUuid(uuidv4())),
|
|
51
|
+
deleteMemory: mock().mockResolvedValue(undefined),
|
|
52
|
+
deleteManyMemories: mock().mockResolvedValue(undefined),
|
|
53
|
+
deleteAllMemories: mock().mockResolvedValue(undefined),
|
|
54
|
+
countMemories: mock().mockResolvedValue(0),
|
|
55
|
+
getRoomsByIds: mock().mockResolvedValue([]),
|
|
56
|
+
createRooms: mock().mockResolvedValue([stringToUuid(uuidv4())]),
|
|
57
|
+
deleteRoom: mock().mockResolvedValue(undefined),
|
|
58
|
+
getRoomsForParticipant: mock().mockResolvedValue([]),
|
|
59
|
+
getRoomsForParticipants: mock().mockResolvedValue([]),
|
|
60
|
+
addParticipantsRoom: mock().mockResolvedValue(true),
|
|
61
|
+
removeParticipant: mock().mockResolvedValue(true),
|
|
62
|
+
getParticipantsForEntity: mock().mockResolvedValue([]),
|
|
63
|
+
getParticipantsForRoom: mock().mockResolvedValue([]),
|
|
64
|
+
getParticipantUserState: mock().mockResolvedValue(null),
|
|
65
|
+
setParticipantUserState: mock().mockResolvedValue(undefined),
|
|
66
|
+
createRelationship: mock().mockResolvedValue(true),
|
|
67
|
+
getRelationship: mock().mockResolvedValue(null),
|
|
68
|
+
getRelationships: mock().mockResolvedValue([]),
|
|
69
|
+
getAgent: mock().mockResolvedValue(null),
|
|
70
|
+
getAgents: mock().mockResolvedValue([]),
|
|
71
|
+
createAgent: mock().mockResolvedValue(true),
|
|
72
|
+
updateAgent: mock().mockResolvedValue(true),
|
|
73
|
+
deleteAgent: mock().mockResolvedValue(true),
|
|
74
|
+
ensureEmbeddingDimension: mock().mockResolvedValue(undefined),
|
|
75
|
+
getEntitiesForRoom: mock().mockResolvedValue([]),
|
|
76
|
+
updateEntity: mock().mockResolvedValue(undefined),
|
|
77
|
+
getComponent: mock().mockResolvedValue(null),
|
|
78
|
+
getComponents: mock().mockResolvedValue([]),
|
|
79
|
+
createComponent: mock().mockResolvedValue(true),
|
|
80
|
+
updateComponent: mock().mockResolvedValue(undefined),
|
|
81
|
+
deleteComponent: mock().mockResolvedValue(undefined),
|
|
82
|
+
createWorld: mock().mockResolvedValue(stringToUuid(uuidv4())),
|
|
83
|
+
getWorld: mock().mockResolvedValue(null),
|
|
84
|
+
getAllWorlds: mock().mockResolvedValue([]),
|
|
85
|
+
updateWorld: mock().mockResolvedValue(undefined),
|
|
86
|
+
updateRoom: mock().mockResolvedValue(undefined),
|
|
87
|
+
getRoomsByWorld: mock().mockResolvedValue([]),
|
|
88
|
+
updateRelationship: mock().mockResolvedValue(undefined),
|
|
89
|
+
getCache: mock().mockResolvedValue(undefined),
|
|
90
|
+
setCache: mock().mockResolvedValue(true),
|
|
91
|
+
deleteCache: mock().mockResolvedValue(true),
|
|
92
|
+
createTask: mock().mockResolvedValue(stringToUuid(uuidv4())),
|
|
93
|
+
getTasks: mock().mockResolvedValue([]),
|
|
94
|
+
getTask: mock().mockResolvedValue(null),
|
|
95
|
+
getTasksByName: mock().mockResolvedValue([]),
|
|
96
|
+
updateTask: mock().mockResolvedValue(undefined),
|
|
97
|
+
deleteTask: mock().mockResolvedValue(undefined),
|
|
98
|
+
updateMemory: mock().mockResolvedValue(true),
|
|
99
|
+
getLogs: mock().mockResolvedValue([]),
|
|
100
|
+
deleteLog: mock().mockResolvedValue(undefined),
|
|
101
|
+
removeWorld: mock().mockResolvedValue(undefined),
|
|
102
|
+
deleteRoomsByWorldId: function (_worldId: UUID): Promise<void> {
|
|
103
|
+
throw new Error('Function not implemented.');
|
|
104
|
+
},
|
|
105
|
+
getMemoriesByWorldId: function (_params: {
|
|
106
|
+
worldId: UUID;
|
|
107
|
+
count?: number;
|
|
108
|
+
tableName?: string;
|
|
109
|
+
}): Promise<Memory[]> {
|
|
110
|
+
throw new Error('Function not implemented.');
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
// Mock action creator (matches your example)
|
|
115
|
+
const createMockAction = (name: string): Action => ({
|
|
116
|
+
name,
|
|
117
|
+
description: `Test action ${name}`,
|
|
118
|
+
similes: [`like ${name}`],
|
|
119
|
+
examples: [],
|
|
120
|
+
handler: mock().mockResolvedValue(undefined),
|
|
121
|
+
validate: mock().mockImplementation(async () => true),
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// Mock Memory creator
|
|
125
|
+
const createMockMemory = (
|
|
126
|
+
text: string,
|
|
127
|
+
id?: UUID,
|
|
128
|
+
entityId?: UUID,
|
|
129
|
+
roomId?: UUID,
|
|
130
|
+
agentId?: UUID
|
|
131
|
+
): Memory => ({
|
|
132
|
+
id: id ?? stringToUuid(uuidv4()),
|
|
133
|
+
entityId: entityId ?? stringToUuid(uuidv4()),
|
|
134
|
+
agentId: agentId, // Pass agentId if needed
|
|
135
|
+
roomId: roomId ?? stringToUuid(uuidv4()),
|
|
136
|
+
content: { text }, // Assuming simple text content
|
|
137
|
+
createdAt: Date.now(),
|
|
138
|
+
metadata: { type: MemoryType.MESSAGE }, // Simple metadata
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// Mock State creator
|
|
142
|
+
const createMockState = (text = '', values = {}, data = {}): State => ({
|
|
143
|
+
values,
|
|
144
|
+
data,
|
|
145
|
+
text,
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// Mock Character
|
|
149
|
+
const mockCharacter: Character = {
|
|
150
|
+
id: stringToUuid(uuidv4()),
|
|
151
|
+
name: 'Test Character',
|
|
152
|
+
plugins: ['@elizaos/plugin-sql'],
|
|
153
|
+
username: 'test',
|
|
154
|
+
bio: ['Test bio'],
|
|
155
|
+
messageExamples: [], // Ensure required fields are present
|
|
156
|
+
postExamples: [],
|
|
157
|
+
topics: [],
|
|
158
|
+
adjectives: [],
|
|
159
|
+
style: {
|
|
160
|
+
all: [],
|
|
161
|
+
chat: [],
|
|
162
|
+
post: [],
|
|
163
|
+
},
|
|
164
|
+
// Add other fields if your runtime logic depends on them
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
// --- Test Suite ---
|
|
168
|
+
|
|
169
|
+
describe('AgentRuntime (Non-Instrumented Baseline)', () => {
|
|
170
|
+
let runtime: AgentRuntime;
|
|
171
|
+
let agentId: UUID;
|
|
172
|
+
|
|
173
|
+
beforeEach(() => {
|
|
174
|
+
mock.restore(); // Bun:test equivalent of clearAllMocks
|
|
175
|
+
|
|
176
|
+
// Reset all mock call counts manually but keep return values
|
|
177
|
+
Object.values(mockDatabaseAdapter).forEach((mockFn) => {
|
|
178
|
+
if (mockFn && typeof mockFn.mockClear === 'function') {
|
|
179
|
+
mockFn.mockClear();
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
agentId = mockCharacter.id!; // Use character's ID
|
|
184
|
+
|
|
185
|
+
// Instantiate runtime correctly, passing adapter in options object
|
|
186
|
+
runtime = new AgentRuntime({
|
|
187
|
+
character: mockCharacter,
|
|
188
|
+
agentId: agentId,
|
|
189
|
+
adapter: mockDatabaseAdapter, // Correct way to pass adapter
|
|
190
|
+
// No plugins passed here by default, tests can pass them if needed
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('should construct without errors', () => {
|
|
195
|
+
expect(runtime).toBeInstanceOf(AgentRuntime);
|
|
196
|
+
expect(runtime.agentId).toEqual(agentId);
|
|
197
|
+
expect(runtime.character).toEqual(mockCharacter);
|
|
198
|
+
expect(runtime.adapter).toBe(mockDatabaseAdapter);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('should register database adapter via constructor', () => {
|
|
202
|
+
// This is implicitly tested by the constructor test above
|
|
203
|
+
expect(runtime.adapter).toBeDefined();
|
|
204
|
+
expect(runtime.adapter).toEqual(mockDatabaseAdapter);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
describe('Plugin Registration', () => {
|
|
208
|
+
it('should register a simple plugin', async () => {
|
|
209
|
+
const mockPlugin: Plugin = { name: 'TestPlugin', description: 'A test plugin' };
|
|
210
|
+
await runtime.registerPlugin(mockPlugin);
|
|
211
|
+
console.log('runtime.plugins', runtime.plugins);
|
|
212
|
+
// Check if the plugin is added to the internal list
|
|
213
|
+
expect(runtime.plugins.some((p) => p.name === 'TestPlugin')).toBe(true);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it('should call plugin init function', async () => {
|
|
217
|
+
const initMock = mock().mockResolvedValue(undefined);
|
|
218
|
+
const mockPlugin: Plugin = {
|
|
219
|
+
name: 'InitPlugin',
|
|
220
|
+
description: 'Plugin with init',
|
|
221
|
+
init: initMock,
|
|
222
|
+
};
|
|
223
|
+
await runtime.registerPlugin(mockPlugin);
|
|
224
|
+
expect(initMock).toHaveBeenCalledTimes(1);
|
|
225
|
+
expect(initMock).toHaveBeenCalledWith(expect.anything(), runtime); // Check if called with config and runtime
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it('should register plugin features (actions, providers, models) when initialized', async () => {
|
|
229
|
+
const actionHandler = mock();
|
|
230
|
+
const providerGet = mock().mockResolvedValue({ text: 'provider_text' });
|
|
231
|
+
const modelHandler = mock().mockResolvedValue('model_result');
|
|
232
|
+
|
|
233
|
+
const mockPlugin: Plugin = {
|
|
234
|
+
name: 'FeaturesPlugin',
|
|
235
|
+
description: 'Plugin with features',
|
|
236
|
+
actions: [
|
|
237
|
+
{
|
|
238
|
+
name: 'TestAction',
|
|
239
|
+
description: 'Test action',
|
|
240
|
+
handler: actionHandler,
|
|
241
|
+
validate: async () => true,
|
|
242
|
+
},
|
|
243
|
+
],
|
|
244
|
+
providers: [{ name: 'TestProvider', get: providerGet }],
|
|
245
|
+
models: { [ModelType.TEXT_SMALL]: modelHandler },
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
// Re-create runtime passing plugin in constructor
|
|
249
|
+
runtime = new AgentRuntime({
|
|
250
|
+
character: mockCharacter,
|
|
251
|
+
agentId: agentId,
|
|
252
|
+
adapter: mockDatabaseAdapter,
|
|
253
|
+
plugins: [mockPlugin], // Pass plugin during construction
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
// Mock adapter calls needed for initialize
|
|
257
|
+
const ensureAgentExistsSpy = spyOn(
|
|
258
|
+
AgentRuntime.prototype,
|
|
259
|
+
'ensureAgentExists'
|
|
260
|
+
).mockResolvedValue({
|
|
261
|
+
...mockCharacter,
|
|
262
|
+
id: agentId, // ensureAgentExists should return the agent
|
|
263
|
+
createdAt: Date.now(),
|
|
264
|
+
updatedAt: Date.now(),
|
|
265
|
+
enabled: true,
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
(mockDatabaseAdapter.getEntityByIds as any).mockResolvedValue([
|
|
269
|
+
{
|
|
270
|
+
id: agentId,
|
|
271
|
+
agentId: agentId,
|
|
272
|
+
names: [mockCharacter.name],
|
|
273
|
+
},
|
|
274
|
+
]);
|
|
275
|
+
(mockDatabaseAdapter.getEntitiesByIds as any).mockResolvedValue([
|
|
276
|
+
{
|
|
277
|
+
id: agentId,
|
|
278
|
+
agentId: agentId,
|
|
279
|
+
names: [mockCharacter.name],
|
|
280
|
+
},
|
|
281
|
+
]);
|
|
282
|
+
(mockDatabaseAdapter.getRoomsByIds as any).mockResolvedValue([]);
|
|
283
|
+
(mockDatabaseAdapter.getParticipantsForRoom as any).mockResolvedValue([]);
|
|
284
|
+
|
|
285
|
+
await runtime.initialize(); // Initialize to process registrations
|
|
286
|
+
|
|
287
|
+
expect(runtime.actions.some((a) => a.name === 'TestAction')).toBe(true);
|
|
288
|
+
expect(runtime.providers.some((p) => p.name === 'TestProvider')).toBe(true);
|
|
289
|
+
expect(runtime.models.has(ModelType.TEXT_SMALL)).toBe(true);
|
|
290
|
+
ensureAgentExistsSpy.mockRestore();
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
describe('Initialization', () => {
|
|
295
|
+
let ensureAgentExistsSpy: any;
|
|
296
|
+
beforeEach(() => {
|
|
297
|
+
// Mock adapter calls needed for a successful initialize
|
|
298
|
+
ensureAgentExistsSpy = spyOn(AgentRuntime.prototype, 'ensureAgentExists').mockResolvedValue({
|
|
299
|
+
...mockCharacter,
|
|
300
|
+
id: agentId, // ensureAgentExists should return the agent
|
|
301
|
+
createdAt: Date.now(),
|
|
302
|
+
updatedAt: Date.now(),
|
|
303
|
+
enabled: true,
|
|
304
|
+
});
|
|
305
|
+
(mockDatabaseAdapter.getEntityByIds as any).mockResolvedValue([
|
|
306
|
+
{
|
|
307
|
+
id: agentId,
|
|
308
|
+
agentId: agentId,
|
|
309
|
+
names: [mockCharacter.name],
|
|
310
|
+
},
|
|
311
|
+
]);
|
|
312
|
+
(mockDatabaseAdapter.getEntitiesByIds as any).mockResolvedValue([
|
|
313
|
+
{
|
|
314
|
+
id: agentId,
|
|
315
|
+
agentId: agentId,
|
|
316
|
+
names: [mockCharacter.name],
|
|
317
|
+
},
|
|
318
|
+
]);
|
|
319
|
+
(mockDatabaseAdapter.getRoomsByIds as any).mockResolvedValue([]);
|
|
320
|
+
(mockDatabaseAdapter.getParticipantsForRoom as any).mockResolvedValue([]);
|
|
321
|
+
// mockDatabaseAdapter.getAgent is NOT called by initialize anymore after ensureAgentExists returns the agent
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
afterEach(() => {
|
|
325
|
+
ensureAgentExistsSpy.mockRestore();
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
it('should call adapter.init and core setup methods for an existing agent', async () => {
|
|
329
|
+
await runtime.initialize();
|
|
330
|
+
|
|
331
|
+
expect(mockDatabaseAdapter.init).toHaveBeenCalledTimes(1);
|
|
332
|
+
expect(runtime.ensureAgentExists).toHaveBeenCalledWith(mockCharacter);
|
|
333
|
+
// expect(mockDatabaseAdapter.getAgent).toHaveBeenCalledWith(agentId); // This is no longer called
|
|
334
|
+
expect(mockDatabaseAdapter.getEntitiesByIds).toHaveBeenCalledWith([agentId]);
|
|
335
|
+
expect(mockDatabaseAdapter.getRoomsByIds).toHaveBeenCalledWith([agentId]);
|
|
336
|
+
expect(mockDatabaseAdapter.createRooms).toHaveBeenCalled();
|
|
337
|
+
expect(mockDatabaseAdapter.addParticipantsRoom).toHaveBeenCalledWith([agentId], agentId);
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
it('should create a new agent if one does not exist', async () => {
|
|
341
|
+
// No need to override the spy, initialize should handle it.
|
|
342
|
+
await runtime.initialize();
|
|
343
|
+
|
|
344
|
+
expect(mockDatabaseAdapter.init).toHaveBeenCalledTimes(1);
|
|
345
|
+
expect(runtime.ensureAgentExists).toHaveBeenCalledWith(mockCharacter);
|
|
346
|
+
expect(mockDatabaseAdapter.getEntitiesByIds).toHaveBeenCalledWith([agentId]);
|
|
347
|
+
expect(mockDatabaseAdapter.getRoomsByIds).toHaveBeenCalledWith([agentId]);
|
|
348
|
+
expect(mockDatabaseAdapter.createRooms).toHaveBeenCalled();
|
|
349
|
+
expect(mockDatabaseAdapter.addParticipantsRoom).toHaveBeenCalledWith([agentId], agentId);
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
it('should throw if adapter is not available during initialize', async () => {
|
|
353
|
+
// Create runtime without passing adapter
|
|
354
|
+
const runtimeWithoutAdapter = new AgentRuntime({
|
|
355
|
+
character: mockCharacter,
|
|
356
|
+
agentId: agentId,
|
|
357
|
+
});
|
|
358
|
+
await expect(runtimeWithoutAdapter.initialize()).rejects.toThrow(
|
|
359
|
+
/Database adapter not initialized/
|
|
360
|
+
);
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
// Add more tests for initialize: existing entity, existing room, knowledge processing etc.
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
describe('State Composition', () => {
|
|
367
|
+
it('should call provider get methods', async () => {
|
|
368
|
+
const provider1Get = mock().mockResolvedValue({ text: 'p1_text', values: { p1_val: 1 } });
|
|
369
|
+
const provider2Get = mock().mockResolvedValue({ text: 'p2_text', values: { p2_val: 2 } });
|
|
370
|
+
const provider1: Provider = { name: 'P1', get: provider1Get };
|
|
371
|
+
const provider2: Provider = { name: 'P2', get: provider2Get };
|
|
372
|
+
|
|
373
|
+
runtime.registerProvider(provider1);
|
|
374
|
+
runtime.registerProvider(provider2);
|
|
375
|
+
|
|
376
|
+
const message = createMockMemory('test message', undefined, undefined, undefined, agentId);
|
|
377
|
+
const state = await runtime.composeState(message);
|
|
378
|
+
|
|
379
|
+
expect(provider1Get).toHaveBeenCalledTimes(1);
|
|
380
|
+
// The cached state passed will be the initial empty-ish one
|
|
381
|
+
expect(provider1Get).toHaveBeenCalledWith(runtime, message, {
|
|
382
|
+
values: {},
|
|
383
|
+
data: {},
|
|
384
|
+
text: '',
|
|
385
|
+
});
|
|
386
|
+
expect(provider2Get).toHaveBeenCalledTimes(1);
|
|
387
|
+
expect(provider2Get).toHaveBeenCalledWith(runtime, message, {
|
|
388
|
+
values: {},
|
|
389
|
+
data: {},
|
|
390
|
+
text: '',
|
|
391
|
+
});
|
|
392
|
+
expect(state.text).toContain('p1_text');
|
|
393
|
+
expect(state.text).toContain('p2_text');
|
|
394
|
+
expect(state.values).toHaveProperty('p1_val', 1);
|
|
395
|
+
expect(state.values).toHaveProperty('p2_val', 2);
|
|
396
|
+
// Check combined values includes provider outputs
|
|
397
|
+
expect(state.values).toHaveProperty('providers'); // Check if the combined text is stored
|
|
398
|
+
expect(state.data.providers.P1.values).toEqual({ p1_val: 1 }); // Check provider data cache
|
|
399
|
+
expect(state.data.providers.P2.values).toEqual({ p2_val: 2 });
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
it('should filter providers', async () => {
|
|
403
|
+
const provider1Get = mock().mockResolvedValue({ text: 'p1_text' });
|
|
404
|
+
const provider2Get = mock().mockResolvedValue({ text: 'p2_text' });
|
|
405
|
+
const provider1: Provider = { name: 'P1', get: provider1Get };
|
|
406
|
+
const provider2: Provider = { name: 'P2', get: provider2Get };
|
|
407
|
+
|
|
408
|
+
runtime.registerProvider(provider1);
|
|
409
|
+
runtime.registerProvider(provider2);
|
|
410
|
+
|
|
411
|
+
const message = createMockMemory('test message', undefined, undefined, undefined, agentId);
|
|
412
|
+
const state = await runtime.composeState(message, ['P1'], true); // Filter to only P1
|
|
413
|
+
|
|
414
|
+
expect(provider1Get).toHaveBeenCalledTimes(1);
|
|
415
|
+
expect(provider2Get).not.toHaveBeenCalled();
|
|
416
|
+
expect(state.text).toBe('p1_text');
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
// Add tests for includeList, caching behavior
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
describe('Model Usage', () => {
|
|
423
|
+
it('should call registered model handler', async () => {
|
|
424
|
+
const modelHandler = mock().mockResolvedValue('success');
|
|
425
|
+
const modelType = ModelType.TEXT_LARGE;
|
|
426
|
+
|
|
427
|
+
runtime.registerModel(modelType, modelHandler, 'test-provider');
|
|
428
|
+
|
|
429
|
+
const params = { prompt: 'test prompt', someOption: true };
|
|
430
|
+
const result = await runtime.useModel(modelType, params);
|
|
431
|
+
|
|
432
|
+
expect(modelHandler).toHaveBeenCalledTimes(1);
|
|
433
|
+
// Check that handler was called with runtime and merged params
|
|
434
|
+
expect(modelHandler).toHaveBeenCalledWith(
|
|
435
|
+
runtime,
|
|
436
|
+
expect.objectContaining({ ...params, runtime: runtime })
|
|
437
|
+
);
|
|
438
|
+
expect(result).toEqual('success');
|
|
439
|
+
// Check if log was called (part of useModel logic)
|
|
440
|
+
expect(mockDatabaseAdapter.log).toHaveBeenCalledWith(
|
|
441
|
+
expect.objectContaining({ type: `useModel:${modelType}` })
|
|
442
|
+
);
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
it('should throw if model type is not registered', async () => {
|
|
446
|
+
const modelType = 'UNREGISTERED_MODEL' as ModelTypeName;
|
|
447
|
+
const params = { prompt: 'test' };
|
|
448
|
+
await expect(runtime.useModel(modelType, params)).rejects.toThrow(/No handler found/);
|
|
449
|
+
});
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
describe('Action Processing', () => {
|
|
453
|
+
let mockActionHandler: any; // Use 'any' or specific function type
|
|
454
|
+
let testAction: Action;
|
|
455
|
+
let message: Memory;
|
|
456
|
+
let responseMemory: Memory;
|
|
457
|
+
|
|
458
|
+
beforeEach(() => {
|
|
459
|
+
mockActionHandler = mock().mockResolvedValue(undefined);
|
|
460
|
+
testAction = createMockAction('TestAction');
|
|
461
|
+
testAction.handler = mockActionHandler; // Assign mock handler
|
|
462
|
+
|
|
463
|
+
runtime.registerAction(testAction);
|
|
464
|
+
|
|
465
|
+
message = createMockMemory('user message', undefined, undefined, undefined, agentId);
|
|
466
|
+
responseMemory = createMockMemory(
|
|
467
|
+
'agent response',
|
|
468
|
+
undefined,
|
|
469
|
+
undefined,
|
|
470
|
+
message.roomId,
|
|
471
|
+
agentId
|
|
472
|
+
); // Same room
|
|
473
|
+
responseMemory.content.actions = ['TestAction']; // Specify action to run
|
|
474
|
+
|
|
475
|
+
// Mock composeState as it's called within processActions
|
|
476
|
+
spyOn(runtime, 'composeState').mockResolvedValue(createMockState('composed state text'));
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
it('should find and execute the correct action handler', async () => {
|
|
480
|
+
await runtime.processActions(message, [responseMemory]);
|
|
481
|
+
|
|
482
|
+
expect(runtime.composeState).toHaveBeenCalled(); // Verify state was composed
|
|
483
|
+
expect(mockActionHandler).toHaveBeenCalledTimes(1);
|
|
484
|
+
// Check arguments passed to the handler
|
|
485
|
+
expect(mockActionHandler).toHaveBeenCalledWith(
|
|
486
|
+
runtime,
|
|
487
|
+
message,
|
|
488
|
+
expect.objectContaining({ text: 'composed state text' }), // Check composed state
|
|
489
|
+
expect.objectContaining({
|
|
490
|
+
context: expect.objectContaining({
|
|
491
|
+
previousResults: expect.any(Array),
|
|
492
|
+
getPreviousResult: expect.any(Function),
|
|
493
|
+
}),
|
|
494
|
+
}), // options now contains context
|
|
495
|
+
undefined, // callback
|
|
496
|
+
[responseMemory] // responses array
|
|
497
|
+
);
|
|
498
|
+
expect(mockDatabaseAdapter.log).toHaveBeenCalledWith(
|
|
499
|
+
expect.objectContaining({
|
|
500
|
+
type: 'action',
|
|
501
|
+
body: expect.objectContaining({ action: 'TestAction' }),
|
|
502
|
+
})
|
|
503
|
+
);
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
// Add tests for action not found, simile matching, handler errors
|
|
507
|
+
it('should not execute if no action name matches', async () => {
|
|
508
|
+
responseMemory.content.actions = ['NonExistentAction'];
|
|
509
|
+
await runtime.processActions(message, [responseMemory]);
|
|
510
|
+
expect(mockActionHandler).not.toHaveBeenCalled();
|
|
511
|
+
expect(mockDatabaseAdapter.log).not.toHaveBeenCalledWith(
|
|
512
|
+
expect.objectContaining({ type: 'action' })
|
|
513
|
+
);
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
it('should prioritize exact action name matches over fuzzy matches', async () => {
|
|
517
|
+
// Create two actions where one name is a substring of another
|
|
518
|
+
const replyHandler = mock().mockResolvedValue(undefined);
|
|
519
|
+
const replyWithImageHandler = mock().mockResolvedValue(undefined);
|
|
520
|
+
|
|
521
|
+
const replyAction: Action = {
|
|
522
|
+
name: 'REPLY',
|
|
523
|
+
description: 'Simple reply action',
|
|
524
|
+
similes: [],
|
|
525
|
+
examples: [],
|
|
526
|
+
handler: replyHandler,
|
|
527
|
+
validate: mock().mockImplementation(async () => true),
|
|
528
|
+
};
|
|
529
|
+
|
|
530
|
+
const replyWithImageAction: Action = {
|
|
531
|
+
name: 'REPLY_WITH_IMAGE',
|
|
532
|
+
description: 'Reply with image action',
|
|
533
|
+
similes: [],
|
|
534
|
+
examples: [],
|
|
535
|
+
handler: replyWithImageHandler,
|
|
536
|
+
validate: mock().mockImplementation(async () => true),
|
|
537
|
+
};
|
|
538
|
+
|
|
539
|
+
// Register both actions
|
|
540
|
+
runtime.registerAction(replyAction);
|
|
541
|
+
runtime.registerAction(replyWithImageAction);
|
|
542
|
+
|
|
543
|
+
// Test 1: When asking for 'REPLY', it should match REPLY exactly, not REPLY_WITH_IMAGE
|
|
544
|
+
responseMemory.content.actions = ['REPLY'];
|
|
545
|
+
await runtime.processActions(message, [responseMemory]);
|
|
546
|
+
|
|
547
|
+
expect(replyHandler).toHaveBeenCalledTimes(1);
|
|
548
|
+
expect(replyWithImageHandler).not.toHaveBeenCalled();
|
|
549
|
+
|
|
550
|
+
// Reset mocks
|
|
551
|
+
replyHandler.mockClear();
|
|
552
|
+
replyWithImageHandler.mockClear();
|
|
553
|
+
|
|
554
|
+
// Test 2: When asking for 'REPLY_WITH_IMAGE', it should match REPLY_WITH_IMAGE exactly
|
|
555
|
+
responseMemory.content.actions = ['REPLY_WITH_IMAGE'];
|
|
556
|
+
await runtime.processActions(message, [responseMemory]);
|
|
557
|
+
|
|
558
|
+
expect(replyWithImageHandler).toHaveBeenCalledTimes(1);
|
|
559
|
+
expect(replyHandler).not.toHaveBeenCalled();
|
|
560
|
+
});
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
// --- Adapter Passthrough Tests ---
|
|
564
|
+
describe('Adapter Passthrough', () => {
|
|
565
|
+
it('createEntity should call adapter.createEntities', async () => {
|
|
566
|
+
const entityData = { id: stringToUuid(uuidv4()), agentId: agentId, names: ['Test Entity'] };
|
|
567
|
+
await runtime.createEntity(entityData);
|
|
568
|
+
expect(mockDatabaseAdapter.createEntities).toHaveBeenCalledTimes(1);
|
|
569
|
+
expect(mockDatabaseAdapter.createEntities).toHaveBeenCalledWith([entityData]);
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
it('getMemoryById should call adapter.getMemoryById', async () => {
|
|
573
|
+
const memoryId = stringToUuid(uuidv4());
|
|
574
|
+
await runtime.getMemoryById(memoryId);
|
|
575
|
+
expect(mockDatabaseAdapter.getMemoryById).toHaveBeenCalledTimes(1);
|
|
576
|
+
expect(mockDatabaseAdapter.getMemoryById).toHaveBeenCalledWith(memoryId);
|
|
577
|
+
});
|
|
578
|
+
// Add more tests for other adapter methods if full coverage is desired
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
// --- Event Emitter Tests ---
|
|
582
|
+
describe('Event Emitter (on/emit/off)', () => {
|
|
583
|
+
it('should register and emit events', () => {
|
|
584
|
+
const handler = mock();
|
|
585
|
+
const eventName = 'testEvent';
|
|
586
|
+
const eventData = { info: 'data' };
|
|
587
|
+
|
|
588
|
+
runtime.on(eventName, handler);
|
|
589
|
+
runtime.emit(eventName, eventData);
|
|
590
|
+
|
|
591
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
592
|
+
expect(handler).toHaveBeenCalledWith(eventData);
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
it('should remove event handler with off', () => {
|
|
596
|
+
const handler = mock();
|
|
597
|
+
const eventName = 'testEvent';
|
|
598
|
+
|
|
599
|
+
runtime.on(eventName, handler);
|
|
600
|
+
runtime.off(eventName, handler);
|
|
601
|
+
runtime.emit(eventName, { info: 'data' });
|
|
602
|
+
|
|
603
|
+
expect(handler).not.toHaveBeenCalled();
|
|
604
|
+
});
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
// --- Tests from original suite ---
|
|
608
|
+
describe('Original Suite Tests', () => {
|
|
609
|
+
// Note: These might need slight adaptation if they relied on Jest specifics
|
|
610
|
+
// or different mock setups.
|
|
611
|
+
|
|
612
|
+
// Copied from your original suite:
|
|
613
|
+
describe('model provider management', () => {
|
|
614
|
+
it('should provide access to the configured model provider', () => {
|
|
615
|
+
// In this refactored structure, 'provider' likely refers to the runtime instance itself
|
|
616
|
+
// which acts as the primary interface.
|
|
617
|
+
const provider = runtime; // The runtime instance manages models
|
|
618
|
+
expect(provider).toBeDefined();
|
|
619
|
+
// You might add more specific checks here, e.g., ensuring getModel exists
|
|
620
|
+
expect(runtime.getModel).toBeInstanceOf(Function);
|
|
621
|
+
});
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
// Copied from your original suite:
|
|
625
|
+
describe('state management', () => {
|
|
626
|
+
it('should compose state with additional keys', async () => {
|
|
627
|
+
// Use the helper function for consistency
|
|
628
|
+
const message: Memory = createMockMemory(
|
|
629
|
+
'test message',
|
|
630
|
+
stringToUuid('11111111-e89b-12d3-a456-426614174003'), // Use valid UUIDs
|
|
631
|
+
stringToUuid('22222222-e89b-12d3-a456-426614174004'),
|
|
632
|
+
stringToUuid('33333333-e89b-12d3-a456-426614174003'), // Room ID
|
|
633
|
+
agentId
|
|
634
|
+
);
|
|
635
|
+
|
|
636
|
+
// Mock provider needed by composeState
|
|
637
|
+
const providerGet = mock().mockResolvedValue({ text: 'provider text' });
|
|
638
|
+
runtime.registerProvider({ name: 'TestProvider', get: providerGet });
|
|
639
|
+
|
|
640
|
+
const state = await runtime.composeState(message);
|
|
641
|
+
expect(state).toHaveProperty('values');
|
|
642
|
+
expect(state).toHaveProperty('text');
|
|
643
|
+
expect(state).toHaveProperty('data');
|
|
644
|
+
// Add more specific state checks if needed
|
|
645
|
+
expect(state.text).toContain('provider text'); // Check provider text is included
|
|
646
|
+
});
|
|
647
|
+
});
|
|
648
|
+
|
|
649
|
+
// Copied from your original suite:
|
|
650
|
+
describe('action management', () => {
|
|
651
|
+
it('should register an action', () => {
|
|
652
|
+
const action = createMockAction('testAction');
|
|
653
|
+
runtime.registerAction(action);
|
|
654
|
+
expect(runtime.actions).toContain(action);
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
it('should allow registering multiple actions', () => {
|
|
658
|
+
const action1 = createMockAction('testAction1');
|
|
659
|
+
const action2 = createMockAction('testAction2');
|
|
660
|
+
runtime.registerAction(action1);
|
|
661
|
+
runtime.registerAction(action2);
|
|
662
|
+
expect(runtime.actions).toContain(action1);
|
|
663
|
+
expect(runtime.actions).toContain(action2);
|
|
664
|
+
});
|
|
665
|
+
});
|
|
666
|
+
|
|
667
|
+
describe('model settings from character configuration', () => {
|
|
668
|
+
it('should apply character model settings as defaults and allow overrides', async () => {
|
|
669
|
+
// Create character with model settings
|
|
670
|
+
const characterWithSettings: Character = {
|
|
671
|
+
...mockCharacter,
|
|
672
|
+
settings: {
|
|
673
|
+
MODEL_MAX_TOKEN: 4096,
|
|
674
|
+
MODEL_TEMPERATURE: 0.5,
|
|
675
|
+
MODEL_FREQ_PENALTY: 0.8,
|
|
676
|
+
MODEL_PRESENCE_PENALTY: 0.9,
|
|
677
|
+
// Test invalid values that should be ignored
|
|
678
|
+
MODEL_INVALID: 'not-a-number',
|
|
679
|
+
},
|
|
680
|
+
};
|
|
681
|
+
|
|
682
|
+
// Create runtime with character settings
|
|
683
|
+
const runtimeWithSettings = new AgentRuntime({
|
|
684
|
+
character: characterWithSettings,
|
|
685
|
+
adapter: mockDatabaseAdapter,
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
// Mock a model handler to capture params
|
|
689
|
+
let capturedParams: any = null;
|
|
690
|
+
const mockHandler = mock().mockImplementation(async (_runtime: any, params: any) => {
|
|
691
|
+
capturedParams = params;
|
|
692
|
+
return 'test response';
|
|
693
|
+
});
|
|
694
|
+
|
|
695
|
+
// Register the mock model
|
|
696
|
+
runtimeWithSettings.registerModel(ModelType.TEXT_SMALL, mockHandler, 'test-provider');
|
|
697
|
+
|
|
698
|
+
// Test 1: Model settings are applied as defaults
|
|
699
|
+
await runtimeWithSettings.useModel(ModelType.TEXT_SMALL, {
|
|
700
|
+
prompt: 'test prompt',
|
|
701
|
+
});
|
|
702
|
+
|
|
703
|
+
expect(capturedParams.maxTokens).toBe(4096);
|
|
704
|
+
expect(capturedParams.temperature).toBe(0.5);
|
|
705
|
+
expect(capturedParams.frequencyPenalty).toBe(0.8);
|
|
706
|
+
expect(capturedParams.presencePenalty).toBe(0.9);
|
|
707
|
+
expect(capturedParams.prompt).toBe('test prompt');
|
|
708
|
+
|
|
709
|
+
// Test 2: Explicit parameters override character defaults
|
|
710
|
+
await runtimeWithSettings.useModel(ModelType.TEXT_SMALL, {
|
|
711
|
+
prompt: 'test prompt 2',
|
|
712
|
+
temperature: 0.2,
|
|
713
|
+
maxTokens: 2048,
|
|
714
|
+
});
|
|
715
|
+
|
|
716
|
+
expect(capturedParams.temperature).toBe(0.2);
|
|
717
|
+
expect(capturedParams.maxTokens).toBe(2048);
|
|
718
|
+
expect(capturedParams.frequencyPenalty).toBe(0.8); // Still from character
|
|
719
|
+
expect(capturedParams.presencePenalty).toBe(0.9); // Still from character
|
|
720
|
+
|
|
721
|
+
// Test 3: No settings configured - use only provided params
|
|
722
|
+
const characterNoSettings: Character = {
|
|
723
|
+
...mockCharacter,
|
|
724
|
+
name: 'TestAgentNoSettings',
|
|
725
|
+
};
|
|
726
|
+
const runtimeNoSettings = new AgentRuntime({
|
|
727
|
+
character: characterNoSettings,
|
|
728
|
+
adapter: mockDatabaseAdapter,
|
|
729
|
+
});
|
|
730
|
+
|
|
731
|
+
// Use same mockHandler for the second test
|
|
732
|
+
mockHandler.mockClear();
|
|
733
|
+
runtimeNoSettings.registerModel(ModelType.TEXT_SMALL, mockHandler, 'test-provider');
|
|
734
|
+
|
|
735
|
+
await runtimeNoSettings.useModel(ModelType.TEXT_SMALL, {
|
|
736
|
+
prompt: 'test prompt 3',
|
|
737
|
+
temperature: 0.7,
|
|
738
|
+
});
|
|
739
|
+
|
|
740
|
+
expect(capturedParams.temperature).toBe(0.7);
|
|
741
|
+
expect(capturedParams.maxTokens).toBeUndefined();
|
|
742
|
+
expect(capturedParams.frequencyPenalty).toBeUndefined();
|
|
743
|
+
expect(capturedParams.presencePenalty).toBeUndefined();
|
|
744
|
+
});
|
|
745
|
+
});
|
|
746
|
+
|
|
747
|
+
describe('model settings from character configuration', () => {
|
|
748
|
+
it('should support per-model-type configuration with proper fallback chain', async () => {
|
|
749
|
+
// Create character with mixed settings: defaults, model-specific, and legacy
|
|
750
|
+
const characterWithMixedSettings: Character = {
|
|
751
|
+
...mockCharacter,
|
|
752
|
+
settings: {
|
|
753
|
+
// Default settings (apply to all models)
|
|
754
|
+
DEFAULT_TEMPERATURE: 0.7,
|
|
755
|
+
DEFAULT_MAX_TOKENS: 2048,
|
|
756
|
+
|
|
757
|
+
// Model-specific settings (override defaults)
|
|
758
|
+
TEXT_SMALL_TEMPERATURE: 0.5,
|
|
759
|
+
TEXT_SMALL_MAX_TOKENS: 1024,
|
|
760
|
+
TEXT_LARGE_TEMPERATURE: 0.8,
|
|
761
|
+
TEXT_LARGE_FREQUENCY_PENALTY: 0.5,
|
|
762
|
+
OBJECT_SMALL_TEMPERATURE: 0.3,
|
|
763
|
+
OBJECT_LARGE_PRESENCE_PENALTY: 0.6,
|
|
764
|
+
|
|
765
|
+
// Legacy settings (should be lowest priority)
|
|
766
|
+
MODEL_TEMPERATURE: 0.9,
|
|
767
|
+
MODEL_MAX_TOKEN: 4096,
|
|
768
|
+
MODEL_FREQ_PENALTY: 0.7,
|
|
769
|
+
MODEL_PRESENCE_PENALTY: 0.8,
|
|
770
|
+
},
|
|
771
|
+
};
|
|
772
|
+
|
|
773
|
+
const runtimeWithMixedSettings = new AgentRuntime({
|
|
774
|
+
character: characterWithMixedSettings,
|
|
775
|
+
adapter: mockDatabaseAdapter,
|
|
776
|
+
});
|
|
777
|
+
|
|
778
|
+
// Mock handlers to capture params
|
|
779
|
+
let capturedTextSmall: any = null;
|
|
780
|
+
let capturedTextLarge: any = null;
|
|
781
|
+
let capturedObjectSmall: any = null;
|
|
782
|
+
let capturedObjectLarge: any = null;
|
|
783
|
+
|
|
784
|
+
const mockTextSmallHandler = mock().mockImplementation(
|
|
785
|
+
async (_runtime: any, params: any) => {
|
|
786
|
+
capturedTextSmall = params;
|
|
787
|
+
return 'text small response';
|
|
788
|
+
}
|
|
789
|
+
);
|
|
790
|
+
const mockTextLargeHandler = mock().mockImplementation(
|
|
791
|
+
async (_runtime: any, params: any) => {
|
|
792
|
+
capturedTextLarge = params;
|
|
793
|
+
return 'text large response';
|
|
794
|
+
}
|
|
795
|
+
);
|
|
796
|
+
const mockObjectSmallHandler = mock().mockImplementation(
|
|
797
|
+
async (_runtime: any, params: any) => {
|
|
798
|
+
capturedObjectSmall = params;
|
|
799
|
+
return { type: 'small' };
|
|
800
|
+
}
|
|
801
|
+
);
|
|
802
|
+
const mockObjectLargeHandler = mock().mockImplementation(
|
|
803
|
+
async (_runtime: any, params: any) => {
|
|
804
|
+
capturedObjectLarge = params;
|
|
805
|
+
return { type: 'large' };
|
|
806
|
+
}
|
|
807
|
+
);
|
|
808
|
+
|
|
809
|
+
// Register all models
|
|
810
|
+
runtimeWithMixedSettings.registerModel(
|
|
811
|
+
ModelType.TEXT_SMALL,
|
|
812
|
+
mockTextSmallHandler,
|
|
813
|
+
'test-provider'
|
|
814
|
+
);
|
|
815
|
+
runtimeWithMixedSettings.registerModel(
|
|
816
|
+
ModelType.TEXT_LARGE,
|
|
817
|
+
mockTextLargeHandler,
|
|
818
|
+
'test-provider'
|
|
819
|
+
);
|
|
820
|
+
runtimeWithMixedSettings.registerModel(
|
|
821
|
+
ModelType.OBJECT_SMALL,
|
|
822
|
+
mockObjectSmallHandler,
|
|
823
|
+
'test-provider'
|
|
824
|
+
);
|
|
825
|
+
runtimeWithMixedSettings.registerModel(
|
|
826
|
+
ModelType.OBJECT_LARGE,
|
|
827
|
+
mockObjectLargeHandler,
|
|
828
|
+
'test-provider'
|
|
829
|
+
);
|
|
830
|
+
|
|
831
|
+
// Test 1: TEXT_SMALL - should use model-specific settings, fall back to defaults/legacy
|
|
832
|
+
await runtimeWithMixedSettings.useModel(ModelType.TEXT_SMALL, {
|
|
833
|
+
prompt: 'test text small',
|
|
834
|
+
});
|
|
835
|
+
|
|
836
|
+
expect(capturedTextSmall.temperature).toBe(0.5); // Model-specific
|
|
837
|
+
expect(capturedTextSmall.maxTokens).toBe(1024); // Model-specific
|
|
838
|
+
expect(capturedTextSmall.frequencyPenalty).toBe(0.7); // Legacy fallback
|
|
839
|
+
expect(capturedTextSmall.presencePenalty).toBe(0.8); // Legacy fallback
|
|
840
|
+
|
|
841
|
+
// Test 2: TEXT_LARGE - mixed model-specific and defaults
|
|
842
|
+
await runtimeWithMixedSettings.useModel(ModelType.TEXT_LARGE, {
|
|
843
|
+
prompt: 'test text large',
|
|
844
|
+
});
|
|
845
|
+
|
|
846
|
+
expect(capturedTextLarge.temperature).toBe(0.8); // Model-specific
|
|
847
|
+
expect(capturedTextLarge.maxTokens).toBe(2048); // Default fallback
|
|
848
|
+
expect(capturedTextLarge.frequencyPenalty).toBe(0.5); // Model-specific
|
|
849
|
+
expect(capturedTextLarge.presencePenalty).toBe(0.8); // Legacy fallback
|
|
850
|
+
|
|
851
|
+
// Test 3: OBJECT_SMALL - some model-specific, rest from defaults/legacy
|
|
852
|
+
await runtimeWithMixedSettings.useModel(ModelType.OBJECT_SMALL, {
|
|
853
|
+
prompt: 'test object small',
|
|
854
|
+
});
|
|
855
|
+
|
|
856
|
+
expect(capturedObjectSmall.temperature).toBe(0.3); // Model-specific
|
|
857
|
+
expect(capturedObjectSmall.maxTokens).toBe(2048); // Default fallback
|
|
858
|
+
expect(capturedObjectSmall.frequencyPenalty).toBe(0.7); // Legacy fallback
|
|
859
|
+
expect(capturedObjectSmall.presencePenalty).toBe(0.8); // Legacy fallback
|
|
860
|
+
|
|
861
|
+
// Test 4: OBJECT_LARGE - minimal model-specific settings
|
|
862
|
+
await runtimeWithMixedSettings.useModel(ModelType.OBJECT_LARGE, {
|
|
863
|
+
prompt: 'test object large',
|
|
864
|
+
});
|
|
865
|
+
|
|
866
|
+
expect(capturedObjectLarge.temperature).toBe(0.7); // Default fallback
|
|
867
|
+
expect(capturedObjectLarge.maxTokens).toBe(2048); // Default fallback
|
|
868
|
+
expect(capturedObjectLarge.frequencyPenalty).toBe(0.7); // Legacy fallback
|
|
869
|
+
expect(capturedObjectLarge.presencePenalty).toBe(0.6); // Model-specific
|
|
870
|
+
});
|
|
871
|
+
|
|
872
|
+
it('should allow direct params to override all configuration levels', async () => {
|
|
873
|
+
const characterWithAllSettings: Character = {
|
|
874
|
+
...mockCharacter,
|
|
875
|
+
settings: {
|
|
876
|
+
// All levels of configuration
|
|
877
|
+
DEFAULT_TEMPERATURE: 0.7,
|
|
878
|
+
TEXT_SMALL_TEMPERATURE: 0.5,
|
|
879
|
+
MODEL_TEMPERATURE: 0.9,
|
|
880
|
+
},
|
|
881
|
+
};
|
|
882
|
+
|
|
883
|
+
const runtime = new AgentRuntime({
|
|
884
|
+
character: characterWithAllSettings,
|
|
885
|
+
adapter: mockDatabaseAdapter,
|
|
886
|
+
});
|
|
887
|
+
|
|
888
|
+
let capturedParams: any = null;
|
|
889
|
+
const mockHandler = mock().mockImplementation(async (_runtime: any, params: any) => {
|
|
890
|
+
capturedParams = params;
|
|
891
|
+
return 'response';
|
|
892
|
+
});
|
|
893
|
+
|
|
894
|
+
runtime.registerModel(ModelType.TEXT_SMALL, mockHandler, 'test-provider');
|
|
895
|
+
|
|
896
|
+
// Direct params should override everything
|
|
897
|
+
await runtime.useModel(ModelType.TEXT_SMALL, {
|
|
898
|
+
prompt: 'test',
|
|
899
|
+
temperature: 0.1, // This should win
|
|
900
|
+
});
|
|
901
|
+
|
|
902
|
+
expect(capturedParams.temperature).toBe(0.1);
|
|
903
|
+
});
|
|
904
|
+
|
|
905
|
+
it('should handle models without specific configuration support', async () => {
|
|
906
|
+
const characterWithSettings: Character = {
|
|
907
|
+
...mockCharacter,
|
|
908
|
+
settings: {
|
|
909
|
+
DEFAULT_TEMPERATURE: 0.7,
|
|
910
|
+
TEXT_SMALL_TEMPERATURE: 0.5,
|
|
911
|
+
// No specific settings for TEXT_REASONING_SMALL
|
|
912
|
+
},
|
|
913
|
+
};
|
|
914
|
+
|
|
915
|
+
const runtime = new AgentRuntime({
|
|
916
|
+
character: characterWithSettings,
|
|
917
|
+
adapter: mockDatabaseAdapter,
|
|
918
|
+
});
|
|
919
|
+
|
|
920
|
+
let capturedParams: any = null;
|
|
921
|
+
const mockHandler = mock().mockImplementation(async (_runtime: any, params: any) => {
|
|
922
|
+
capturedParams = params;
|
|
923
|
+
return 'response';
|
|
924
|
+
});
|
|
925
|
+
|
|
926
|
+
// Register a model type that doesn't have specific configuration support
|
|
927
|
+
runtime.registerModel(ModelType.TEXT_REASONING_SMALL, mockHandler, 'test-provider');
|
|
928
|
+
|
|
929
|
+
await runtime.useModel(ModelType.TEXT_REASONING_SMALL, {
|
|
930
|
+
prompt: 'test reasoning',
|
|
931
|
+
});
|
|
932
|
+
|
|
933
|
+
// Should fall back to default settings
|
|
934
|
+
expect(capturedParams.temperature).toBe(0.7);
|
|
935
|
+
expect(capturedParams.maxTokens).toBeUndefined(); // No default for this
|
|
936
|
+
});
|
|
937
|
+
|
|
938
|
+
it('should validate and ignore invalid numeric values at all configuration levels', async () => {
|
|
939
|
+
const characterWithInvalidSettings: Character = {
|
|
940
|
+
...mockCharacter,
|
|
941
|
+
settings: {
|
|
942
|
+
// Mix of valid and invalid values at different levels
|
|
943
|
+
DEFAULT_TEMPERATURE: 'not-a-number',
|
|
944
|
+
DEFAULT_MAX_TOKENS: 2048,
|
|
945
|
+
TEXT_SMALL_TEMPERATURE: 0.5,
|
|
946
|
+
TEXT_SMALL_MAX_TOKENS: 'invalid',
|
|
947
|
+
TEXT_SMALL_FREQUENCY_PENALTY: null,
|
|
948
|
+
MODEL_TEMPERATURE: undefined,
|
|
949
|
+
MODEL_PRESENCE_PENALTY: 0.8,
|
|
950
|
+
},
|
|
951
|
+
};
|
|
952
|
+
|
|
953
|
+
const runtime = new AgentRuntime({
|
|
954
|
+
character: characterWithInvalidSettings,
|
|
955
|
+
adapter: mockDatabaseAdapter,
|
|
956
|
+
});
|
|
957
|
+
|
|
958
|
+
let capturedParams: any = null;
|
|
959
|
+
const mockHandler = mock().mockImplementation(async (_runtime: any, params: any) => {
|
|
960
|
+
capturedParams = params;
|
|
961
|
+
return 'response';
|
|
962
|
+
});
|
|
963
|
+
|
|
964
|
+
runtime.registerModel(ModelType.TEXT_SMALL, mockHandler, 'test-provider');
|
|
965
|
+
|
|
966
|
+
await runtime.useModel(ModelType.TEXT_SMALL, {
|
|
967
|
+
prompt: 'test invalid',
|
|
968
|
+
});
|
|
969
|
+
|
|
970
|
+
// Valid values should be used, invalid ones ignored
|
|
971
|
+
expect(capturedParams.temperature).toBe(0.5); // Valid model-specific
|
|
972
|
+
expect(capturedParams.maxTokens).toBe(2048); // Valid default (model-specific was invalid)
|
|
973
|
+
expect(capturedParams.frequencyPenalty).toBeUndefined(); // All invalid
|
|
974
|
+
expect(capturedParams.presencePenalty).toBe(0.8); // Valid legacy
|
|
975
|
+
});
|
|
976
|
+
});
|
|
977
|
+
});
|
|
978
|
+
}); // End of main describe block
|