From 990851296828168c2106eb3f4668833e9e5a7463 Mon Sep 17 00:00:00 2001 From: Slavi Pantaleev Date: Tue, 1 Oct 2024 21:06:54 +0300 Subject: [PATCH] Add support for on-demand involvement Fixes https://github.com/etkecc/baibot/issues/15 --- README.md | 2 +- docs/access.md | 1 + docs/configuration/text-generation.md | 7 +- docs/features.md | 2 + ...eneration-on-demand-reply-involvement.webp | Bin 0 -> 93806 bytes ...neration-on-demand-thread-involvement.webp | Bin 0 -> 61516 bytes docs/usage.md | 24 +- src/bot/messaging.rs | 40 +- src/controller/chat_completion/mod.rs | 129 ++++- src/controller/determination/mod.rs | 77 +-- src/controller/determination/tests.rs | 52 +- src/controller/image/generation.rs | 4 +- src/conversation/llm/tests.rs | 26 +- src/conversation/llm/utils.rs | 6 +- src/conversation/matrix/entity.rs | 39 +- src/conversation/matrix/utils/mod.rs | 520 ++++++++++++++---- src/conversation/matrix/utils/tests.rs | 139 ++++- src/conversation/matrix_llm_bridge.rs | 39 +- src/conversation/mod.rs | 4 +- src/entity/interaction_context.rs | 13 + src/entity/message_payload.rs | 20 +- src/entity/mod.rs | 4 +- src/entity/thread_context.rs | 13 - 23 files changed, 832 insertions(+), 329 deletions(-) create mode 100644 docs/screenshots/text-generation-on-demand-reply-involvement.webp create mode 100644 docs/screenshots/text-generation-on-demand-thread-involvement.webp create mode 100644 src/entity/interaction_context.rs delete mode 100644 src/entity/thread_context.rs diff --git a/README.md b/README.md index 893b651..54698f2 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ It's influenced by [chaz](https://github.com/arcuru/chaz), but does **not** use ![Introduction and general usage](./docs/screenshots/introduction-and-general-usage.webp) -You can find more screenshots on the the [🌟 Features](./docs/features.md) and other [πŸ“š Documentation](./docs/README.md) pages, as well as in the [docs/screenshots](./docs/screenshots) directory. +You can find more screenshots on the [🌟 Features](./docs/features.md) and other [πŸ“š Documentation](./docs/README.md) pages, as well as in the [docs/screenshots](./docs/screenshots) directory. ## πŸš€ Getting Started diff --git a/docs/access.md b/docs/access.md index e3fd263..68c11aa 100644 --- a/docs/access.md +++ b/docs/access.md @@ -16,6 +16,7 @@ Users: - βœ… can **invite the bot to rooms** - βœ… can **use all the bot's [features](./features.md)** ([πŸ’¬ Text Generation](./features.md#-text-generation), [🦻 Speech-to-Text](./features.md#-speech-to-text), etc.) by sending room messages +- βœ… can **mention the bot** in threads and reply chains to provoke it to respond to non-user messages (see [πŸ“– Usage / πŸ’¬ Text Generation / On-demand involvement](./usage.md#on-demand-involvement)) - βœ… can **change the bot's configuration in a room** (e.g. `!bai config room ...` commands) - ❌ cannot **change the bot's global configuration** (e.g. `!bai config global ...` commands) - ❌ cannot **create new [πŸ€– Agents](./agents.md)** (neither in rooms, nor globally). See [πŸ’Ό Room-local agent managers](#-room-local-agent-managers) for controlling which users can create agents. diff --git a/docs/configuration/text-generation.md b/docs/configuration/text-generation.md index 5b7b428..16689cf 100644 --- a/docs/configuration/text-generation.md +++ b/docs/configuration/text-generation.md @@ -13,7 +13,7 @@ You may also wish to see: In Direct Message rooms with the bot (1:1 rooms), it most usually makes sense for the bot to respond to **all** of your messages, as shown on this [πŸ–ΌοΈ screenshot](../screenshots/text-generation.webp). -In group rooms (with multiple users), it may be more appropriate for the bot to only respond to messages that are **prefixed** with the command prefix (e.g. `!bai`), so that other chat exchange in the room will not trigger it. Such a setup is shown on this [πŸ–ΌοΈ screenshot](../screenshots/text-generation-prefix-requirement.webp). +In group rooms (with multiple users), it may be more appropriate for the bot to only respond to messages that are **prefixed** with the command prefix (e.g. `!bai`) or which are [mentioning](https://spec.matrix.org/latest/client-server-api/#user-and-room-mentions) the bot (e.g. `@baibot`), so that other chat exchange in the room will not trigger it. Such a setup is shown on the [πŸ–ΌοΈ On-demand involvement in the room](../screenshots/text-generation-prefix-requirement.webp) screenshot. There are exceptions to these rules, and you can configure the bot to respond only to prefixed messages in a 1:1 room, or to respond to all messages even in a multi-user group room. @@ -27,7 +27,10 @@ By default, the bot is **auto-configured (upon joining a new room)** to use the Example: `!bai config room text-generation set-prefix-requirement-type command_prefix` (this can also be set globally, see [πŸ› οΈ Room Settings](./README.md#room-settings)) -Regardless of this configuration, **the bot will also respond to messages which directly [mention](https://spec.matrix.org/latest/client-server-api/#user-and-room-mentions) the bot** (e.g. `@baibot`), even if they are not prefixed. An example of this can be seen on this [πŸ–ΌοΈ screenshot](../screenshots/text-generation-prefix-requirement.webp). +Regardless of this configuration, **the bot will also respond to messages by allowed [πŸ‘₯ Users](../access.md#-users) which directly [mention](https://spec.matrix.org/latest/client-server-api/#user-and-room-mentions) the bot** (e.g. `@baibot`), even if they are not prefixed. An example of this can be seen on these screenshots: + +- [πŸ–ΌοΈ On-demand involvement in a thread](../screenshots/text-generation-on-demand-thread-involvement.webp) +- [πŸ–ΌοΈ On-demand involvement in a reply chain](../screenshots/text-generation-on-demand-reply-involvement.webp) ### πŸͺ„ Auto Usage diff --git a/docs/features.md b/docs/features.md index 657bd4d..aff8288 100644 --- a/docs/features.md +++ b/docs/features.md @@ -28,6 +28,8 @@ Text Generation is the bot's ability to **respond to users' text messages with t In multi-user (group) rooms, to avoid disturbing the normal conversation between people, the bot is auto-configured to only respond to messages starting with the command prefix (`!bai`) or direct mentions via the [πŸ’¬ Text Generation / πŸ—Ÿ Prefix Requirement Type](./configuration/text-generation.md#-prefix-requirement-type) setting. +Normally, the bot only responds to allowed [πŸ‘₯ Users](./access.md#-users). In certain cases, it's useful for an allowed user to provoke the bot to respond even in foreign threads or reply chains. You can learn more about this feature in the [πŸ“– Usage / πŸ’¬ Text Generation / On-demand involvement](./usage.md#on-demand-involvement) section. + A few other features (like [πŸ—£οΈ Text-to-Speech](#️-text-to-speech) and [🦻 Speech-to-Text](#-speech-to-text)) combine well with Text Generation, so you **don't necessarily need to communicate with the bot via text** (with [Seamless voice interaction](#seamless-voice-interaction), you can communicate only with voice). You may also wish to see: diff --git a/docs/screenshots/text-generation-on-demand-reply-involvement.webp b/docs/screenshots/text-generation-on-demand-reply-involvement.webp new file mode 100644 index 0000000000000000000000000000000000000000..2905a1d0d7003e8a135b487417ad7fc0e186d07c GIT binary patch literal 93806 zcmb@tWmH_1Pku&5Zv9}U4sV*?(XjH8rc_TKlLeZN27 z9HYBy)vB7kW_8swt7?u`k`xm&u?7LCiwY^IDR5|f0ssJCK3-_xfLkztq_D8U573WG z0O$`(BP$1R0RX_t+RIZQ;QJl2=EC20zd@71Mm%u9Bc&@6lDHL|L@~|;Y0Rg z%+mgu>wlN|k6d`;Zw^KP00`lSgn*H)z2gV1|3S04INJW9Q$J`_BQpcz54z%m{$l^} zfFJbIAHLx~=(9hx>0k6;i$b+mQ4;=G8|nw2(DYwu!+)WT%kp0tKpQ^cLqh~$1F!-Z{G|{0^Ba7~e2jnE*1?3C=`R)t zoDcv2u>l0$k^ulv2>`%bED-pT3k1I90RUiY06>rRKlN>X0011nKlri#0=GOTS>xTYN_;0)YKV|;QejlDo`0FbO;8z-076>&u_#GG@ zBWAb=F;Om3nDG%I70Q+!RY^0oW@_;PdHYwtz)GN*=*m%kne zJ1~_3OxQ~r2VMc&->-q$PxbGM@9fW)_M0>O9X|a)iWlG=*mKPL%ZvQu<#J3!_Y1+^ z+k%fPu<-=;$>#Uwlis0^2mj8Spiet6?cL^Kai8Fx|JLV$f83`M2)u)MZGT>TC79|? z*?h+D&spbR1MWS5ziYoayty2v{FHz9xy$JW{(hh0ukmRB9=}(=H3NY^VvYz_yYJrT zeV&0acOXD!An={Q7V$aft<#s^(`)|~_ObaoX256PhtwPA@!^)>)MpRa1k?o1ztb%1 z^>+zAv)}vNbZ>T71Gj-~e=RT;2=(Uhz`RQTAit1v z%HRF2%-Q{fc;>U!-Qe@>?e-bscl#c}s!x+oG?4Wz|Gs%wd%U~c#}0`1F8`A9t{3Nn z@gDJJ`ojM(b*(oCeDwzSe17MCi+SFBjQP#K#{ULvdG>j+c|!cH=kq?fsqy~wuKWyq z1it8<`2gQReDK~K-kD$Y9xoq!Hhh4)OZ{#*WA;OGZq_G$QqP}d=n=l3#(wIJFPLF0ksNd zs-?|hp^O}UZthrr}keMhcXZy3zkMb9nT z{Xkg|3)AijSL}EE9b)Qd@kOhNMX=Np6HmBqTeoqRl>@88c7 z|9e%Cz^NX31*!&$l46*eW<uRrCpQ#Ju-QQV>cq&Vc(KYSe=Q$&9{Ao#_bs_uEnN?lVMkAw?%!-{93Mg$~>iw-AJ;xa`?7hlj z*2_pⓈ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 O5O%059e32N%YOk_t{n;h literal 0 HcmV?d00001 diff --git a/docs/screenshots/text-generation-on-demand-thread-involvement.webp b/docs/screenshots/text-generation-on-demand-thread-involvement.webp new file mode 100644 index 0000000000000000000000000000000000000000..df6bfb75bd675d74970c5ddef252982faabeeedb GIT binary patch literal 61516 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 H-$eflHoX!g literal 0 HcmV?d00001 diff --git a/docs/usage.md b/docs/usage.md index 6e74feb..484365f 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -11,10 +11,11 @@ This is related to the [πŸ’¬ Text Generation](./features.md#-text-generation) fe If there's a text-generation handler agent configured, the bot **may** respond to messages sent in the room. -πŸ–ΌοΈ See screenshots of: +See screenshots of: -- the [default Text Generation flow](./screenshots/text-generation.webp) for 1:1 rooms -- the [Text Generation flow in multi-user rooms](./screenshots/text-generation-prefix-requirement.webp) (where the [πŸ—Ÿ Prefix Requirement](./configuration/text-generation.md#-prefix-requirement-type) setting is auto-configured to "required") +- πŸ–ΌοΈ [the default Text Generation flow](./screenshots/text-generation.webp) in 1:1 rooms +- πŸ–ΌοΈ [the Text Generation flow in multi-user rooms](./screenshots/text-generation-prefix-requirement.webp) (where the [πŸ—Ÿ Prefix Requirement](./configuration/text-generation.md#-prefix-requirement-type) setting is auto-configured to "required") +- [on-demand involvement](#on-demand-involvement) Whether the bot responds depends on: @@ -24,12 +25,27 @@ Whether the bot responds depends on: - (🎨 agent capabilities) whether the configured `text-generation` (or `catch-all`) handler agent actually supports text-generation. The provider may lack support for this feature or it may be disabled in the [πŸ€– agents](./agents.md) configuration -- (the [πŸ—Ÿ Prefix Requirement](./configuration/text-generation.md#-prefix-requirement-type) setting) whether a prefix (e.g. `!bai`) is required in front of messages sent to the room. For multi-user rooms, this setting defaults to "required" +- (the [πŸ—Ÿ Prefix Requirement](./configuration/text-generation.md#-prefix-requirement-type) setting) whether a prefix (e.g. `!bai`) or user mention (e.g. `@baibot`) is required for messages sent to the room. For multi-user rooms, this setting defaults to "required". See [on-demand involvement](#on-demand-involvement) for details. Room messages start a threaded conversation where you can continue back-and-forth communication with the bot. Unless you've enabled the [♻️ Context Management](./features.md#️-context-management) feature, all messages will be sent to the agent's API each time. If the context management feature is enabled, older messages may be dropped. +#### On-demand involvement + +In the following 2 cases, it's useful to involve the bot in conversations on-demand: + +1. For multi-user rooms (with the [πŸ—Ÿ Prefix Requirement](./configuration/text-generation.md#-prefix-requirement-type) setting set to "required") +2. In rooms with foreign users (users that are not authorized bot [πŸ‘₯ users](./access.md#-users)) + +In these instances, an allowed [πŸ‘₯ user](./access.md#-users) can also provoke the bot to respond to **any** thread or reply chain by [mentioning](https://spec.matrix.org/latest/client-server-api/#user-and-room-mentions) the bot (e.g. `@baibot Hello!`). The following screenshots demonstrate this behavior: + +- [πŸ–ΌοΈ On-demand involvement in the room](./screenshots/text-generation-prefix-requirement.webp) +- [πŸ–ΌοΈ On-demand involvement in a thread](./screenshots/text-generation-on-demand-thread-involvement.webp) (the Alice user in this example is not an allowed user, yet her messages are still considered as part of the conversation context) +- [πŸ–ΌοΈ On-demand involvement in a reply chain](./screenshots/text-generation-on-demand-reply-involvement.webp) (the Alice user in this example is not an allowed user, yet her messages are still considered as part of the conversation context) + +πŸ’‘ **NOTE**: Normally, the bot **only considers messages from allowed [πŸ‘₯ Users](./access.md#-users)** and ignores all other messages when responding. However, **when the bot is explicitly invoked (via mention)** in a thread or reply chain, **it will consider all messages** in the thread and reply chain (even those from foreign users) as part of the conversation context. + ### πŸ—£οΈ Text-to-Speech diff --git a/src/bot/messaging.rs b/src/bot/messaging.rs index 11967be..fe90ff5 100644 --- a/src/bot/messaging.rs +++ b/src/bot/messaging.rs @@ -11,7 +11,7 @@ use mxlink::{CallbackError, MessageResponseType}; use tracing::Instrument; use crate::{ - conversation::matrix::determine_thread_context_for_room_event, + conversation::matrix::determine_interaction_context_for_room_event, entity::{MessageContext, MessagePayload, RoomConfigContext, TriggerEventInfo}, }; @@ -239,7 +239,7 @@ impl Messaging { } }; - let thread_context = determine_thread_context_for_room_event( + let interaction_context = determine_interaction_context_for_room_event( self.bot.user_id(), &room, &event, @@ -248,16 +248,18 @@ impl Messaging { ) .await; - let thread_context = match thread_context { + let interaction_context = match interaction_context { Ok(value) => value, Err(err) => { - tracing::error!(?err, "Failed to determine thread context for event"); + tracing::error!(?err, "Failed to determine interaction context for event"); return Ok(()); } }; - let Some(thread_context) = thread_context else { - tracing::debug!("Ignoring message with unknown thread context (likely not a threaded message or a top-level message)"); + let Some(interaction_context) = interaction_context else { + tracing::debug!( + "Ignoring message with unknown interaction context (likely not a message for us)" + ); return Ok(()); }; @@ -276,33 +278,13 @@ impl Messaging { room_config_context, self.bot.admin_pattern_regexes().clone(), trigger_event_info, - thread_context.info.clone(), + interaction_context.thread_info.clone(), ); - let bot_display_name = self - .bot - .room_display_name_fetcher() - .own_display_name_in_room(message_context.room()) - .await; - - let bot_display_name = match bot_display_name { - Ok(value) => value, - Err(err) => { - tracing::warn!( - ?err, - "Failed to fetch bot display name. Proceeding without it" - ); - None - } - }; - - // The first event in the thread determines which handler processes the current event. let controller_type = crate::controller::determine_controller( self.bot.command_prefix(), - &thread_context.first_message, + &interaction_context.trigger, &message_context, - self.bot.user_id(), - &bot_display_name, ); tracing::info!(?controller_type, "Determined controller"); @@ -310,7 +292,7 @@ impl Messaging { let _ = room .send_single_receipt( ReceiptType::Read, - thread_context.info.clone().into(), + interaction_context.thread_info.clone().into(), event.event_id.clone(), ) .await; diff --git a/src/controller/chat_completion/mod.rs b/src/controller/chat_completion/mod.rs index 04dae46..6ab7f9e 100644 --- a/src/controller/chat_completion/mod.rs +++ b/src/controller/chat_completion/mod.rs @@ -18,13 +18,28 @@ use crate::entity::roomconfig::{ use crate::entity::MessagePayload; use crate::strings; use crate::utils::text_to_speech::create_transcribed_message_text; -use crate::{conversation::create_llm_conversation_for_matrix_thread, entity::MessageContext, Bot}; +use crate::{ + conversation::{ + create_llm_conversation_for_matrix_reply_chain, create_llm_conversation_for_matrix_thread, + matrix::create_list_of_bot_user_prefixes_to_strip, + }, + entity::MessageContext, + Bot, +}; #[derive(Debug, PartialEq)] pub enum ChatCompletionControllerType { - ViaText { prefixes_to_strip: Vec }, + // Invoked via a command prefix (e.g. `!bai Hello!`) + TextCommand, + // Invoked via a mention (e.g. `@baibot Hello!`) + TextMention, + // Invoked via a direct message (e.g. `Hello!`) + TextDirect, - ViaAudio, + Audio, + + ThreadMention, + ReplyMention, } struct TextToSpeechEligiblePayload { @@ -125,7 +140,15 @@ pub async fn handle( None }; - let response_type = MessageResponseType::InThread(message_context.thread_info().clone()); + let response_type = match controller_type { + // When we're triggered via a reply mention, we reply to the message that triggered us. + ChatCompletionControllerType::ReplyMention => { + MessageResponseType::Reply(message_context.thread_info().last_event_id.clone()) + } + + // In all other cases, we're dealing with a threaded conversation, so we reply in the thread. + _ => MessageResponseType::InThread(message_context.thread_info().clone()), + }; let text_to_speech_eligible_payload = handle_stage_text_generation( bot, @@ -353,24 +376,78 @@ async fn handle_stage_text_generation( ) .await?; - let prefixes_to_strip = match controller_type { - ChatCompletionControllerType::ViaText { prefixes_to_strip } => prefixes_to_strip.clone(), - ChatCompletionControllerType::ViaAudio => vec![], + // We only strip text from the first message if we're invoked via a command prefix. + // Otherwise, we do bot-user mentions stripping on all messages below. + let first_message_prefixes_to_strip = match controller_type { + ChatCompletionControllerType::TextCommand => vec![bot.command_prefix().to_owned()], + _ => vec![], }; - let params = MatrixMessageProcessingParams::new( - bot.user_id().as_str().to_owned(), - message_context.combined_admin_and_user_regexes(), - ) - .with_first_message_stripped_prefixes(prefixes_to_strip); + let bot_display_name = bot + .room_display_name_fetcher() + .own_display_name_in_room(message_context.room()) + .await; - let conversation = create_llm_conversation_for_matrix_thread( - matrix_link.clone(), - message_context.room(), - message_context.thread_info().root_event_id.clone(), - ¶ms, - ) - .await; + let bot_display_name = match bot_display_name { + Ok(value) => value, + Err(err) => { + tracing::warn!( + ?err, + "Failed to fetch bot display name. Proceeding without it" + ); + None + } + }; + + let bot_user_prefixes_to_strip = + create_list_of_bot_user_prefixes_to_strip(bot.user_id(), &bot_display_name); + + let allowed_users = match controller_type { + // Regular chat completion only operates on messages from allowed users. + ChatCompletionControllerType::TextCommand + | ChatCompletionControllerType::TextMention + | ChatCompletionControllerType::TextDirect + | ChatCompletionControllerType::Audio => { + Some(message_context.combined_admin_and_user_regexes()) + } + + // When we're triggered via an explicit mention (thread or reply), we wish to operate against the mention's whole context + // (the whole thread or the whole reply chain upward of the message that triggered us). + // + // This is to allow admins and users to trigger text-generation for other users' messages. + // When we're dragged into a conversation by a known (to us) user, we'd like to process all messages in the conversation, + // not just those from allowed users. + ChatCompletionControllerType::ThreadMention + | ChatCompletionControllerType::ReplyMention => None, + }; + + let params = MatrixMessageProcessingParams::new(bot.user_id().to_owned(), allowed_users) + .with_first_message_prefixes_to_strip(first_message_prefixes_to_strip) + .with_bot_user_prefixes_to_strip(bot_user_prefixes_to_strip); + + let conversation = match controller_type { + // When we're triggered via a reply mention, the context is the whole reply chain upward of the message that triggered us. + ChatCompletionControllerType::ReplyMention => { + create_llm_conversation_for_matrix_reply_chain( + &bot.room_event_fetcher().clone(), + message_context.room(), + message_context.thread_info().last_event_id.clone(), + ¶ms, + ) + .await + } + + // Everything else is happening in a thread, so the context is the whole thread. + _ => { + create_llm_conversation_for_matrix_thread( + matrix_link.clone(), + message_context.room(), + message_context.thread_info().root_event_id.clone(), + ¶ms, + ) + .await + } + }; let conversation = match conversation { Ok(conversation) => conversation, @@ -565,11 +642,15 @@ async fn handle_stage_speech_to_text_actual_transcribing( // // Regardless of how we post this message, it will be posted as a notice, // which can indicate to the bot (for potential future text-generation purposes) that this message is not a bot message. - let (transcribed_text, annotate_message_with_reaction) = if let MessageResponseType::InThread(_) = response_type { - (create_transcribed_message_text(&speech_to_text_result.text), false) - } else { - (speech_to_text_result.text, true) - }; + let (transcribed_text, annotate_message_with_reaction) = + if let MessageResponseType::InThread(_) = response_type { + ( + create_transcribed_message_text(&speech_to_text_result.text), + false, + ) + } else { + (speech_to_text_result.text, true) + }; let result = bot .messaging() diff --git a/src/controller/determination/mod.rs b/src/controller/determination/mod.rs index 737235d..7fdd117 100644 --- a/src/controller/determination/mod.rs +++ b/src/controller/determination/mod.rs @@ -1,13 +1,11 @@ #[cfg(test)] mod tests; -use mxlink::matrix_sdk::ruma::OwnedUserId; - use super::chat_completion::ChatCompletionControllerType; use crate::{ entity::{ - roomconfig::TextGenerationPrefixRequirementType, MessageContext, MessagePayload, - ThreadContextFirstMessage, + roomconfig::TextGenerationPrefixRequirementType, InteractionTrigger, MessageContext, + MessagePayload, }, strings, }; @@ -16,12 +14,16 @@ use super::ControllerType; pub fn determine_controller( command_prefix: &str, - first_thread_message: &ThreadContextFirstMessage, + first_thread_message: &InteractionTrigger, message_context: &MessageContext, - bot_user_id: &OwnedUserId, - bot_display_name: &Option, ) -> ControllerType { match &first_thread_message.payload { + MessagePayload::SynthethicChatCompletionTriggerInThread => { + ControllerType::ChatCompletion(ChatCompletionControllerType::ThreadMention) + } + MessagePayload::SynthethicChatCompletionTriggerForReply => { + ControllerType::ChatCompletion(ChatCompletionControllerType::ReplyMention) + } MessagePayload::Text(text_message_content) => { let prefix_requirement_type = message_context .room_config_context() @@ -32,8 +34,6 @@ pub fn determine_controller( &text_message_content.body, prefix_requirement_type, first_thread_message.is_mentioning_bot, - bot_user_id, - bot_display_name, ) } MessagePayload::Encrypted(thread_info) => { @@ -47,7 +47,7 @@ pub fn determine_controller( } } MessagePayload::Audio(_) => { - ControllerType::ChatCompletion(ChatCompletionControllerType::ViaAudio) + ControllerType::ChatCompletion(ChatCompletionControllerType::Audio) } MessagePayload::Reaction { .. } => { panic!("Handling reaction as first message in thread does not make sense") @@ -60,8 +60,6 @@ fn determine_text_controller( text: &str, room_text_generation_prefix_requirement_type: TextGenerationPrefixRequirementType, is_mentioning_bot: bool, - bot_user_id: &OwnedUserId, - bot_display_name: &Option, ) -> ControllerType { let text = text.trim(); @@ -102,53 +100,26 @@ fn determine_text_controller( // Otherwise, it depends on the prefix requirement for text generation - it may be routed for chat completion or ignored. if is_mentioning_bot { - // Different clients do mentions differently. - // The body text containing the mention usually contains one of: - // - the full user ID (includes a @ prefix by default) - // - the localpart (with a @ prefix) - // - the localpart (without a @ prefix) - // - the display name (with a @ prefix) - // - the display name (without a @ prefix) - // - // Some add a `: ` suffix after the mention. - // - // There's no guarantee that the mention is at the start even. - // It being there is most common and we try to strip it from there - // as best as we can. - let bot_user_id_localpart = bot_user_id.localpart(); - - let mut prefixes_to_strip = vec![ - bot_user_id.as_str().to_owned(), - format!("@{}", bot_user_id_localpart), - bot_user_id_localpart.to_owned(), - ]; - - if let Some(bot_display_name) = bot_display_name { - prefixes_to_strip.push(format!("@{}", bot_display_name)); - prefixes_to_strip.push(bot_display_name.to_owned()); - } - - prefixes_to_strip.push(":".to_owned()); - - return ControllerType::ChatCompletion(ChatCompletionControllerType::ViaText { - prefixes_to_strip, - }); + return ControllerType::ChatCompletion(ChatCompletionControllerType::TextMention); } + // Regardless of what the prefix requirement is, if we encounter a command prefix, we'll consider it a chat completion via command prefix invokation. + // This is to correctly indicate to the chat completion controller that a command prefix was used, + // so that it can be stripped from the beginning of the message. + if text.starts_with(command_prefix) { + return ControllerType::ChatCompletion(ChatCompletionControllerType::TextCommand); + } + + // We're dealing with a regular message that does not start with a command prefix. + match room_text_generation_prefix_requirement_type { TextGenerationPrefixRequirementType::CommandPrefix => { - if text.starts_with(command_prefix) { - ControllerType::ChatCompletion(ChatCompletionControllerType::ViaText { - prefixes_to_strip: vec![command_prefix.to_owned()], - }) - } else { - ControllerType::Ignore - } + // A prefix is required, but we've already checked (above) that the message does not start with a command prefix. + // It's to be ignored. + ControllerType::Ignore } TextGenerationPrefixRequirementType::No => { - ControllerType::ChatCompletion(ChatCompletionControllerType::ViaText { - prefixes_to_strip: vec![], - }) + ControllerType::ChatCompletion(ChatCompletionControllerType::TextDirect) } } } diff --git a/src/controller/determination/tests.rs b/src/controller/determination/tests.rs index 5655dd6..a8d15da 100644 --- a/src/controller/determination/tests.rs +++ b/src/controller/determination/tests.rs @@ -4,9 +4,6 @@ fn determine_text_controller() { use super::ControllerType; use crate::controller; - let bot_user_id = mxlink::matrix_sdk::ruma::owned_user_id!("@bot:example.com"); - let bot_display_name = "Bot"; - let command_prefix = "!bai"; struct TestCase { @@ -44,9 +41,7 @@ fn determine_text_controller() { is_mentioning_bot: false, room_text_generation_prefix_requirement_type: super::TextGenerationPrefixRequirementType::No, - expected: ControllerType::ChatCompletion(ChatCompletionControllerType::ViaText { - prefixes_to_strip: vec![], - }), + expected: ControllerType::ChatCompletion(ChatCompletionControllerType::TextCommand), }, TestCase { name: "Access top-level", @@ -110,9 +105,7 @@ fn determine_text_controller() { is_mentioning_bot: false, room_text_generation_prefix_requirement_type: super::TextGenerationPrefixRequirementType::No, - expected: ControllerType::ChatCompletion(ChatCompletionControllerType::ViaText { - prefixes_to_strip: vec![], - }), + expected: ControllerType::ChatCompletion(ChatCompletionControllerType::TextDirect), }, TestCase { name: "Regular text is ignored when prefix is required", @@ -128,9 +121,7 @@ fn determine_text_controller() { is_mentioning_bot: false, room_text_generation_prefix_requirement_type: super::TextGenerationPrefixRequirementType::CommandPrefix, - expected: ControllerType::ChatCompletion(ChatCompletionControllerType::ViaText { - prefixes_to_strip: vec!["!bai".to_owned()], - }), + expected: ControllerType::ChatCompletion(ChatCompletionControllerType::TextCommand), }, TestCase { name: "Command-prefixed text triggers completion even when prefix is not required", @@ -138,58 +129,35 @@ fn determine_text_controller() { is_mentioning_bot: false, room_text_generation_prefix_requirement_type: super::TextGenerationPrefixRequirementType::No, - expected: ControllerType::ChatCompletion(ChatCompletionControllerType::ViaText { - prefixes_to_strip: vec![], - }), + expected: ControllerType::ChatCompletion(ChatCompletionControllerType::TextCommand), }, TestCase { - name: "Regular message with bot mention triggers completion stripping bot id and display name (no prefix requirement)", + name: "Regular message with bot mention triggers completion (no prefix requirement)", input: "Regular text goes here", is_mentioning_bot: true, room_text_generation_prefix_requirement_type: super::TextGenerationPrefixRequirementType::No, - expected: ControllerType::ChatCompletion(ChatCompletionControllerType::ViaText { - prefixes_to_strip: vec![ - "@bot:example.com".to_owned(), - "@bot".to_owned(), - "bot".to_owned(), - "@Bot".to_owned(), - "Bot".to_owned(), - ":".to_owned(), - ], - }), + expected: ControllerType::ChatCompletion(ChatCompletionControllerType::TextMention), }, - // This test case is the same as the one above, just with a different prefix requirement. + // This test case is the same as the one above, just with a different prefix requirement setting. // We expect the same result. TestCase { - name: "Regular message with bot mention triggers completion stripping bot id and display name (command_prefix requirement)", + name: + "Regular message with bot mention triggers completion (command prefix requirement)", input: "Regular text goes here", is_mentioning_bot: true, room_text_generation_prefix_requirement_type: super::TextGenerationPrefixRequirementType::CommandPrefix, - expected: ControllerType::ChatCompletion(ChatCompletionControllerType::ViaText { - prefixes_to_strip: vec![ - "@bot:example.com".to_owned(), - "@bot".to_owned(), - "bot".to_owned(), - "@Bot".to_owned(), - "Bot".to_owned(), - ":".to_owned(), - ], - }), + expected: ControllerType::ChatCompletion(ChatCompletionControllerType::TextMention), }, ]; for test_case in test_cases { - let bot_display_name = Some(bot_display_name.to_owned()); - let result = super::determine_text_controller( command_prefix, test_case.input, test_case.room_text_generation_prefix_requirement_type, test_case.is_mentioning_bot, - &bot_user_id, - &bot_display_name, ); assert_eq!(result, test_case.expected, "Test case: {}", test_case.name); } diff --git a/src/controller/image/generation.rs b/src/controller/image/generation.rs index 4cf5843..cdbb9cb 100644 --- a/src/controller/image/generation.rs +++ b/src/controller/image/generation.rs @@ -35,8 +35,8 @@ pub async fn handle_image( }; let params = MatrixMessageProcessingParams::new( - bot.user_id().as_str().to_owned(), - message_context.combined_admin_and_user_regexes(), + bot.user_id().to_owned(), + Some(message_context.combined_admin_and_user_regexes()), ); let conversation = create_llm_conversation_for_matrix_thread( diff --git a/src/conversation/llm/tests.rs b/src/conversation/llm/tests.rs index 2a1d2fc..a50b9cd 100644 --- a/src/conversation/llm/tests.rs +++ b/src/conversation/llm/tests.rs @@ -1,3 +1,5 @@ +use mxlink::matrix_sdk::ruma::OwnedUserId; + use crate::utils::status::create_error_message_text; use crate::utils::text_to_speech::create_transcribed_message_text; @@ -5,15 +7,17 @@ use super::*; #[test] fn test_messages_by_the_bot_are_identified_correctly() { - let bot_user_id = "@bot:example.com"; + let bot_user_id = + OwnedUserId::try_from("@bot:example.com").expect("Failed to parse bot user ID"); let matrix_message = super::super::matrix::MatrixMessage { sender_id: bot_user_id.to_owned(), message_type: super::super::matrix::MatrixMessageType::Text, message_text: "Hello!".to_owned(), + mentioned_users: vec![], }; - let llm_message = convert_matrix_message_to_llm_message(&matrix_message, bot_user_id).unwrap(); + let llm_message = convert_matrix_message_to_llm_message(&matrix_message, &bot_user_id).unwrap(); assert_eq!(llm_message.author, Author::Assistant); assert_eq!(llm_message.message_text, "Hello!"); @@ -22,7 +26,8 @@ fn test_messages_by_the_bot_are_identified_correctly() { #[test] fn test_notice_messages_by_bot_with_speech_to_text_prefix_are_cleaned_up_and_considered_sent_by_user( ) { - let bot_user_id = "@bot:example.com"; + let bot_user_id = + OwnedUserId::try_from("@bot:example.com").expect("Failed to parse bot user ID"); let source_message_text = "Hello!"; let message_text = create_transcribed_message_text(source_message_text); @@ -33,9 +38,10 @@ fn test_notice_messages_by_bot_with_speech_to_text_prefix_are_cleaned_up_and_con sender_id: bot_user_id.to_owned(), message_type: super::super::matrix::MatrixMessageType::Notice, message_text, + mentioned_users: vec![], }; - let llm_message = convert_matrix_message_to_llm_message(&matrix_message, bot_user_id).unwrap(); + let llm_message = convert_matrix_message_to_llm_message(&matrix_message, &bot_user_id).unwrap(); assert_eq!(llm_message.author, Author::User); assert_eq!(llm_message.message_text, source_message_text); @@ -43,7 +49,8 @@ fn test_notice_messages_by_bot_with_speech_to_text_prefix_are_cleaned_up_and_con #[test] fn test_notice_error_messages_by_bot_are_ignored() { - let bot_user_id = "@bot:example.com"; + let bot_user_id = + OwnedUserId::try_from("@bot:example.com").expect("Failed to parse bot user ID"); let source_message_text = "Some error happened"; let message_text = create_error_message_text(source_message_text); @@ -54,9 +61,10 @@ fn test_notice_error_messages_by_bot_are_ignored() { sender_id: bot_user_id.to_owned(), message_type: super::super::matrix::MatrixMessageType::Notice, message_text, + mentioned_users: vec![], }; - let llm_message = convert_matrix_message_to_llm_message(&matrix_message, bot_user_id); + let llm_message = convert_matrix_message_to_llm_message(&matrix_message, &bot_user_id); assert!(llm_message.is_none()); } @@ -68,7 +76,8 @@ fn test_other_notice_messages_by_the_bot_are_ignored() { // (except for speech-to-text-created transcriptions - see `test_notice_messages_by_bot_with_speech_to_text_prefix_are_cleaned_up_and_considered_sent_by_user()`). // This test is to make sure that we don't accidentally start accepting other notice messages. - let bot_user_id = "@bot:example.com"; + let bot_user_id = + OwnedUserId::try_from("@bot:example.com").expect("Failed to parse bot user ID"); let message_text = "Something something"; @@ -76,9 +85,10 @@ fn test_other_notice_messages_by_the_bot_are_ignored() { sender_id: bot_user_id.to_owned(), message_type: super::super::matrix::MatrixMessageType::Notice, message_text: message_text.to_owned(), + mentioned_users: vec![], }; - let llm_message = convert_matrix_message_to_llm_message(&matrix_message, bot_user_id); + let llm_message = convert_matrix_message_to_llm_message(&matrix_message, &bot_user_id); assert!(llm_message.is_none()); } diff --git a/src/conversation/llm/utils.rs b/src/conversation/llm/utils.rs index 89c8296..be6f663 100644 --- a/src/conversation/llm/utils.rs +++ b/src/conversation/llm/utils.rs @@ -1,12 +1,14 @@ +use matrix_sdk::ruma::OwnedUserId; + use super::{Author, Message}; use crate::conversation::matrix::{MatrixMessage, MatrixMessageType}; use crate::utils::text_to_speech as text_to_speech_utils; pub fn convert_matrix_message_to_llm_message( matrix_message: &MatrixMessage, - bot_user_id: &str, + bot_user_id: &OwnedUserId, ) -> Option { - if matrix_message.sender_id == bot_user_id { + if matrix_message.sender_id == bot_user_id.as_str() { return convert_bot_message(matrix_message); } diff --git a/src/conversation/matrix/entity.rs b/src/conversation/matrix/entity.rs index bbb41bc..978ba77 100644 --- a/src/conversation/matrix/entity.rs +++ b/src/conversation/matrix/entity.rs @@ -1,10 +1,13 @@ use regex::Regex; +use mxlink::matrix_sdk::ruma::OwnedUserId; + #[derive(Clone)] pub struct MatrixMessage { - pub sender_id: String, + pub sender_id: OwnedUserId, pub message_type: MatrixMessageType, pub message_text: String, + pub mentioned_users: Vec, } #[derive(Clone)] @@ -13,26 +16,42 @@ pub enum MatrixMessageType { Notice, } -#[derive(Default, Clone)] +#[derive(Clone)] pub struct MatrixMessageProcessingParams { - pub(crate) bot_user_id: String, - pub(crate) allowed_users: Vec, + pub(crate) bot_user_id: OwnedUserId, - // If non-empty, these prefixes will be stripped when processing the message - pub(crate) first_message_stripped_prefixes: Vec, + /// The prefixes that will be stripped when processing the messages in the context (thread or reply chain), + /// which are found to be mentioning the bot user (`bot_user_id`). + pub(crate) bot_user_prefixes_to_strip: Vec, + + /// The prefixes that will be stripped when processing the 1st message in the context (thread or reply chain). + pub(crate) first_message_prefixes_to_strip: Vec, + + /// A list of users whose messages are allowed. + /// If None, all messages are allowed. + /// If Some, only messages from the allowed users (and the bot itself, `bot_user_id`) are allowed. + pub(crate) allowed_users: Option>, } impl MatrixMessageProcessingParams { - pub fn new(bot_user_id: String, allowed_users: Vec) -> Self { + pub fn new(bot_user_id: OwnedUserId, allowed_users: Option>) -> Self { Self { bot_user_id, + bot_user_prefixes_to_strip: vec![], + + first_message_prefixes_to_strip: vec![], + allowed_users, - ..Default::default() } } - pub fn with_first_message_stripped_prefixes(mut self, value: Vec) -> Self { - self.first_message_stripped_prefixes = value; + pub fn with_bot_user_prefixes_to_strip(mut self, value: Vec) -> Self { + self.bot_user_prefixes_to_strip = value; + self + } + + pub fn with_first_message_prefixes_to_strip(mut self, value: Vec) -> Self { + self.first_message_prefixes_to_strip = value; self } } diff --git a/src/conversation/matrix/utils/mod.rs b/src/conversation/matrix/utils/mod.rs index 3f28986..cb37ccb 100644 --- a/src/conversation/matrix/utils/mod.rs +++ b/src/conversation/matrix/utils/mod.rs @@ -5,7 +5,9 @@ use std::sync::Arc; use mxlink::matrix_sdk::ruma::{OwnedEventId, OwnedUserId}; use mxlink::matrix_sdk::{ + deserialized_responses::TimelineEvent, ruma::events::{ + relation::Thread, room::message::{ MessageType, OriginalSyncRoomMessageEvent, Relation, RoomMessageEventContent, }, @@ -16,7 +18,12 @@ use mxlink::matrix_sdk::{ use mxlink::{MatrixLink, ThreadGetMessagesParams, ThreadInfo}; use super::{MatrixMessage, MatrixMessageProcessingParams, MatrixMessageType, RoomEventFetcher}; -use crate::entity::{MessagePayload, ThreadContext, ThreadContextFirstMessage}; +use crate::entity::{InteractionContext, InteractionTrigger, MessagePayload}; + +struct DetailedMessagePayload { + is_mentioning_bot: bool, + message_payload: MessagePayload, +} pub async fn get_matrix_messages_in_thread( matrix_link: MatrixLink, @@ -42,23 +49,123 @@ pub async fn get_matrix_messages_in_thread( Ok(messages) } -pub async fn process_matrix_messages_in_thread( +pub async fn get_matrix_messages_in_reply_chain( + event_fetcher: &Arc, + room: &Room, + event_id: OwnedEventId, +) -> Result, mxlink::matrix_sdk::Error> { + let messages_native = + get_matrix_messages_in_reply_chain_native(event_fetcher, room, event_id).await?; + + let mut messages: Vec = Vec::new(); + + for matrix_native_message in messages_native { + let Some(message) = convert_matrix_native_event_to_matrix_message(&matrix_native_message) + else { + continue; + }; + + messages.push(message); + } + + Ok(messages) +} + +async fn get_matrix_messages_in_reply_chain_native( + event_fetcher: &Arc, + room: &Room, + event_id: OwnedEventId, +) -> Result, mxlink::matrix_sdk::Error> { + let mut next_event_id = Some(event_id.clone()); + + let mut messages: Vec = Vec::new(); + let mut handled_event_ids: Vec = Vec::new(); + + while let Some(next_event_id_in_loop) = next_event_id { + let event = event_fetcher + .fetch_event_in_room(&next_event_id_in_loop, room) + .await + .unwrap(); + + if handled_event_ids.contains(&next_event_id_in_loop) { + tracing::warn!( + "Not following loop-causing event: {}", + next_event_id_in_loop + ); + break; + } + + handled_event_ids.push(next_event_id_in_loop.clone()); + + let event_deserialized = event.event.deserialize()?; + + let AnyTimelineEvent::MessageLike(message_like_event) = event_deserialized else { + tracing::warn!( + "Not proceeding past non-MessageLike event: {:?}", + event_deserialized + ); + break; + }; + + next_event_id = match message_like_event.clone() { + AnyMessageLikeEvent::RoomEncrypted(_) => None, + AnyMessageLikeEvent::RoomMessage(room_message) => { + if let MessageLikeEvent::Original(room_message_original) = room_message { + match room_message_original.content.relates_to { + Some(Relation::Reply { in_reply_to }) => Some(in_reply_to.event_id.clone()), + _ => None, + } + } else { + None + } + } + _ => None, + }; + + messages.push(message_like_event); + } + + messages.reverse(); + + Ok(messages) +} + +pub async fn process_matrix_messages( messages: &[MatrixMessage], params: &MatrixMessageProcessingParams, ) -> Vec { let mut messages_filtered: Vec = Vec::new(); for (i, message) in messages.iter().enumerate() { - if !is_message_from_allowed_sender(message, ¶ms.bot_user_id, ¶ms.allowed_users) { + if !is_message_from_allowed_sender( + message, + ¶ms.bot_user_id, + params.allowed_users.as_deref(), + ) { continue; } let mut message = message.clone(); - if i == 0 && !params.first_message_stripped_prefixes.is_empty() { + if i == 0 && !params.first_message_prefixes_to_strip.is_empty() { let mut message_text = message.message_text.clone(); - for prefix in ¶ms.first_message_stripped_prefixes { + for prefix in ¶ms.first_message_prefixes_to_strip { + if let Some(message_text_stripped) = message_text.strip_prefix(prefix) { + message_text = message_text_stripped.to_owned(); + } + } + + message.message_text = message_text.trim().to_owned(); + } + + // We only strip `bot_user_prefixes_to_strip`-defined prefixes from messages that mention the bot user. + if !params.bot_user_prefixes_to_strip.is_empty() + && message.mentioned_users.contains(¶ms.bot_user_id) + { + let mut message_text = message.message_text.clone(); + + for prefix in ¶ms.bot_user_prefixes_to_strip { if let Some(message_text_stripped) = message_text.strip_prefix(prefix) { message_text = message_text_stripped.to_owned(); } @@ -73,16 +180,25 @@ pub async fn process_matrix_messages_in_thread( messages_filtered } +/// Tells if the given message is from an allowed sender. +/// +/// If allowed_users is None, all messages are allowed. +/// If allowed_users is Some, only messages from the allowed users (and the `bot_user_id`) are allowed. fn is_message_from_allowed_sender( matrix_message: &MatrixMessage, - bot_user_id: &str, - allowed_users: &[regex::Regex], + bot_user_id: &OwnedUserId, + allowed_users: Option<&[regex::Regex]>, ) -> bool { - if matrix_message.sender_id == bot_user_id { + if matrix_message.sender_id == *bot_user_id { return true; } - if mxidwc::match_user_id(&matrix_message.sender_id, allowed_users) { + if let Some(allowed_users) = allowed_users { + if mxidwc::match_user_id(matrix_message.sender_id.as_str(), allowed_users) { + return true; + } + } else { + // No allowed users configured, so all messages are allowed return true; } @@ -108,30 +224,58 @@ pub fn convert_matrix_native_event_to_matrix_message( _ => return None, }; + let is_reply = matches!(room_message.relates_to, Some(Relation::Reply { .. })); + + let text = if is_reply { + // For regular replies, we need to strip the fallback-for-rich replies part. + // See: https://spec.matrix.org/v1.11/client-server-api/#fallbacks-for-rich-replies + strip_rich_reply_fallback_text(&text) + } else { + text + }; + + let mentioned_users = room_message + .mentions + .map(|m| m.user_ids.iter().map(|u| u.to_owned()).collect()) + .unwrap_or(vec![]); + Some(MatrixMessage { - sender_id: matrix_native_event.sender().to_string(), + sender_id: matrix_native_event.sender().to_owned(), message_type: if is_notice { MatrixMessageType::Notice } else { MatrixMessageType::Text }, message_text: text, + mentioned_users, }) } -/// Determines the thread context (relationship within the thread + first thread message payload) for an incoming (new) room event. -/// This room event is assumed to be the "newest message" in the thread (or a top-level message). -/// If the given event is a regular reply (not a thread reply), this function will return `None`. -/// If the given event is a top-level message, this function will consider this event as the start of the thread. -/// If the given event is a thread reply, this function will inspect the thread root event and will return the thread context. -/// If the thread root event is not found, is redacted, or is of some unsupported MessagePayload type, this function will return `None`. -pub async fn determine_thread_context_for_room_event( +/// Determines the interaction context for an incoming (new) room event. +/// +/// This context is created based on the "newest message" (`current_event`), which is: +/// - either a top-level message, which may or may not be mentioning the bot +/// - this function will inspect the event and will likely start a new threaded conversation +/// +/// - or a thread reply +/// - this function will inspect the thread root event and will return the interaction context +/// - if the bot only reacts to prefixed messsages (or mentions), this function may ignore the given thread reply, unless it mentions the bot (which causes a synthetic "first message" to be produced) +/// - if the thread root event is not found, is redacted, or is of some unsupported MessagePayload type, this function will return `None` +/// +/// - or an in-room (non-threaded) reply to a room message, which may or may not be mentioning the bot +/// - replies that do not mention the bot cause this function to return `None` +/// - other replies create a interaction context which points to a "first message" which is synthetic +#[tracing::instrument(name = "determine_interaction_context_for_room_event", skip_all, fields(room_id = room.room_id().as_str(), event_id = current_event.event_id.as_str()))] +pub async fn determine_interaction_context_for_room_event( bot_user_id: &OwnedUserId, room: &Room, current_event: &OriginalSyncRoomMessageEvent, current_event_payload: &MessagePayload, event_fetcher: &Arc, -) -> anyhow::Result> { +) -> anyhow::Result> { + let current_event_is_mentioning_bot = + is_event_mentioning_bot(¤t_event.content, bot_user_id); + let Some(relation) = ¤t_event.content.relates_to else { // This is a top-level message. We consider it the start of the thread. let thread_info = ThreadInfo::new( @@ -139,25 +283,73 @@ pub async fn determine_thread_context_for_room_event( current_event.event_id.clone(), ); - let is_mentioning_bot = is_event_mentioning_bot(¤t_event.content, bot_user_id); - - return Ok(Some(ThreadContext { - info: thread_info, - first_message: ThreadContextFirstMessage { - is_mentioning_bot, + return Ok(Some(InteractionContext { + thread_info, + trigger: InteractionTrigger { + is_mentioning_bot: current_event_is_mentioning_bot, payload: current_event_payload.clone(), }, })); }; - let Relation::Thread(thread) = relation else { - // This is a reply or a replacement, etc. It's not a thread. - // We don't care about this. - return Ok(None); - }; + match relation { + Relation::Thread(thread) => { + determine_interaction_context_for_room_event_related_to_thread( + bot_user_id, + room, + current_event, + event_fetcher, + current_event_is_mentioning_bot, + thread, + ) + .await + } + Relation::Reply { in_reply_to } => { + determine_interaction_context_for_room_event_related_to_reply( + current_event, + current_event_is_mentioning_bot, + in_reply_to.event_id.clone(), + ) + .await + } + // This is a replacement or something else. It's not something we support. + _ => return Ok(None), + } +} + +async fn determine_interaction_context_for_room_event_related_to_thread( + bot_user_id: &OwnedUserId, + room: &Room, + current_event: &OriginalSyncRoomMessageEvent, + event_fetcher: &Arc, + current_event_is_mentioning_bot: bool, + thread: &Thread, +) -> anyhow::Result> { let thread_info = ThreadInfo::new(thread.event_id.clone(), current_event.event_id.clone()); + tracing::trace!( + ?current_event_is_mentioning_bot, + is_thread_root_only = thread_info.is_thread_root_only(), + "Dealing with a thread reply", + ); + + if current_event_is_mentioning_bot && !thread_info.is_thread_root_only() { + // If the current event is a thread reply and is mentioning the bot, + // it's probably someone trying to involve us in the threaded conversation. + // See: https://github.com/etkecc/baibot/issues/15 + // + // In such cases, we don't care what the thread root event is like or what the current event is like, + // we want text-generation to be triggered for this whole thread regardless. + return Ok(Some(InteractionContext { + thread_info, + trigger: InteractionTrigger { + is_mentioning_bot: true, + payload: MessagePayload::SynthethicChatCompletionTriggerInThread, + }, + })); + } + let start_time = std::time::Instant::now(); let thread_start_timeline_event = event_fetcher @@ -183,82 +375,46 @@ pub async fn determine_thread_context_for_room_event( "Fetched thread start event" ); - let thread_start_timeline_event_deserialized = - match thread_start_timeline_event.event.deserialize() { - Ok(value) => value, - Err(err) => { - return Err(anyhow::format_err!( - "Failed to deserialize thread start event {}: {:?}", - thread.event_id, - err - )); - } - }; + let thread_start_detailed_message_payload = timeline_event_to_detailed_message_payload( + &thread.event_id, + thread_start_timeline_event, + thread_info.clone(), + bot_user_id, + )?; - let AnyTimelineEvent::MessageLike(thread_start_message_like_event) = - thread_start_timeline_event_deserialized - else { - tracing::trace!( - "Ignoring non-MessageLike thread start event: {:?}", - thread_start_timeline_event_deserialized - ); + let Some(detailed_message_payload) = thread_start_detailed_message_payload else { return Ok(None); }; - let (thread_start_message_is_mentioning_bot, thread_start_message_payload) = - match thread_start_message_like_event { - AnyMessageLikeEvent::RoomEncrypted(room_message) => { - tracing::warn!( - "Could not inspect thread start event {} because it failed to decrypt: {:?}", - thread.event_id.clone(), - room_message - ); + Ok(Some(InteractionContext { + thread_info, + trigger: InteractionTrigger { + is_mentioning_bot: detailed_message_payload.is_mentioning_bot, + payload: detailed_message_payload.message_payload, + }, + })) +} - // There's no way to know and it doesn't matter anyway. - let is_mentioning_bot = false; +async fn determine_interaction_context_for_room_event_related_to_reply( + current_event: &OriginalSyncRoomMessageEvent, + current_event_is_mentioning_bot: bool, + reply_to_event_id: OwnedEventId, +) -> anyhow::Result> { + tracing::trace!(?current_event_is_mentioning_bot, "Dealing with a reply"); - ( - is_mentioning_bot, - MessagePayload::Encrypted(thread_info.clone()), - ) - } - AnyMessageLikeEvent::RoomMessage(room_message) => { - if let MessageLikeEvent::Original(room_message_original) = room_message { - let room_message_payload: Result = - room_message_original.content.msgtype.clone().try_into(); + if !current_event_is_mentioning_bot { + // If the current event is not mentioning the bot, we don't care about it. + tracing::trace!("Ignoring reply event which does not mention the bot"); + return Ok(None); + } - let Ok(room_message_payload) = room_message_payload else { - tracing::debug!( - msg_type = room_message_original.content.msgtype(), - "Ignoring thread start message of unknown type", - ); - return Ok(None); - }; + let thread_info = ThreadInfo::new(reply_to_event_id.clone(), current_event.event_id.clone()); - let is_mentioning_bot = - is_event_mentioning_bot(&room_message_original.content, bot_user_id); - - (is_mentioning_bot, room_message_payload) - } else { - tracing::error!("Ignoring thread start message which appears to be redacted"); - - return Ok(None); - } - } - other => { - tracing::trace!( - "Ignoring unknown MessageLike thread start event: {:?}", - other - ); - return Ok(None); - } - }; - - Ok(Some(ThreadContext { - info: thread_info, - first_message: ThreadContextFirstMessage { - is_mentioning_bot: thread_start_message_is_mentioning_bot, - payload: thread_start_message_payload, + Ok(Some(InteractionContext { + thread_info, + trigger: InteractionTrigger { + is_mentioning_bot: true, + payload: MessagePayload::SynthethicChatCompletionTriggerForReply, }, })) } @@ -267,21 +423,157 @@ fn is_event_mentioning_bot( event_content: &RoomMessageEventContent, bot_user_id: &OwnedUserId, ) -> bool { - if let Some(mentions) = &event_content.mentions { - mentions - .user_ids - .iter() - .any(|user_id| user_id == bot_user_id) - } else { - // For compatibility with clients that do not support the new Mentions specification - // (see https://spec.matrix.org/latest/client-server-api/#user-and-room-mentions), - // we also do string matching here. - // - // It may be even better to match not only against the MXID, but also against the bot's - // room-specific display name. - // - // We may consider dropping this string-matching behavior altogether in the future, - // so improving this compatibility block is not a high priority. - event_content.body().contains(bot_user_id.as_str()) - } + // As a fallback, we used to do string matching (`event_content.body().contains(bot_user_id.as_str())`) here as well. + // However, this is unreliable. In 2024+, clients that do not have proper mentions support should get fixed, + // instead of us having to deal with the possibility of false positives. + // + let Some(mentions) = &event_content.mentions else { + return false; + }; + + mentions + .user_ids + .iter() + .any(|user_id| user_id == bot_user_id) +} + +/// Strips the rich reply fallback text from the given text. +/// See: https://spec.matrix.org/v1.11/client-server-api/#fallbacks-for-rich-replies +/// +/// Example: +/// ```rust,ignore +/// let text = "> <@admin:example.com> What's the difference between Matrix and XMPP?\n\nAnswer me"; +/// let stripped_text = strip_rich_reply_fallback_text(text); +/// assert_eq!(stripped_text, "Answer me"); +/// ``` +fn strip_rich_reply_fallback_text(text: &str) -> String { + let lines = text.lines(); + let mut stripped_lines = Vec::new(); + let mut encountered_non_prefix = false; + + for line in lines { + if !encountered_non_prefix && line.starts_with("> ") { + continue; + } else { + encountered_non_prefix = true; + stripped_lines.push(line); + } + } + + stripped_lines.join("\n").trim().to_owned() +} + +fn timeline_event_to_detailed_message_payload( + timeline_event_id: &OwnedEventId, + timeline_event: TimelineEvent, + thread_info: ThreadInfo, + bot_user_id: &OwnedUserId, +) -> anyhow::Result> { + let timeline_event_deserialized = match timeline_event.event.deserialize() { + Ok(value) => value, + Err(err) => { + return Err(anyhow::format_err!( + "Failed to deserialize timeline event {}: {:?}", + timeline_event_id, + err + )); + } + }; + + let AnyTimelineEvent::MessageLike(thread_start_message_like_event) = + timeline_event_deserialized + else { + tracing::trace!( + "Ignoring non-MessageLike timeline event: {:?}", + timeline_event_deserialized + ); + return Ok(None); + }; + + let (is_mentioning_bot, message_payload) = match thread_start_message_like_event { + AnyMessageLikeEvent::RoomEncrypted(room_message) => { + tracing::warn!( + "Could not inspect event {} because it failed to decrypt: {:?}", + timeline_event_id.clone(), + room_message + ); + + // There's no way to know and it doesn't matter anyway. + let is_mentioning_bot = false; + + ( + is_mentioning_bot, + MessagePayload::Encrypted(thread_info.clone()), + ) + } + AnyMessageLikeEvent::RoomMessage(room_message) => { + if let MessageLikeEvent::Original(room_message_original) = room_message { + let room_message_payload: Result = + room_message_original.content.msgtype.clone().try_into(); + + let Ok(room_message_payload) = room_message_payload else { + tracing::debug!( + msg_type = room_message_original.content.msgtype(), + "Ignoring event message of unknown type", + ); + return Ok(None); + }; + + let is_mentioning_bot = + is_event_mentioning_bot(&room_message_original.content, bot_user_id); + + (is_mentioning_bot, room_message_payload) + } else { + tracing::error!("Ignoring event message which appears to be redacted"); + + return Ok(None); + } + } + other => { + tracing::trace!("Ignoring unknown MessageLike event: {:?}", other); + return Ok(None); + } + }; + + Ok(Some(DetailedMessagePayload { + is_mentioning_bot, + message_payload, + })) +} + +/// Creates a list of prefixes to strip from the beginning of message texts that mention the bot user. +/// +/// Different clients do mentions differently. +/// The body text containing the mention usually contains one of: +/// - the full user ID (includes a @ prefix by default) +/// - the localpart (with a @ prefix) +/// - the localpart (without a @ prefix) +/// - the display name (with a @ prefix) +/// - the display name (without a @ prefix) +/// +/// Some add a `: ` suffix after the mention. +/// +/// There's no guarantee that the mention is at the start even. +/// It being there is most common and we try to strip it from there +/// as best as we can. +pub fn create_list_of_bot_user_prefixes_to_strip( + bot_user_id: &OwnedUserId, + bot_display_name: &Option, +) -> Vec { + let bot_user_id_localpart = bot_user_id.localpart(); + + let mut prefixes_to_strip = vec![ + bot_user_id.as_str().to_owned(), + format!("@{}", bot_user_id_localpart), + bot_user_id_localpart.to_owned(), + ]; + + if let Some(bot_display_name) = bot_display_name { + prefixes_to_strip.push(format!("@{}", bot_display_name)); + prefixes_to_strip.push(bot_display_name.to_owned()); + } + + prefixes_to_strip.push(":".to_owned()); + + prefixes_to_strip } diff --git a/src/conversation/matrix/utils/tests.rs b/src/conversation/matrix/utils/tests.rs index f7ede76..3383d49 100644 --- a/src/conversation/matrix/utils/tests.rs +++ b/src/conversation/matrix/utils/tests.rs @@ -1,29 +1,35 @@ +use mxlink::matrix_sdk::ruma::OwnedUserId; + use crate::conversation::matrix::{ MatrixMessage, MatrixMessageProcessingParams, MatrixMessageType, }; #[test] fn is_message_from_allowed_sender() { - let bot_user_id = "@bot:example.com"; - let allowed_user_id = "@user.someone:example.com"; - let unallowed_user_id = "@another:example.com"; + let bot_user_id = + OwnedUserId::try_from("@bot:example.com").expect("Failed to parse bot user ID"); + let allowed_user_id = OwnedUserId::try_from("@user.someone:example.com").unwrap(); + let unallowed_user_id = OwnedUserId::try_from("@another:example.com").unwrap(); let bot_message = MatrixMessage { sender_id: bot_user_id.to_owned(), message_type: MatrixMessageType::Text, message_text: "Hello!".to_owned(), + mentioned_users: vec![], }; let allowed_user_message = MatrixMessage { sender_id: allowed_user_id.to_owned(), message_type: MatrixMessageType::Text, message_text: "Hello!".to_owned(), + mentioned_users: vec![], }; let unallowed_user_message = MatrixMessage { sender_id: unallowed_user_id.to_owned(), message_type: MatrixMessageType::Text, message_text: "Hello!".to_owned(), + mentioned_users: vec![], }; let parsed_regex = match mxidwc::parse_pattern("@user.*:example.com") { @@ -36,65 +42,96 @@ fn is_message_from_allowed_sender() { let allowed_users = vec![parsed_regex]; assert!( - super::is_message_from_allowed_sender(&bot_message, bot_user_id, &vec![]), + super::is_message_from_allowed_sender(&bot_message, &bot_user_id, Some(&allowed_users)), "Bot message should be allowed" ); assert!( - super::is_message_from_allowed_sender(&allowed_user_message, bot_user_id, &allowed_users), + super::is_message_from_allowed_sender( + &allowed_user_message, + &bot_user_id, + Some(&allowed_users) + ), "Allowed user message should be allowed" ); assert!( !super::is_message_from_allowed_sender( &unallowed_user_message, - bot_user_id, - &allowed_users + &bot_user_id, + Some(&allowed_users), ), "Unallowed user message should be ignored" ); + + assert!( + super::is_message_from_allowed_sender(&unallowed_user_message, &bot_user_id, None,), + "An empty list of allowed users lets everyone through" + ); } #[tokio::test] -async fn process_matrix_messages_in_thread() { - let bot_user_id = "@bot:example.com"; - let allowed_user_id = "@user.someone:example.com"; - let unallowed_user_id = "@another:example.com"; +async fn process_matrix_messages() { + let bot_user_id = + OwnedUserId::try_from("@bot:example.com").expect("Failed to parse bot user ID"); + let allowed_user_id = OwnedUserId::try_from("@user.someone:example.com").unwrap(); + let unallowed_user_id = OwnedUserId::try_from("@another:example.com").unwrap(); let allowed_user_message = MatrixMessage { sender_id: allowed_user_id.to_owned(), message_type: MatrixMessageType::Text, message_text: "Hello from the user!".to_owned(), + mentioned_users: vec![], }; let allowed_user_message_with_prefix = MatrixMessage { sender_id: allowed_user_id.to_owned(), message_type: MatrixMessageType::Text, message_text: "!bai Hello from the user!".to_owned(), + mentioned_users: vec![], }; let allowed_user_message_with_prefix_no_space = MatrixMessage { sender_id: allowed_user_id.to_owned(), message_type: MatrixMessageType::Text, message_text: "!baiHello from the user!".to_owned(), + mentioned_users: vec![], }; let allowed_user_message_with_prefix_full_width_space = MatrixMessage { sender_id: allowed_user_id.to_owned(), message_type: MatrixMessageType::Text, message_text: "!baiγ€€Hello from the user!".to_owned(), + mentioned_users: vec![], }; let bot_message = MatrixMessage { sender_id: bot_user_id.to_owned(), message_type: MatrixMessageType::Text, message_text: "Hello from the bot!".to_owned(), + mentioned_users: vec![], + }; + + let allowed_user_message_with_bot_mention = MatrixMessage { + sender_id: allowed_user_id.to_owned(), + message_type: MatrixMessageType::Text, + message_text: "@baibot: Hello from the user!".to_owned(), + mentioned_users: vec![bot_user_id.to_owned()], + }; + + // The message text is the same as above - it mentions the bot, but the actually-mentioned user is another user. + let allowed_user_message_with_another_user_mention = MatrixMessage { + sender_id: allowed_user_id.to_owned(), + message_type: MatrixMessageType::Text, + message_text: allowed_user_message_with_bot_mention.message_text.clone(), + mentioned_users: vec![allowed_user_id.to_owned()], }; let unallowed_user_message = MatrixMessage { sender_id: unallowed_user_id.to_owned(), message_type: MatrixMessageType::Text, message_text: "Hello from an unallowed user!".to_owned(), + mentioned_users: vec![], }; let parsed_regex = match mxidwc::parse_pattern("@user.*:example.com") { @@ -106,12 +143,24 @@ async fn process_matrix_messages_in_thread() { let allowed_users = vec![parsed_regex]; - let message_processing_params_basic = - super::MatrixMessageProcessingParams::new(bot_user_id.to_owned(), allowed_users.clone()); + let message_processing_params_basic = super::MatrixMessageProcessingParams::new( + bot_user_id.to_owned(), + Some(allowed_users.clone()), + ); let message_processing_params_with_prefix_stripping = - super::MatrixMessageProcessingParams::new(bot_user_id.to_owned(), allowed_users.clone()) - .with_first_message_stripped_prefixes(vec!["!bai".to_owned()]); + super::MatrixMessageProcessingParams::new( + bot_user_id.to_owned(), + Some(allowed_users.clone()), + ) + .with_first_message_prefixes_to_strip(vec!["!bai".to_owned()]); + + let message_processing_params_with_bot_user_prefix_stripping = + super::MatrixMessageProcessingParams::new( + bot_user_id.to_owned(), + Some(allowed_users.clone()), + ) + .with_bot_user_prefixes_to_strip(vec!["@baibot: ".to_owned(), "@baibot".to_owned()]); struct TestCase { name: String, @@ -195,10 +244,23 @@ async fn process_matrix_messages_in_thread() { "!bai Hello from the user!".to_owned(), ], }, + TestCase { + name: "Messages that mention the bot user get the bot user prefix stripped" + .to_owned(), + messages: vec![ + allowed_user_message_with_bot_mention.clone(), + allowed_user_message_with_another_user_mention.clone(), + ], + message_processing_params: message_processing_params_with_bot_user_prefix_stripping.clone(), + expected_message_texts: vec![ + "Hello from the user!".to_owned(), + "@baibot: Hello from the user!".to_owned(), + ], + }, ]; for test_case in test_cases { - let processed_messages = super::process_matrix_messages_in_thread( + let processed_messages = super::process_matrix_messages( &test_case.messages, &test_case.message_processing_params, ) @@ -216,3 +278,48 @@ async fn process_matrix_messages_in_thread() { ); } } + +#[test] +fn strip_rich_reply_fallback_text() { + let text = "> <@admin:example.com> What's the difference between Matrix and XMPP?\n\nAnswer me"; + let stripped_text = super::strip_rich_reply_fallback_text(text); + assert_eq!(stripped_text, "Answer me"); +} + +#[test] +fn create_list_of_bot_user_prefixes_to_strip() { + let bot_user_id = + OwnedUserId::try_from("@baibot:example.com").expect("Failed to parse bot user ID"); + + // Test case 1: Bot user with no display name + let bot_display_name = None; + let prefixes = + super::create_list_of_bot_user_prefixes_to_strip(&bot_user_id, &bot_display_name); + + assert_eq!( + prefixes, + vec![ + "@baibot:example.com".to_string(), + "@baibot".to_string(), + "baibot".to_string(), + ":".to_string() + ] + ); + + // Test case 2: Bot user with display name + let bot_display_name = Some("Assistant".to_string()); + let prefixes = + super::create_list_of_bot_user_prefixes_to_strip(&bot_user_id, &bot_display_name); + + assert_eq!( + prefixes, + vec![ + "@baibot:example.com".to_string(), + "@baibot".to_string(), + "baibot".to_string(), + "@Assistant".to_string(), + "Assistant".to_string(), + ":".to_string() + ] + ); +} diff --git a/src/conversation/matrix_llm_bridge.rs b/src/conversation/matrix_llm_bridge.rs index 4528190..522bd2f 100644 --- a/src/conversation/matrix_llm_bridge.rs +++ b/src/conversation/matrix_llm_bridge.rs @@ -1,9 +1,14 @@ +use std::sync::Arc; + use mxlink::matrix_sdk::ruma::OwnedEventId; use mxlink::MatrixLink; +use crate::conversation::matrix::MatrixMessage; + use super::llm::{convert_matrix_message_to_llm_message, Conversation, Message}; use super::matrix::{ - get_matrix_messages_in_thread, process_matrix_messages_in_thread, MatrixMessageProcessingParams, + get_matrix_messages_in_reply_chain, get_matrix_messages_in_thread, process_matrix_messages, + MatrixMessageProcessingParams, RoomEventFetcher, }; pub async fn create_llm_conversation_for_matrix_thread( @@ -14,7 +19,33 @@ pub async fn create_llm_conversation_for_matrix_thread( ) -> Result { let messages = get_matrix_messages_in_thread(matrix_link, room, thread_id).await?; - let messages_filtered = process_matrix_messages_in_thread(&messages, params).await; + let llm_messages = filter_messages_and_convert_to_llm_messages(messages, params).await; + + Ok(Conversation { + messages: llm_messages, + }) +} + +pub async fn create_llm_conversation_for_matrix_reply_chain( + event_fetcher: &Arc, + room: &mxlink::matrix_sdk::Room, + event_id: OwnedEventId, + params: &MatrixMessageProcessingParams, +) -> Result { + let messages = get_matrix_messages_in_reply_chain(event_fetcher, room, event_id).await?; + + let llm_messages = filter_messages_and_convert_to_llm_messages(messages, params).await; + + Ok(Conversation { + messages: llm_messages, + }) +} + +async fn filter_messages_and_convert_to_llm_messages( + messages: Vec, + params: &MatrixMessageProcessingParams, +) -> Vec { + let messages_filtered = process_matrix_messages(&messages, params).await; let mut llm_messages: Vec = Vec::new(); @@ -28,7 +59,5 @@ pub async fn create_llm_conversation_for_matrix_thread( llm_messages.push(llm_message); } - Ok(Conversation { - messages: llm_messages, - }) + llm_messages } diff --git a/src/conversation/mod.rs b/src/conversation/mod.rs index 9d02ad3..3ce90da 100644 --- a/src/conversation/mod.rs +++ b/src/conversation/mod.rs @@ -2,4 +2,6 @@ pub(crate) mod llm; pub(crate) mod matrix; mod matrix_llm_bridge; -pub(crate) use matrix_llm_bridge::create_llm_conversation_for_matrix_thread; +pub(crate) use matrix_llm_bridge::{ + create_llm_conversation_for_matrix_reply_chain, create_llm_conversation_for_matrix_thread, +}; diff --git a/src/entity/interaction_context.rs b/src/entity/interaction_context.rs new file mode 100644 index 0000000..3307539 --- /dev/null +++ b/src/entity/interaction_context.rs @@ -0,0 +1,13 @@ +use mxlink::ThreadInfo; + +use super::MessagePayload; + +pub struct InteractionContext { + pub thread_info: ThreadInfo, + pub trigger: InteractionTrigger, +} + +pub struct InteractionTrigger { + pub is_mentioning_bot: bool, + pub payload: MessagePayload, +} diff --git a/src/entity/message_payload.rs b/src/entity/message_payload.rs index 98d09a1..027f69f 100644 --- a/src/entity/message_payload.rs +++ b/src/entity/message_payload.rs @@ -6,10 +6,28 @@ use mxlink::matrix_sdk::ruma::{OwnedEventId, OwnedUserId}; use mxlink::ThreadInfo; /// MessagePayload is like matrix-sdk's MessageType, but represents only message types that the bot deals with and payloads are massaged a bit. +/// +/// This also includes a few synthetic events. #[derive(Debug, Clone)] pub enum MessagePayload { - Text(TextMessageEventContent), + /// A synthetic message payload that indicates that the bot should produce a reply inside a thread. + /// This does not represent an actual message event, it's just a way to trigger a chat completion. + /// + /// When this is invoked, the ThreadInfo contains the full thread details (which represents our context). + /// + /// See: https://github.com/etkecc/baibot/issues/15 + SynthethicChatCompletionTriggerInThread, + /// A synthetic message payload that indicates that the bot should produce a reply to a specific message. + /// This does not represent an actual message event, it's just a way to trigger a chat completion. + /// + /// When this is invoked, the ThreadInfo would refer to the reply-message that triggered us. + /// We can follow the chain upward from it to get the full context. + /// + /// See: https://github.com/etkecc/baibot/issues/15 + SynthethicChatCompletionTriggerForReply, + + Text(TextMessageEventContent), Audio(AudioMessageEventContent), Reaction { diff --git a/src/entity/mod.rs b/src/entity/mod.rs index bb6ed3a..158ef2a 100644 --- a/src/entity/mod.rs +++ b/src/entity/mod.rs @@ -1,15 +1,15 @@ pub mod catch_up_marker; pub mod cfg; pub mod globalconfig; +mod interaction_context; mod message_context; mod message_payload; mod room_config_context; pub mod roomconfig; -mod thread_context; mod trigger_event_info; +pub use interaction_context::{InteractionContext, InteractionTrigger}; pub use message_context::MessageContext; pub use message_payload::MessagePayload; pub use room_config_context::RoomConfigContext; -pub use thread_context::{ThreadContext, ThreadContextFirstMessage}; pub use trigger_event_info::TriggerEventInfo; diff --git a/src/entity/thread_context.rs b/src/entity/thread_context.rs deleted file mode 100644 index 207a18f..0000000 --- a/src/entity/thread_context.rs +++ /dev/null @@ -1,13 +0,0 @@ -use mxlink::ThreadInfo; - -use super::MessagePayload; - -pub struct ThreadContext { - pub info: ThreadInfo, - pub first_message: ThreadContextFirstMessage, -} - -pub struct ThreadContextFirstMessage { - pub is_mentioning_bot: bool, - pub payload: MessagePayload, -}