Skip to content

Commit 0ae3da6

Browse files
committedFeb 24, 2025
Enhance RoomResource to manage skill cards and improve message processing
1 parent 1614af1 commit 0ae3da6

File tree

2 files changed

+99
-97
lines changed

2 files changed

+99
-97
lines changed
 

‎packages/host/app/lib/matrix-classes/message-builder.ts

+7-9
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
APP_BOXEL_MESSAGE_MSGTYPE,
1717
} from '@cardstack/runtime-common/matrix-constants';
1818

19+
import { Skill } from '@cardstack/host/components/ai-assistant/skill-menu';
1920
import type CommandService from '@cardstack/host/services/command-service';
2021

2122
import MatrixService from '@cardstack/host/services/matrix-service';
@@ -50,6 +51,7 @@ export default class MessageBuilder {
5051
author: RoomMember;
5152
index: number;
5253
serializedCardFromFragments: (eventId: string) => LooseSingleCardDocument;
54+
skills: Skill[];
5355
events: DiscreteMatrixEvent[];
5456
commandResultEvent?: CommandResultEvent;
5557
},
@@ -156,10 +158,6 @@ export default class MessageBuilder {
156158
}
157159

158160
updateMessage(message: Message) {
159-
if (this.event.content['m.relates_to']?.rel_type !== 'm.replace') {
160-
return;
161-
}
162-
163161
if (message.created.getTime() > this.event.origin_server_ts) {
164162
message.created = new Date(this.event.origin_server_ts);
165163
return;
@@ -173,11 +171,11 @@ export default class MessageBuilder {
173171
: undefined;
174172
message.updated = new Date();
175173

176-
if (this.event.content.msgtype === APP_BOXEL_COMMAND_MSGTYPE) {
177-
if (!message.command) {
178-
message.command = this.buildMessageCommand(message);
179-
}
180-
174+
if (
175+
this.event.content.msgtype === APP_BOXEL_COMMAND_MSGTYPE &&
176+
this.event.content.data?.toolCall
177+
) {
178+
message.command = this.buildMessageCommand(message);
181179
message.isStreamingFinished = true;
182180
message.formattedMessage = this.formattedMessageForCommand;
183181

‎packages/host/app/resources/room.ts

+92-88
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,6 @@ import type CardService from '../services/card-service';
4747
import type CommandService from '../services/command-service';
4848
import type MatrixService from '../services/matrix-service';
4949

50-
interface SkillId {
51-
skillCardId: string;
52-
skillEventId: string;
53-
isActive: boolean;
54-
}
55-
5650
interface Args {
5751
named: {
5852
roomId: string | undefined;
@@ -62,6 +56,8 @@ interface Args {
6256

6357
export class RoomResource extends Resource<Args> {
6458
private _messageCache: TrackedMap<string, Message> = new TrackedMap();
59+
private _skillEventIdToCardIdCache: TrackedMap<string, string> =
60+
new TrackedMap();
6561
private _skillCardsCache: TrackedMap<string, SkillCard> = new TrackedMap();
6662
private _nameEventsCache: TrackedMap<string, RoomNameEvent> =
6763
new TrackedMap();
@@ -103,7 +99,33 @@ export class RoomResource extends Resource<Args> {
10399
if (!memberIds || !memberIds.includes(this.matrixService.aiBotUserId)) {
104100
return;
105101
}
106-
await this.loadFromEvents(roomId);
102+
103+
let index = this._messageCache.size;
104+
// This is brought up to this level so if the
105+
// load task is rerun we can stop processing
106+
for (let event of this.sortedEvents) {
107+
switch (event.type) {
108+
case 'm.room.member':
109+
await this.loadRoomMemberEvent(roomId, event);
110+
break;
111+
case 'm.room.message':
112+
if (this.isCardFragmentEvent(event)) {
113+
await this.loadCardFragment(event);
114+
} else {
115+
await this.loadRoomMessage({ roomId, event, index });
116+
}
117+
break;
118+
case APP_BOXEL_COMMAND_RESULT_EVENT_TYPE:
119+
this.updateMessageCommandResult({ roomId, event, index });
120+
break;
121+
case 'm.room.create':
122+
await this.loadRoomCreateEvent(event);
123+
break;
124+
case 'm.room.name':
125+
await this.loadRoomNameEvent(event);
126+
break;
127+
}
128+
}
107129
} catch (e) {
108130
throw new Error(`Error loading room ${e}`);
109131
}
@@ -165,61 +187,41 @@ export class RoomResource extends Resource<Args> {
165187
return this.events.sort((a, b) => a.origin_server_ts - b.origin_server_ts);
166188
}
167189

168-
@cached
169-
get skillIds(): SkillId[] {
190+
get allSkillEventIds(): Set<string> {
170191
let skillsConfig = this.matrixRoom?.skillsConfig;
171192
if (!skillsConfig) {
172-
return [];
193+
return new Set();
173194
}
174-
let result: SkillId[] = [];
175-
for (let eventId of [
195+
return new Set([
176196
...skillsConfig.enabledEventIds,
177197
...skillsConfig.disabledEventIds,
178-
]) {
179-
let cardDoc;
180-
try {
181-
cardDoc = this.serializedCardFromFragments(eventId);
182-
} catch {
183-
// the skill card fragments might not be loaded yet
198+
]);
199+
}
200+
201+
get skills(): Skill[] {
202+
let skillsConfig = this.matrixRoom?.skillsConfig;
203+
if (!skillsConfig) {
204+
return [];
205+
}
206+
let result: Skill[] = [];
207+
for (let skillEventId of this.allSkillEventIds) {
208+
let cardId = this._skillEventIdToCardIdCache.get(skillEventId);
209+
if (!cardId) {
184210
continue;
185211
}
186-
if (!cardDoc.data.id) {
212+
let skillCard = this._skillCardsCache.get(cardId);
213+
if (!skillCard) {
187214
continue;
188215
}
189-
let cardId = cardDoc.data.id;
190-
if (!this._skillCardsCache.has(cardId)) {
191-
this.cardService
192-
.createFromSerialized(cardDoc.data, cardDoc)
193-
.then((skillsCard) => {
194-
this._skillCardsCache.set(cardId, skillsCard as SkillCard);
195-
});
196-
}
197216
result.push({
198-
skillCardId: cardDoc.data.id,
199-
skillEventId: eventId,
200-
isActive: skillsConfig.enabledEventIds.includes(eventId),
217+
card: skillCard,
218+
skillEventId,
219+
isActive: skillsConfig.enabledEventIds.includes(skillEventId),
201220
});
202221
}
203222
return result;
204223
}
205224

206-
@cached
207-
get skills(): Skill[] {
208-
return this.skillIds
209-
.map(({ skillCardId, skillEventId, isActive }) => {
210-
let card = this._skillCardsCache.get(skillCardId);
211-
if (card) {
212-
return {
213-
card,
214-
skillEventId,
215-
isActive,
216-
};
217-
}
218-
return null;
219-
})
220-
.filter(Boolean) as Skill[];
221-
}
222-
223225
@cached
224226
get created() {
225227
if (this._createEvent) {
@@ -280,30 +282,6 @@ export class RoomResource extends Resource<Args> {
280282
}
281283
});
282284

283-
private async loadFromEvents(roomId: string) {
284-
let index = this._messageCache.size;
285-
286-
for (let event of this.sortedEvents) {
287-
switch (event.type) {
288-
case 'm.room.member':
289-
await this.loadRoomMemberEvent(roomId, event);
290-
break;
291-
case 'm.room.message':
292-
this.loadRoomMessage({ roomId, event, index });
293-
break;
294-
case APP_BOXEL_COMMAND_RESULT_EVENT_TYPE:
295-
this.updateMessageCommandResult({ roomId, event, index });
296-
break;
297-
case 'm.room.create':
298-
await this.loadRoomCreateEvent(event);
299-
break;
300-
case 'm.room.name':
301-
await this.loadRoomNameEvent(event);
302-
break;
303-
}
304-
}
305-
}
306-
307285
private async loadRoomMemberEvent(
308286
roomId: string,
309287
event: InviteEvent | JoinEvent | LeaveEvent,
@@ -330,23 +308,6 @@ export class RoomResource extends Resource<Args> {
330308
roomId: string;
331309
event: MessageEvent | CommandEvent | CardMessageEvent;
332310
index: number;
333-
}) {
334-
if (event.content.msgtype === APP_BOXEL_CARDFRAGMENT_MSGTYPE) {
335-
this._fragmentCache.set(event.event_id, event.content);
336-
return;
337-
}
338-
339-
this.upsertMessage({ roomId, event, index });
340-
}
341-
342-
private upsertMessage({
343-
roomId,
344-
event,
345-
index,
346-
}: {
347-
roomId: string;
348-
event: MessageEvent | CommandEvent | CardMessageEvent;
349-
index: number;
350311
}) {
351312
let effectiveEventId = this.getEffectiveEventId(event);
352313

@@ -362,6 +323,7 @@ export class RoomResource extends Resource<Args> {
362323
index,
363324
serializedCardFromFragments: this.serializedCardFromFragments,
364325
events: this.events,
326+
skills: this.skills,
365327
});
366328

367329
if (!message) {
@@ -407,6 +369,7 @@ export class RoomResource extends Resource<Args> {
407369
index,
408370
serializedCardFromFragments: this.serializedCardFromFragments,
409371
events: this.events,
372+
skills: this.skills,
410373
commandResultEvent: event,
411374
});
412375
messageBuilder.updateMessageCommandResult(message);
@@ -508,6 +471,47 @@ export class RoomResource extends Resource<Args> {
508471
!this.isDisplayingCode(message),
509472
);
510473
}
474+
475+
private async loadCardFragment(
476+
event: CardMessageEvent & {
477+
content: { msgtype: typeof APP_BOXEL_CARDFRAGMENT_MSGTYPE };
478+
},
479+
) {
480+
let eventId = event.event_id;
481+
this._fragmentCache.set(eventId, event.content);
482+
if (
483+
!this.allSkillEventIds.has(eventId) ||
484+
this._skillEventIdToCardIdCache.has(eventId)
485+
) {
486+
return;
487+
}
488+
await this.loadSkillCardIntoCache(eventId);
489+
}
490+
491+
private async loadSkillCardIntoCache(eventId: string) {
492+
let cardDoc = this.serializedCardFromFragments(eventId);
493+
if (!cardDoc.data.id) {
494+
console.warn(
495+
`No card id found for skill event id ${eventId}, this should not happen, this can happen if you add a skill card to a room without saving it, and without giving it an ID`,
496+
);
497+
return;
498+
}
499+
let cardId = cardDoc.data.id;
500+
let skillCard = (await this.cardService.createFromSerialized(
501+
cardDoc.data,
502+
cardDoc,
503+
)) as SkillCard;
504+
this._skillCardsCache.set(cardId, skillCard);
505+
this._skillEventIdToCardIdCache.set(eventId, cardId);
506+
}
507+
508+
private isCardFragmentEvent(
509+
event: MessageEvent | CommandEvent | CardMessageEvent,
510+
): event is CardMessageEvent & {
511+
content: { msgtype: typeof APP_BOXEL_CARDFRAGMENT_MSGTYPE };
512+
} {
513+
return event.content.msgtype === APP_BOXEL_CARDFRAGMENT_MSGTYPE;
514+
}
511515
}
512516

513517
export function getRoom(

0 commit comments

Comments
 (0)