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, 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/classes/conversation-object.ts b/packages/usdk/packages/upstreet-agent/packages/react-agents/classes/conversation-object.ts index 240749580..36c2b0792 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,6 +25,7 @@ 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; mentionsRegex: RegExp | null = null; constructor({ @@ -31,12 +33,14 @@ export class ConversationObject extends EventTarget { agentsMap = new Map(), scene = null, getHash = () => '', + replyFn = null, mentionsRegex = null, }: { agent: ActiveAgentObject | null; agentsMap?: Map; scene?: SceneObject | null; getHash?: GetHashFn; + replyFn?: ReplyFn; mentionsRegex?: RegExp | null; }) { super(); @@ -45,6 +49,7 @@ export class ConversationObject extends EventTarget { this.agentsMap = agentsMap; this.scene = scene; this.getHash = getHash; + this.replyFn = replyFn; this.mentionsRegex = mentionsRegex; this.messageCache = new MessageCacheConstructor({ loader: async () => { @@ -142,6 +147,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; @@ -303,6 +312,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 f461bf701..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 @@ -7,6 +7,7 @@ import { ExtendableMessageEvent, ActionMessageEventData, PlayableAudioStream, + ActionMessage, } from '../types'; import { ConversationObject, @@ -80,7 +81,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) @@ -93,6 +95,13 @@ const bindOutgoing = ({ text = conversation.formatOutgoingMessageMentions(text); } + + if (replyToMessageId) { + message.args.text = text; + conversation.reply(message, replyToMessageId); + return; + } + discordBotClient.input.writeText(text, { channelId, userId, @@ -122,6 +131,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 { @@ -260,6 +299,11 @@ export class DiscordBot extends EventTarget { discordBotClient, channelId, }); + bindReply({ + conversation, + discordBotClient, + channelId, + }); // console.log('write text to channel', { // channelId, @@ -307,6 +351,11 @@ export class DiscordBot extends EventTarget { discordBotClient, userId, }); + bindReply({ + conversation, + discordBotClient, + userId, + }); // console.log('write text to user', { // userId, @@ -362,6 +411,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 @@ -382,6 +432,7 @@ export class DiscordBot extends EventTarget { method: 'say', args: { text: formattedMessage, + 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 605c40cf4..7b3a46f6d 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,17 +10,32 @@ export const ChatActions = () => { type="say" description={dedent`\ Say something in the chat. - If you want to mention a player, use the @userId format. + + You should use replyToMessageId to reference a specific previous message, usage guidelines: + 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 + 3. In all other cases, send your message without a reply reference. + + Not every message needs a reply reference so you should only use it when necessary `} 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 364df23f2..5f6327073 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 @@ -288,9 +288,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..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 @@ -214,15 +214,18 @@ export type Attachment = FormattedAttachment & { url?: string; }; export type ActionMessage = { + messageId: string; userId: string; 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; @@ -296,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; 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,