From 49e7075c499bff2097a478f54a767beedc7cb71c Mon Sep 17 00:00:00 2001 From: Abdurrehman Subhani Date: Fri, 13 Dec 2024 12:00:16 +0500 Subject: [PATCH 1/5] add replyToMessageId to say action, add messageId to conversations --- .../react-agents/classes/chats-manager.ts | 10 +++++++++- .../react-agents/components/core/chat.tsx | 15 +++++++++++++++ .../components/util/default-components.tsx | 3 ++- .../packages/react-agents/types/react-agents.d.ts | 1 + .../react-agents/util/loadMessagesFromDatabase.js | 10 ++++++---- .../packages/react-agents/util/message-utils.ts | 1 + .../react-agents/util/saveMessageToDatabase.js | 1 + 7 files changed, 35 insertions(+), 6 deletions(-) diff --git a/packages/usdk/packages/upstreet-agent/packages/react-agents/classes/chats-manager.ts b/packages/usdk/packages/upstreet-agent/packages/react-agents/classes/chats-manager.ts index 607af52ac..aec12088e 100644 --- a/packages/usdk/packages/upstreet-agent/packages/react-agents/classes/chats-manager.ts +++ b/packages/usdk/packages/upstreet-agent/packages/react-agents/classes/chats-manager.ts @@ -190,7 +190,15 @@ export class ChatsManager { multiplayerConnection.addEventListener('chat', async (e) => { const { playerId, message } = e.data; if (playerId !== agent.id) { - await conversation.addLocalMessage(message); + const playerSpec = conversation.agentsMap.get(playerId); + const agent = { + id: playerId, + name: playerSpec.name, + }; + const formattedMessage = formatConversationMessage(message, { + agent, + }); + await conversation.addLocalMessage(formattedMessage); // } else { // // XXX fix this // console.warn('received own message from realms "chat" event; this should not happen', message); diff --git a/packages/usdk/packages/upstreet-agent/packages/react-agents/components/core/chat.tsx b/packages/usdk/packages/upstreet-agent/packages/react-agents/components/core/chat.tsx index 019562fc1..46beb916b 100644 --- a/packages/usdk/packages/upstreet-agent/packages/react-agents/components/core/chat.tsx +++ b/packages/usdk/packages/upstreet-agent/packages/react-agents/components/core/chat.tsx @@ -10,15 +10,30 @@ export const ChatActions = () => { type="say" description={dedent`\ Say something in the chat. + + You should use replyToMessageId in two specific cases: + 1. When you are directly tagged or mentioned in a message + 2. When you need to reference a specific previous message according to the conversation context. + + In all other cases, send your message without a reply reference. `} schema={ z.object({ text: z.string(), + replyToMessageId: z.string().optional(), }) } examples={[ { text: 'Hello, there! How are you doing?', + + }, + { + text: 'What are you talking about?', + }, + { + text: 'Not much, what about you?', + replyToMessageId: '123', }, ]} // handler={async (e: PendingActionEvent) => { diff --git a/packages/usdk/packages/upstreet-agent/packages/react-agents/components/util/default-components.tsx b/packages/usdk/packages/upstreet-agent/packages/react-agents/components/util/default-components.tsx index ec14109dc..53c765592 100644 --- a/packages/usdk/packages/upstreet-agent/packages/react-agents/components/util/default-components.tsx +++ b/packages/usdk/packages/upstreet-agent/packages/react-agents/components/util/default-components.tsx @@ -281,9 +281,10 @@ const CachedMessagesPrompt = () => { '\n' + cachedMessages .map((action) => { - const { /*userId,*/ name, method, args, attachments = [], timestamp } = action; + const { /*userId,*/ messageId, name, method, args, attachments = [], timestamp } = action; const j = { // userId, + messageId, name, method, args, diff --git a/packages/usdk/packages/upstreet-agent/packages/react-agents/types/react-agents.d.ts b/packages/usdk/packages/upstreet-agent/packages/react-agents/types/react-agents.d.ts index fee90b32a..3df86b8a2 100644 --- a/packages/usdk/packages/upstreet-agent/packages/react-agents/types/react-agents.d.ts +++ b/packages/usdk/packages/upstreet-agent/packages/react-agents/types/react-agents.d.ts @@ -214,6 +214,7 @@ export type Attachment = FormattedAttachment & { url?: string; }; export type ActionMessage = { + messageId: string; userId: string; name: string; method: string; diff --git a/packages/usdk/packages/upstreet-agent/packages/react-agents/util/loadMessagesFromDatabase.js b/packages/usdk/packages/upstreet-agent/packages/react-agents/util/loadMessagesFromDatabase.js index 4f8d444fd..1c470f840 100644 --- a/packages/usdk/packages/upstreet-agent/packages/react-agents/util/loadMessagesFromDatabase.js +++ b/packages/usdk/packages/upstreet-agent/packages/react-agents/util/loadMessagesFromDatabase.js @@ -5,8 +5,9 @@ export async function loadMessagesFromDatabase({ limit, }) { const { error, data } = await supabase - .from( 'agent_messages' ) + .from('agent_messages') .select([ + 'id', 'method', 'args', 'attachments', @@ -16,8 +17,8 @@ export async function loadMessagesFromDatabase({ ].join(',')) .eq('user_id', agentId) .eq('conversation_id', conversationId) - .order( 'created_at', { ascending: true }) - .limit( limit ); + .order('created_at', { ascending: true }) + .limit(limit); if (!error) { return decodeMessages(data); } else { @@ -26,7 +27,8 @@ export async function loadMessagesFromDatabase({ } function decodeMessages(messages) { - return messages.map( message => ({ + return messages.map(message => ({ + messageId: message.id, method: message.method, args: message.args, attachments: message.attachments, diff --git a/packages/usdk/packages/upstreet-agent/packages/react-agents/util/message-utils.ts b/packages/usdk/packages/upstreet-agent/packages/react-agents/util/message-utils.ts index 25c3bb7d6..2f96d69ac 100644 --- a/packages/usdk/packages/upstreet-agent/packages/react-agents/util/message-utils.ts +++ b/packages/usdk/packages/upstreet-agent/packages/react-agents/util/message-utils.ts @@ -14,6 +14,7 @@ export const formatConversationMessage = (rawMessage: PendingActionMessage, { const { method, args, attachments } = rawMessage; const timestamp = new Date(); const newMessage = { + messageId: crypto.randomUUID(), userId, name, method, diff --git a/packages/usdk/packages/upstreet-agent/packages/react-agents/util/saveMessageToDatabase.js b/packages/usdk/packages/upstreet-agent/packages/react-agents/util/saveMessageToDatabase.js index 39211d815..7e5932838 100644 --- a/packages/usdk/packages/upstreet-agent/packages/react-agents/util/saveMessageToDatabase.js +++ b/packages/usdk/packages/upstreet-agent/packages/react-agents/util/saveMessageToDatabase.js @@ -26,6 +26,7 @@ async function encodeMessage(message, jwt, userId, conversationId) { args: message.args, }), { jwt }); return { + id: message.messageId, method: message.method, args: message.args, attachments: message.attachments, From ca6256d2be3f757e181d64f61b000dd5051fac94 Mon Sep 17 00:00:00 2001 From: Abdurrehman Subhani Date: Fri, 13 Dec 2024 15:41:08 +0500 Subject: [PATCH 2/5] add replyFn callback function to conversation object, update discord manager to used replyFn for say messages having "replyToMessageId", add getMessageById method to messageCache, --- .../classes/conversation-object.ts | 16 +++++++ .../react-agents/classes/discord-manager.ts | 46 ++++++++++++++++++- .../react-agents/classes/message-cache.ts | 3 ++ .../react-agents/components/core/chat.tsx | 3 +- .../react-agents/types/react-agents.d.ts | 3 ++ 5 files changed, 69 insertions(+), 2 deletions(-) diff --git a/packages/usdk/packages/upstreet-agent/packages/react-agents/classes/conversation-object.ts b/packages/usdk/packages/upstreet-agent/packages/react-agents/classes/conversation-object.ts index 27d0395ab..3577c0714 100644 --- a/packages/usdk/packages/upstreet-agent/packages/react-agents/classes/conversation-object.ts +++ b/packages/usdk/packages/upstreet-agent/packages/react-agents/classes/conversation-object.ts @@ -8,6 +8,7 @@ import { PlayableAudioStream, GetHashFn, MessageCache, + ReplyFn, } from '../types' import { SceneObject } from '../classes/scene-object'; import { Player } from 'react-agents-client/util/player.mjs'; @@ -24,17 +25,20 @@ export class ConversationObject extends EventTarget { getHash: GetHashFn; // XXX this can be a string, since conversation hashes do not change (?) messageCache: MessageCache; numTyping: number = 0; + replyFn: ReplyFn | null; constructor({ agent, agentsMap = new Map(), scene = null, getHash = () => '', + replyFn = null, }: { agent: ActiveAgentObject | null; agentsMap?: Map; scene?: SceneObject | null; getHash?: GetHashFn; + replyFn?: ReplyFn; }) { super(); @@ -42,6 +46,7 @@ export class ConversationObject extends EventTarget { this.agentsMap = agentsMap; this.scene = scene; this.getHash = getHash; + this.replyFn = replyFn; this.messageCache = new MessageCacheConstructor({ loader: async () => { const supabase = this.agent.appContextValue.useSupabase(); @@ -138,6 +143,10 @@ export class ConversationObject extends EventTarget { ].join('\n'); } + getMessageById(messageId: string) { + return this.messageCache.getMessageById(messageId); + } + getCachedMessages(filter?: MessageFilter) { const agent = filter?.agent; const idMatches = agent?.idMatches; @@ -198,6 +207,13 @@ export class ConversationObject extends EventTarget { return [] as ActionMessage[]; } */ + async reply(message: ActionMessage, replyToMessageId: string) { + if (!this.replyFn) { + throw new Error('No reply function configured for this conversation'); + } + await this.replyFn(message, replyToMessageId); + } + // handle a message from the network async addLocalMessage(message: ActionMessage) { const { diff --git a/packages/usdk/packages/upstreet-agent/packages/react-agents/classes/discord-manager.ts b/packages/usdk/packages/upstreet-agent/packages/react-agents/classes/discord-manager.ts index 67e7e1fb1..470e4b9ac 100644 --- a/packages/usdk/packages/upstreet-agent/packages/react-agents/classes/discord-manager.ts +++ b/packages/usdk/packages/upstreet-agent/packages/react-agents/classes/discord-manager.ts @@ -7,6 +7,7 @@ import { ExtendableMessageEvent, ActionMessageEventData, PlayableAudioStream, + ActionMessage, } from '../types'; import { ConversationObject, @@ -77,7 +78,8 @@ const bindOutgoing = ({ if (method === 'say') { let { text, - } = args as { text: string }; + replyToMessageId, + } = args as { text: string, replyToMessageId?: string }; if (attachments && Object.keys(attachments).length > 0) { text += '\n' + Object.values(attachments) @@ -86,6 +88,11 @@ const bindOutgoing = ({ .join('\n'); } + if (replyToMessageId) { + conversation.reply(message, replyToMessageId); + return; + } + discordBotClient.input.writeText(text, { channelId, userId, @@ -238,6 +245,23 @@ export class DiscordBot extends EventTarget { getHash: () => { return `discord:channel:${channelId}`; }, + replyFn: async (message: ActionMessage, replyToMessageId: string) => { + console.log('replyFn', { + message, + replyToMessageId, + }); + + const replyObject = { + content: message.args.text, + reply: { + messageReference: replyToMessageId, + }, + }; + discordBotClient.input.writeText(replyObject, { + channelId, + userId: message.args.src_user_id, + }); + }, }); this.agent.conversationManager.addConversation(conversation); @@ -284,6 +308,24 @@ export class DiscordBot extends EventTarget { getHash: () => { return `discord:dm:${userId}`; }, + replyFn: async (message: ActionMessage, replyToMessageId: string) => { + const replyMessage = conversation.getMessageById(replyToMessageId); + const { discordMessageId } = replyMessage?.args; + const { + text, + } = message.args; + + const replyObject = { + content: text, + reply: { + messageReference: discordMessageId, + }, + }; + + discordBotClient.input.writeText(replyObject, { + userId, + }); + }, }); this.agent.conversationManager.addConversation(conversation); @@ -353,6 +395,7 @@ export class DiscordBot extends EventTarget { text, channelId, // if there is no channelId, it's a DM // XXX discord channel/dm distinction can be made more explicit with a type: string field... + messageId, } = e.data; // look up conversation @@ -367,6 +410,7 @@ export class DiscordBot extends EventTarget { method: 'say', args: { text, + discordMessageId: messageId, }, }; const id = getIdFromUserId(userId); diff --git a/packages/usdk/packages/upstreet-agent/packages/react-agents/classes/message-cache.ts b/packages/usdk/packages/upstreet-agent/packages/react-agents/classes/message-cache.ts index 10d5829a0..32e848760 100644 --- a/packages/usdk/packages/upstreet-agent/packages/react-agents/classes/message-cache.ts +++ b/packages/usdk/packages/upstreet-agent/packages/react-agents/classes/message-cache.ts @@ -21,6 +21,9 @@ export class MessageCache extends EventTarget { this.loader = loader; } + getMessageById(messageId: string) { + return this.#messages.find((m) => m.messageId === messageId); + } getMessages() { return this.#messages; } diff --git a/packages/usdk/packages/upstreet-agent/packages/react-agents/components/core/chat.tsx b/packages/usdk/packages/upstreet-agent/packages/react-agents/components/core/chat.tsx index 46beb916b..392071d55 100644 --- a/packages/usdk/packages/upstreet-agent/packages/react-agents/components/core/chat.tsx +++ b/packages/usdk/packages/upstreet-agent/packages/react-agents/components/core/chat.tsx @@ -13,7 +13,8 @@ export const ChatActions = () => { You should use replyToMessageId in two specific cases: 1. When you are directly tagged or mentioned in a message - 2. When you need to reference a specific previous message according to the conversation context. + 2. When you need to reference a specific previous message according to the conversation context + 3. Not every message needs a reply reference so you should only use it when necessary In all other cases, send your message without a reply reference. `} diff --git a/packages/usdk/packages/upstreet-agent/packages/react-agents/types/react-agents.d.ts b/packages/usdk/packages/upstreet-agent/packages/react-agents/types/react-agents.d.ts index 3df86b8a2..cf0c4b765 100644 --- a/packages/usdk/packages/upstreet-agent/packages/react-agents/types/react-agents.d.ts +++ b/packages/usdk/packages/upstreet-agent/packages/react-agents/types/react-agents.d.ts @@ -219,11 +219,13 @@ export type ActionMessage = { name: string; method: string; args: any; + replyToMessageId?: string; attachments?: Attachment[]; human: boolean; // XXX can be converted to flags hidden: boolean; timestamp: Date; }; +export type ReplyFn = (message: ActionMessage, replyToMessageId: string) => Promise; export type PendingActionMessage = { method: string; args: any; @@ -297,6 +299,7 @@ export type Debouncer = EventTarget & { export type MessageCache = EventTarget & { getMessages(): ActionMessage[]; + getMessageById(messageId: string): ActionMessage | undefined; pushMessage(message: ActionMessage): Promise; // prependMessages(messages: ActionMessage[]): Promise; trim(): void; From 06768f04d94d75c07c48b1a5341cea4c019c4e21 Mon Sep 17 00:00:00 2001 From: Abdurrehman Subhani Date: Fri, 13 Dec 2024 15:49:24 +0500 Subject: [PATCH 3/5] add bindReply function --- .../react-agents/classes/discord-manager.ts | 75 ++++++++++--------- 1 file changed, 40 insertions(+), 35 deletions(-) diff --git a/packages/usdk/packages/upstreet-agent/packages/react-agents/classes/discord-manager.ts b/packages/usdk/packages/upstreet-agent/packages/react-agents/classes/discord-manager.ts index 470e4b9ac..04347aa6f 100644 --- a/packages/usdk/packages/upstreet-agent/packages/react-agents/classes/discord-manager.ts +++ b/packages/usdk/packages/upstreet-agent/packages/react-agents/classes/discord-manager.ts @@ -122,6 +122,36 @@ const bindOutgoing = ({ // }); }; +const bindReply = ({ + conversation, + discordBotClient, + channelId, + userId, +}: { + conversation: ConversationObject, + discordBotClient: DiscordBotClient, + channelId?: string, + userId?: string, +}) => { + conversation.replyFn = async (message: ActionMessage, replyToMessageId: string) => { + const replyMessage = conversation.getMessageById(replyToMessageId); + const { discordMessageId } = replyMessage?.args; + const { text } = message.args; + + const replyObject = { + content: text, + reply: { + messageReference: discordMessageId, + }, + }; + + discordBotClient.input.writeText(replyObject, { + channelId, + userId, + }); + }; +}; + // export class DiscordBot extends EventTarget { @@ -245,23 +275,6 @@ export class DiscordBot extends EventTarget { getHash: () => { return `discord:channel:${channelId}`; }, - replyFn: async (message: ActionMessage, replyToMessageId: string) => { - console.log('replyFn', { - message, - replyToMessageId, - }); - - const replyObject = { - content: message.args.text, - reply: { - messageReference: replyToMessageId, - }, - }; - discordBotClient.input.writeText(replyObject, { - channelId, - userId: message.args.src_user_id, - }); - }, }); this.agent.conversationManager.addConversation(conversation); @@ -276,6 +289,11 @@ export class DiscordBot extends EventTarget { discordBotClient, channelId, }); + bindReply({ + conversation, + discordBotClient, + channelId, + }); // console.log('write text to channel', { // channelId, @@ -308,24 +326,6 @@ export class DiscordBot extends EventTarget { getHash: () => { return `discord:dm:${userId}`; }, - replyFn: async (message: ActionMessage, replyToMessageId: string) => { - const replyMessage = conversation.getMessageById(replyToMessageId); - const { discordMessageId } = replyMessage?.args; - const { - text, - } = message.args; - - const replyObject = { - content: text, - reply: { - messageReference: discordMessageId, - }, - }; - - discordBotClient.input.writeText(replyObject, { - userId, - }); - }, }); this.agent.conversationManager.addConversation(conversation); @@ -340,6 +340,11 @@ export class DiscordBot extends EventTarget { discordBotClient, userId, }); + bindReply({ + conversation, + discordBotClient, + userId, + }); // console.log('write text to user', { // userId, From 1ae0f0cc00b2cc25b7554b9c92b7e02d337805da Mon Sep 17 00:00:00 2001 From: Abdurrehman Subhani Date: Fri, 13 Dec 2024 16:13:08 +0500 Subject: [PATCH 4/5] "messageId" build fix on chat app missing messageId --- apps/chat/components/ui/multiplayer-actions.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/chat/components/ui/multiplayer-actions.tsx b/apps/chat/components/ui/multiplayer-actions.tsx index d51b32a37..ce3200765 100644 --- a/apps/chat/components/ui/multiplayer-actions.tsx +++ b/apps/chat/components/ui/multiplayer-actions.tsx @@ -183,6 +183,7 @@ export function MultiplayerActionsProvider({ children }: MultiplayerActionsProvi const timestamp = new Date(); const message: ActionMessage = { + messageId: crypto.randomUUID(), method, userId, name, From d795c0996f9c319a7f970dcbaaac7bff9b774713 Mon Sep 17 00:00:00 2001 From: Abdurrehman Subhani Date: Wed, 18 Dec 2024 13:20:01 +0500 Subject: [PATCH 5/5] update to use formatted text in reference message --- .../packages/react-agents/classes/discord-manager.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/packages/usdk/packages/upstreet-agent/packages/react-agents/classes/discord-manager.ts b/packages/usdk/packages/upstreet-agent/packages/react-agents/classes/discord-manager.ts index b7bf36c03..26f62a783 100644 --- a/packages/usdk/packages/upstreet-agent/packages/react-agents/classes/discord-manager.ts +++ b/packages/usdk/packages/upstreet-agent/packages/react-agents/classes/discord-manager.ts @@ -95,13 +95,10 @@ const bindOutgoing = ({ text = conversation.formatOutgoingMessageMentions(text); } - + if (replyToMessageId) { - const replyMessage = { - ...message, - text: text, - } - conversation.reply(replyMessage, replyToMessageId); + message.args.text = text; + conversation.reply(message, replyToMessageId); return; }