@elizaos/core 1.5.0 → 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 +102 -102
- package/dist/browser/index.browser.js.map +5 -5
- 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/dist/node/index.node.js +91 -12
- package/dist/node/index.node.js.map +10 -10
- 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
package/src/runtime.ts
ADDED
|
@@ -0,0 +1,2514 @@
|
|
|
1
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
2
|
+
|
|
3
|
+
// Interface for working memory entries
|
|
4
|
+
interface WorkingMemoryEntry {
|
|
5
|
+
actionName: string;
|
|
6
|
+
result: ActionResult;
|
|
7
|
+
timestamp: number;
|
|
8
|
+
}
|
|
9
|
+
import { createUniqueUuid } from './entities';
|
|
10
|
+
import { getEnv, getNumberEnv } from './utils/environment';
|
|
11
|
+
import { BufferUtils } from './utils/buffer';
|
|
12
|
+
import { decryptSecret, getSalt, safeReplacer } from './index';
|
|
13
|
+
import { createLogger } from './logger';
|
|
14
|
+
import {
|
|
15
|
+
ChannelType,
|
|
16
|
+
EventType,
|
|
17
|
+
ModelType,
|
|
18
|
+
MODEL_SETTINGS,
|
|
19
|
+
type Content,
|
|
20
|
+
type MemoryMetadata,
|
|
21
|
+
type Character,
|
|
22
|
+
type Action,
|
|
23
|
+
type Evaluator,
|
|
24
|
+
type Provider,
|
|
25
|
+
type HandlerCallback,
|
|
26
|
+
type IDatabaseAdapter,
|
|
27
|
+
type Entity,
|
|
28
|
+
type Room,
|
|
29
|
+
type World,
|
|
30
|
+
type SendHandlerFunction,
|
|
31
|
+
type TargetInfo,
|
|
32
|
+
type ModelParamsMap,
|
|
33
|
+
type ModelResultMap,
|
|
34
|
+
type ModelTypeName,
|
|
35
|
+
type Plugin,
|
|
36
|
+
type Route,
|
|
37
|
+
type UUID,
|
|
38
|
+
type Service,
|
|
39
|
+
type ServiceTypeName,
|
|
40
|
+
type State,
|
|
41
|
+
type TaskWorker,
|
|
42
|
+
type Agent,
|
|
43
|
+
type Log,
|
|
44
|
+
type Participant,
|
|
45
|
+
type Relationship,
|
|
46
|
+
type Task,
|
|
47
|
+
type Memory,
|
|
48
|
+
type ModelHandler,
|
|
49
|
+
type RuntimeSettings,
|
|
50
|
+
type Component,
|
|
51
|
+
IAgentRuntime,
|
|
52
|
+
type ActionResult,
|
|
53
|
+
type ActionContext,
|
|
54
|
+
} from './types';
|
|
55
|
+
|
|
56
|
+
import { BM25 } from './search';
|
|
57
|
+
import { stringToUuid } from './utils';
|
|
58
|
+
|
|
59
|
+
const environmentSettings: RuntimeSettings = {};
|
|
60
|
+
|
|
61
|
+
export class Semaphore {
|
|
62
|
+
private permits: number;
|
|
63
|
+
private waiting: Array<() => void> = [];
|
|
64
|
+
constructor(count: number) {
|
|
65
|
+
this.permits = count;
|
|
66
|
+
}
|
|
67
|
+
async acquire(): Promise<void> {
|
|
68
|
+
if (this.permits > 0) {
|
|
69
|
+
this.permits -= 1;
|
|
70
|
+
return Promise.resolve();
|
|
71
|
+
}
|
|
72
|
+
return new Promise<void>((resolve) => {
|
|
73
|
+
this.waiting.push(resolve);
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
release(): void {
|
|
77
|
+
this.permits += 1;
|
|
78
|
+
const nextResolve = this.waiting.shift();
|
|
79
|
+
if (nextResolve && this.permits > 0) {
|
|
80
|
+
this.permits -= 1;
|
|
81
|
+
nextResolve();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
type ServiceResolver = (service: Service) => void;
|
|
87
|
+
|
|
88
|
+
export class AgentRuntime implements IAgentRuntime {
|
|
89
|
+
readonly #conversationLength = 32 as number;
|
|
90
|
+
readonly agentId: UUID;
|
|
91
|
+
readonly character: Character;
|
|
92
|
+
public adapter!: IDatabaseAdapter;
|
|
93
|
+
readonly actions: Action[] = [];
|
|
94
|
+
readonly evaluators: Evaluator[] = [];
|
|
95
|
+
readonly providers: Provider[] = [];
|
|
96
|
+
readonly plugins: Plugin[] = [];
|
|
97
|
+
private isInitialized = false;
|
|
98
|
+
events: Map<string, ((params: any) => Promise<void>)[]> = new Map();
|
|
99
|
+
stateCache = new Map<
|
|
100
|
+
UUID,
|
|
101
|
+
{
|
|
102
|
+
values: { [key: string]: any };
|
|
103
|
+
data: { [key: string]: any };
|
|
104
|
+
text: string;
|
|
105
|
+
}
|
|
106
|
+
>();
|
|
107
|
+
readonly fetch = fetch;
|
|
108
|
+
services = new Map<ServiceTypeName, Service[]>();
|
|
109
|
+
private serviceTypes = new Map<ServiceTypeName, (typeof Service)[]>();
|
|
110
|
+
models = new Map<string, ModelHandler[]>();
|
|
111
|
+
routes: Route[] = [];
|
|
112
|
+
private taskWorkers = new Map<string, TaskWorker>();
|
|
113
|
+
private sendHandlers = new Map<string, SendHandlerFunction>();
|
|
114
|
+
private eventHandlers: Map<string, ((data: any) => void)[]> = new Map();
|
|
115
|
+
|
|
116
|
+
// A map of all plugins available to the runtime, keyed by name, for dependency resolution.
|
|
117
|
+
private allAvailablePlugins = new Map<string, Plugin>();
|
|
118
|
+
// The initial list of plugins specified by the character configuration.
|
|
119
|
+
private characterPlugins: Plugin[] = [];
|
|
120
|
+
|
|
121
|
+
public logger;
|
|
122
|
+
private settings: RuntimeSettings;
|
|
123
|
+
private servicesInitQueue = new Set<typeof Service>();
|
|
124
|
+
private servicePromiseHandles = new Map<string, ServiceResolver>(); // write
|
|
125
|
+
private servicePromises = new Map<string, Promise<Service>>(); // read
|
|
126
|
+
public initPromise;
|
|
127
|
+
private initResolver: ((value?: unknown) => void) | undefined;
|
|
128
|
+
private currentRunId?: UUID; // Track the current run ID
|
|
129
|
+
private currentActionContext?: {
|
|
130
|
+
// Track current action execution context
|
|
131
|
+
actionName: string;
|
|
132
|
+
actionId: UUID;
|
|
133
|
+
prompts: Array<{
|
|
134
|
+
modelType: string;
|
|
135
|
+
prompt: string;
|
|
136
|
+
timestamp: number;
|
|
137
|
+
}>;
|
|
138
|
+
};
|
|
139
|
+
private maxWorkingMemoryEntries: number = 50; // Default value, can be overridden
|
|
140
|
+
|
|
141
|
+
constructor(opts: {
|
|
142
|
+
conversationLength?: number;
|
|
143
|
+
agentId?: UUID;
|
|
144
|
+
character?: Character;
|
|
145
|
+
plugins?: Plugin[];
|
|
146
|
+
fetch?: typeof fetch;
|
|
147
|
+
adapter?: IDatabaseAdapter;
|
|
148
|
+
settings?: RuntimeSettings;
|
|
149
|
+
events?: { [key: string]: ((params: any) => void)[] };
|
|
150
|
+
allAvailablePlugins?: Plugin[];
|
|
151
|
+
}) {
|
|
152
|
+
this.agentId =
|
|
153
|
+
opts.character?.id ??
|
|
154
|
+
opts?.agentId ??
|
|
155
|
+
stringToUuid(opts.character?.name ?? uuidv4() + opts.character?.username);
|
|
156
|
+
this.character = opts.character as Character;
|
|
157
|
+
const logLevel = getEnv('LOG_LEVEL', 'info');
|
|
158
|
+
|
|
159
|
+
this.initPromise = new Promise((resolve) => {
|
|
160
|
+
this.initResolver = resolve;
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// Create the logger with appropriate level - only show debug logs when explicitly configured
|
|
164
|
+
this.logger = createLogger({
|
|
165
|
+
agentName: this.character?.name,
|
|
166
|
+
logLevel: logLevel as any,
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
this.#conversationLength = opts.conversationLength ?? this.#conversationLength;
|
|
170
|
+
if (opts.adapter) {
|
|
171
|
+
this.registerDatabaseAdapter(opts.adapter);
|
|
172
|
+
}
|
|
173
|
+
this.fetch = (opts.fetch as typeof fetch) ?? this.fetch;
|
|
174
|
+
this.settings = opts.settings ?? environmentSettings;
|
|
175
|
+
|
|
176
|
+
this.plugins = []; // Initialize plugins as an empty array
|
|
177
|
+
this.characterPlugins = opts?.plugins ?? []; // Store the original character plugins
|
|
178
|
+
|
|
179
|
+
if (opts.allAvailablePlugins) {
|
|
180
|
+
for (const plugin of opts.allAvailablePlugins) {
|
|
181
|
+
if (plugin?.name) {
|
|
182
|
+
this.allAvailablePlugins.set(plugin.name, plugin);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
this.logger.debug(`Success: Agent ID: ${this.agentId}`);
|
|
188
|
+
this.currentRunId = undefined; // Initialize run ID tracker
|
|
189
|
+
|
|
190
|
+
// Set max working memory entries from settings or environment
|
|
191
|
+
if (opts.settings?.MAX_WORKING_MEMORY_ENTRIES) {
|
|
192
|
+
this.maxWorkingMemoryEntries = parseInt(opts.settings.MAX_WORKING_MEMORY_ENTRIES, 10) || 50;
|
|
193
|
+
} else {
|
|
194
|
+
this.maxWorkingMemoryEntries = getNumberEnv('MAX_WORKING_MEMORY_ENTRIES', 50) as number;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Create a new run ID for tracking a sequence of model calls
|
|
200
|
+
*/
|
|
201
|
+
createRunId(): UUID {
|
|
202
|
+
return uuidv4() as UUID;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Start a new run for tracking prompts
|
|
207
|
+
*/
|
|
208
|
+
startRun(): UUID {
|
|
209
|
+
this.currentRunId = this.createRunId();
|
|
210
|
+
return this.currentRunId;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* End the current run
|
|
215
|
+
*/
|
|
216
|
+
endRun(): void {
|
|
217
|
+
this.currentRunId = undefined;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Get the current run ID (creates one if it doesn't exist)
|
|
222
|
+
*/
|
|
223
|
+
getCurrentRunId(): UUID {
|
|
224
|
+
if (!this.currentRunId) {
|
|
225
|
+
this.currentRunId = this.createRunId();
|
|
226
|
+
}
|
|
227
|
+
return this.currentRunId;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
async registerPlugin(plugin: Plugin): Promise<void> {
|
|
231
|
+
if (!plugin?.name) {
|
|
232
|
+
// Ensure plugin and plugin.name are defined
|
|
233
|
+
const errorMsg = 'Plugin or plugin name is undefined';
|
|
234
|
+
this.logger.error(`*** registerPlugin: ${errorMsg}`);
|
|
235
|
+
throw new Error(`*** registerPlugin: ${errorMsg}`);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Check if a plugin with the same name is already registered.
|
|
239
|
+
const existingPlugin = this.plugins.find((p) => p.name === plugin.name);
|
|
240
|
+
if (existingPlugin) {
|
|
241
|
+
this.logger.warn(
|
|
242
|
+
`${this.character.name}(${this.agentId}) - Plugin ${plugin.name} is already registered. Skipping re-registration.`
|
|
243
|
+
);
|
|
244
|
+
return; // Do not proceed further with other registration steps
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Add the plugin to the runtime's list of active plugins
|
|
248
|
+
(this.plugins as Plugin[]).push(plugin);
|
|
249
|
+
this.logger.debug(
|
|
250
|
+
`Success: Plugin ${plugin.name} added to active plugins for ${this.character.name}(${this.agentId}).`
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
if (plugin.init) {
|
|
254
|
+
try {
|
|
255
|
+
await plugin.init(plugin.config || {}, this);
|
|
256
|
+
this.logger.debug(`Success: Plugin ${plugin.name} initialized successfully`);
|
|
257
|
+
} catch (error) {
|
|
258
|
+
// Check if the error is related to missing API keys
|
|
259
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
260
|
+
if (
|
|
261
|
+
errorMessage.includes('API key') ||
|
|
262
|
+
errorMessage.includes('environment variables') ||
|
|
263
|
+
errorMessage.includes('Invalid plugin configuration')
|
|
264
|
+
) {
|
|
265
|
+
console.warn(`Plugin ${plugin.name} requires configuration. ${errorMessage}`);
|
|
266
|
+
console.warn(
|
|
267
|
+
'Please check your environment variables and ensure all required API keys are set.'
|
|
268
|
+
);
|
|
269
|
+
console.warn('You can set these in your .env file.');
|
|
270
|
+
} else {
|
|
271
|
+
throw error;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
if (plugin.adapter) {
|
|
276
|
+
this.logger.debug(`Registering database adapter for plugin ${plugin.name}`);
|
|
277
|
+
this.registerDatabaseAdapter(plugin.adapter);
|
|
278
|
+
}
|
|
279
|
+
if (plugin.actions) {
|
|
280
|
+
for (const action of plugin.actions) {
|
|
281
|
+
this.registerAction(action);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
if (plugin.evaluators) {
|
|
285
|
+
for (const evaluator of plugin.evaluators) {
|
|
286
|
+
this.registerEvaluator(evaluator);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
if (plugin.providers) {
|
|
290
|
+
for (const provider of plugin.providers) {
|
|
291
|
+
this.registerProvider(provider);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
if (plugin.models) {
|
|
295
|
+
for (const [modelType, handler] of Object.entries(plugin.models)) {
|
|
296
|
+
this.registerModel(
|
|
297
|
+
modelType as ModelTypeName,
|
|
298
|
+
handler as (params: any) => Promise<any>,
|
|
299
|
+
plugin.name,
|
|
300
|
+
plugin?.priority
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
if (plugin.routes) {
|
|
305
|
+
for (const route of plugin.routes) {
|
|
306
|
+
this.routes.push(route);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
if (plugin.events) {
|
|
310
|
+
for (const [eventName, eventHandlers] of Object.entries(plugin.events)) {
|
|
311
|
+
for (const eventHandler of eventHandlers) {
|
|
312
|
+
this.registerEvent(eventName, eventHandler);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
if (plugin.services) {
|
|
317
|
+
for (const service of plugin.services) {
|
|
318
|
+
// ensure we have a promise, so when it's actually loaded via registerService,
|
|
319
|
+
// we can trigger the loading of service dependencies
|
|
320
|
+
if (!this.servicePromises.has(service.serviceType)) {
|
|
321
|
+
this._createServiceResolver(service.serviceType as ServiceTypeName);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (this.isInitialized) {
|
|
325
|
+
await this.registerService(service);
|
|
326
|
+
} else {
|
|
327
|
+
this.servicesInitQueue.add(service);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
getAllServices(): Map<ServiceTypeName, Service[]> {
|
|
334
|
+
return this.services;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
async stop() {
|
|
338
|
+
this.logger.debug(`runtime::stop - character ${this.character.name}`);
|
|
339
|
+
for (const [serviceName, services] of this.services) {
|
|
340
|
+
this.logger.debug(`runtime::stop - requesting service stop for ${serviceName}`);
|
|
341
|
+
for (const service of services) {
|
|
342
|
+
await service.stop();
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
async initialize(): Promise<void> {
|
|
348
|
+
if (this.isInitialized) {
|
|
349
|
+
this.logger.warn('Agent already initialized');
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
const pluginRegistrationPromises: Promise<void>[] = [];
|
|
353
|
+
|
|
354
|
+
// The resolution is now expected to happen in the CLI layer (e.g., startAgent)
|
|
355
|
+
// The runtime now accepts a pre-resolved, ordered list of plugins.
|
|
356
|
+
const pluginsToLoad = this.characterPlugins;
|
|
357
|
+
|
|
358
|
+
for (const plugin of pluginsToLoad) {
|
|
359
|
+
if (plugin) {
|
|
360
|
+
pluginRegistrationPromises.push(this.registerPlugin(plugin));
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
await Promise.all(pluginRegistrationPromises);
|
|
364
|
+
|
|
365
|
+
if (!this.adapter) {
|
|
366
|
+
this.logger.error(
|
|
367
|
+
'Database adapter not initialized. Make sure @elizaos/plugin-sql is included in your plugins.'
|
|
368
|
+
);
|
|
369
|
+
throw new Error(
|
|
370
|
+
'Database adapter not initialized. The SQL plugin (@elizaos/plugin-sql) is required for agent initialization. Please ensure it is included in your character configuration.'
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
try {
|
|
374
|
+
await this.adapter.init();
|
|
375
|
+
|
|
376
|
+
// Run migrations for all loaded plugins
|
|
377
|
+
this.logger.info('Running plugin migrations...');
|
|
378
|
+
await this.runPluginMigrations();
|
|
379
|
+
this.logger.info('Plugin migrations completed.');
|
|
380
|
+
|
|
381
|
+
const existingAgent = await this.ensureAgentExists(this.character as Partial<Agent>);
|
|
382
|
+
if (!existingAgent) {
|
|
383
|
+
const errorMsg = `Agent ${this.character.name} does not exist in database after ensureAgentExists call`;
|
|
384
|
+
throw new Error(errorMsg);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// No need to transform agent's own ID
|
|
388
|
+
let agentEntity = await this.getEntityById(this.agentId);
|
|
389
|
+
|
|
390
|
+
if (!agentEntity) {
|
|
391
|
+
const created = await this.createEntity({
|
|
392
|
+
id: this.agentId,
|
|
393
|
+
names: [this.character.name],
|
|
394
|
+
metadata: {},
|
|
395
|
+
agentId: existingAgent.id!,
|
|
396
|
+
});
|
|
397
|
+
if (!created) {
|
|
398
|
+
const errorMsg = `Failed to create entity for agent ${this.agentId}`;
|
|
399
|
+
throw new Error(errorMsg);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
agentEntity = await this.getEntityById(this.agentId);
|
|
403
|
+
if (!agentEntity) throw new Error(`Agent entity not found for ${this.agentId}`);
|
|
404
|
+
|
|
405
|
+
this.logger.debug(`Success: Agent entity created successfully for ${this.character.name}`);
|
|
406
|
+
}
|
|
407
|
+
} catch (error: any) {
|
|
408
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
409
|
+
this.logger.error(`Failed to create agent entity: ${errorMsg}`);
|
|
410
|
+
throw error;
|
|
411
|
+
}
|
|
412
|
+
try {
|
|
413
|
+
// Room creation and participant setup
|
|
414
|
+
const room = await this.getRoom(this.agentId);
|
|
415
|
+
if (!room) {
|
|
416
|
+
await this.createRoom({
|
|
417
|
+
id: this.agentId,
|
|
418
|
+
name: this.character.name,
|
|
419
|
+
source: 'elizaos',
|
|
420
|
+
type: ChannelType.SELF,
|
|
421
|
+
channelId: this.agentId,
|
|
422
|
+
serverId: this.agentId,
|
|
423
|
+
worldId: this.agentId,
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
const participants = await this.adapter.getParticipantsForRoom(this.agentId);
|
|
427
|
+
if (!participants.includes(this.agentId)) {
|
|
428
|
+
const added = await this.addParticipant(this.agentId, this.agentId);
|
|
429
|
+
if (!added) {
|
|
430
|
+
const errorMsg = `Failed to add agent ${this.agentId} as participant to its own room`;
|
|
431
|
+
throw new Error(errorMsg);
|
|
432
|
+
}
|
|
433
|
+
this.logger.debug(`Agent ${this.character.name} linked to its own room successfully`);
|
|
434
|
+
}
|
|
435
|
+
} catch (error: any) {
|
|
436
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
437
|
+
this.logger.error(`Failed to add agent as participant: ${errorMsg}`);
|
|
438
|
+
throw error;
|
|
439
|
+
}
|
|
440
|
+
const embeddingModel = this.getModel(ModelType.TEXT_EMBEDDING);
|
|
441
|
+
if (!embeddingModel) {
|
|
442
|
+
this.logger.warn(
|
|
443
|
+
`[AgentRuntime][${this.character.name}] No TEXT_EMBEDDING model registered. Skipping embedding dimension setup.`
|
|
444
|
+
);
|
|
445
|
+
} else {
|
|
446
|
+
await this.ensureEmbeddingDimension();
|
|
447
|
+
}
|
|
448
|
+
for (const service of this.servicesInitQueue) {
|
|
449
|
+
await this.registerService(service);
|
|
450
|
+
}
|
|
451
|
+
this.isInitialized = true;
|
|
452
|
+
if (this.initResolver) {
|
|
453
|
+
this.initResolver(); // resolve initPromise
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
async runPluginMigrations(): Promise<void> {
|
|
458
|
+
const drizzle = (this.adapter as any)?.db;
|
|
459
|
+
if (!drizzle) {
|
|
460
|
+
this.logger.warn('Drizzle instance not found on adapter, skipping plugin migrations.');
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
const pluginsWithSchemas = this.plugins.filter((p) => p.schema);
|
|
465
|
+
this.logger.info(`Found ${pluginsWithSchemas.length} plugins with schemas to migrate.`);
|
|
466
|
+
|
|
467
|
+
for (const p of pluginsWithSchemas) {
|
|
468
|
+
if (p.schema) {
|
|
469
|
+
this.logger.info(`Running migrations for plugin: ${p.name}`);
|
|
470
|
+
try {
|
|
471
|
+
// You might need a more generic way to run migrations if they are not all Drizzle-based
|
|
472
|
+
// For now, assuming a function on the adapter or a utility function
|
|
473
|
+
if (this.adapter && 'runMigrations' in this.adapter) {
|
|
474
|
+
await (this.adapter as any).runMigrations(p.schema, p.name);
|
|
475
|
+
this.logger.info(`Successfully migrated plugin: ${p.name}`);
|
|
476
|
+
}
|
|
477
|
+
} catch (error) {
|
|
478
|
+
this.logger.error(
|
|
479
|
+
error instanceof Error ? error : new Error(String(error)),
|
|
480
|
+
`Failed to migrate plugin ${p.name}`
|
|
481
|
+
);
|
|
482
|
+
// Decide if you want to throw or continue
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
async getConnection(): Promise<unknown> {
|
|
489
|
+
// Updated return type
|
|
490
|
+
if (!this.adapter) {
|
|
491
|
+
throw new Error('Database adapter not registered');
|
|
492
|
+
}
|
|
493
|
+
return this.adapter.getConnection();
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
setSetting(key: string, value: string | boolean | null | any, secret = false) {
|
|
497
|
+
if (secret) {
|
|
498
|
+
if (!this.character.secrets) {
|
|
499
|
+
this.character.secrets = {};
|
|
500
|
+
}
|
|
501
|
+
this.character.secrets[key] = value;
|
|
502
|
+
} else {
|
|
503
|
+
if (!this.character.settings) {
|
|
504
|
+
this.character.settings = {};
|
|
505
|
+
}
|
|
506
|
+
this.character.settings[key] = value;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
getSetting(key: string): string | boolean | null | any {
|
|
511
|
+
const value =
|
|
512
|
+
this.character.secrets?.[key] ||
|
|
513
|
+
this.character.settings?.[key] ||
|
|
514
|
+
(typeof this.character.settings === 'object' &&
|
|
515
|
+
this.character.settings !== null &&
|
|
516
|
+
'secrets' in this.character.settings &&
|
|
517
|
+
(this.character.settings as Record<string, any>).secrets?.[key]) ||
|
|
518
|
+
this.settings[key];
|
|
519
|
+
const decryptedValue = decryptSecret(value, getSalt());
|
|
520
|
+
if (decryptedValue === 'true') return true;
|
|
521
|
+
if (decryptedValue === 'false') return false;
|
|
522
|
+
return decryptedValue || null;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
getConversationLength() {
|
|
526
|
+
return this.#conversationLength;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
registerDatabaseAdapter(adapter: IDatabaseAdapter) {
|
|
530
|
+
if (this.adapter) {
|
|
531
|
+
this.logger.warn(
|
|
532
|
+
'Database adapter already registered. Additional adapters will be ignored. This may lead to unexpected behavior.'
|
|
533
|
+
);
|
|
534
|
+
} else {
|
|
535
|
+
this.adapter = adapter;
|
|
536
|
+
this.logger.debug('Success: Database adapter registered successfully.');
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
registerProvider(provider: Provider) {
|
|
541
|
+
this.providers.push(provider);
|
|
542
|
+
this.logger.debug(`Success: Provider ${provider.name} registered successfully.`);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
registerAction(action: Action) {
|
|
546
|
+
if (this.actions.find((a) => a.name === action.name)) {
|
|
547
|
+
this.logger.warn(
|
|
548
|
+
`${this.character.name}(${this.agentId}) - Action ${action.name} already exists. Skipping registration.`
|
|
549
|
+
);
|
|
550
|
+
} else {
|
|
551
|
+
try {
|
|
552
|
+
this.actions.push(action);
|
|
553
|
+
this.logger.success(
|
|
554
|
+
`${this.character.name}(${this.agentId}) - Action ${action.name} registered successfully.`
|
|
555
|
+
);
|
|
556
|
+
} catch (e) {
|
|
557
|
+
console.error('Error registering action', e);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
registerEvaluator(evaluator: Evaluator) {
|
|
563
|
+
this.evaluators.push(evaluator);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// Helper functions for immutable action plan updates
|
|
567
|
+
private updateActionPlan<T>(plan: T, updates: Partial<T>): T {
|
|
568
|
+
return { ...plan, ...updates };
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
private updateActionStep<T, S>(
|
|
572
|
+
plan: T & { steps: S[] },
|
|
573
|
+
index: number,
|
|
574
|
+
stepUpdates: Partial<S>
|
|
575
|
+
): T & { steps: S[] } {
|
|
576
|
+
// Add bounds checking
|
|
577
|
+
if (!plan.steps || index < 0 || index >= plan.steps.length) {
|
|
578
|
+
this.logger.warn(
|
|
579
|
+
`Invalid step index: ${index} for plan with ${plan.steps?.length || 0} steps`
|
|
580
|
+
);
|
|
581
|
+
return plan;
|
|
582
|
+
}
|
|
583
|
+
return {
|
|
584
|
+
...plan,
|
|
585
|
+
steps: plan.steps.map((step: S, i: number) =>
|
|
586
|
+
i === index ? { ...step, ...stepUpdates } : step
|
|
587
|
+
),
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
async processActions(
|
|
592
|
+
message: Memory,
|
|
593
|
+
responses: Memory[],
|
|
594
|
+
state?: State,
|
|
595
|
+
callback?: HandlerCallback
|
|
596
|
+
): Promise<void> {
|
|
597
|
+
// Determine if we have multiple actions to execute
|
|
598
|
+
const allActions: string[] = [];
|
|
599
|
+
for (const response of responses) {
|
|
600
|
+
if (response.content?.actions && response.content.actions.length > 0) {
|
|
601
|
+
allActions.push(...response.content.actions);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
const hasMultipleActions = allActions.length > 1;
|
|
606
|
+
const runId = this.createRunId();
|
|
607
|
+
|
|
608
|
+
// Create action plan if multiple actions
|
|
609
|
+
let actionPlan: {
|
|
610
|
+
runId: UUID;
|
|
611
|
+
totalSteps: number;
|
|
612
|
+
currentStep: number;
|
|
613
|
+
steps: Array<{
|
|
614
|
+
action: string;
|
|
615
|
+
status: 'pending' | 'completed' | 'failed';
|
|
616
|
+
result?: ActionResult;
|
|
617
|
+
error?: string;
|
|
618
|
+
}>;
|
|
619
|
+
thought: string;
|
|
620
|
+
startTime: number;
|
|
621
|
+
} | null = null;
|
|
622
|
+
|
|
623
|
+
if (hasMultipleActions) {
|
|
624
|
+
// Extract thought from response content
|
|
625
|
+
const thought =
|
|
626
|
+
responses[0]?.content?.thought ||
|
|
627
|
+
`Executing ${allActions.length} actions: ${allActions.join(', ')}`;
|
|
628
|
+
|
|
629
|
+
actionPlan = {
|
|
630
|
+
runId,
|
|
631
|
+
totalSteps: allActions.length,
|
|
632
|
+
currentStep: 0,
|
|
633
|
+
steps: allActions.map((action) => ({
|
|
634
|
+
action,
|
|
635
|
+
status: 'pending' as const,
|
|
636
|
+
})),
|
|
637
|
+
thought,
|
|
638
|
+
startTime: Date.now(),
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
let actionIndex = 0;
|
|
643
|
+
|
|
644
|
+
for (const response of responses) {
|
|
645
|
+
if (!response.content?.actions || response.content.actions.length === 0) {
|
|
646
|
+
this.logger.warn('No action found in the response content.');
|
|
647
|
+
continue;
|
|
648
|
+
}
|
|
649
|
+
const actions = response.content.actions;
|
|
650
|
+
|
|
651
|
+
// Initialize action results array for this run
|
|
652
|
+
const actionResults: ActionResult[] = [];
|
|
653
|
+
let accumulatedState = state;
|
|
654
|
+
|
|
655
|
+
function normalizeAction(actionString: string) {
|
|
656
|
+
return actionString.toLowerCase().replace(/_/g, '');
|
|
657
|
+
}
|
|
658
|
+
this.logger.debug(`Found actions: ${this.actions.map((a) => normalizeAction(a.name))}`);
|
|
659
|
+
|
|
660
|
+
for (const responseAction of actions) {
|
|
661
|
+
// Update current step in plan immutably
|
|
662
|
+
if (actionPlan) {
|
|
663
|
+
actionPlan = this.updateActionPlan(actionPlan, { currentStep: actionIndex + 1 });
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
// Compose state with previous action results and plan
|
|
667
|
+
accumulatedState = await this.composeState(message, [
|
|
668
|
+
'RECENT_MESSAGES',
|
|
669
|
+
'ACTION_STATE', // This will include the action plan
|
|
670
|
+
]);
|
|
671
|
+
|
|
672
|
+
// Add action plan to state if it exists
|
|
673
|
+
if (actionPlan && accumulatedState.data) {
|
|
674
|
+
accumulatedState.data.actionPlan = actionPlan;
|
|
675
|
+
accumulatedState.data.actionResults = actionResults;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
this.logger.debug(`Success: Calling action: ${responseAction}`);
|
|
679
|
+
const normalizedResponseAction = normalizeAction(responseAction);
|
|
680
|
+
|
|
681
|
+
// First try exact match
|
|
682
|
+
let action = this.actions.find(
|
|
683
|
+
(a: { name: string }) => normalizeAction(a.name) === normalizedResponseAction
|
|
684
|
+
);
|
|
685
|
+
|
|
686
|
+
if (!action) {
|
|
687
|
+
// Then try fuzzy matching
|
|
688
|
+
action = this.actions.find(
|
|
689
|
+
(a: { name: string }) =>
|
|
690
|
+
normalizeAction(a.name).includes(normalizedResponseAction) ||
|
|
691
|
+
normalizedResponseAction.includes(normalizeAction(a.name))
|
|
692
|
+
);
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
if (action) {
|
|
696
|
+
this.logger.debug(`Success: Found action: ${action?.name}`);
|
|
697
|
+
} else {
|
|
698
|
+
this.logger.debug('Attempting to find action in similes.');
|
|
699
|
+
for (const _action of this.actions) {
|
|
700
|
+
// First try exact match in similes
|
|
701
|
+
const exactSimileMatch = _action.similes?.find(
|
|
702
|
+
(simile) => normalizeAction(simile) === normalizedResponseAction
|
|
703
|
+
);
|
|
704
|
+
|
|
705
|
+
if (exactSimileMatch) {
|
|
706
|
+
action = _action;
|
|
707
|
+
this.logger.debug(`Success: Action found in similes (exact match): ${action.name}`);
|
|
708
|
+
break;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
// Then try fuzzy match in similes
|
|
712
|
+
const fuzzySimileMatch = _action.similes?.find(
|
|
713
|
+
(simile) =>
|
|
714
|
+
normalizeAction(simile).includes(normalizedResponseAction) ||
|
|
715
|
+
normalizedResponseAction.includes(normalizeAction(simile))
|
|
716
|
+
);
|
|
717
|
+
|
|
718
|
+
if (fuzzySimileMatch) {
|
|
719
|
+
action = _action;
|
|
720
|
+
this.logger.debug(`Success: Action found in similes (fuzzy match): ${action.name}`);
|
|
721
|
+
break;
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
if (!action) {
|
|
726
|
+
const errorMsg = `No action found for: ${responseAction}`;
|
|
727
|
+
this.logger.error(errorMsg);
|
|
728
|
+
|
|
729
|
+
// Update plan with error immutably
|
|
730
|
+
if (actionPlan && actionPlan.steps[actionIndex]) {
|
|
731
|
+
actionPlan = this.updateActionStep(actionPlan, actionIndex, {
|
|
732
|
+
status: 'failed',
|
|
733
|
+
error: errorMsg,
|
|
734
|
+
});
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
const actionMemory: Memory = {
|
|
738
|
+
id: uuidv4() as UUID,
|
|
739
|
+
entityId: message.entityId,
|
|
740
|
+
roomId: message.roomId,
|
|
741
|
+
worldId: message.worldId,
|
|
742
|
+
content: {
|
|
743
|
+
thought: errorMsg,
|
|
744
|
+
source: 'auto',
|
|
745
|
+
type: 'action_result',
|
|
746
|
+
actionName: responseAction,
|
|
747
|
+
actionStatus: 'failed',
|
|
748
|
+
runId,
|
|
749
|
+
},
|
|
750
|
+
};
|
|
751
|
+
await this.createMemory(actionMemory, 'messages');
|
|
752
|
+
actionIndex++;
|
|
753
|
+
continue;
|
|
754
|
+
}
|
|
755
|
+
if (!action.handler) {
|
|
756
|
+
this.logger.error(`Action ${action.name} has no handler.`);
|
|
757
|
+
|
|
758
|
+
// Update plan with error immutably
|
|
759
|
+
if (actionPlan && actionPlan.steps[actionIndex]) {
|
|
760
|
+
actionPlan = this.updateActionStep(actionPlan, actionIndex, {
|
|
761
|
+
status: 'failed',
|
|
762
|
+
error: 'No handler',
|
|
763
|
+
});
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
actionIndex++;
|
|
767
|
+
continue;
|
|
768
|
+
}
|
|
769
|
+
try {
|
|
770
|
+
this.logger.debug(`Executing handler for action: ${action.name}`);
|
|
771
|
+
|
|
772
|
+
// Start tracking this action's execution
|
|
773
|
+
const actionId = uuidv4() as UUID;
|
|
774
|
+
this.currentActionContext = {
|
|
775
|
+
actionName: action.name,
|
|
776
|
+
actionId,
|
|
777
|
+
prompts: [],
|
|
778
|
+
};
|
|
779
|
+
|
|
780
|
+
// Create action context with plan information
|
|
781
|
+
const actionContext: ActionContext = {
|
|
782
|
+
previousResults: actionResults,
|
|
783
|
+
getPreviousResult: (actionName: string) => {
|
|
784
|
+
return actionResults.find((r) => r.data?.actionName === actionName);
|
|
785
|
+
},
|
|
786
|
+
};
|
|
787
|
+
|
|
788
|
+
// Add plan information to options if multiple actions
|
|
789
|
+
const options: { [key: string]: unknown } = {
|
|
790
|
+
context: actionContext,
|
|
791
|
+
};
|
|
792
|
+
|
|
793
|
+
if (actionPlan) {
|
|
794
|
+
options.actionPlan = {
|
|
795
|
+
totalSteps: actionPlan.totalSteps,
|
|
796
|
+
currentStep: actionPlan.currentStep,
|
|
797
|
+
steps: actionPlan.steps,
|
|
798
|
+
thought: actionPlan.thought,
|
|
799
|
+
};
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
// Execute action with context
|
|
803
|
+
const result = await action.handler(
|
|
804
|
+
this,
|
|
805
|
+
message,
|
|
806
|
+
accumulatedState,
|
|
807
|
+
options,
|
|
808
|
+
callback,
|
|
809
|
+
responses
|
|
810
|
+
);
|
|
811
|
+
|
|
812
|
+
// Handle backward compatibility for void, null, true, false returns
|
|
813
|
+
const isLegacyReturn =
|
|
814
|
+
result === undefined || result === null || typeof result === 'boolean';
|
|
815
|
+
|
|
816
|
+
// Only create ActionResult if we have a proper result
|
|
817
|
+
let actionResult: ActionResult | null = null;
|
|
818
|
+
|
|
819
|
+
if (!isLegacyReturn) {
|
|
820
|
+
// Ensure we have an ActionResult with required success field
|
|
821
|
+
if (
|
|
822
|
+
typeof result === 'object' &&
|
|
823
|
+
result !== null &&
|
|
824
|
+
('values' in result || 'data' in result || 'text' in result)
|
|
825
|
+
) {
|
|
826
|
+
// Ensure success field exists with default true
|
|
827
|
+
actionResult = {
|
|
828
|
+
...result,
|
|
829
|
+
success: 'success' in result ? result.success : true, // Default to true if not specified
|
|
830
|
+
} as ActionResult;
|
|
831
|
+
} else {
|
|
832
|
+
actionResult = {
|
|
833
|
+
success: true, // Default success for legacy results
|
|
834
|
+
data: {
|
|
835
|
+
actionName: action.name,
|
|
836
|
+
legacyResult: result,
|
|
837
|
+
},
|
|
838
|
+
};
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
actionResults.push(actionResult);
|
|
842
|
+
|
|
843
|
+
// Merge returned values into state
|
|
844
|
+
if (actionResult.values) {
|
|
845
|
+
accumulatedState = {
|
|
846
|
+
...accumulatedState,
|
|
847
|
+
values: { ...accumulatedState.values, ...actionResult.values },
|
|
848
|
+
data: {
|
|
849
|
+
...(accumulatedState.data || {}),
|
|
850
|
+
actionResults: [...(accumulatedState.data?.actionResults || []), actionResult],
|
|
851
|
+
actionPlan,
|
|
852
|
+
},
|
|
853
|
+
};
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
// Store in working memory (in state data) with cleanup
|
|
857
|
+
if (actionResult && accumulatedState.data) {
|
|
858
|
+
if (!accumulatedState.data.workingMemory) accumulatedState.data.workingMemory = {};
|
|
859
|
+
|
|
860
|
+
// Add new entry first, then clean up if we exceed the limit
|
|
861
|
+
const memoryKey = `action_${responseAction}_${uuidv4()}`;
|
|
862
|
+
const memoryEntry: WorkingMemoryEntry = {
|
|
863
|
+
actionName: action.name,
|
|
864
|
+
result: actionResult,
|
|
865
|
+
timestamp: Date.now(),
|
|
866
|
+
};
|
|
867
|
+
accumulatedState.data.workingMemory[memoryKey] = memoryEntry;
|
|
868
|
+
|
|
869
|
+
// Clean up old entries if we now exceed the limit
|
|
870
|
+
const entries = Object.entries(accumulatedState.data.workingMemory);
|
|
871
|
+
if (entries.length > this.maxWorkingMemoryEntries) {
|
|
872
|
+
// Sort by timestamp (newest first) and keep only the most recent entries
|
|
873
|
+
const sorted = entries.sort((a, b) => {
|
|
874
|
+
const entryA = a[1] as WorkingMemoryEntry | null;
|
|
875
|
+
const entryB = b[1] as WorkingMemoryEntry | null;
|
|
876
|
+
const timestampA = entryA?.timestamp ?? 0;
|
|
877
|
+
const timestampB = entryB?.timestamp ?? 0;
|
|
878
|
+
return timestampB - timestampA;
|
|
879
|
+
});
|
|
880
|
+
// Keep exactly maxWorkingMemoryEntries entries (including the new one we just added)
|
|
881
|
+
accumulatedState.data.workingMemory = Object.fromEntries(
|
|
882
|
+
sorted.slice(0, this.maxWorkingMemoryEntries)
|
|
883
|
+
);
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
// Update plan with success immutably
|
|
888
|
+
if (actionPlan && actionPlan.steps[actionIndex]) {
|
|
889
|
+
actionPlan = this.updateActionStep(actionPlan, actionIndex, {
|
|
890
|
+
status: 'completed',
|
|
891
|
+
result: actionResult,
|
|
892
|
+
});
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
// Store action result as memory
|
|
897
|
+
const actionMemory: Memory = {
|
|
898
|
+
id: actionId,
|
|
899
|
+
entityId: this.agentId,
|
|
900
|
+
roomId: message.roomId,
|
|
901
|
+
worldId: message.worldId,
|
|
902
|
+
content: {
|
|
903
|
+
text: actionResult?.text || `Executed action: ${action.name}`,
|
|
904
|
+
source: 'action',
|
|
905
|
+
type: 'action_result',
|
|
906
|
+
actionName: action.name,
|
|
907
|
+
actionStatus: actionResult?.success ? 'completed' : 'failed',
|
|
908
|
+
actionResult: isLegacyReturn ? { legacy: result } : actionResult,
|
|
909
|
+
runId,
|
|
910
|
+
...(actionPlan && {
|
|
911
|
+
planStep: `${actionPlan.currentStep}/${actionPlan.totalSteps}`,
|
|
912
|
+
planThought: actionPlan.thought,
|
|
913
|
+
}),
|
|
914
|
+
},
|
|
915
|
+
metadata: {
|
|
916
|
+
type: 'action_result',
|
|
917
|
+
actionName: action.name,
|
|
918
|
+
runId,
|
|
919
|
+
actionId,
|
|
920
|
+
...(actionPlan && {
|
|
921
|
+
totalSteps: actionPlan.totalSteps,
|
|
922
|
+
currentStep: actionPlan.currentStep,
|
|
923
|
+
}),
|
|
924
|
+
},
|
|
925
|
+
};
|
|
926
|
+
await this.createMemory(actionMemory, 'messages');
|
|
927
|
+
|
|
928
|
+
this.logger.debug(
|
|
929
|
+
`Action ${action.name} completed`,
|
|
930
|
+
JSON.stringify({
|
|
931
|
+
isLegacyReturn,
|
|
932
|
+
result: isLegacyReturn ? result : undefined,
|
|
933
|
+
hasValues: actionResult ? !!actionResult.values : false,
|
|
934
|
+
hasData: actionResult ? !!actionResult.data : false,
|
|
935
|
+
hasText: actionResult ? !!actionResult.text : false,
|
|
936
|
+
})
|
|
937
|
+
);
|
|
938
|
+
|
|
939
|
+
// log to database with collected prompts
|
|
940
|
+
await this.adapter.log({
|
|
941
|
+
entityId: message.entityId,
|
|
942
|
+
roomId: message.roomId,
|
|
943
|
+
type: 'action',
|
|
944
|
+
body: {
|
|
945
|
+
action: action.name,
|
|
946
|
+
actionId,
|
|
947
|
+
message: message.content.text,
|
|
948
|
+
messageId: message.id,
|
|
949
|
+
state: accumulatedState,
|
|
950
|
+
responses,
|
|
951
|
+
result: isLegacyReturn ? { legacy: result } : actionResult,
|
|
952
|
+
isLegacyReturn,
|
|
953
|
+
prompts: this.currentActionContext?.prompts || [],
|
|
954
|
+
promptCount: this.currentActionContext?.prompts.length || 0,
|
|
955
|
+
runId,
|
|
956
|
+
...(actionPlan && {
|
|
957
|
+
planStep: `${actionPlan.currentStep}/${actionPlan.totalSteps}`,
|
|
958
|
+
planThought: actionPlan.thought,
|
|
959
|
+
}),
|
|
960
|
+
},
|
|
961
|
+
});
|
|
962
|
+
|
|
963
|
+
// Clear action context
|
|
964
|
+
this.currentActionContext = undefined;
|
|
965
|
+
} catch (error: any) {
|
|
966
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
967
|
+
this.logger.error(error);
|
|
968
|
+
|
|
969
|
+
// Update plan with error using immutable pattern
|
|
970
|
+
if (actionPlan && actionPlan.steps[actionIndex]) {
|
|
971
|
+
actionPlan = this.updateActionStep(actionPlan, actionIndex, {
|
|
972
|
+
status: 'failed',
|
|
973
|
+
error: errorMessage,
|
|
974
|
+
});
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
// Clear action context on error
|
|
978
|
+
this.currentActionContext = undefined;
|
|
979
|
+
|
|
980
|
+
// Create error result
|
|
981
|
+
const errorResult: ActionResult = {
|
|
982
|
+
success: false, // Required field
|
|
983
|
+
data: {
|
|
984
|
+
actionName: action.name,
|
|
985
|
+
error: errorMessage,
|
|
986
|
+
errorObject: error,
|
|
987
|
+
},
|
|
988
|
+
};
|
|
989
|
+
actionResults.push(errorResult);
|
|
990
|
+
|
|
991
|
+
const actionMemory: Memory = {
|
|
992
|
+
id: uuidv4() as UUID,
|
|
993
|
+
content: {
|
|
994
|
+
thought: errorMessage,
|
|
995
|
+
source: 'auto',
|
|
996
|
+
type: 'action_result',
|
|
997
|
+
actionName: action.name,
|
|
998
|
+
actionStatus: 'failed',
|
|
999
|
+
error: errorMessage,
|
|
1000
|
+
runId,
|
|
1001
|
+
...(actionPlan && {
|
|
1002
|
+
planStep: `${actionPlan.currentStep}/${actionPlan.totalSteps}`,
|
|
1003
|
+
planThought: actionPlan.thought,
|
|
1004
|
+
}),
|
|
1005
|
+
},
|
|
1006
|
+
entityId: this.agentId,
|
|
1007
|
+
roomId: message.roomId,
|
|
1008
|
+
worldId: message.worldId,
|
|
1009
|
+
metadata: {
|
|
1010
|
+
type: 'action_result',
|
|
1011
|
+
actionName: action.name,
|
|
1012
|
+
runId,
|
|
1013
|
+
error: true,
|
|
1014
|
+
...(actionPlan && {
|
|
1015
|
+
totalSteps: actionPlan.totalSteps,
|
|
1016
|
+
currentStep: actionPlan.currentStep,
|
|
1017
|
+
}),
|
|
1018
|
+
},
|
|
1019
|
+
};
|
|
1020
|
+
await this.createMemory(actionMemory, 'messages');
|
|
1021
|
+
|
|
1022
|
+
// Decide whether to continue or abort
|
|
1023
|
+
// For now, only abort on critical errors
|
|
1024
|
+
if (error?.critical || error?.code === 'CRITICAL_ERROR') {
|
|
1025
|
+
throw error;
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
actionIndex++;
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
// Store accumulated results for evaluators and providers
|
|
1033
|
+
if (message.id) {
|
|
1034
|
+
this.stateCache.set(`${message.id}_action_results`, {
|
|
1035
|
+
values: { actionResults },
|
|
1036
|
+
data: { actionResults, actionPlan },
|
|
1037
|
+
text: JSON.stringify(actionResults),
|
|
1038
|
+
});
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
async evaluate(
|
|
1044
|
+
message: Memory,
|
|
1045
|
+
state: State,
|
|
1046
|
+
didRespond?: boolean,
|
|
1047
|
+
callback?: HandlerCallback,
|
|
1048
|
+
responses?: Memory[]
|
|
1049
|
+
) {
|
|
1050
|
+
const evaluatorPromises = this.evaluators.map(async (evaluator: Evaluator) => {
|
|
1051
|
+
if (!evaluator.handler) {
|
|
1052
|
+
return null;
|
|
1053
|
+
}
|
|
1054
|
+
if (!didRespond && !evaluator.alwaysRun) {
|
|
1055
|
+
return null;
|
|
1056
|
+
}
|
|
1057
|
+
const result = await evaluator.validate(this, message, state);
|
|
1058
|
+
if (result) {
|
|
1059
|
+
return evaluator;
|
|
1060
|
+
}
|
|
1061
|
+
return null;
|
|
1062
|
+
});
|
|
1063
|
+
const evaluators = (await Promise.all(evaluatorPromises)).filter(Boolean) as Evaluator[];
|
|
1064
|
+
if (evaluators.length === 0) {
|
|
1065
|
+
return [];
|
|
1066
|
+
}
|
|
1067
|
+
state = await this.composeState(message, ['RECENT_MESSAGES', 'EVALUATORS']);
|
|
1068
|
+
await Promise.all(
|
|
1069
|
+
evaluators.map(async (evaluator) => {
|
|
1070
|
+
if (evaluator.handler) {
|
|
1071
|
+
await evaluator.handler(this, message, state, {}, callback, responses);
|
|
1072
|
+
this.adapter.log({
|
|
1073
|
+
entityId: message.entityId,
|
|
1074
|
+
roomId: message.roomId,
|
|
1075
|
+
type: 'evaluator',
|
|
1076
|
+
body: {
|
|
1077
|
+
evaluator: evaluator.name,
|
|
1078
|
+
messageId: message.id,
|
|
1079
|
+
message: message.content.text,
|
|
1080
|
+
state,
|
|
1081
|
+
},
|
|
1082
|
+
});
|
|
1083
|
+
}
|
|
1084
|
+
})
|
|
1085
|
+
);
|
|
1086
|
+
return evaluators;
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
// highly SQL optimized queries
|
|
1090
|
+
async ensureConnections(
|
|
1091
|
+
entities: any[],
|
|
1092
|
+
rooms: any[],
|
|
1093
|
+
source: string,
|
|
1094
|
+
world: any
|
|
1095
|
+
): Promise<void> {
|
|
1096
|
+
// guards
|
|
1097
|
+
if (!entities) {
|
|
1098
|
+
console.trace();
|
|
1099
|
+
this.logger.error('ensureConnections - no entities');
|
|
1100
|
+
return;
|
|
1101
|
+
}
|
|
1102
|
+
if (!rooms || rooms.length === 0) {
|
|
1103
|
+
console.trace();
|
|
1104
|
+
this.logger.error('ensureConnections - no rooms');
|
|
1105
|
+
return;
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
// Create/ensure the world exists for this server
|
|
1109
|
+
await this.ensureWorldExists({ ...world, agentId: this.agentId });
|
|
1110
|
+
|
|
1111
|
+
const firstRoom = rooms[0];
|
|
1112
|
+
|
|
1113
|
+
// Helper function for chunking arrays
|
|
1114
|
+
const chunkArray = (arr: any[], size: number) =>
|
|
1115
|
+
arr.reduce((chunks: any[][], item: any, i: number) => {
|
|
1116
|
+
if (i % size === 0) chunks.push([]);
|
|
1117
|
+
chunks[chunks.length - 1].push(item);
|
|
1118
|
+
return chunks;
|
|
1119
|
+
}, []);
|
|
1120
|
+
|
|
1121
|
+
// Step 1: Create all rooms FIRST (before adding any participants)
|
|
1122
|
+
const roomIds = rooms.map((r: any) => r.id);
|
|
1123
|
+
const roomExistsCheck = await this.getRoomsByIds(roomIds);
|
|
1124
|
+
const roomsIdExists = roomExistsCheck?.map((r: any) => r.id);
|
|
1125
|
+
const roomsToCreate = roomIds.filter((id: any) => !roomsIdExists?.includes(id));
|
|
1126
|
+
|
|
1127
|
+
const rf = {
|
|
1128
|
+
worldId: world.id,
|
|
1129
|
+
serverId: world.serverId,
|
|
1130
|
+
source,
|
|
1131
|
+
agentId: this.agentId,
|
|
1132
|
+
};
|
|
1133
|
+
|
|
1134
|
+
if (roomsToCreate.length) {
|
|
1135
|
+
this.logger.debug(
|
|
1136
|
+
'runtime/ensureConnections - create',
|
|
1137
|
+
roomsToCreate.length.toLocaleString(),
|
|
1138
|
+
'rooms'
|
|
1139
|
+
);
|
|
1140
|
+
const roomObjsToCreate = rooms
|
|
1141
|
+
.filter((r: any) => roomsToCreate.includes(r.id))
|
|
1142
|
+
.map((r: any) => ({ ...r, ...rf }));
|
|
1143
|
+
await this.createRooms(roomObjsToCreate);
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
// Step 2: Create all entities
|
|
1147
|
+
const entityIds = entities.map((e: any) => e.id);
|
|
1148
|
+
const entityExistsCheck = await this.adapter.getEntitiesByIds(entityIds);
|
|
1149
|
+
const entitiesToUpdate = entityExistsCheck?.map((e: any) => e.id);
|
|
1150
|
+
const entitiesToCreate = entities.filter((e: any) => !entitiesToUpdate?.includes(e.id));
|
|
1151
|
+
|
|
1152
|
+
const r = {
|
|
1153
|
+
roomId: firstRoom.id,
|
|
1154
|
+
channelId: firstRoom.channelId,
|
|
1155
|
+
type: firstRoom.type,
|
|
1156
|
+
};
|
|
1157
|
+
const wf = {
|
|
1158
|
+
worldId: world.id,
|
|
1159
|
+
serverId: world.serverId,
|
|
1160
|
+
};
|
|
1161
|
+
|
|
1162
|
+
if (entitiesToCreate.length) {
|
|
1163
|
+
this.logger.debug(
|
|
1164
|
+
'runtime/ensureConnections - creating',
|
|
1165
|
+
entitiesToCreate.length.toLocaleString(),
|
|
1166
|
+
'entities...'
|
|
1167
|
+
);
|
|
1168
|
+
const ef = {
|
|
1169
|
+
...r,
|
|
1170
|
+
...wf,
|
|
1171
|
+
source,
|
|
1172
|
+
agentId: this.agentId,
|
|
1173
|
+
};
|
|
1174
|
+
const entitiesToCreateWFields = entitiesToCreate.map((e: any) => ({ ...e, ...ef }));
|
|
1175
|
+
// pglite doesn't like over 10k records
|
|
1176
|
+
const batches = chunkArray(entitiesToCreateWFields, 5000);
|
|
1177
|
+
for (const batch of batches) {
|
|
1178
|
+
await this.createEntities(batch);
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
// Step 3: Now add all participants (rooms and entities must exist by now)
|
|
1183
|
+
// Always add the agent to the first room
|
|
1184
|
+
await this.ensureParticipantInRoom(this.agentId, firstRoom.id);
|
|
1185
|
+
|
|
1186
|
+
// Add all entities to the first room
|
|
1187
|
+
const entityIdsInFirstRoom = await this.getParticipantsForRoom(firstRoom.id);
|
|
1188
|
+
const entityIdsInFirstRoomFiltered = entityIdsInFirstRoom.filter(Boolean);
|
|
1189
|
+
const missingIdsInRoom = entityIds.filter(
|
|
1190
|
+
(id: any) => !entityIdsInFirstRoomFiltered.includes(id)
|
|
1191
|
+
);
|
|
1192
|
+
|
|
1193
|
+
if (missingIdsInRoom.length) {
|
|
1194
|
+
this.logger.debug(
|
|
1195
|
+
'runtime/ensureConnections - Missing',
|
|
1196
|
+
missingIdsInRoom.length.toLocaleString(),
|
|
1197
|
+
'connections in',
|
|
1198
|
+
firstRoom.id
|
|
1199
|
+
);
|
|
1200
|
+
// pglite handle this at over 10k records fine though
|
|
1201
|
+
const batches = chunkArray(missingIdsInRoom, 5000);
|
|
1202
|
+
for (const batch of batches) {
|
|
1203
|
+
await this.addParticipantsRoom(batch, firstRoom.id);
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
this.logger.success(`Success: Successfully connected world`);
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
async ensureConnection({
|
|
1211
|
+
entityId,
|
|
1212
|
+
roomId,
|
|
1213
|
+
worldId,
|
|
1214
|
+
worldName,
|
|
1215
|
+
userName,
|
|
1216
|
+
name,
|
|
1217
|
+
source,
|
|
1218
|
+
type,
|
|
1219
|
+
channelId,
|
|
1220
|
+
serverId,
|
|
1221
|
+
userId,
|
|
1222
|
+
metadata,
|
|
1223
|
+
}: {
|
|
1224
|
+
entityId: UUID;
|
|
1225
|
+
roomId: UUID;
|
|
1226
|
+
worldId: UUID;
|
|
1227
|
+
worldName?: string;
|
|
1228
|
+
userName?: string;
|
|
1229
|
+
name?: string;
|
|
1230
|
+
source?: string;
|
|
1231
|
+
type?: ChannelType;
|
|
1232
|
+
channelId?: string;
|
|
1233
|
+
serverId?: string;
|
|
1234
|
+
userId?: UUID;
|
|
1235
|
+
metadata?: Record<string, any>;
|
|
1236
|
+
}) {
|
|
1237
|
+
if (!worldId && serverId) {
|
|
1238
|
+
worldId = createUniqueUuid(this, serverId);
|
|
1239
|
+
}
|
|
1240
|
+
const names = [name, userName].filter(Boolean) as string[];
|
|
1241
|
+
const entityMetadata = {
|
|
1242
|
+
[source!]: {
|
|
1243
|
+
id: userId,
|
|
1244
|
+
name: name,
|
|
1245
|
+
userName: userName,
|
|
1246
|
+
},
|
|
1247
|
+
};
|
|
1248
|
+
try {
|
|
1249
|
+
// First check if the entity exists
|
|
1250
|
+
const entity = await this.getEntityById(entityId);
|
|
1251
|
+
|
|
1252
|
+
if (!entity) {
|
|
1253
|
+
try {
|
|
1254
|
+
const success = await this.createEntity({
|
|
1255
|
+
id: entityId,
|
|
1256
|
+
names,
|
|
1257
|
+
metadata: entityMetadata,
|
|
1258
|
+
agentId: this.agentId,
|
|
1259
|
+
});
|
|
1260
|
+
if (success) {
|
|
1261
|
+
this.logger.debug(
|
|
1262
|
+
`Created new entity ${entityId} for user ${name || userName || 'unknown'}`
|
|
1263
|
+
);
|
|
1264
|
+
} else {
|
|
1265
|
+
throw new Error(`Failed to create entity ${entityId}`);
|
|
1266
|
+
}
|
|
1267
|
+
} catch (error: any) {
|
|
1268
|
+
if (error.message?.includes('duplicate key') || error.code === '23505') {
|
|
1269
|
+
this.logger.debug(
|
|
1270
|
+
`Entity ${entityId} exists in database but not for this agent. This is normal in multi-agent setups.`
|
|
1271
|
+
);
|
|
1272
|
+
} else {
|
|
1273
|
+
throw error;
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
} else {
|
|
1277
|
+
await this.adapter.updateEntity({
|
|
1278
|
+
id: entityId,
|
|
1279
|
+
names: [...new Set([...(entity.names || []), ...names])].filter(Boolean) as string[],
|
|
1280
|
+
metadata: {
|
|
1281
|
+
...entity.metadata,
|
|
1282
|
+
[source!]: {
|
|
1283
|
+
...(entity.metadata?.[source!] as Record<string, any>),
|
|
1284
|
+
id: userId,
|
|
1285
|
+
name: name,
|
|
1286
|
+
userName: userName,
|
|
1287
|
+
},
|
|
1288
|
+
},
|
|
1289
|
+
agentId: this.agentId,
|
|
1290
|
+
});
|
|
1291
|
+
}
|
|
1292
|
+
await this.ensureWorldExists({
|
|
1293
|
+
id: worldId,
|
|
1294
|
+
name: worldName || serverId ? `World for server ${serverId}` : `World for room ${roomId}`,
|
|
1295
|
+
agentId: this.agentId,
|
|
1296
|
+
serverId: serverId || 'default',
|
|
1297
|
+
metadata,
|
|
1298
|
+
});
|
|
1299
|
+
await this.ensureRoomExists({
|
|
1300
|
+
id: roomId,
|
|
1301
|
+
name: name || 'default',
|
|
1302
|
+
source: source || 'default',
|
|
1303
|
+
type: type || ChannelType.DM,
|
|
1304
|
+
channelId,
|
|
1305
|
+
serverId,
|
|
1306
|
+
worldId,
|
|
1307
|
+
});
|
|
1308
|
+
try {
|
|
1309
|
+
await this.ensureParticipantInRoom(entityId, roomId);
|
|
1310
|
+
} catch (error: any) {
|
|
1311
|
+
if (error.message?.includes('not found')) {
|
|
1312
|
+
const added = await this.addParticipant(entityId, roomId);
|
|
1313
|
+
if (!added) {
|
|
1314
|
+
throw new Error(`Failed to add participant ${entityId} to room ${roomId}`);
|
|
1315
|
+
}
|
|
1316
|
+
this.logger.debug(`Added participant ${entityId} to room ${roomId} directly`);
|
|
1317
|
+
} else {
|
|
1318
|
+
throw error;
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
await this.ensureParticipantInRoom(this.agentId, roomId);
|
|
1322
|
+
|
|
1323
|
+
this.logger.debug(`Success: Successfully connected entity ${entityId} in room ${roomId}`);
|
|
1324
|
+
} catch (error) {
|
|
1325
|
+
this.logger.error(
|
|
1326
|
+
`Failed to ensure connection: ${error instanceof Error ? error.message : String(error)}`
|
|
1327
|
+
);
|
|
1328
|
+
throw error;
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
async ensureParticipantInRoom(entityId: UUID, roomId: UUID) {
|
|
1333
|
+
// Make sure entity exists in database before adding as participant
|
|
1334
|
+
const entity = await this.getEntityById(entityId);
|
|
1335
|
+
|
|
1336
|
+
// If entity is not found but it's not the agent itself, we might still want to proceed
|
|
1337
|
+
// This can happen when an entity exists in the database but isn't associated with this agent
|
|
1338
|
+
if (!entity && entityId !== this.agentId) {
|
|
1339
|
+
this.logger.warn(
|
|
1340
|
+
`Entity ${entityId} not directly accessible to agent ${this.agentId}. Will attempt to add as participant anyway.`
|
|
1341
|
+
);
|
|
1342
|
+
} else if (!entity && entityId === this.agentId) {
|
|
1343
|
+
throw new Error(`Agent entity ${entityId} not found, cannot add as participant.`);
|
|
1344
|
+
} else if (!entity) {
|
|
1345
|
+
throw new Error(`User entity ${entityId} not found, cannot add as participant.`);
|
|
1346
|
+
}
|
|
1347
|
+
const participants = await this.adapter.getParticipantsForRoom(roomId);
|
|
1348
|
+
if (!participants.includes(entityId)) {
|
|
1349
|
+
// Add participant using the ID
|
|
1350
|
+
const added = await this.addParticipant(entityId, roomId);
|
|
1351
|
+
|
|
1352
|
+
if (!added) {
|
|
1353
|
+
throw new Error(`Failed to add participant ${entityId} to room ${roomId}`);
|
|
1354
|
+
}
|
|
1355
|
+
if (entityId === this.agentId) {
|
|
1356
|
+
this.logger.debug(`Agent ${this.character.name} linked to room ${roomId} successfully.`);
|
|
1357
|
+
} else {
|
|
1358
|
+
this.logger.debug(`User ${entityId} linked to room ${roomId} successfully.`);
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
async removeParticipant(entityId: UUID, roomId: UUID): Promise<boolean> {
|
|
1364
|
+
return await this.adapter.removeParticipant(entityId, roomId);
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
async getParticipantsForEntity(entityId: UUID): Promise<Participant[]> {
|
|
1368
|
+
return await this.adapter.getParticipantsForEntity(entityId);
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
async getParticipantsForRoom(roomId: UUID): Promise<UUID[]> {
|
|
1372
|
+
return await this.adapter.getParticipantsForRoom(roomId);
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
async addParticipant(entityId: UUID, roomId: UUID): Promise<boolean> {
|
|
1376
|
+
return await this.adapter.addParticipantsRoom([entityId], roomId);
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
async addParticipantsRoom(entityIds: UUID[], roomId: UUID): Promise<boolean> {
|
|
1380
|
+
return await this.adapter.addParticipantsRoom(entityIds, roomId);
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
/**
|
|
1384
|
+
* Ensure the existence of a world.
|
|
1385
|
+
*/
|
|
1386
|
+
async ensureWorldExists({ id, name, serverId, metadata }: World) {
|
|
1387
|
+
const world = await this.getWorld(id);
|
|
1388
|
+
if (!world) {
|
|
1389
|
+
this.logger.debug(
|
|
1390
|
+
'Creating world:',
|
|
1391
|
+
JSON.stringify({
|
|
1392
|
+
id,
|
|
1393
|
+
name,
|
|
1394
|
+
serverId,
|
|
1395
|
+
agentId: this.agentId,
|
|
1396
|
+
})
|
|
1397
|
+
);
|
|
1398
|
+
await this.adapter.createWorld({
|
|
1399
|
+
id,
|
|
1400
|
+
name,
|
|
1401
|
+
agentId: this.agentId,
|
|
1402
|
+
serverId: serverId || 'default',
|
|
1403
|
+
metadata,
|
|
1404
|
+
});
|
|
1405
|
+
this.logger.debug(`World ${id} created successfully.`);
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
async ensureRoomExists({ id, name, source, type, channelId, serverId, worldId, metadata }: Room) {
|
|
1410
|
+
if (!worldId) throw new Error('worldId is required');
|
|
1411
|
+
const room = await this.getRoom(id);
|
|
1412
|
+
if (!room) {
|
|
1413
|
+
await this.createRoom({
|
|
1414
|
+
id,
|
|
1415
|
+
name,
|
|
1416
|
+
agentId: this.agentId,
|
|
1417
|
+
source,
|
|
1418
|
+
type,
|
|
1419
|
+
channelId,
|
|
1420
|
+
serverId,
|
|
1421
|
+
worldId,
|
|
1422
|
+
metadata,
|
|
1423
|
+
});
|
|
1424
|
+
this.logger.debug(`Room ${id} created successfully.`);
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
async composeState(
|
|
1429
|
+
message: Memory,
|
|
1430
|
+
includeList: string[] | null = null,
|
|
1431
|
+
onlyInclude = false,
|
|
1432
|
+
skipCache = false
|
|
1433
|
+
): Promise<State> {
|
|
1434
|
+
const filterList = onlyInclude ? includeList : null;
|
|
1435
|
+
const emptyObj = {
|
|
1436
|
+
values: {},
|
|
1437
|
+
data: {},
|
|
1438
|
+
text: '',
|
|
1439
|
+
} as State;
|
|
1440
|
+
const cachedState =
|
|
1441
|
+
skipCache || !message.id ? emptyObj : (await this.stateCache.get(message.id)) || emptyObj;
|
|
1442
|
+
const providerNames = new Set<string>();
|
|
1443
|
+
if (filterList && filterList.length > 0) {
|
|
1444
|
+
filterList.forEach((name) => providerNames.add(name));
|
|
1445
|
+
} else {
|
|
1446
|
+
this.providers
|
|
1447
|
+
.filter((p) => !p.private && !p.dynamic)
|
|
1448
|
+
.forEach((p) => providerNames.add(p.name));
|
|
1449
|
+
}
|
|
1450
|
+
if (!filterList && includeList && includeList.length > 0) {
|
|
1451
|
+
includeList.forEach((name) => providerNames.add(name));
|
|
1452
|
+
}
|
|
1453
|
+
const providersToGet = Array.from(
|
|
1454
|
+
new Set(this.providers.filter((p) => providerNames.has(p.name)))
|
|
1455
|
+
).sort((a, b) => (a.position || 0) - (b.position || 0));
|
|
1456
|
+
const providerData = await Promise.all(
|
|
1457
|
+
providersToGet.map(async (provider) => {
|
|
1458
|
+
const start = Date.now();
|
|
1459
|
+
try {
|
|
1460
|
+
const result = await provider.get(this, message, cachedState);
|
|
1461
|
+
const duration = Date.now() - start;
|
|
1462
|
+
|
|
1463
|
+
this.logger.debug(`${provider.name} Provider took ${duration}ms to respond`);
|
|
1464
|
+
return {
|
|
1465
|
+
...result,
|
|
1466
|
+
providerName: provider.name,
|
|
1467
|
+
};
|
|
1468
|
+
} catch (error: any) {
|
|
1469
|
+
console.error('provider error', provider.name, error);
|
|
1470
|
+
return { values: {}, text: '', data: {}, providerName: provider.name };
|
|
1471
|
+
}
|
|
1472
|
+
})
|
|
1473
|
+
);
|
|
1474
|
+
const currentProviderResults = { ...(cachedState.data?.providers || {}) };
|
|
1475
|
+
for (const freshResult of providerData) {
|
|
1476
|
+
currentProviderResults[freshResult.providerName] = freshResult;
|
|
1477
|
+
}
|
|
1478
|
+
const orderedTexts: string[] = [];
|
|
1479
|
+
for (const provider of providersToGet) {
|
|
1480
|
+
const result = currentProviderResults[provider.name];
|
|
1481
|
+
if (result && result.text && result.text.trim() !== '') {
|
|
1482
|
+
orderedTexts.push(result.text);
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
const providersText = orderedTexts.join('\n');
|
|
1486
|
+
const aggregatedStateValues = { ...(cachedState.values || {}) };
|
|
1487
|
+
for (const provider of providersToGet) {
|
|
1488
|
+
const providerResult = currentProviderResults[provider.name];
|
|
1489
|
+
if (providerResult && providerResult.values && typeof providerResult.values === 'object') {
|
|
1490
|
+
Object.assign(aggregatedStateValues, providerResult.values);
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
for (const providerName in currentProviderResults) {
|
|
1494
|
+
if (!providersToGet.some((p) => p.name === providerName)) {
|
|
1495
|
+
const providerResult = currentProviderResults[providerName];
|
|
1496
|
+
if (providerResult && providerResult.values && typeof providerResult.values === 'object') {
|
|
1497
|
+
Object.assign(aggregatedStateValues, providerResult.values);
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
const newState = {
|
|
1502
|
+
values: {
|
|
1503
|
+
...aggregatedStateValues,
|
|
1504
|
+
providers: providersText,
|
|
1505
|
+
},
|
|
1506
|
+
data: {
|
|
1507
|
+
...(cachedState.data || {}),
|
|
1508
|
+
providers: currentProviderResults,
|
|
1509
|
+
},
|
|
1510
|
+
text: providersText,
|
|
1511
|
+
} as State;
|
|
1512
|
+
if (message.id) {
|
|
1513
|
+
this.stateCache.set(message.id, newState);
|
|
1514
|
+
}
|
|
1515
|
+
return newState;
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
getService<T extends Service = Service>(serviceName: ServiceTypeName | string): T | null {
|
|
1519
|
+
const serviceInstances = this.services.get(serviceName as ServiceTypeName);
|
|
1520
|
+
if (!serviceInstances || serviceInstances.length === 0) {
|
|
1521
|
+
// it's not a warn, a plugin might just not be installed
|
|
1522
|
+
this.logger.debug(`Service ${serviceName} not found`);
|
|
1523
|
+
return null;
|
|
1524
|
+
}
|
|
1525
|
+
return serviceInstances[0] as T;
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
/**
|
|
1529
|
+
* Type-safe service getter that ensures the correct service type is returned
|
|
1530
|
+
* @template T - The expected service class type
|
|
1531
|
+
* @param serviceName - The service type name
|
|
1532
|
+
* @returns The service instance with proper typing, or null if not found
|
|
1533
|
+
*/
|
|
1534
|
+
getTypedService<T extends Service = Service>(serviceName: ServiceTypeName | string): T | null {
|
|
1535
|
+
return this.getService<T>(serviceName);
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
/**
|
|
1539
|
+
* Get all services of a specific type
|
|
1540
|
+
* @template T - The expected service class type
|
|
1541
|
+
* @param serviceName - The service type name
|
|
1542
|
+
* @returns Array of service instances with proper typing
|
|
1543
|
+
*/
|
|
1544
|
+
getServicesByType<T extends Service = Service>(serviceName: ServiceTypeName | string): T[] {
|
|
1545
|
+
const serviceInstances = this.services.get(serviceName as ServiceTypeName);
|
|
1546
|
+
if (!serviceInstances || serviceInstances.length === 0) {
|
|
1547
|
+
this.logger.debug(`No services found for type ${serviceName}`);
|
|
1548
|
+
return [];
|
|
1549
|
+
}
|
|
1550
|
+
return serviceInstances as T[];
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
/**
|
|
1554
|
+
* Get all registered service types
|
|
1555
|
+
* @returns Array of registered service type names
|
|
1556
|
+
*/
|
|
1557
|
+
getRegisteredServiceTypes(): ServiceTypeName[] {
|
|
1558
|
+
return Array.from(this.services.keys());
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1561
|
+
/**
|
|
1562
|
+
* Check if a service type is registered
|
|
1563
|
+
* @param serviceType - The service type to check
|
|
1564
|
+
* @returns true if the service is registered
|
|
1565
|
+
*/
|
|
1566
|
+
hasService(serviceType: ServiceTypeName | string): boolean {
|
|
1567
|
+
const serviceInstances = this.services.get(serviceType as ServiceTypeName);
|
|
1568
|
+
return serviceInstances !== undefined && serviceInstances.length > 0;
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
async registerService(serviceDef: typeof Service): Promise<void> {
|
|
1572
|
+
const serviceType = serviceDef.serviceType as ServiceTypeName;
|
|
1573
|
+
if (!serviceType) {
|
|
1574
|
+
this.logger.warn(
|
|
1575
|
+
`Service ${serviceDef.name} is missing serviceType. Please define a static serviceType property.`
|
|
1576
|
+
);
|
|
1577
|
+
return;
|
|
1578
|
+
}
|
|
1579
|
+
this.logger.debug(
|
|
1580
|
+
`${this.character.name}(${this.agentId}) - Registering service:`,
|
|
1581
|
+
serviceType
|
|
1582
|
+
);
|
|
1583
|
+
|
|
1584
|
+
try {
|
|
1585
|
+
const serviceInstance = await serviceDef.start(this);
|
|
1586
|
+
|
|
1587
|
+
// Initialize arrays if they don't exist
|
|
1588
|
+
if (!this.services.has(serviceType)) {
|
|
1589
|
+
this.services.set(serviceType, []);
|
|
1590
|
+
}
|
|
1591
|
+
if (!this.serviceTypes.has(serviceType)) {
|
|
1592
|
+
this.serviceTypes.set(serviceType, []);
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
// Add the service to the arrays
|
|
1596
|
+
this.services.get(serviceType)!.push(serviceInstance);
|
|
1597
|
+
this.serviceTypes.get(serviceType)!.push(serviceDef);
|
|
1598
|
+
|
|
1599
|
+
// inform everyone that's waiting for this service, that it's now available
|
|
1600
|
+
// removes the need for polling and timers
|
|
1601
|
+
const resolve = this.servicePromiseHandles.get(serviceType);
|
|
1602
|
+
if (resolve) {
|
|
1603
|
+
resolve(serviceInstance);
|
|
1604
|
+
} else {
|
|
1605
|
+
this.logger.debug(
|
|
1606
|
+
`${this.character.name} - Service ${serviceType} has no servicePromiseHandle`
|
|
1607
|
+
);
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
if (typeof (serviceDef as any).registerSendHandlers === 'function') {
|
|
1611
|
+
(serviceDef as any).registerSendHandlers(this, serviceInstance);
|
|
1612
|
+
}
|
|
1613
|
+
this.logger.debug(
|
|
1614
|
+
`${this.character.name}(${this.agentId}) - Service ${serviceType} registered successfully`
|
|
1615
|
+
);
|
|
1616
|
+
} catch (error: any) {
|
|
1617
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1618
|
+
this.logger.error(
|
|
1619
|
+
`${this.character.name}(${this.agentId}) - Failed to register service ${serviceType}: ${errorMessage}`
|
|
1620
|
+
);
|
|
1621
|
+
throw error;
|
|
1622
|
+
}
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
/// ensures servicePromises & servicePromiseHandles for a serviceType
|
|
1626
|
+
private _createServiceResolver(serviceType: ServiceTypeName) {
|
|
1627
|
+
// consider this in the future iterations
|
|
1628
|
+
// const { promise, resolve, reject } = Promise.withResolvers<T>();
|
|
1629
|
+
let resolver: ServiceResolver | undefined;
|
|
1630
|
+
this.servicePromises.set(
|
|
1631
|
+
serviceType,
|
|
1632
|
+
new Promise<Service>((resolve) => {
|
|
1633
|
+
resolver = resolve;
|
|
1634
|
+
})
|
|
1635
|
+
);
|
|
1636
|
+
if (!resolver) {
|
|
1637
|
+
throw new Error(`Failed to create resolver for service ${serviceType}`);
|
|
1638
|
+
}
|
|
1639
|
+
this.servicePromiseHandles.set(serviceType, resolver);
|
|
1640
|
+
return this.servicePromises.get(serviceType)!;
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
/// returns a promise that's resolved once this service is loaded
|
|
1644
|
+
getServiceLoadPromise(serviceType: ServiceTypeName): Promise<Service> {
|
|
1645
|
+
// if this.isInitialized then the this p will exist and already be resolved
|
|
1646
|
+
let p = this.servicePromises.get(serviceType);
|
|
1647
|
+
if (!p) {
|
|
1648
|
+
// not initialized or registered yet, registerPlugin is already smart enough to check to see if we make it here
|
|
1649
|
+
p = this._createServiceResolver(serviceType);
|
|
1650
|
+
}
|
|
1651
|
+
return p;
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
registerModel(
|
|
1655
|
+
modelType: ModelTypeName,
|
|
1656
|
+
handler: (params: any) => Promise<any>,
|
|
1657
|
+
provider: string,
|
|
1658
|
+
priority?: number
|
|
1659
|
+
) {
|
|
1660
|
+
const modelKey = typeof modelType === 'string' ? modelType : ModelType[modelType];
|
|
1661
|
+
if (!this.models.has(modelKey)) {
|
|
1662
|
+
this.models.set(modelKey, []);
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
const registrationOrder = Date.now();
|
|
1666
|
+
this.models.get(modelKey)?.push({
|
|
1667
|
+
handler,
|
|
1668
|
+
provider,
|
|
1669
|
+
priority: priority || 0,
|
|
1670
|
+
registrationOrder,
|
|
1671
|
+
});
|
|
1672
|
+
this.models.get(modelKey)?.sort((a, b) => {
|
|
1673
|
+
if ((b.priority || 0) !== (a.priority || 0)) {
|
|
1674
|
+
return (b.priority || 0) - (a.priority || 0);
|
|
1675
|
+
}
|
|
1676
|
+
return (a.registrationOrder || 0) - (b.registrationOrder || 0);
|
|
1677
|
+
});
|
|
1678
|
+
}
|
|
1679
|
+
|
|
1680
|
+
getModel(
|
|
1681
|
+
modelType: ModelTypeName,
|
|
1682
|
+
provider?: string
|
|
1683
|
+
): ((runtime: IAgentRuntime, params: any) => Promise<any>) | undefined {
|
|
1684
|
+
const modelKey = typeof modelType === 'string' ? modelType : ModelType[modelType];
|
|
1685
|
+
const models = this.models.get(modelKey);
|
|
1686
|
+
if (!models?.length) {
|
|
1687
|
+
return undefined;
|
|
1688
|
+
}
|
|
1689
|
+
if (provider) {
|
|
1690
|
+
const modelWithProvider = models.find((m) => m.provider === provider);
|
|
1691
|
+
if (modelWithProvider) {
|
|
1692
|
+
this.logger.debug(
|
|
1693
|
+
`[AgentRuntime][${this.character.name}] Using model ${modelKey} from provider ${provider}`
|
|
1694
|
+
);
|
|
1695
|
+
return modelWithProvider.handler;
|
|
1696
|
+
} else {
|
|
1697
|
+
this.logger.warn(
|
|
1698
|
+
`[AgentRuntime][${this.character.name}] No model found for provider ${provider}`
|
|
1699
|
+
);
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1703
|
+
// Return highest priority handler (first in array after sorting)
|
|
1704
|
+
this.logger.debug(
|
|
1705
|
+
`[AgentRuntime][${this.character.name}] Using model ${modelKey} from provider ${models[0].provider}`
|
|
1706
|
+
);
|
|
1707
|
+
return models[0].handler;
|
|
1708
|
+
}
|
|
1709
|
+
|
|
1710
|
+
/**
|
|
1711
|
+
* Retrieves model configuration settings from character settings with support for
|
|
1712
|
+
* model-specific overrides and default fallbacks.
|
|
1713
|
+
*
|
|
1714
|
+
* Precedence order (highest to lowest):
|
|
1715
|
+
* 1. Model-specific settings (e.g., TEXT_SMALL_TEMPERATURE)
|
|
1716
|
+
* 2. Default settings (e.g., DEFAULT_TEMPERATURE)
|
|
1717
|
+
* 3. Legacy settings for backwards compatibility (e.g., MODEL_TEMPERATURE)
|
|
1718
|
+
*
|
|
1719
|
+
* @param modelType The specific model type to get settings for
|
|
1720
|
+
* @returns Object containing model parameters if they exist, or null if no settings are configured
|
|
1721
|
+
*/
|
|
1722
|
+
private getModelSettings(modelType?: ModelTypeName): Record<string, number> | null {
|
|
1723
|
+
const modelSettings: Record<string, number> = {};
|
|
1724
|
+
|
|
1725
|
+
// Helper to get a setting value with fallback chain
|
|
1726
|
+
const getSettingWithFallback = (
|
|
1727
|
+
param: 'MAX_TOKENS' | 'TEMPERATURE' | 'FREQUENCY_PENALTY' | 'PRESENCE_PENALTY',
|
|
1728
|
+
legacyKey: string
|
|
1729
|
+
): number | null => {
|
|
1730
|
+
// Try model-specific setting first
|
|
1731
|
+
if (modelType) {
|
|
1732
|
+
const modelSpecificKey = `${modelType}_${param}`;
|
|
1733
|
+
const modelValue = this.getSetting(modelSpecificKey);
|
|
1734
|
+
if (modelValue !== null && modelValue !== undefined) {
|
|
1735
|
+
const numValue = Number(modelValue);
|
|
1736
|
+
if (!isNaN(numValue)) {
|
|
1737
|
+
return numValue;
|
|
1738
|
+
}
|
|
1739
|
+
// If model-specific value exists but is invalid, continue to fallbacks
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
// Fall back to default setting
|
|
1744
|
+
const defaultKey = `DEFAULT_${param}`;
|
|
1745
|
+
const defaultValue = this.getSetting(defaultKey);
|
|
1746
|
+
if (defaultValue !== null && defaultValue !== undefined) {
|
|
1747
|
+
const numValue = Number(defaultValue);
|
|
1748
|
+
if (!isNaN(numValue)) {
|
|
1749
|
+
return numValue;
|
|
1750
|
+
}
|
|
1751
|
+
// If default value exists but is invalid, continue to legacy
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1754
|
+
// Fall back to legacy setting for backwards compatibility
|
|
1755
|
+
const legacyValue = this.getSetting(legacyKey);
|
|
1756
|
+
if (legacyValue !== null && legacyValue !== undefined) {
|
|
1757
|
+
const numValue = Number(legacyValue);
|
|
1758
|
+
if (!isNaN(numValue)) {
|
|
1759
|
+
return numValue;
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1763
|
+
return null;
|
|
1764
|
+
};
|
|
1765
|
+
|
|
1766
|
+
// Get settings with proper fallback chain
|
|
1767
|
+
const maxTokens = getSettingWithFallback('MAX_TOKENS', MODEL_SETTINGS.MODEL_MAX_TOKEN);
|
|
1768
|
+
const temperature = getSettingWithFallback('TEMPERATURE', MODEL_SETTINGS.MODEL_TEMPERATURE);
|
|
1769
|
+
const frequencyPenalty = getSettingWithFallback(
|
|
1770
|
+
'FREQUENCY_PENALTY',
|
|
1771
|
+
MODEL_SETTINGS.MODEL_FREQ_PENALTY
|
|
1772
|
+
);
|
|
1773
|
+
const presencePenalty = getSettingWithFallback(
|
|
1774
|
+
'PRESENCE_PENALTY',
|
|
1775
|
+
MODEL_SETTINGS.MODEL_PRESENCE_PENALTY
|
|
1776
|
+
);
|
|
1777
|
+
|
|
1778
|
+
// Add settings if they exist
|
|
1779
|
+
if (maxTokens !== null) modelSettings.maxTokens = maxTokens;
|
|
1780
|
+
if (temperature !== null) modelSettings.temperature = temperature;
|
|
1781
|
+
if (frequencyPenalty !== null) modelSettings.frequencyPenalty = frequencyPenalty;
|
|
1782
|
+
if (presencePenalty !== null) modelSettings.presencePenalty = presencePenalty;
|
|
1783
|
+
|
|
1784
|
+
// Return null if no settings were configured
|
|
1785
|
+
return Object.keys(modelSettings).length > 0 ? modelSettings : null;
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1788
|
+
async useModel<T extends ModelTypeName, R = ModelResultMap[T]>(
|
|
1789
|
+
modelType: T,
|
|
1790
|
+
params: Omit<ModelParamsMap[T], 'runtime'> | any,
|
|
1791
|
+
provider?: string
|
|
1792
|
+
): Promise<R> {
|
|
1793
|
+
const modelKey = typeof modelType === 'string' ? modelType : ModelType[modelType];
|
|
1794
|
+
const promptContent =
|
|
1795
|
+
params?.prompt ||
|
|
1796
|
+
params?.input ||
|
|
1797
|
+
(Array.isArray(params?.messages) ? JSON.stringify(params.messages) : null);
|
|
1798
|
+
const model = this.getModel(modelKey, provider);
|
|
1799
|
+
if (!model) {
|
|
1800
|
+
const errorMsg = `No handler found for delegate type: ${modelKey}`;
|
|
1801
|
+
throw new Error(errorMsg);
|
|
1802
|
+
}
|
|
1803
|
+
|
|
1804
|
+
// Log input parameters (keep debug log if useful)
|
|
1805
|
+
this.logger.debug(
|
|
1806
|
+
`[useModel] ${modelKey} input: ` +
|
|
1807
|
+
JSON.stringify(params, safeReplacer(), 2).replace(/\\n/g, '\n')
|
|
1808
|
+
);
|
|
1809
|
+
let paramsWithRuntime: any;
|
|
1810
|
+
if (
|
|
1811
|
+
params === null ||
|
|
1812
|
+
params === undefined ||
|
|
1813
|
+
typeof params !== 'object' ||
|
|
1814
|
+
Array.isArray(params) ||
|
|
1815
|
+
BufferUtils.isBuffer(params)
|
|
1816
|
+
) {
|
|
1817
|
+
paramsWithRuntime = params;
|
|
1818
|
+
} else {
|
|
1819
|
+
// Include model settings from character configuration if available
|
|
1820
|
+
const modelSettings = this.getModelSettings(modelKey);
|
|
1821
|
+
|
|
1822
|
+
if (modelSettings) {
|
|
1823
|
+
// Apply model settings if configured
|
|
1824
|
+
paramsWithRuntime = {
|
|
1825
|
+
...modelSettings, // Apply model settings first (includes defaults and model-specific)
|
|
1826
|
+
...params, // Then apply specific params (allowing overrides)
|
|
1827
|
+
runtime: this,
|
|
1828
|
+
};
|
|
1829
|
+
} else {
|
|
1830
|
+
// No model settings configured, use params as-is
|
|
1831
|
+
paramsWithRuntime = {
|
|
1832
|
+
...params,
|
|
1833
|
+
runtime: this,
|
|
1834
|
+
};
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1837
|
+
const startTime =
|
|
1838
|
+
typeof performance !== 'undefined' && typeof performance.now === 'function'
|
|
1839
|
+
? performance.now()
|
|
1840
|
+
: Date.now();
|
|
1841
|
+
try {
|
|
1842
|
+
const response = await model(this, paramsWithRuntime);
|
|
1843
|
+
const elapsedTime =
|
|
1844
|
+
(typeof performance !== 'undefined' && typeof performance.now === 'function'
|
|
1845
|
+
? performance.now()
|
|
1846
|
+
: Date.now()) - startTime;
|
|
1847
|
+
|
|
1848
|
+
// Log timing / response (keep debug log if useful)
|
|
1849
|
+
this.logger.debug(
|
|
1850
|
+
`[useModel] ${modelKey} output (took ${Number(elapsedTime.toFixed(2)).toLocaleString()}ms):`,
|
|
1851
|
+
Array.isArray(response)
|
|
1852
|
+
? `${JSON.stringify(response.slice(0, 5))}...${JSON.stringify(response.slice(-5))} (${
|
|
1853
|
+
response.length
|
|
1854
|
+
} items)`
|
|
1855
|
+
: JSON.stringify(response, safeReplacer(), 2).replace(/\\n/g, '\n')
|
|
1856
|
+
);
|
|
1857
|
+
|
|
1858
|
+
// Log all prompts except TEXT_EMBEDDING to track agent behavior
|
|
1859
|
+
if (modelKey !== ModelType.TEXT_EMBEDDING && promptContent) {
|
|
1860
|
+
// If we're in an action context, collect the prompt
|
|
1861
|
+
if (this.currentActionContext) {
|
|
1862
|
+
this.currentActionContext.prompts.push({
|
|
1863
|
+
modelType: modelKey,
|
|
1864
|
+
prompt: promptContent,
|
|
1865
|
+
timestamp: Date.now(),
|
|
1866
|
+
});
|
|
1867
|
+
}
|
|
1868
|
+
}
|
|
1869
|
+
|
|
1870
|
+
// Keep the existing model logging for backward compatibility
|
|
1871
|
+
this.adapter.log({
|
|
1872
|
+
entityId: this.agentId,
|
|
1873
|
+
roomId: this.agentId,
|
|
1874
|
+
body: {
|
|
1875
|
+
modelType,
|
|
1876
|
+
modelKey,
|
|
1877
|
+
params: {
|
|
1878
|
+
...(typeof params === 'object' && !Array.isArray(params) && params ? params : {}),
|
|
1879
|
+
prompt: promptContent,
|
|
1880
|
+
},
|
|
1881
|
+
prompt: promptContent,
|
|
1882
|
+
runId: this.getCurrentRunId(),
|
|
1883
|
+
timestamp: Date.now(),
|
|
1884
|
+
executionTime: elapsedTime,
|
|
1885
|
+
provider: provider || this.models.get(modelKey)?.[0]?.provider || 'unknown',
|
|
1886
|
+
actionContext: this.currentActionContext
|
|
1887
|
+
? {
|
|
1888
|
+
actionName: this.currentActionContext.actionName,
|
|
1889
|
+
actionId: this.currentActionContext.actionId,
|
|
1890
|
+
}
|
|
1891
|
+
: undefined,
|
|
1892
|
+
response:
|
|
1893
|
+
Array.isArray(response) && response.every((x) => typeof x === 'number')
|
|
1894
|
+
? '[array]'
|
|
1895
|
+
: response,
|
|
1896
|
+
},
|
|
1897
|
+
type: `useModel:${modelKey}`,
|
|
1898
|
+
});
|
|
1899
|
+
|
|
1900
|
+
return response as R;
|
|
1901
|
+
} catch (error: any) {
|
|
1902
|
+
throw error;
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
|
|
1906
|
+
registerEvent(event: string, handler: (params: any) => Promise<void>) {
|
|
1907
|
+
if (!this.events.has(event)) {
|
|
1908
|
+
this.events.set(event, []);
|
|
1909
|
+
}
|
|
1910
|
+
this.events.get(event)?.push(handler);
|
|
1911
|
+
}
|
|
1912
|
+
|
|
1913
|
+
getEvent(event: string): ((params: any) => Promise<void>)[] | undefined {
|
|
1914
|
+
return this.events.get(event);
|
|
1915
|
+
}
|
|
1916
|
+
|
|
1917
|
+
async emitEvent(event: string | string[], params: any) {
|
|
1918
|
+
const events = Array.isArray(event) ? event : [event];
|
|
1919
|
+
for (const eventName of events) {
|
|
1920
|
+
const eventHandlers = this.events.get(eventName);
|
|
1921
|
+
if (!eventHandlers) {
|
|
1922
|
+
continue;
|
|
1923
|
+
}
|
|
1924
|
+
try {
|
|
1925
|
+
let paramsWithRuntime = { runtime: this };
|
|
1926
|
+
if (typeof params === 'object' && params) {
|
|
1927
|
+
paramsWithRuntime = { ...params, ...paramsWithRuntime };
|
|
1928
|
+
}
|
|
1929
|
+
await Promise.all(eventHandlers.map((handler) => handler(paramsWithRuntime)));
|
|
1930
|
+
} catch (error) {
|
|
1931
|
+
this.logger.error(`Error during emitEvent for ${eventName} (handler execution): ${error}`);
|
|
1932
|
+
// throw error; // Re-throw if necessary
|
|
1933
|
+
}
|
|
1934
|
+
}
|
|
1935
|
+
}
|
|
1936
|
+
|
|
1937
|
+
async ensureEmbeddingDimension() {
|
|
1938
|
+
this.logger.debug(`[AgentRuntime][${this.character.name}] Starting ensureEmbeddingDimension`);
|
|
1939
|
+
|
|
1940
|
+
if (!this.adapter) {
|
|
1941
|
+
throw new Error(
|
|
1942
|
+
`[AgentRuntime][${this.character.name}] Database adapter not initialized before ensureEmbeddingDimension`
|
|
1943
|
+
);
|
|
1944
|
+
}
|
|
1945
|
+
try {
|
|
1946
|
+
const model = this.getModel(ModelType.TEXT_EMBEDDING);
|
|
1947
|
+
if (!model) {
|
|
1948
|
+
throw new Error(
|
|
1949
|
+
`[AgentRuntime][${this.character.name}] No TEXT_EMBEDDING model registered`
|
|
1950
|
+
);
|
|
1951
|
+
}
|
|
1952
|
+
|
|
1953
|
+
this.logger.debug(`[AgentRuntime][${this.character.name}] Getting embedding dimensions`);
|
|
1954
|
+
const embedding = await this.useModel(ModelType.TEXT_EMBEDDING, null);
|
|
1955
|
+
if (!embedding || !embedding.length) {
|
|
1956
|
+
throw new Error(`[AgentRuntime][${this.character.name}] Invalid embedding received`);
|
|
1957
|
+
}
|
|
1958
|
+
|
|
1959
|
+
this.logger.debug(
|
|
1960
|
+
`[AgentRuntime][${this.character.name}] Setting embedding dimension: ${embedding.length}`
|
|
1961
|
+
);
|
|
1962
|
+
await this.adapter.ensureEmbeddingDimension(embedding.length);
|
|
1963
|
+
this.logger.debug(
|
|
1964
|
+
`[AgentRuntime][${this.character.name}] Successfully set embedding dimension`
|
|
1965
|
+
);
|
|
1966
|
+
} catch (error) {
|
|
1967
|
+
this.logger.debug(
|
|
1968
|
+
`[AgentRuntime][${this.character.name}] Error in ensureEmbeddingDimension: ${error}`
|
|
1969
|
+
);
|
|
1970
|
+
throw error;
|
|
1971
|
+
}
|
|
1972
|
+
}
|
|
1973
|
+
|
|
1974
|
+
registerTaskWorker(taskHandler: TaskWorker): void {
|
|
1975
|
+
if (this.taskWorkers.has(taskHandler.name)) {
|
|
1976
|
+
this.logger.warn(
|
|
1977
|
+
`Task definition ${taskHandler.name} already registered. Will be overwritten.`
|
|
1978
|
+
);
|
|
1979
|
+
}
|
|
1980
|
+
this.taskWorkers.set(taskHandler.name, taskHandler);
|
|
1981
|
+
}
|
|
1982
|
+
|
|
1983
|
+
getTaskWorker(name: string): TaskWorker | undefined {
|
|
1984
|
+
return this.taskWorkers.get(name);
|
|
1985
|
+
}
|
|
1986
|
+
|
|
1987
|
+
get db(): any {
|
|
1988
|
+
return this.adapter.db;
|
|
1989
|
+
}
|
|
1990
|
+
async init(): Promise<void> {
|
|
1991
|
+
await this.adapter.init();
|
|
1992
|
+
}
|
|
1993
|
+
async close(): Promise<void> {
|
|
1994
|
+
await this.adapter.close();
|
|
1995
|
+
}
|
|
1996
|
+
async getAgent(agentId: UUID): Promise<Agent | null> {
|
|
1997
|
+
return await this.adapter.getAgent(agentId);
|
|
1998
|
+
}
|
|
1999
|
+
async getAgents(): Promise<Partial<Agent>[]> {
|
|
2000
|
+
return await this.adapter.getAgents();
|
|
2001
|
+
}
|
|
2002
|
+
async createAgent(agent: Partial<Agent>): Promise<boolean> {
|
|
2003
|
+
return await this.adapter.createAgent(agent);
|
|
2004
|
+
}
|
|
2005
|
+
async updateAgent(agentId: UUID, agent: Partial<Agent>): Promise<boolean> {
|
|
2006
|
+
return await this.adapter.updateAgent(agentId, agent);
|
|
2007
|
+
}
|
|
2008
|
+
async deleteAgent(agentId: UUID): Promise<boolean> {
|
|
2009
|
+
return await this.adapter.deleteAgent(agentId);
|
|
2010
|
+
}
|
|
2011
|
+
async ensureAgentExists(agent: Partial<Agent>): Promise<Agent> {
|
|
2012
|
+
if (!agent.name) {
|
|
2013
|
+
throw new Error('Agent name is required');
|
|
2014
|
+
}
|
|
2015
|
+
|
|
2016
|
+
const agents = await this.adapter.getAgents();
|
|
2017
|
+
const existingAgentId = agents.find((a) => a.name === agent.name)?.id;
|
|
2018
|
+
|
|
2019
|
+
if (existingAgentId) {
|
|
2020
|
+
// Update the agent on restart with the latest character configuration
|
|
2021
|
+
const updatedAgent = {
|
|
2022
|
+
...agent,
|
|
2023
|
+
id: existingAgentId,
|
|
2024
|
+
updatedAt: Date.now(),
|
|
2025
|
+
};
|
|
2026
|
+
|
|
2027
|
+
await this.adapter.updateAgent(existingAgentId, updatedAgent);
|
|
2028
|
+
const existingAgent = await this.adapter.getAgent(existingAgentId);
|
|
2029
|
+
|
|
2030
|
+
if (!existingAgent) {
|
|
2031
|
+
throw new Error(`Failed to retrieve agent after update: ${existingAgentId}`);
|
|
2032
|
+
}
|
|
2033
|
+
|
|
2034
|
+
this.logger.debug(`Updated existing agent ${agent.name} on restart`);
|
|
2035
|
+
return existingAgent;
|
|
2036
|
+
}
|
|
2037
|
+
|
|
2038
|
+
// Create new agent if it doesn't exist
|
|
2039
|
+
const newAgent: Agent = {
|
|
2040
|
+
...agent,
|
|
2041
|
+
id: stringToUuid(agent.name),
|
|
2042
|
+
} as Agent;
|
|
2043
|
+
|
|
2044
|
+
const created = await this.adapter.createAgent(newAgent);
|
|
2045
|
+
if (!created) {
|
|
2046
|
+
throw new Error(`Failed to create agent: ${agent.name}`);
|
|
2047
|
+
}
|
|
2048
|
+
|
|
2049
|
+
this.logger.debug(`Created new agent ${agent.name}`);
|
|
2050
|
+
return newAgent;
|
|
2051
|
+
}
|
|
2052
|
+
async getEntityById(entityId: UUID): Promise<Entity | null> {
|
|
2053
|
+
const entities = await this.adapter.getEntitiesByIds([entityId]);
|
|
2054
|
+
if (!entities?.length) return null;
|
|
2055
|
+
return entities[0];
|
|
2056
|
+
}
|
|
2057
|
+
|
|
2058
|
+
async getEntitiesByIds(entityIds: UUID[]): Promise<Entity[] | null> {
|
|
2059
|
+
return await this.adapter.getEntitiesByIds(entityIds);
|
|
2060
|
+
}
|
|
2061
|
+
async getEntitiesForRoom(roomId: UUID, includeComponents?: boolean): Promise<Entity[]> {
|
|
2062
|
+
return await this.adapter.getEntitiesForRoom(roomId, includeComponents);
|
|
2063
|
+
}
|
|
2064
|
+
async createEntity(entity: Entity): Promise<boolean> {
|
|
2065
|
+
if (!entity.agentId) {
|
|
2066
|
+
entity.agentId = this.agentId;
|
|
2067
|
+
}
|
|
2068
|
+
return await this.createEntities([entity]);
|
|
2069
|
+
}
|
|
2070
|
+
|
|
2071
|
+
async createEntities(entities: Entity[]): Promise<boolean> {
|
|
2072
|
+
entities.forEach((e) => {
|
|
2073
|
+
e.agentId = this.agentId;
|
|
2074
|
+
});
|
|
2075
|
+
return await this.adapter.createEntities(entities);
|
|
2076
|
+
}
|
|
2077
|
+
|
|
2078
|
+
async updateEntity(entity: Entity): Promise<void> {
|
|
2079
|
+
await this.adapter.updateEntity(entity);
|
|
2080
|
+
}
|
|
2081
|
+
async getComponent(
|
|
2082
|
+
entityId: UUID,
|
|
2083
|
+
type: string,
|
|
2084
|
+
worldId?: UUID,
|
|
2085
|
+
sourceEntityId?: UUID
|
|
2086
|
+
): Promise<Component | null> {
|
|
2087
|
+
return await this.adapter.getComponent(entityId, type, worldId, sourceEntityId);
|
|
2088
|
+
}
|
|
2089
|
+
async getComponents(entityId: UUID, worldId?: UUID, sourceEntityId?: UUID): Promise<Component[]> {
|
|
2090
|
+
return await this.adapter.getComponents(entityId, worldId, sourceEntityId);
|
|
2091
|
+
}
|
|
2092
|
+
async createComponent(component: Component): Promise<boolean> {
|
|
2093
|
+
return await this.adapter.createComponent(component);
|
|
2094
|
+
}
|
|
2095
|
+
async updateComponent(component: Component): Promise<void> {
|
|
2096
|
+
await this.adapter.updateComponent(component);
|
|
2097
|
+
}
|
|
2098
|
+
async deleteComponent(componentId: UUID): Promise<void> {
|
|
2099
|
+
await this.adapter.deleteComponent(componentId);
|
|
2100
|
+
}
|
|
2101
|
+
async addEmbeddingToMemory(memory: Memory): Promise<Memory> {
|
|
2102
|
+
if (memory.embedding) {
|
|
2103
|
+
return memory;
|
|
2104
|
+
}
|
|
2105
|
+
const memoryText = memory.content.text;
|
|
2106
|
+
if (!memoryText) {
|
|
2107
|
+
throw new Error('Cannot generate embedding: Memory content is empty');
|
|
2108
|
+
}
|
|
2109
|
+
try {
|
|
2110
|
+
memory.embedding = await this.useModel(ModelType.TEXT_EMBEDDING, {
|
|
2111
|
+
text: memoryText,
|
|
2112
|
+
});
|
|
2113
|
+
} catch (error: any) {
|
|
2114
|
+
this.logger.error('Failed to generate embedding:', error);
|
|
2115
|
+
memory.embedding = await this.useModel(ModelType.TEXT_EMBEDDING, null);
|
|
2116
|
+
}
|
|
2117
|
+
return memory;
|
|
2118
|
+
}
|
|
2119
|
+
|
|
2120
|
+
async queueEmbeddingGeneration(
|
|
2121
|
+
memory: Memory,
|
|
2122
|
+
priority: 'high' | 'normal' | 'low' = 'normal'
|
|
2123
|
+
): Promise<void> {
|
|
2124
|
+
// Skip if memory is null or undefined
|
|
2125
|
+
if (!memory) {
|
|
2126
|
+
return;
|
|
2127
|
+
}
|
|
2128
|
+
|
|
2129
|
+
// Skip if memory already has embeddings
|
|
2130
|
+
if (memory.embedding) {
|
|
2131
|
+
return;
|
|
2132
|
+
}
|
|
2133
|
+
|
|
2134
|
+
// Skip if no text content
|
|
2135
|
+
if (!memory.content?.text) {
|
|
2136
|
+
this.logger.debug('Skipping embedding generation for memory without text content');
|
|
2137
|
+
return;
|
|
2138
|
+
}
|
|
2139
|
+
|
|
2140
|
+
// Emit event for async embedding generation
|
|
2141
|
+
await this.emitEvent(EventType.EMBEDDING_GENERATION_REQUESTED, {
|
|
2142
|
+
runtime: this,
|
|
2143
|
+
memory,
|
|
2144
|
+
priority,
|
|
2145
|
+
source: 'runtime',
|
|
2146
|
+
retryCount: 0,
|
|
2147
|
+
maxRetries: 3,
|
|
2148
|
+
});
|
|
2149
|
+
}
|
|
2150
|
+
async getMemories(params: {
|
|
2151
|
+
entityId?: UUID;
|
|
2152
|
+
agentId?: UUID;
|
|
2153
|
+
roomId?: UUID;
|
|
2154
|
+
count?: number;
|
|
2155
|
+
unique?: boolean;
|
|
2156
|
+
tableName: string;
|
|
2157
|
+
start?: number;
|
|
2158
|
+
end?: number;
|
|
2159
|
+
}): Promise<Memory[]> {
|
|
2160
|
+
return await this.adapter.getMemories(params);
|
|
2161
|
+
}
|
|
2162
|
+
async getAllMemories(): Promise<Memory[]> {
|
|
2163
|
+
const tables = ['memories', 'messages', 'facts', 'documents'];
|
|
2164
|
+
const allMemories: Memory[] = [];
|
|
2165
|
+
|
|
2166
|
+
for (const tableName of tables) {
|
|
2167
|
+
try {
|
|
2168
|
+
const memories = await this.adapter.getMemories({
|
|
2169
|
+
agentId: this.agentId,
|
|
2170
|
+
tableName,
|
|
2171
|
+
count: 10000, // Get a large number to fetch all
|
|
2172
|
+
});
|
|
2173
|
+
allMemories.push(...memories);
|
|
2174
|
+
} catch (error) {
|
|
2175
|
+
// Continue with other tables if one fails
|
|
2176
|
+
this.logger.debug(`Failed to get memories from table ${tableName}: ${error}`);
|
|
2177
|
+
}
|
|
2178
|
+
}
|
|
2179
|
+
|
|
2180
|
+
return allMemories;
|
|
2181
|
+
}
|
|
2182
|
+
async getMemoryById(id: UUID): Promise<Memory | null> {
|
|
2183
|
+
return await this.adapter.getMemoryById(id);
|
|
2184
|
+
}
|
|
2185
|
+
async getMemoriesByIds(ids: UUID[], tableName?: string): Promise<Memory[]> {
|
|
2186
|
+
return await this.adapter.getMemoriesByIds(ids, tableName);
|
|
2187
|
+
}
|
|
2188
|
+
async getMemoriesByRoomIds(params: {
|
|
2189
|
+
tableName: string;
|
|
2190
|
+
roomIds: UUID[];
|
|
2191
|
+
limit?: number;
|
|
2192
|
+
}): Promise<Memory[]> {
|
|
2193
|
+
return await this.adapter.getMemoriesByRoomIds(params);
|
|
2194
|
+
}
|
|
2195
|
+
|
|
2196
|
+
async getCachedEmbeddings(params: {
|
|
2197
|
+
query_table_name: string;
|
|
2198
|
+
query_threshold: number;
|
|
2199
|
+
query_input: string;
|
|
2200
|
+
query_field_name: string;
|
|
2201
|
+
query_field_sub_name: string;
|
|
2202
|
+
query_match_count: number;
|
|
2203
|
+
}): Promise<{ embedding: number[]; levenshtein_score: number }[]> {
|
|
2204
|
+
return await this.adapter.getCachedEmbeddings(params);
|
|
2205
|
+
}
|
|
2206
|
+
async log(params: {
|
|
2207
|
+
body: { [key: string]: unknown };
|
|
2208
|
+
entityId: UUID;
|
|
2209
|
+
roomId: UUID;
|
|
2210
|
+
type: string;
|
|
2211
|
+
}): Promise<void> {
|
|
2212
|
+
await this.adapter.log(params);
|
|
2213
|
+
}
|
|
2214
|
+
async searchMemories(params: {
|
|
2215
|
+
embedding: number[];
|
|
2216
|
+
query?: string;
|
|
2217
|
+
match_threshold?: number;
|
|
2218
|
+
count?: number;
|
|
2219
|
+
roomId?: UUID;
|
|
2220
|
+
unique?: boolean;
|
|
2221
|
+
worldId?: UUID;
|
|
2222
|
+
entityId?: UUID;
|
|
2223
|
+
tableName: string;
|
|
2224
|
+
}): Promise<Memory[]> {
|
|
2225
|
+
const memories = await this.adapter.searchMemories(params);
|
|
2226
|
+
if (params.query) {
|
|
2227
|
+
const rerankedMemories = await this.rerankMemories(params.query, memories);
|
|
2228
|
+
return rerankedMemories;
|
|
2229
|
+
}
|
|
2230
|
+
return memories;
|
|
2231
|
+
}
|
|
2232
|
+
async rerankMemories(query: string, memories: Memory[]): Promise<Memory[]> {
|
|
2233
|
+
const docs = memories.map((memory) => ({
|
|
2234
|
+
title: memory.id,
|
|
2235
|
+
content: memory.content.text,
|
|
2236
|
+
}));
|
|
2237
|
+
const bm25 = new BM25(docs);
|
|
2238
|
+
const results = bm25.search(query, memories.length);
|
|
2239
|
+
return results.map((result) => memories[result.index]);
|
|
2240
|
+
}
|
|
2241
|
+
async createMemory(memory: Memory, tableName: string, unique?: boolean): Promise<UUID> {
|
|
2242
|
+
if (unique !== undefined) memory.unique = unique;
|
|
2243
|
+
return await this.adapter.createMemory(memory, tableName, unique);
|
|
2244
|
+
}
|
|
2245
|
+
async updateMemory(
|
|
2246
|
+
memory: Partial<Memory> & { id: UUID; metadata?: MemoryMetadata }
|
|
2247
|
+
): Promise<boolean> {
|
|
2248
|
+
return await this.adapter.updateMemory(memory);
|
|
2249
|
+
}
|
|
2250
|
+
async deleteMemory(memoryId: UUID): Promise<void> {
|
|
2251
|
+
await this.adapter.deleteMemory(memoryId);
|
|
2252
|
+
}
|
|
2253
|
+
async deleteManyMemories(memoryIds: UUID[]): Promise<void> {
|
|
2254
|
+
await this.adapter.deleteManyMemories(memoryIds);
|
|
2255
|
+
}
|
|
2256
|
+
async clearAllAgentMemories(): Promise<void> {
|
|
2257
|
+
this.logger.info(`Clearing all memories for agent ${this.character.name} (${this.agentId})`);
|
|
2258
|
+
|
|
2259
|
+
const allMemories = await this.getAllMemories();
|
|
2260
|
+
const memoryIds = allMemories
|
|
2261
|
+
.map((memory) => memory.id)
|
|
2262
|
+
.filter((id): id is UUID => id !== undefined);
|
|
2263
|
+
|
|
2264
|
+
if (memoryIds.length === 0) {
|
|
2265
|
+
this.logger.info('No memories found to delete');
|
|
2266
|
+
return;
|
|
2267
|
+
}
|
|
2268
|
+
|
|
2269
|
+
this.logger.info(`Found ${memoryIds.length} memories to delete`);
|
|
2270
|
+
await this.adapter.deleteManyMemories(memoryIds);
|
|
2271
|
+
|
|
2272
|
+
this.logger.info(`Successfully cleared all ${memoryIds.length} memories for agent`);
|
|
2273
|
+
}
|
|
2274
|
+
async deleteAllMemories(roomId: UUID, tableName: string): Promise<void> {
|
|
2275
|
+
await this.adapter.deleteAllMemories(roomId, tableName);
|
|
2276
|
+
}
|
|
2277
|
+
async countMemories(roomId: UUID, unique?: boolean, tableName?: string): Promise<number> {
|
|
2278
|
+
return await this.adapter.countMemories(roomId, unique, tableName);
|
|
2279
|
+
}
|
|
2280
|
+
async getLogs(params: {
|
|
2281
|
+
entityId: UUID;
|
|
2282
|
+
roomId?: UUID;
|
|
2283
|
+
type?: string;
|
|
2284
|
+
count?: number;
|
|
2285
|
+
offset?: number;
|
|
2286
|
+
}): Promise<Log[]> {
|
|
2287
|
+
return await this.adapter.getLogs(params);
|
|
2288
|
+
}
|
|
2289
|
+
async deleteLog(logId: UUID): Promise<void> {
|
|
2290
|
+
await this.adapter.deleteLog(logId);
|
|
2291
|
+
}
|
|
2292
|
+
async createWorld(world: World): Promise<UUID> {
|
|
2293
|
+
return await this.adapter.createWorld(world);
|
|
2294
|
+
}
|
|
2295
|
+
async getWorld(id: UUID): Promise<World | null> {
|
|
2296
|
+
return await this.adapter.getWorld(id);
|
|
2297
|
+
}
|
|
2298
|
+
async removeWorld(worldId: UUID): Promise<void> {
|
|
2299
|
+
await this.adapter.removeWorld(worldId);
|
|
2300
|
+
}
|
|
2301
|
+
async getAllWorlds(): Promise<World[]> {
|
|
2302
|
+
return await this.adapter.getAllWorlds();
|
|
2303
|
+
}
|
|
2304
|
+
async updateWorld(world: World): Promise<void> {
|
|
2305
|
+
await this.adapter.updateWorld(world);
|
|
2306
|
+
}
|
|
2307
|
+
async getRoom(roomId: UUID): Promise<Room | null> {
|
|
2308
|
+
const rooms = await this.adapter.getRoomsByIds([roomId]);
|
|
2309
|
+
if (!rooms?.length) return null;
|
|
2310
|
+
return rooms[0];
|
|
2311
|
+
}
|
|
2312
|
+
|
|
2313
|
+
async getRoomsByIds(roomIds: UUID[]): Promise<Room[] | null> {
|
|
2314
|
+
return await this.adapter.getRoomsByIds(roomIds);
|
|
2315
|
+
}
|
|
2316
|
+
async createRoom({ id, name, source, type, channelId, serverId, worldId }: Room): Promise<UUID> {
|
|
2317
|
+
if (!worldId) throw new Error('worldId is required');
|
|
2318
|
+
const res = await this.adapter.createRooms([
|
|
2319
|
+
{
|
|
2320
|
+
id,
|
|
2321
|
+
name,
|
|
2322
|
+
source,
|
|
2323
|
+
type,
|
|
2324
|
+
channelId,
|
|
2325
|
+
serverId,
|
|
2326
|
+
worldId,
|
|
2327
|
+
},
|
|
2328
|
+
]);
|
|
2329
|
+
if (!res.length) throw new Error('Failed to create room');
|
|
2330
|
+
return res[0];
|
|
2331
|
+
}
|
|
2332
|
+
|
|
2333
|
+
async createRooms(rooms: Room[]): Promise<UUID[]> {
|
|
2334
|
+
return await this.adapter.createRooms(rooms);
|
|
2335
|
+
}
|
|
2336
|
+
|
|
2337
|
+
async deleteRoom(roomId: UUID): Promise<void> {
|
|
2338
|
+
await this.adapter.deleteRoom(roomId);
|
|
2339
|
+
}
|
|
2340
|
+
async deleteRoomsByWorldId(worldId: UUID): Promise<void> {
|
|
2341
|
+
await this.adapter.deleteRoomsByWorldId(worldId);
|
|
2342
|
+
}
|
|
2343
|
+
async updateRoom(room: Room): Promise<void> {
|
|
2344
|
+
await this.adapter.updateRoom(room);
|
|
2345
|
+
}
|
|
2346
|
+
async getRoomsForParticipant(entityId: UUID): Promise<UUID[]> {
|
|
2347
|
+
return await this.adapter.getRoomsForParticipant(entityId);
|
|
2348
|
+
}
|
|
2349
|
+
async getRoomsForParticipants(userIds: UUID[]): Promise<UUID[]> {
|
|
2350
|
+
return await this.adapter.getRoomsForParticipants(userIds);
|
|
2351
|
+
}
|
|
2352
|
+
|
|
2353
|
+
// deprecate this one
|
|
2354
|
+
async getRooms(worldId: UUID): Promise<Room[]> {
|
|
2355
|
+
return await this.adapter.getRoomsByWorld(worldId);
|
|
2356
|
+
}
|
|
2357
|
+
|
|
2358
|
+
async getRoomsByWorld(worldId: UUID): Promise<Room[]> {
|
|
2359
|
+
return await this.adapter.getRoomsByWorld(worldId);
|
|
2360
|
+
}
|
|
2361
|
+
async getParticipantUserState(
|
|
2362
|
+
roomId: UUID,
|
|
2363
|
+
entityId: UUID
|
|
2364
|
+
): Promise<'FOLLOWED' | 'MUTED' | null> {
|
|
2365
|
+
return await this.adapter.getParticipantUserState(roomId, entityId);
|
|
2366
|
+
}
|
|
2367
|
+
async setParticipantUserState(
|
|
2368
|
+
roomId: UUID,
|
|
2369
|
+
entityId: UUID,
|
|
2370
|
+
state: 'FOLLOWED' | 'MUTED' | null
|
|
2371
|
+
): Promise<void> {
|
|
2372
|
+
await this.adapter.setParticipantUserState(roomId, entityId, state);
|
|
2373
|
+
}
|
|
2374
|
+
async createRelationship(params: {
|
|
2375
|
+
sourceEntityId: UUID;
|
|
2376
|
+
targetEntityId: UUID;
|
|
2377
|
+
tags?: string[];
|
|
2378
|
+
metadata?: { [key: string]: any };
|
|
2379
|
+
}): Promise<boolean> {
|
|
2380
|
+
return await this.adapter.createRelationship(params);
|
|
2381
|
+
}
|
|
2382
|
+
async updateRelationship(relationship: Relationship): Promise<void> {
|
|
2383
|
+
await this.adapter.updateRelationship(relationship);
|
|
2384
|
+
}
|
|
2385
|
+
async getRelationship(params: {
|
|
2386
|
+
sourceEntityId: UUID;
|
|
2387
|
+
targetEntityId: UUID;
|
|
2388
|
+
}): Promise<Relationship | null> {
|
|
2389
|
+
return await this.adapter.getRelationship(params);
|
|
2390
|
+
}
|
|
2391
|
+
async getRelationships(params: { entityId: UUID; tags?: string[] }): Promise<Relationship[]> {
|
|
2392
|
+
return await this.adapter.getRelationships(params);
|
|
2393
|
+
}
|
|
2394
|
+
async getCache<T>(key: string): Promise<T | undefined> {
|
|
2395
|
+
return await this.adapter.getCache<T>(key);
|
|
2396
|
+
}
|
|
2397
|
+
async setCache<T>(key: string, value: T): Promise<boolean> {
|
|
2398
|
+
return await this.adapter.setCache<T>(key, value);
|
|
2399
|
+
}
|
|
2400
|
+
async deleteCache(key: string): Promise<boolean> {
|
|
2401
|
+
return await this.adapter.deleteCache(key);
|
|
2402
|
+
}
|
|
2403
|
+
async createTask(task: Task): Promise<UUID> {
|
|
2404
|
+
return await this.adapter.createTask(task);
|
|
2405
|
+
}
|
|
2406
|
+
async getTasks(params: { roomId?: UUID; tags?: string[]; entityId?: UUID }): Promise<Task[]> {
|
|
2407
|
+
return await this.adapter.getTasks(params);
|
|
2408
|
+
}
|
|
2409
|
+
async getTask(id: UUID): Promise<Task | null> {
|
|
2410
|
+
return await this.adapter.getTask(id);
|
|
2411
|
+
}
|
|
2412
|
+
async getTasksByName(name: string): Promise<Task[]> {
|
|
2413
|
+
return await this.adapter.getTasksByName(name);
|
|
2414
|
+
}
|
|
2415
|
+
async updateTask(id: UUID, task: Partial<Task>): Promise<void> {
|
|
2416
|
+
await this.adapter.updateTask(id, task);
|
|
2417
|
+
}
|
|
2418
|
+
async deleteTask(id: UUID): Promise<void> {
|
|
2419
|
+
await this.adapter.deleteTask(id);
|
|
2420
|
+
}
|
|
2421
|
+
on(event: string, callback: (data: any) => void): void {
|
|
2422
|
+
if (!this.eventHandlers.has(event)) {
|
|
2423
|
+
this.eventHandlers.set(event, []);
|
|
2424
|
+
}
|
|
2425
|
+
this.eventHandlers.get(event)?.push(callback);
|
|
2426
|
+
}
|
|
2427
|
+
off(event: string, callback: (data: any) => void): void {
|
|
2428
|
+
if (!this.eventHandlers.has(event)) {
|
|
2429
|
+
return;
|
|
2430
|
+
}
|
|
2431
|
+
const handlers = this.eventHandlers.get(event)!;
|
|
2432
|
+
const index = handlers.indexOf(callback);
|
|
2433
|
+
if (index !== -1) {
|
|
2434
|
+
handlers.splice(index, 1);
|
|
2435
|
+
}
|
|
2436
|
+
}
|
|
2437
|
+
emit(event: string, data: any): void {
|
|
2438
|
+
if (!this.eventHandlers.has(event)) {
|
|
2439
|
+
return;
|
|
2440
|
+
}
|
|
2441
|
+
for (const handler of this.eventHandlers.get(event)!) {
|
|
2442
|
+
handler(data);
|
|
2443
|
+
}
|
|
2444
|
+
}
|
|
2445
|
+
async sendControlMessage(params: {
|
|
2446
|
+
roomId: UUID;
|
|
2447
|
+
action: 'enable_input' | 'disable_input';
|
|
2448
|
+
target?: string;
|
|
2449
|
+
}): Promise<void> {
|
|
2450
|
+
try {
|
|
2451
|
+
const { roomId, action, target } = params;
|
|
2452
|
+
const controlMessage = {
|
|
2453
|
+
type: 'control',
|
|
2454
|
+
payload: {
|
|
2455
|
+
action,
|
|
2456
|
+
target,
|
|
2457
|
+
},
|
|
2458
|
+
roomId,
|
|
2459
|
+
};
|
|
2460
|
+
await this.emitEvent('CONTROL_MESSAGE', {
|
|
2461
|
+
runtime: this,
|
|
2462
|
+
message: controlMessage,
|
|
2463
|
+
source: 'agent',
|
|
2464
|
+
});
|
|
2465
|
+
|
|
2466
|
+
this.logger.debug(`Sent control message: ${action} to room ${roomId}`);
|
|
2467
|
+
} catch (error) {
|
|
2468
|
+
this.logger.error(`Error sending control message: ${error}`);
|
|
2469
|
+
}
|
|
2470
|
+
}
|
|
2471
|
+
registerSendHandler(source: string, handler: SendHandlerFunction): void {
|
|
2472
|
+
if (this.sendHandlers.has(source)) {
|
|
2473
|
+
this.logger.warn(`Send handler for source '${source}' already registered. Overwriting.`);
|
|
2474
|
+
}
|
|
2475
|
+
this.sendHandlers.set(source, handler);
|
|
2476
|
+
this.logger.info(`Registered send handler for source: ${source}`);
|
|
2477
|
+
}
|
|
2478
|
+
async sendMessageToTarget(target: TargetInfo, content: Content): Promise<void> {
|
|
2479
|
+
const handler = this.sendHandlers.get(target.source);
|
|
2480
|
+
if (!handler) {
|
|
2481
|
+
const errorMsg = `No send handler registered for source: ${target.source}`;
|
|
2482
|
+
this.logger.error(errorMsg);
|
|
2483
|
+
// Optionally throw or just log the error
|
|
2484
|
+
throw new Error(errorMsg);
|
|
2485
|
+
}
|
|
2486
|
+
try {
|
|
2487
|
+
await handler(this, target, content);
|
|
2488
|
+
} catch (error: any) {
|
|
2489
|
+
this.logger.error(`Error executing send handler for source ${target.source}:`, error);
|
|
2490
|
+
throw error; // Re-throw error after logging and tracing
|
|
2491
|
+
}
|
|
2492
|
+
}
|
|
2493
|
+
async getMemoriesByWorldId(params: {
|
|
2494
|
+
worldId: UUID;
|
|
2495
|
+
count?: number;
|
|
2496
|
+
tableName?: string;
|
|
2497
|
+
}): Promise<Memory[]> {
|
|
2498
|
+
return await this.adapter.getMemoriesByWorldId(params);
|
|
2499
|
+
}
|
|
2500
|
+
async runMigrations(migrationsPaths?: string[]): Promise<void> {
|
|
2501
|
+
if (this.adapter && 'runMigrations' in this.adapter) {
|
|
2502
|
+
await (this.adapter as any).runMigrations(migrationsPaths);
|
|
2503
|
+
} else {
|
|
2504
|
+
this.logger.warn('Database adapter does not support migrations.');
|
|
2505
|
+
}
|
|
2506
|
+
}
|
|
2507
|
+
|
|
2508
|
+
async isReady(): Promise<boolean> {
|
|
2509
|
+
if (!this.adapter) {
|
|
2510
|
+
throw new Error('Database adapter not registered');
|
|
2511
|
+
}
|
|
2512
|
+
return await this.adapter.isReady();
|
|
2513
|
+
}
|
|
2514
|
+
}
|