Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Commands on skill cards #2132

Merged
merged 63 commits into from
Feb 26, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
45951e5
Skills can contain links to commands
IanCal Feb 9, 2025
bc512a3
Add sending of command definitions
IanCal Feb 10, 2025
5d68bc2
Add function name to commands in skills, support in aibot
IanCal Feb 10, 2025
8eb5e5b
Add lookup and loading of the commands on the host side
IanCal Feb 10, 2025
94d149a
Remove logging
IanCal Feb 10, 2025
e1a463e
Improve comment to explain what we're actually doing
IanCal Feb 10, 2025
1e96eb4
Export default and class name
IanCal Feb 10, 2025
4423aed
Undo erroneous capitalisation
IanCal Feb 10, 2025
69d94e3
No default export, keep original casing
IanCal Feb 11, 2025
e0912ca
Fix imports
IanCal Feb 11, 2025
7ab89f8
Fix more named imports
IanCal Feb 11, 2025
d97f4c8
Inconsistent casing
IanCal Feb 11, 2025
c0e3850
Stronger typing for TS
IanCal Feb 11, 2025
3eb3739
Remove unused import
IanCal Feb 11, 2025
4039f66
Add boxel UI state command to shimmed modules and fix import
IanCal Feb 11, 2025
7efc2d6
Fix casing in import
IanCal Feb 11, 2025
e068363
Again, casing issues
IanCal Feb 11, 2025
b6d0297
Test running command from skill
IanCal Feb 11, 2025
63d3519
Test for commands on skills
IanCal Feb 11, 2025
56b2c12
Fix hash suffix
IanCal Feb 11, 2025
b0f66c3
Update hashing function
IanCal Feb 11, 2025
a69a31a
Remove logging
IanCal Feb 11, 2025
a65d474
Avoid creating room resources in multiple places
IanCal Feb 11, 2025
43e109e
Improve setting of active llm
IanCal Feb 11, 2025
7d986f9
Load skills as we load events
IanCal Feb 11, 2025
7fb4bf4
Remove unneccessary async
IanCal Feb 11, 2025
4cbc99a
Support processing more out of order, add members and user ids as qui…
IanCal Feb 12, 2025
fcfd03c
Iterate specifically
IanCal Feb 12, 2025
1f7fe65
test removing restriction on users for tests
IanCal Feb 12, 2025
8a22952
Bring back limits for room resource loading
IanCal Feb 12, 2025
a7ea41d
Remove pauses?
IanCal Feb 13, 2025
54beb97
Merge branch 'main' into return-of-the-skill-commands
IanCal Feb 13, 2025
bd907fa
Fix eslint issues
lukemelia Feb 17, 2025
a85f478
Lint fixes
lukemelia Feb 17, 2025
7df2e0e
Merge branch 'main' into return-of-the-skill-commands
lukemelia Feb 17, 2025
78860a3
Merge branch 'main' into return-of-the-skill-commands
lukemelia Feb 17, 2025
738afb3
Eliminate a relative reference in seed realm that shouldn't be relative
lukemelia Feb 17, 2025
393fe3e
Merge branch 'main' into return-of-the-skill-commands
lukemelia Feb 17, 2025
d01b19a
Better handling of commands exported as default
lukemelia Feb 18, 2025
7544d50
Merge branch 'main' into return-of-the-skill-commands
lukemelia Feb 18, 2025
22ecb4e
Merge branch 'faster-matrix-specs' into return-of-the-skill-commands
lukemelia Feb 18, 2025
5c32d82
Merge branch 'main' into return-of-the-skill-commands
lukemelia Feb 18, 2025
2d3ad9d
Safer checking for toolCall
lukemelia Feb 18, 2025
447be78
Merge branch 'main' into return-of-the-skill-commands
lukemelia Feb 19, 2025
b20fb12
Fix an import in generator code
lukemelia Feb 19, 2025
167b145
Merge branch 'main' into return-of-the-skill-commands
lukemelia Feb 19, 2025
ba864b2
Merge branch 'main' into return-of-the-skill-commands
lukemelia Feb 19, 2025
7be953a
Merge branch 'reuse-room-resource' into return-of-the-skill-commands
lukemelia Feb 19, 2025
8b99982
Merge branch 'main' into return-of-the-skill-commands
lukemelia Feb 19, 2025
bad2fd1
Merge branch 'check-room-members-before-processing-events' into retur…
lukemelia Feb 21, 2025
65b9a47
Merge branch 'main' into return-of-the-skill-commands
lukemelia Feb 21, 2025
5e51d57
Merge branch 'main' into return-of-the-skill-commands
lukemelia Feb 24, 2025
d46fd35
Merge branch 'main' into return-of-the-skill-commands
lukemelia Feb 24, 2025
0182bd5
Remove unneeded changes
lukemelia Feb 24, 2025
a88f906
AI test fixes
lukemelia Feb 24, 2025
0ffa9f1
Merge branch 'code-ref-modules' into return-of-the-skill-commands
lukemelia Feb 24, 2025
57d4693
Merge branch 'main' into return-of-the-skill-commands
lukemelia Feb 24, 2025
3a8219e
Merge branch 'main' into return-of-the-skill-commands
lukemelia Feb 25, 2025
2c43b0e
Merge branch 'main' into return-of-the-skill-commands
lukemelia Feb 25, 2025
af764f2
Merge branch 'main' into return-of-the-skill-commands
lukemelia Feb 25, 2025
519e618
Merge branch 'main' into return-of-the-skill-commands
lukemelia Feb 25, 2025
4309af8
Merge branch 'main' into return-of-the-skill-commands
lukemelia Feb 25, 2025
e218961
Merge branch 'main' into return-of-the-skill-commands
lukemelia Feb 26, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 19 additions & 10 deletions packages/host/app/lib/matrix-classes/message-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { inject as service } from '@ember/service';

