@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.
Files changed (90) hide show
  1. package/dist/browser/index.browser.js +102 -102
  2. package/dist/browser/index.browser.js.map +5 -5
  3. package/dist/browser/index.d.ts +3 -1
  4. package/dist/index.d.ts +2 -3
  5. package/dist/index.js +1 -5
  6. package/dist/node/index.d.ts +3 -1
  7. package/dist/node/index.node.js +91 -12
  8. package/dist/node/index.node.js.map +10 -10
  9. package/package.json +10 -4
  10. package/src/__tests__/action-chaining-simple.test.ts +203 -0
  11. package/src/__tests__/actions.test.ts +218 -0
  12. package/src/__tests__/buffer.test.ts +337 -0
  13. package/src/__tests__/character-validation.test.ts +309 -0
  14. package/src/__tests__/database.test.ts +750 -0
  15. package/src/__tests__/entities.test.ts +727 -0
  16. package/src/__tests__/env.test.ts +23 -0
  17. package/src/__tests__/environment.test.ts +285 -0
  18. package/src/__tests__/logger-browser-node.test.ts +716 -0
  19. package/src/__tests__/logger.test.ts +403 -0
  20. package/src/__tests__/messages.test.ts +196 -0
  21. package/src/__tests__/mockCharacter.ts +544 -0
  22. package/src/__tests__/parsing.test.ts +58 -0
  23. package/src/__tests__/prompts.test.ts +159 -0
  24. package/src/__tests__/roles.test.ts +331 -0
  25. package/src/__tests__/runtime-embedding.test.ts +343 -0
  26. package/src/__tests__/runtime.test.ts +978 -0
  27. package/src/__tests__/search.test.ts +15 -0
  28. package/src/__tests__/services-by-type.test.ts +204 -0
  29. package/src/__tests__/services.test.ts +136 -0
  30. package/src/__tests__/settings.test.ts +810 -0
  31. package/src/__tests__/utils.test.ts +1105 -0
  32. package/src/__tests__/uuid.test.ts +94 -0
  33. package/src/actions.ts +122 -0
  34. package/src/database.ts +579 -0
  35. package/src/entities.ts +406 -0
  36. package/src/index.browser.ts +48 -0
  37. package/src/index.node.ts +39 -0
  38. package/src/index.ts +50 -0
  39. package/src/logger.ts +527 -0
  40. package/src/prompts.ts +243 -0
  41. package/src/roles.ts +85 -0
  42. package/src/runtime.ts +2514 -0
  43. package/src/schemas/character.ts +149 -0
  44. package/src/search.ts +1543 -0
  45. package/src/sentry/instrument.browser.ts +65 -0
  46. package/src/sentry/instrument.node.ts +57 -0
  47. package/src/sentry/instrument.ts +82 -0
  48. package/src/services.ts +105 -0
  49. package/src/settings.ts +409 -0
  50. package/src/test_resources/constants.ts +12 -0
  51. package/src/test_resources/testSetup.ts +21 -0
  52. package/src/test_resources/types.ts +22 -0
  53. package/src/types/agent.ts +112 -0
  54. package/src/types/browser.ts +145 -0
  55. package/src/types/components.ts +184 -0
  56. package/src/types/database.ts +348 -0
  57. package/src/types/email.ts +162 -0
  58. package/src/types/environment.ts +129 -0
  59. package/src/types/events.ts +249 -0
  60. package/src/types/index.ts +29 -0
  61. package/src/types/knowledge.ts +65 -0
  62. package/src/types/lp.ts +124 -0
  63. package/src/types/memory.ts +228 -0
  64. package/src/types/message.ts +233 -0
  65. package/src/types/messaging.ts +57 -0
  66. package/src/types/model.ts +359 -0
  67. package/src/types/pdf.ts +77 -0
  68. package/src/types/plugin.ts +78 -0
  69. package/src/types/post.ts +271 -0
  70. package/src/types/primitives.ts +97 -0
  71. package/src/types/runtime.ts +190 -0
  72. package/src/types/service.ts +198 -0
  73. package/src/types/settings.ts +30 -0
  74. package/src/types/state.ts +60 -0
  75. package/src/types/task.ts +72 -0
  76. package/src/types/tee.ts +107 -0
  77. package/src/types/testing.ts +30 -0
  78. package/src/types/token.ts +96 -0
  79. package/src/types/transcription.ts +133 -0
  80. package/src/types/video.ts +108 -0
  81. package/src/types/wallet.ts +56 -0
  82. package/src/types/web-search.ts +146 -0
  83. package/src/utils/__tests__/buffer.test.ts +80 -0
  84. package/src/utils/__tests__/environment.test.ts +58 -0
  85. package/src/utils/__tests__/stringToUuid.test.ts +88 -0
  86. package/src/utils/buffer.ts +312 -0
  87. package/src/utils/environment.ts +316 -0
  88. package/src/utils/server-health.ts +117 -0
  89. package/src/utils.ts +1076 -0
  90. 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
+ }