@elizaos/plugin-matrix 2.0.0-alpha.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,1045 @@
1
+ // src/index.ts
2
+ import { logger as logger2 } from "@elizaos/core";
3
+
4
+ // src/service.ts
5
+ import { Service, logger } from "@elizaos/core";
6
+ import * as sdk from "matrix-js-sdk";
7
+
8
+ // src/types.ts
9
+ var MAX_MATRIX_MESSAGE_LENGTH = 4000;
10
+ var MATRIX_SERVICE_NAME = "matrix";
11
+ var MatrixEventTypes;
12
+ ((MatrixEventTypes2) => {
13
+ MatrixEventTypes2["MESSAGE_RECEIVED"] = "MATRIX_MESSAGE_RECEIVED";
14
+ MatrixEventTypes2["MESSAGE_SENT"] = "MATRIX_MESSAGE_SENT";
15
+ MatrixEventTypes2["ROOM_JOINED"] = "MATRIX_ROOM_JOINED";
16
+ MatrixEventTypes2["ROOM_LEFT"] = "MATRIX_ROOM_LEFT";
17
+ MatrixEventTypes2["INVITE_RECEIVED"] = "MATRIX_INVITE_RECEIVED";
18
+ MatrixEventTypes2["REACTION_RECEIVED"] = "MATRIX_REACTION_RECEIVED";
19
+ MatrixEventTypes2["TYPING_RECEIVED"] = "MATRIX_TYPING_RECEIVED";
20
+ MatrixEventTypes2["SYNC_COMPLETE"] = "MATRIX_SYNC_COMPLETE";
21
+ MatrixEventTypes2["CONNECTION_READY"] = "MATRIX_CONNECTION_READY";
22
+ MatrixEventTypes2["CONNECTION_LOST"] = "MATRIX_CONNECTION_LOST";
23
+ })(MatrixEventTypes ||= {});
24
+ function isValidMatrixUserId(userId) {
25
+ return /^@[^:]+:.+$/.test(userId);
26
+ }
27
+ function isValidMatrixRoomId(roomId) {
28
+ return /^![^:]+:.+$/.test(roomId);
29
+ }
30
+ function isValidMatrixRoomAlias(alias) {
31
+ return /^#[^:]+:.+$/.test(alias);
32
+ }
33
+ function getMatrixLocalpart(matrixId) {
34
+ const match = matrixId.match(/^[@#!]([^:]+):/);
35
+ return match ? match[1] : matrixId;
36
+ }
37
+ function getMatrixServerpart(matrixId) {
38
+ const match = matrixId.match(/:(.+)$/);
39
+ return match ? match[1] : "";
40
+ }
41
+ function getMatrixUserDisplayName(user) {
42
+ return user.displayName || getMatrixLocalpart(user.userId);
43
+ }
44
+ function matrixMxcToHttp(mxcUrl, homeserver) {
45
+ if (!mxcUrl.startsWith("mxc://")) {
46
+ return;
47
+ }
48
+ const [serverName, mediaId] = mxcUrl.slice(6).split("/");
49
+ if (!serverName || !mediaId) {
50
+ return;
51
+ }
52
+ const base = homeserver.replace(/\/$/, "");
53
+ return `${base}/_matrix/media/v3/download/${serverName}/${mediaId}`;
54
+ }
55
+
56
+ class MatrixPluginError extends Error {
57
+ constructor(message) {
58
+ super(message);
59
+ this.name = "MatrixPluginError";
60
+ }
61
+ }
62
+
63
+ class MatrixServiceNotInitializedError extends MatrixPluginError {
64
+ constructor(message = "Matrix service is not initialized") {
65
+ super(message);
66
+ this.name = "MatrixServiceNotInitializedError";
67
+ }
68
+ }
69
+
70
+ class MatrixNotConnectedError extends MatrixPluginError {
71
+ constructor(message = "Matrix client is not connected") {
72
+ super(message);
73
+ this.name = "MatrixNotConnectedError";
74
+ }
75
+ }
76
+
77
+ class MatrixConfigurationError extends MatrixPluginError {
78
+ settingName;
79
+ constructor(message, settingName) {
80
+ super(message);
81
+ this.name = "MatrixConfigurationError";
82
+ this.settingName = settingName;
83
+ }
84
+ }
85
+
86
+ class MatrixApiError extends MatrixPluginError {
87
+ errcode;
88
+ constructor(message, errcode) {
89
+ super(message);
90
+ this.name = "MatrixApiError";
91
+ this.errcode = errcode;
92
+ }
93
+ }
94
+
95
+ // src/service.ts
96
+ class MatrixService extends Service {
97
+ static serviceType = MATRIX_SERVICE_NAME;
98
+ capabilityDescription = "Matrix messaging service for chat communication";
99
+ settings;
100
+ client;
101
+ connected = false;
102
+ syncing = false;
103
+ static async start(runtime) {
104
+ const service = new MatrixService;
105
+ await service.initialize(runtime);
106
+ return service;
107
+ }
108
+ static async stopRuntime(runtime) {
109
+ const service = runtime.getService(MATRIX_SERVICE_NAME);
110
+ if (service) {
111
+ await service.stop();
112
+ }
113
+ }
114
+ async initialize(runtime) {
115
+ this.runtime = runtime;
116
+ this.settings = this.loadSettings();
117
+ this.validateSettings();
118
+ this.client = sdk.createClient({
119
+ baseUrl: this.settings.homeserver,
120
+ userId: this.settings.userId,
121
+ accessToken: this.settings.accessToken,
122
+ deviceId: this.settings.deviceId
123
+ });
124
+ this.setupEventHandlers();
125
+ await this.connect();
126
+ logger.info(`Matrix service initialized for ${this.settings.userId} on ${this.settings.homeserver}`);
127
+ }
128
+ loadSettings() {
129
+ const getStringSetting = (key) => {
130
+ const value = this.runtime.getSetting(key);
131
+ return typeof value === "string" ? value : undefined;
132
+ };
133
+ const homeserver = getStringSetting("MATRIX_HOMESERVER");
134
+ const userId = getStringSetting("MATRIX_USER_ID");
135
+ const accessToken = getStringSetting("MATRIX_ACCESS_TOKEN");
136
+ const deviceId = getStringSetting("MATRIX_DEVICE_ID");
137
+ const roomsStr = getStringSetting("MATRIX_ROOMS");
138
+ const autoJoinStr = getStringSetting("MATRIX_AUTO_JOIN");
139
+ const encryptionStr = getStringSetting("MATRIX_ENCRYPTION");
140
+ const requireMentionStr = getStringSetting("MATRIX_REQUIRE_MENTION");
141
+ const rooms = roomsStr ? roomsStr.split(",").map((r) => r.trim()).filter(Boolean) : [];
142
+ return {
143
+ homeserver: homeserver || "",
144
+ userId: userId || "",
145
+ accessToken: accessToken || "",
146
+ deviceId,
147
+ rooms,
148
+ autoJoin: autoJoinStr === "true",
149
+ encryption: encryptionStr === "true",
150
+ requireMention: requireMentionStr === "true",
151
+ enabled: true
152
+ };
153
+ }
154
+ validateSettings() {
155
+ if (!this.settings.homeserver) {
156
+ throw new MatrixConfigurationError("MATRIX_HOMESERVER is required", "MATRIX_HOMESERVER");
157
+ }
158
+ if (!this.settings.userId) {
159
+ throw new MatrixConfigurationError("MATRIX_USER_ID is required", "MATRIX_USER_ID");
160
+ }
161
+ if (!this.settings.accessToken) {
162
+ throw new MatrixConfigurationError("MATRIX_ACCESS_TOKEN is required", "MATRIX_ACCESS_TOKEN");
163
+ }
164
+ }
165
+ setupEventHandlers() {
166
+ this.client.on(sdk.ClientEvent.Sync, (state) => {
167
+ if (state === "PREPARED") {
168
+ this.syncing = true;
169
+ logger.info("Matrix sync complete");
170
+ this.runtime.emitEvent("MATRIX_SYNC_COMPLETE" /* SYNC_COMPLETE */, {
171
+ runtime: this.runtime
172
+ });
173
+ }
174
+ });
175
+ this.client.on(sdk.RoomEvent.Timeline, (event, room, toStartOfTimeline) => {
176
+ if (toStartOfTimeline)
177
+ return;
178
+ if (event.getType() !== "m.room.message")
179
+ return;
180
+ if (event.getSender() === this.settings.userId)
181
+ return;
182
+ this.handleRoomMessage(event, room);
183
+ });
184
+ this.client.on(sdk.RoomMemberEvent.Membership, (event, member) => {
185
+ if (member.userId !== this.settings.userId)
186
+ return;
187
+ if (member.membership === "invite" && this.settings.autoJoin) {
188
+ const roomId = event.getRoomId();
189
+ if (roomId) {
190
+ logger.info(`Auto-joining room ${roomId}`);
191
+ this.client.joinRoom(roomId).catch((err) => {
192
+ logger.error(`Failed to auto-join room: ${err.message}`);
193
+ });
194
+ }
195
+ }
196
+ });
197
+ }
198
+ handleRoomMessage(event, room) {
199
+ const content = event.getContent();
200
+ const msgType = content.msgtype;
201
+ if (msgType !== "m.text")
202
+ return;
203
+ const roomId = event.getRoomId();
204
+ if (!roomId || !room)
205
+ return;
206
+ if (this.settings.requireMention) {
207
+ const body = content.body || "";
208
+ const localpart = getMatrixLocalpart(this.settings.userId);
209
+ const mentionPattern = new RegExp(`@?${localpart}`, "i");
210
+ if (!mentionPattern.test(body)) {
211
+ return;
212
+ }
213
+ }
214
+ const sender = event.getSender();
215
+ const senderMember = room.getMember(sender || "");
216
+ const senderInfo = {
217
+ userId: sender || "",
218
+ displayName: senderMember?.name,
219
+ avatarUrl: senderMember?.getMxcAvatarUrl() || undefined
220
+ };
221
+ const relatesTo = content["m.relates_to"];
222
+ const isEdit = relatesTo?.rel_type === "m.replace";
223
+ const threadId = relatesTo?.rel_type === "m.thread" ? relatesTo.event_id : undefined;
224
+ const replyTo = relatesTo?.["m.in_reply_to"]?.event_id;
225
+ const message = {
226
+ eventId: event.getId() || "",
227
+ roomId,
228
+ sender: sender || "",
229
+ senderInfo,
230
+ content: content.body || "",
231
+ msgType,
232
+ formattedBody: content.formatted_body,
233
+ timestamp: event.getTs(),
234
+ threadId,
235
+ replyTo,
236
+ isEdit,
237
+ replacesEventId: isEdit ? relatesTo?.event_id : undefined
238
+ };
239
+ const matrixRoom = {
240
+ roomId,
241
+ name: room.name,
242
+ topic: room.currentState.getStateEvents("m.room.topic", "")?.getContent()?.topic,
243
+ canonicalAlias: room.getCanonicalAlias() || undefined,
244
+ isEncrypted: room.hasEncryptionStateEvent(),
245
+ isDirect: this.client.getAccountData("m.direct")?.getContent()?.[sender || ""]?.includes(roomId) || false,
246
+ memberCount: room.getJoinedMemberCount()
247
+ };
248
+ logger.debug(`Matrix message from ${senderInfo.displayName || sender} in ${room.name || roomId}: ${message.content.slice(0, 50)}...`);
249
+ this.runtime.emitEvent("MATRIX_MESSAGE_RECEIVED" /* MESSAGE_RECEIVED */, {
250
+ message,
251
+ room: matrixRoom,
252
+ runtime: this.runtime
253
+ });
254
+ }
255
+ async connect() {
256
+ await this.client.startClient({ initialSyncLimit: 10 });
257
+ this.connected = true;
258
+ await new Promise((resolve) => {
259
+ const listener = (state) => {
260
+ if (state === "PREPARED") {
261
+ this.client.removeListener(sdk.ClientEvent.Sync, listener);
262
+ resolve();
263
+ }
264
+ };
265
+ this.client.on(sdk.ClientEvent.Sync, listener);
266
+ });
267
+ for (const room of this.settings.rooms) {
268
+ try {
269
+ await this.joinRoom(room);
270
+ } catch (err) {
271
+ logger.warn(`Failed to join room ${room}: ${err}`);
272
+ }
273
+ }
274
+ }
275
+ async stop() {
276
+ if (this.client) {
277
+ this.client.stopClient();
278
+ }
279
+ this.connected = false;
280
+ logger.info("Matrix service stopped");
281
+ }
282
+ isConnected() {
283
+ return this.connected && this.syncing;
284
+ }
285
+ getUserId() {
286
+ return this.settings.userId;
287
+ }
288
+ getHomeserver() {
289
+ return this.settings.homeserver;
290
+ }
291
+ async getJoinedRooms() {
292
+ const rooms = this.client.getRooms();
293
+ return rooms.filter((room) => room.getMyMembership() === "join").map((room) => ({
294
+ roomId: room.roomId,
295
+ name: room.name,
296
+ topic: room.currentState.getStateEvents("m.room.topic", "")?.getContent()?.topic,
297
+ canonicalAlias: room.getCanonicalAlias() || undefined,
298
+ isEncrypted: room.hasEncryptionStateEvent(),
299
+ isDirect: false,
300
+ memberCount: room.getJoinedMemberCount()
301
+ }));
302
+ }
303
+ async sendMessage(text, options) {
304
+ if (!this.isConnected()) {
305
+ throw new MatrixNotConnectedError;
306
+ }
307
+ const roomId = options?.roomId;
308
+ if (!roomId) {
309
+ return { success: false, error: "Room ID is required" };
310
+ }
311
+ let resolvedRoomId = roomId;
312
+ if (isValidMatrixRoomAlias(roomId)) {
313
+ const resolved = await this.client.getRoomIdForAlias(roomId);
314
+ resolvedRoomId = resolved.room_id;
315
+ }
316
+ const content = {
317
+ msgtype: "m.text",
318
+ body: text
319
+ };
320
+ if (options?.formatted) {
321
+ content.format = "org.matrix.custom.html";
322
+ content.formatted_body = text;
323
+ }
324
+ if (options?.threadId || options?.replyTo) {
325
+ content["m.relates_to"] = {};
326
+ if (options.threadId) {
327
+ content["m.relates_to"].rel_type = "m.thread";
328
+ content["m.relates_to"].event_id = options.threadId;
329
+ }
330
+ if (options.replyTo) {
331
+ content["m.relates_to"]["m.in_reply_to"] = {
332
+ event_id: options.replyTo
333
+ };
334
+ }
335
+ }
336
+ const response = await this.client.sendMessage(resolvedRoomId, content);
337
+ const eventId = response.event_id;
338
+ this.runtime.emitEvent("MATRIX_MESSAGE_SENT" /* MESSAGE_SENT */, {
339
+ roomId: resolvedRoomId,
340
+ eventId,
341
+ content: text,
342
+ runtime: this.runtime
343
+ });
344
+ return {
345
+ success: true,
346
+ eventId,
347
+ roomId: resolvedRoomId
348
+ };
349
+ }
350
+ async sendReaction(roomId, eventId, emoji) {
351
+ if (!this.isConnected()) {
352
+ throw new MatrixNotConnectedError;
353
+ }
354
+ const content = {
355
+ "m.relates_to": {
356
+ rel_type: "m.annotation",
357
+ event_id: eventId,
358
+ key: emoji
359
+ }
360
+ };
361
+ const response = await this.client.sendEvent(roomId, "m.reaction", content);
362
+ return {
363
+ success: true,
364
+ eventId: response.event_id,
365
+ roomId
366
+ };
367
+ }
368
+ async joinRoom(roomIdOrAlias) {
369
+ if (!this.isConnected()) {
370
+ throw new MatrixNotConnectedError;
371
+ }
372
+ const response = await this.client.joinRoom(roomIdOrAlias);
373
+ const roomId = response.roomId;
374
+ logger.info(`Joined room ${roomId}`);
375
+ this.runtime.emitEvent("MATRIX_ROOM_JOINED" /* ROOM_JOINED */, {
376
+ room: { roomId },
377
+ runtime: this.runtime
378
+ });
379
+ return roomId;
380
+ }
381
+ async leaveRoom(roomId) {
382
+ if (!this.isConnected()) {
383
+ throw new MatrixNotConnectedError;
384
+ }
385
+ await this.client.leave(roomId);
386
+ logger.info(`Left room ${roomId}`);
387
+ this.runtime.emitEvent("MATRIX_ROOM_LEFT" /* ROOM_LEFT */, {
388
+ roomId,
389
+ runtime: this.runtime
390
+ });
391
+ }
392
+ async sendTyping(roomId, typing, timeout = 30000) {
393
+ if (!this.isConnected()) {
394
+ return;
395
+ }
396
+ await this.client.sendTyping(roomId, typing, timeout);
397
+ }
398
+ async sendReadReceipt(roomId, eventId) {
399
+ if (!this.isConnected()) {
400
+ return;
401
+ }
402
+ await this.client.sendReadReceipt(new sdk.MatrixEvent({ event_id: eventId, room_id: roomId }));
403
+ }
404
+ }
405
+ // src/actions/sendMessage.ts
406
+ import { composePromptFromState, ModelType, parseJSONObjectFromText } from "@elizaos/core";
407
+ var SEND_MESSAGE_TEMPLATE = `You are helping to extract send message parameters for Matrix.
408
+
409
+ The user wants to send a message to a Matrix room.
410
+
411
+ Recent conversation:
412
+ {{recentMessages}}
413
+
414
+ Extract the following:
415
+ 1. text: The message text to send
416
+ 2. roomId: The room ID (!room:server) or alias (#alias:server), or "current" for the current room
417
+
418
+ Respond with a JSON object like:
419
+ {
420
+ "text": "The message to send",
421
+ "roomId": "current"
422
+ }
423
+
424
+ Only respond with the JSON object, no other text.`;
425
+ var sendMessage = {
426
+ name: "MATRIX_SEND_MESSAGE",
427
+ similes: [
428
+ "SEND_MATRIX_MESSAGE",
429
+ "MESSAGE_MATRIX",
430
+ "MATRIX_TEXT"
431
+ ],
432
+ description: "Send a message to a Matrix room",
433
+ validate: async (runtime, message, _state) => {
434
+ return message.content.source === "matrix";
435
+ },
436
+ handler: async (runtime, message, state, _options, callback) => {
437
+ const matrixService = runtime.getService(MATRIX_SERVICE_NAME);
438
+ if (!matrixService || !matrixService.isConnected()) {
439
+ if (callback) {
440
+ await callback({ text: "Matrix service is not available.", source: "matrix" });
441
+ }
442
+ return { success: false, error: "Matrix service not available" };
443
+ }
444
+ const composedState = state ?? {
445
+ values: {},
446
+ data: {},
447
+ text: ""
448
+ };
449
+ const prompt = await composePromptFromState({
450
+ template: SEND_MESSAGE_TEMPLATE,
451
+ state: composedState
452
+ });
453
+ let messageInfo = null;
454
+ for (let attempt = 0;attempt < 3; attempt++) {
455
+ const response = await runtime.useModel(ModelType.TEXT_SMALL, {
456
+ prompt
457
+ });
458
+ const parsed = parseJSONObjectFromText(response);
459
+ if (parsed?.text) {
460
+ messageInfo = {
461
+ text: String(parsed.text),
462
+ roomId: String(parsed.roomId || "current")
463
+ };
464
+ break;
465
+ }
466
+ }
467
+ if (!messageInfo || !messageInfo.text) {
468
+ if (callback) {
469
+ await callback({
470
+ text: "I couldn't understand what message you want me to send. Please try again.",
471
+ source: "matrix"
472
+ });
473
+ }
474
+ return { success: false, error: "Could not extract message parameters" };
475
+ }
476
+ let targetRoomId;
477
+ if (messageInfo.roomId && messageInfo.roomId !== "current") {
478
+ if (isValidMatrixRoomId(messageInfo.roomId) || isValidMatrixRoomAlias(messageInfo.roomId)) {
479
+ targetRoomId = messageInfo.roomId;
480
+ }
481
+ }
482
+ const roomData = state?.data?.room;
483
+ if (!targetRoomId && roomData?.roomId) {
484
+ targetRoomId = roomData.roomId;
485
+ }
486
+ if (!targetRoomId) {
487
+ if (callback) {
488
+ await callback({
489
+ text: "I couldn't determine which room to send to. Please specify a room.",
490
+ source: "matrix"
491
+ });
492
+ }
493
+ return { success: false, error: "Could not determine target room" };
494
+ }
495
+ const result = await matrixService.sendMessage(messageInfo.text, {
496
+ roomId: targetRoomId
497
+ });
498
+ if (!result.success) {
499
+ if (callback) {
500
+ await callback({
501
+ text: `Failed to send message: ${result.error}`,
502
+ source: "matrix"
503
+ });
504
+ }
505
+ return { success: false, error: result.error };
506
+ }
507
+ if (callback) {
508
+ await callback({
509
+ text: "Message sent successfully.",
510
+ source: message.content.source
511
+ });
512
+ }
513
+ return {
514
+ success: true,
515
+ data: {
516
+ roomId: result.roomId,
517
+ eventId: result.eventId
518
+ }
519
+ };
520
+ },
521
+ examples: [
522
+ [
523
+ {
524
+ name: "{{user1}}",
525
+ content: { text: "Send a message saying 'Hello everyone!'" }
526
+ },
527
+ {
528
+ name: "{{agent}}",
529
+ content: {
530
+ text: "I'll send that message to the room.",
531
+ actions: ["MATRIX_SEND_MESSAGE"]
532
+ }
533
+ }
534
+ ]
535
+ ]
536
+ };
537
+
538
+ // src/actions/sendReaction.ts
539
+ import { composePromptFromState as composePromptFromState2, ModelType as ModelType2, parseJSONObjectFromText as parseJSONObjectFromText2 } from "@elizaos/core";
540
+ var SEND_REACTION_TEMPLATE = `You are helping to extract reaction parameters for Matrix.
541
+
542
+ The user wants to react to a Matrix message with an emoji.
543
+
544
+ Recent conversation:
545
+ {{recentMessages}}
546
+
547
+ Extract the following:
548
+ 1. emoji: The emoji to react with (single emoji character)
549
+ 2. eventId: The event ID of the message to react to (starts with $)
550
+
551
+ Respond with a JSON object like:
552
+ {
553
+ "emoji": "\uD83D\uDC4D",
554
+ "eventId": "$event123"
555
+ }
556
+
557
+ Only respond with the JSON object, no other text.`;
558
+ var sendReaction = {
559
+ name: "MATRIX_SEND_REACTION",
560
+ similes: [
561
+ "REACT_MATRIX",
562
+ "MATRIX_REACT",
563
+ "ADD_MATRIX_REACTION"
564
+ ],
565
+ description: "React to a Matrix message with an emoji",
566
+ validate: async (runtime, message, _state) => {
567
+ return message.content.source === "matrix";
568
+ },
569
+ handler: async (runtime, message, state, _options, callback) => {
570
+ const matrixService = runtime.getService(MATRIX_SERVICE_NAME);
571
+ if (!matrixService || !matrixService.isConnected()) {
572
+ if (callback) {
573
+ await callback({ text: "Matrix service is not available.", source: "matrix" });
574
+ }
575
+ return { success: false, error: "Matrix service not available" };
576
+ }
577
+ const composedState = state ?? {
578
+ values: {},
579
+ data: {},
580
+ text: ""
581
+ };
582
+ const prompt = await composePromptFromState2({
583
+ template: SEND_REACTION_TEMPLATE,
584
+ state: composedState
585
+ });
586
+ let reactionInfo = null;
587
+ for (let attempt = 0;attempt < 3; attempt++) {
588
+ const response = await runtime.useModel(ModelType2.TEXT_SMALL, {
589
+ prompt
590
+ });
591
+ const parsed = parseJSONObjectFromText2(response);
592
+ if (parsed?.emoji && parsed?.eventId) {
593
+ reactionInfo = {
594
+ emoji: String(parsed.emoji),
595
+ eventId: String(parsed.eventId)
596
+ };
597
+ break;
598
+ }
599
+ }
600
+ if (!reactionInfo) {
601
+ if (callback) {
602
+ await callback({
603
+ text: "I couldn't understand the reaction request. Please specify the emoji and message.",
604
+ source: "matrix"
605
+ });
606
+ }
607
+ return { success: false, error: "Could not extract reaction parameters" };
608
+ }
609
+ const roomData = state?.data?.room;
610
+ const roomId = roomData?.roomId;
611
+ if (!roomId) {
612
+ if (callback) {
613
+ await callback({
614
+ text: "I couldn't determine which room this is in.",
615
+ source: "matrix"
616
+ });
617
+ }
618
+ return { success: false, error: "Could not determine room" };
619
+ }
620
+ const result = await matrixService.sendReaction(roomId, reactionInfo.eventId, reactionInfo.emoji);
621
+ if (!result.success) {
622
+ if (callback) {
623
+ await callback({
624
+ text: `Failed to add reaction: ${result.error}`,
625
+ source: "matrix"
626
+ });
627
+ }
628
+ return { success: false, error: result.error };
629
+ }
630
+ if (callback) {
631
+ await callback({
632
+ text: `Added ${reactionInfo.emoji} reaction.`,
633
+ source: message.content.source
634
+ });
635
+ }
636
+ return {
637
+ success: true,
638
+ data: {
639
+ emoji: reactionInfo.emoji,
640
+ eventId: reactionInfo.eventId,
641
+ roomId
642
+ }
643
+ };
644
+ },
645
+ examples: [
646
+ [
647
+ {
648
+ name: "{{user1}}",
649
+ content: { text: "React to the last message with a thumbs up" }
650
+ },
651
+ {
652
+ name: "{{agent}}",
653
+ content: {
654
+ text: "I'll add a thumbs up reaction.",
655
+ actions: ["MATRIX_SEND_REACTION"]
656
+ }
657
+ }
658
+ ]
659
+ ]
660
+ };
661
+
662
+ // src/actions/listRooms.ts
663
+ var listRooms = {
664
+ name: "MATRIX_LIST_ROOMS",
665
+ similes: [
666
+ "LIST_MATRIX_ROOMS",
667
+ "SHOW_ROOMS",
668
+ "GET_ROOMS",
669
+ "MY_ROOMS"
670
+ ],
671
+ description: "List all Matrix rooms the bot has joined",
672
+ validate: async (runtime, message, _state) => {
673
+ return message.content.source === "matrix";
674
+ },
675
+ handler: async (runtime, message, _state, _options, callback) => {
676
+ const matrixService = runtime.getService(MATRIX_SERVICE_NAME);
677
+ if (!matrixService || !matrixService.isConnected()) {
678
+ if (callback) {
679
+ await callback({ text: "Matrix service is not available.", source: "matrix" });
680
+ }
681
+ return { success: false, error: "Matrix service not available" };
682
+ }
683
+ const rooms = await matrixService.getJoinedRooms();
684
+ const roomList = rooms.map((room) => {
685
+ const name = room.name || room.canonicalAlias || room.roomId;
686
+ const members = `${room.memberCount} members`;
687
+ const encrypted = room.isEncrypted ? " (encrypted)" : "";
688
+ return `- ${name} (${members})${encrypted}`;
689
+ });
690
+ const responseText = rooms.length > 0 ? `Joined ${rooms.length} room(s):
691
+
692
+ ${roomList.join(`
693
+ `)}` : "Not currently in any rooms.";
694
+ if (callback) {
695
+ await callback({
696
+ text: responseText,
697
+ source: message.content.source
698
+ });
699
+ }
700
+ return {
701
+ success: true,
702
+ data: {
703
+ roomCount: rooms.length,
704
+ rooms: rooms.map((r) => ({
705
+ roomId: r.roomId,
706
+ name: r.name,
707
+ alias: r.canonicalAlias,
708
+ memberCount: r.memberCount,
709
+ isEncrypted: r.isEncrypted
710
+ }))
711
+ }
712
+ };
713
+ },
714
+ examples: [
715
+ [
716
+ {
717
+ name: "{{user1}}",
718
+ content: { text: "What rooms are you in?" }
719
+ },
720
+ {
721
+ name: "{{agent}}",
722
+ content: {
723
+ text: "I'll list the rooms I've joined.",
724
+ actions: ["MATRIX_LIST_ROOMS"]
725
+ }
726
+ }
727
+ ]
728
+ ]
729
+ };
730
+
731
+ // src/actions/joinRoom.ts
732
+ import { composePromptFromState as composePromptFromState3, ModelType as ModelType3, parseJSONObjectFromText as parseJSONObjectFromText3 } from "@elizaos/core";
733
+ var JOIN_ROOM_TEMPLATE = `You are helping to extract a Matrix room identifier.
734
+
735
+ The user wants to join a Matrix room.
736
+
737
+ Recent conversation:
738
+ {{recentMessages}}
739
+
740
+ Extract the room ID (!room:server) or room alias (#alias:server) to join.
741
+
742
+ Respond with a JSON object like:
743
+ {
744
+ "room": "!room:matrix.org"
745
+ }
746
+
747
+ or:
748
+
749
+ {
750
+ "room": "#alias:matrix.org"
751
+ }
752
+
753
+ Only respond with the JSON object, no other text.`;
754
+ var joinRoom = {
755
+ name: "MATRIX_JOIN_ROOM",
756
+ similes: [
757
+ "JOIN_MATRIX_ROOM",
758
+ "ENTER_ROOM"
759
+ ],
760
+ description: "Join a Matrix room by ID or alias",
761
+ validate: async (runtime, message, _state) => {
762
+ return message.content.source === "matrix";
763
+ },
764
+ handler: async (runtime, message, state, _options, callback) => {
765
+ const matrixService = runtime.getService(MATRIX_SERVICE_NAME);
766
+ if (!matrixService || !matrixService.isConnected()) {
767
+ if (callback) {
768
+ await callback({ text: "Matrix service is not available.", source: "matrix" });
769
+ }
770
+ return { success: false, error: "Matrix service not available" };
771
+ }
772
+ const composedState = state ?? {
773
+ values: {},
774
+ data: {},
775
+ text: ""
776
+ };
777
+ const prompt = await composePromptFromState3({
778
+ template: JOIN_ROOM_TEMPLATE,
779
+ state: composedState
780
+ });
781
+ let room = null;
782
+ for (let attempt = 0;attempt < 3; attempt++) {
783
+ const response = await runtime.useModel(ModelType3.TEXT_SMALL, {
784
+ prompt
785
+ });
786
+ const parsed = parseJSONObjectFromText3(response);
787
+ if (parsed?.room) {
788
+ const roomStr = String(parsed.room).trim();
789
+ if (isValidMatrixRoomId(roomStr) || isValidMatrixRoomAlias(roomStr)) {
790
+ room = roomStr;
791
+ break;
792
+ }
793
+ }
794
+ }
795
+ if (!room) {
796
+ if (callback) {
797
+ await callback({
798
+ text: "I couldn't understand which room you want me to join. Please specify a room ID (!room:server) or alias (#alias:server).",
799
+ source: "matrix"
800
+ });
801
+ }
802
+ return { success: false, error: "Could not extract room identifier" };
803
+ }
804
+ try {
805
+ const roomId = await matrixService.joinRoom(room);
806
+ if (callback) {
807
+ await callback({
808
+ text: `Joined room ${room}.`,
809
+ source: message.content.source
810
+ });
811
+ }
812
+ return {
813
+ success: true,
814
+ data: {
815
+ roomId,
816
+ joined: room
817
+ }
818
+ };
819
+ } catch (err) {
820
+ const error = err instanceof Error ? err.message : String(err);
821
+ if (callback) {
822
+ await callback({
823
+ text: `Failed to join room: ${error}`,
824
+ source: "matrix"
825
+ });
826
+ }
827
+ return { success: false, error };
828
+ }
829
+ },
830
+ examples: [
831
+ [
832
+ {
833
+ name: "{{user1}}",
834
+ content: { text: "Join #general:matrix.org" }
835
+ },
836
+ {
837
+ name: "{{agent}}",
838
+ content: {
839
+ text: "I'll join that room.",
840
+ actions: ["MATRIX_JOIN_ROOM"]
841
+ }
842
+ }
843
+ ]
844
+ ]
845
+ };
846
+
847
+ // src/providers/roomState.ts
848
+ var roomStateProvider = {
849
+ name: "matrixRoomState",
850
+ description: "Provides information about the current Matrix room context",
851
+ get: async (runtime, message, state) => {
852
+ if (message.content.source !== "matrix") {
853
+ return {
854
+ data: {},
855
+ values: {},
856
+ text: ""
857
+ };
858
+ }
859
+ const matrixService = runtime.getService(MATRIX_SERVICE_NAME);
860
+ if (!matrixService || !matrixService.isConnected()) {
861
+ return {
862
+ data: { connected: false },
863
+ values: { connected: false },
864
+ text: ""
865
+ };
866
+ }
867
+ const agentName = state?.agentName || "The agent";
868
+ const room = state?.data?.room;
869
+ const roomId = room?.roomId;
870
+ const roomName = room?.name;
871
+ const isEncrypted = room?.isEncrypted;
872
+ const isDirect = room?.isDirect;
873
+ const memberCount = room?.memberCount;
874
+ const userId = matrixService.getUserId();
875
+ const displayName = getMatrixLocalpart(userId);
876
+ let responseText = "";
877
+ if (isDirect) {
878
+ responseText = `${agentName} is in a direct message conversation on Matrix.`;
879
+ } else {
880
+ const roomLabel = roomName || roomId || "a Matrix room";
881
+ responseText = `${agentName} is currently in Matrix room "${roomLabel}".`;
882
+ if (memberCount) {
883
+ responseText += ` The room has ${memberCount} members.`;
884
+ }
885
+ }
886
+ if (isEncrypted) {
887
+ responseText += " This room has end-to-end encryption enabled.";
888
+ }
889
+ responseText += `
890
+
891
+ Matrix is a decentralized communication protocol. ${agentName} is logged in as ${userId}.`;
892
+ return {
893
+ data: {
894
+ roomId,
895
+ roomName,
896
+ isEncrypted: isEncrypted || false,
897
+ isDirect: isDirect || false,
898
+ memberCount: memberCount || 0,
899
+ userId,
900
+ displayName,
901
+ homeserver: matrixService.getHomeserver(),
902
+ connected: true
903
+ },
904
+ values: {
905
+ roomId,
906
+ roomName,
907
+ isEncrypted: isEncrypted || false,
908
+ isDirect: isDirect || false,
909
+ memberCount: memberCount || 0,
910
+ userId
911
+ },
912
+ text: responseText
913
+ };
914
+ }
915
+ };
916
+
917
+ // src/providers/userContext.ts
918
+ var userContextProvider = {
919
+ name: "matrixUserContext",
920
+ description: "Provides information about the Matrix user in the current conversation",
921
+ get: async (runtime, message, state) => {
922
+ if (message.content.source !== "matrix") {
923
+ return {
924
+ data: {},
925
+ values: {},
926
+ text: ""
927
+ };
928
+ }
929
+ const matrixService = runtime.getService(MATRIX_SERVICE_NAME);
930
+ if (!matrixService || !matrixService.isConnected()) {
931
+ return {
932
+ data: {},
933
+ values: {},
934
+ text: ""
935
+ };
936
+ }
937
+ const agentName = state?.agentName || "The agent";
938
+ const metadata = message.content.metadata;
939
+ const senderInfo = metadata?.senderInfo;
940
+ if (!senderInfo) {
941
+ return {
942
+ data: {},
943
+ values: {},
944
+ text: ""
945
+ };
946
+ }
947
+ const displayName = getMatrixUserDisplayName(senderInfo);
948
+ const localpart = getMatrixLocalpart(senderInfo.userId);
949
+ const responseText = `${agentName} is talking to ${displayName} (${senderInfo.userId}) on Matrix.`;
950
+ return {
951
+ data: {
952
+ userId: senderInfo.userId,
953
+ displayName,
954
+ localpart,
955
+ avatarUrl: senderInfo.avatarUrl
956
+ },
957
+ values: {
958
+ userId: senderInfo.userId,
959
+ displayName,
960
+ localpart
961
+ },
962
+ text: responseText
963
+ };
964
+ }
965
+ };
966
+
967
+ // src/index.ts
968
+ var matrixPlugin = {
969
+ name: "matrix",
970
+ description: "Matrix messaging integration plugin for ElizaOS with E2EE support",
971
+ services: [MatrixService],
972
+ actions: [sendMessage, sendReaction, listRooms, joinRoom],
973
+ providers: [roomStateProvider, userContextProvider],
974
+ tests: [],
975
+ init: async (_config, runtime) => {
976
+ const homeserver = runtime.getSetting("MATRIX_HOMESERVER");
977
+ const userId = runtime.getSetting("MATRIX_USER_ID");
978
+ const accessToken = runtime.getSetting("MATRIX_ACCESS_TOKEN");
979
+ logger2.info("=".repeat(60));
980
+ logger2.info("Matrix Plugin Configuration");
981
+ logger2.info("=".repeat(60));
982
+ logger2.info(` Homeserver: ${homeserver ? `✓ ${homeserver}` : "✗ Missing (required)"}`);
983
+ logger2.info(` User ID: ${userId ? `✓ ${userId}` : "✗ Missing (required)"}`);
984
+ logger2.info(` Access Token: ${accessToken ? "✓ Set" : "✗ Missing (required)"}`);
985
+ logger2.info("=".repeat(60));
986
+ const missing = [];
987
+ if (!homeserver)
988
+ missing.push("MATRIX_HOMESERVER");
989
+ if (!userId)
990
+ missing.push("MATRIX_USER_ID");
991
+ if (!accessToken)
992
+ missing.push("MATRIX_ACCESS_TOKEN");
993
+ if (missing.length > 0) {
994
+ logger2.warn(`Matrix plugin: Missing required configuration: ${missing.join(", ")}`);
995
+ }
996
+ const deviceId = runtime.getSetting("MATRIX_DEVICE_ID");
997
+ const rooms = runtime.getSetting("MATRIX_ROOMS");
998
+ const autoJoin = runtime.getSetting("MATRIX_AUTO_JOIN");
999
+ const encryption = runtime.getSetting("MATRIX_ENCRYPTION");
1000
+ const requireMention = runtime.getSetting("MATRIX_REQUIRE_MENTION");
1001
+ if (deviceId) {
1002
+ logger2.info(` Device ID: ${deviceId}`);
1003
+ }
1004
+ if (rooms) {
1005
+ logger2.info(` Auto-join Rooms: ${rooms}`);
1006
+ }
1007
+ if (autoJoin === "true") {
1008
+ logger2.info(" Auto-join Invites: ✓ Enabled");
1009
+ }
1010
+ if (encryption === "true") {
1011
+ logger2.info(" End-to-End Encryption: ✓ Enabled");
1012
+ }
1013
+ if (requireMention === "true") {
1014
+ logger2.info(" Require Mention: ✓ Enabled (will only respond to mentions in rooms)");
1015
+ }
1016
+ }
1017
+ };
1018
+ var src_default = matrixPlugin;
1019
+ export {
1020
+ userContextProvider,
1021
+ sendReaction,
1022
+ sendMessage,
1023
+ roomStateProvider,
1024
+ matrixMxcToHttp,
1025
+ listRooms,
1026
+ joinRoom,
1027
+ isValidMatrixUserId,
1028
+ isValidMatrixRoomId,
1029
+ isValidMatrixRoomAlias,
1030
+ getMatrixUserDisplayName,
1031
+ getMatrixServerpart,
1032
+ getMatrixLocalpart,
1033
+ src_default as default,
1034
+ MatrixServiceNotInitializedError,
1035
+ MatrixService,
1036
+ MatrixPluginError,
1037
+ MatrixNotConnectedError,
1038
+ MatrixEventTypes,
1039
+ MatrixConfigurationError,
1040
+ MatrixApiError,
1041
+ MAX_MATRIX_MESSAGE_LENGTH,
1042
+ MATRIX_SERVICE_NAME
1043
+ };
1044
+
1045
+ //# debugId=35E58066575BB30D64756E2164756E21