import {
LooseSingleCardDocument,
ResolvedCodeRef,
sanitizeHtml,
} from '@cardstack/runtime-common';

Expand All @@ -16,12 +17,12 @@ import {
APP_BOXEL_MESSAGE_MSGTYPE,
} from '@cardstack/runtime-common/matrix-constants';

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

import MatrixService from '@cardstack/host/services/matrix-service';

import type { CommandStatus } from 'https://cardstack.com/base/command';

import { SerializedFile } from 'https://cardstack.com/base/file-api';
import type {
CardMessageContent,
Expand Down Expand Up @@ -50,6 +51,7 @@ export default class MessageBuilder {
author: RoomMember;
index: number;
serializedCardFromFragments: (eventId: string) => LooseSingleCardDocument;
skills: Skill[];
events: DiscreteMatrixEvent[];
commandResultEvent?: CommandResultEvent;
},
Expand Down Expand Up @@ -156,10 +158,6 @@ export default class MessageBuilder {
}

updateMessage(message: Message) {
if (this.event.content['m.relates_to']?.rel_type !== 'm.replace') {
return;
}

if (message.created.getTime() > this.event.origin_server_ts) {
message.created = new Date(this.event.origin_server_ts);
return;
Expand All @@ -173,11 +171,11 @@ export default class MessageBuilder {
: undefined;
message.updated = new Date();

if (this.event.content.msgtype === APP_BOXEL_COMMAND_MSGTYPE) {
if (!message.command) {
message.command = this.buildMessageCommand(message);
}

if (
this.event.content.msgtype === APP_BOXEL_COMMAND_MSGTYPE &&
this.event.content.data?.toolCall
) {
message.command = this.buildMessageCommand(message);
message.isStreamingFinished = true;
message.formattedMessage = this.formattedMessageForCommand;

Expand Down Expand Up @@ -219,11 +217,22 @@ export default class MessageBuilder {
}) as CommandResultEvent | undefined);

let command = event.content.data.toolCall;
// Find command in skills
let codeRef: ResolvedCodeRef | undefined;
findCommand: for (let skill of this.builderContext.skills) {
for (let skillCommand of skill.card.commands) {
if (command.name === skillCommand.functionName) {
codeRef = skillCommand.codeRef;
break findCommand;
}
}
}
let messageCommand = new MessageCommand(
message,
command.id,
command.name,
command.arguments,
codeRef,
this.builderContext.effectiveEventId,
(commandResultEvent?.content['m.relates_to']?.key ||
'ready') as CommandStatus,
Expand Down
3 changes: 3 additions & 0 deletions packages/host/app/lib/matrix-classes/message-command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { inject as service } from '@ember/service';

import { tracked } from '@glimmer/tracking';

import { ResolvedCodeRef } from '@cardstack/runtime-common';

import type CardService from '@cardstack/host/services/card-service';
import type CommandService from '@cardstack/host/services/command-service';
import type MatrixService from '@cardstack/host/services/matrix-service';
Expand All @@ -25,6 +27,7 @@ export default class MessageCommand {
public toolCallId: string,
name: string,
payload: any, //arguments of toolCall. Its not called arguments due to lint
public codeRef: ResolvedCodeRef | undefined,
public eventId: string,
commandStatus: CommandStatus,
commandResultCardEventId: string | undefined,
Expand Down
33 changes: 30 additions & 3 deletions packages/host/app/resources/room.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { type LooseSingleCardDocument } from '@cardstack/runtime-common';
import {
APP_BOXEL_CARDFRAGMENT_MSGTYPE,
APP_BOXEL_COMMAND_MSGTYPE,
APP_BOXEL_COMMAND_DEFINITIONS_MSGTYPE,
APP_BOXEL_COMMAND_RESULT_EVENT_TYPE,
DEFAULT_LLM,
} from '@cardstack/runtime-common/matrix-constants';
Expand All @@ -31,9 +32,8 @@ import type {
CommandResultEvent,
} from 'https://cardstack.com/base/matrix-event';

import { SkillCard } from 'https://cardstack.com/base/skill-card';
import type { SkillCard } from 'https://cardstack.com/base/skill-card';

import { Skill } from '../components/ai-assistant/skill-menu';
import {
RoomMember,
type RoomMemberInterface,
Expand All @@ -42,6 +42,8 @@ import { Message } from '../lib/matrix-classes/message';

import MessageBuilder from '../lib/matrix-classes/message-builder';

import type { Skill } from '../components/ai-assistant/skill-menu';

import type Room from '../lib/matrix-classes/room';

import type CardService from '../services/card-service';
Expand All @@ -57,9 +59,9 @@ interface Args {

export class RoomResource extends Resource<Args> {
private _messageCache: TrackedMap<string, Message> = new TrackedMap();
private _skillCardsCache: TrackedMap<string, SkillCard> = new TrackedMap();
private _skillEventIdToCardIdCache: TrackedMap<string, string> =
new TrackedMap();
private _skillCardsCache: TrackedMap<string, SkillCard> = new TrackedMap();
private _nameEventsCache: TrackedMap<string, RoomNameEvent> =
new TrackedMap();
@tracked private _createEvent: RoomCreateEvent | undefined;
Expand Down Expand Up @@ -112,6 +114,8 @@ export class RoomResource extends Resource<Args> {
case 'm.room.message':
if (this.isCardFragmentEvent(event)) {
await this.loadCardFragment(event);
} else if (this.isCommandDefinitionsEvent(event)) {
break;
} else {
await this.loadRoomMessage({
roomId,
Expand Down Expand Up @@ -224,6 +228,17 @@ export class RoomResource extends Resource<Args> {
return result;
}

get commands() {
// Usable commands are all commands on *active* skills
let commands = [];
for (let skill of this.skills) {
if (skill.isActive) {
commands.push(...skill.card.commands);
}
}
return commands;
}

@cached
get created() {
if (this._createEvent) {
Expand Down Expand Up @@ -302,6 +317,16 @@ export class RoomResource extends Resource<Args> {
});
}

private isCommandDefinitionsEvent(
event:
| MessageEvent
| CommandEvent
| CardMessageEvent
| CommandDefinitionsEvent,
): event is CommandDefinitionsEvent {
return event.content.msgtype === APP_BOXEL_COMMAND_DEFINITIONS_MSGTYPE;
}

private isCardFragmentEvent(
event:
| MessageEvent
Expand Down Expand Up @@ -374,6 +399,7 @@ export class RoomResource extends Resource<Args> {
index,
serializedCardFromFragments: this.serializedCardFromFragments,
events: this.events,
skills: this.skills,
});

if (!message) {
Expand Down Expand Up @@ -420,6 +446,7 @@ export class RoomResource extends Resource<Args> {
index,
serializedCardFromFragments: this.serializedCardFromFragments,
events: this.events,
skills: this.skills,
commandResultEvent: event,
});
messageBuilder.updateMessageCommandResult(message);
Expand Down
14 changes: 14 additions & 0 deletions packages/host/app/services/command-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
CommandContextStamp,
ResolvedCodeRef,
isResolvedCodeRef,
getClass,
} from '@cardstack/runtime-common';

import type MatrixService from '@cardstack/host/services/matrix-service';
Expand Down Expand Up @@ -130,6 +131,19 @@ export default class CommandService extends Service {

// lookup command
let { command: commandToRun } = this.commands.get(command.name) ?? {};
// If we don't find it in the one-offs, start searching for
// one in the skills we can construct
if (!commandToRun) {
// here we can get the coderef from the messagecommand
let commandCodeRef = command.codeRef;
if (commandCodeRef) {
let CommandConstructor = (await getClass(
commandCodeRef,
this.loaderService.loader,
)) as { new (context: CommandContext): Command<any, any> };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this type should get a name?

commandToRun = new CommandConstructor(this.commandContext);
}
}

if (commandToRun) {
// Get the input type and validate/construct the payload
Expand Down
58 changes: 56 additions & 2 deletions packages/host/app/services/matrix-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
LooseCardResource,
ResolvedCodeRef,
aiBotUsername,
getClass,
} from '@cardstack/runtime-common';

import {
Expand All @@ -42,6 +43,7 @@ import {
APP_BOXEL_CARD_FORMAT,
APP_BOXEL_CARDFRAGMENT_MSGTYPE,
APP_BOXEL_COMMAND_MSGTYPE,
APP_BOXEL_COMMAND_DEFINITIONS_MSGTYPE,
APP_BOXEL_COMMAND_RESULT_EVENT_TYPE,
APP_BOXEL_COMMAND_RESULT_WITH_NO_OUTPUT_MSGTYPE,
APP_BOXEL_COMMAND_RESULT_WITH_OUTPUT_MSGTYPE,
Expand Down Expand Up @@ -73,10 +75,11 @@ import type {
MatrixEvent as DiscreteMatrixEvent,
CommandResultWithNoOutputContent,
CommandResultWithOutputContent,
CommandDefinitionsContent,
} from 'https://cardstack.com/base/matrix-event';

import type { Tool } from 'https://cardstack.com/base/matrix-event';
import { SkillCard } from 'https://cardstack.com/base/skill-card';
import { CommandField, SkillCard } from 'https://cardstack.com/base/skill-card';

import AddSkillsToRoomCommand from '../commands/add-skills-to-room';
import { importResource } from '../resources/import';
Expand Down Expand Up @@ -513,7 +516,8 @@ export default class MatrixService extends Service {
| CardMessageContent
| CardFragmentContent
| CommandResultWithNoOutputContent
| CommandResultWithOutputContent,
| CommandResultWithOutputContent
| CommandDefinitionsContent,
) {
let roomData = await this.ensureRoomData(roomId);
return roomData.mutex.dispatch(async () => {
Expand Down Expand Up @@ -578,11 +582,61 @@ export default class MatrixService extends Service {
}
}

async addCommandDefinitionsToRoomHistory(
commandDefinitions: CommandField[],
roomId: string,
) {
// Create the command defs so getting the json schema
// and send it to the matrix room.
let commandDefinitionSchemas: {
codeRef: ResolvedCodeRef;
tool: Tool;
}[] = [];
const mappings = await basicMappings(this.loaderService.loader);
for (let commandDef of commandDefinitions) {
const Command = await getClass(
commandDef.codeRef,
this.loaderService.loader,
);
const command = new Command(this.commandService.commandContext);
const name = commandDef.functionName;
commandDefinitionSchemas.push({
codeRef: commandDef.codeRef,
tool: {
type: 'function',
function: {
name,
description: command.description,
parameters: {
type: 'object',
properties: {
description: {
type: 'string',
},
...(await command.getInputJsonSchema(this.cardAPI, mappings)),
},
required: ['attributes', 'description'],
},
},
},
});
}
await this.sendEvent(roomId, 'm.room.message', {
msgtype: APP_BOXEL_COMMAND_DEFINITIONS_MSGTYPE,
body: 'Command Definitions',
data: {
commandDefinitions: commandDefinitionSchemas,
},
});
}

async addSkillCardsToRoomHistory(
skills: SkillCard[],
roomId: string,
opts?: CardAPI.SerializeOpts,
): Promise<string[]> {
const commandDefinitions = skills.flatMap((skill) => skill.commands);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to ensure uniqueness here?

await this.addCommandDefinitionsToRoomHistory(commandDefinitions, roomId);
return this.addCardsToRoom(skills, roomId, this.skillCardHashes, opts);
}

Expand Down
Loading