1use std::collections::{HashMap, HashSet};
29use std::fmt;
30use std::ops::{Bound, Range, RangeBounds};
31use std::str::FromStr;
32use std::sync::Arc;
33
34use serde::{Deserialize, Serialize, Serializer};
35
36use crate::completion::Message;
37use crate::message::{StopReason, ToolCall, UserContent};
38
39mod aside;
40mod author;
41mod lineage;
42mod note;
43pub mod pairing;
44mod turn;
45
46pub use aside::{Aside, AsideId};
47pub use author::{Author, AuthorKind, RequestId};
48pub use lineage::Lineage;
49pub use note::Note;
50pub use turn::{TURN, TurnEnd, TurnRecord};
51
52use pairing::{Normalized, PairingError};
53
54pub const FILE_OPEN: &str = "<file path=\"";
57
58pub const FILE_CLOSE: &str = "</file>";
60
61pub fn attached_file(text: &str) -> Option<(&str, &str)> {
64 let rest = text.strip_prefix(FILE_OPEN)?.strip_suffix(FILE_CLOSE)?;
65 rest.split_once("\">\n")
66}
67
68#[derive(Clone, Debug, Default, Serialize, Deserialize)]
84#[serde(try_from = "State")]
85pub struct Conversation {
86 messages: Vec<Arc<Message>>,
89 meta: Vec<Meta>,
91 #[serde(serialize_with = "saved_asides")]
93 asides: Vec<(AsideId, Aside)>,
94 #[serde(skip_serializing_if = "Option::is_none")]
95 halted: Option<Halt>,
96 next_id: u64,
98 next_aside: u64,
100 #[serde(skip)]
103 revision: u64,
104 #[serde(skip)]
106 lineage: Lineage,
107 #[serde(skip)]
108 unsaved: Vec<ChangeRecord>,
109 #[serde(skip)]
112 clock: Option<u64>,
113}
114
115#[derive(Clone, Debug, Serialize, Deserialize)]
119struct Meta {
120 id: MessageId,
121 #[serde(default, skip_serializing_if = "Option::is_none")]
122 author: Option<Author>,
123 #[serde(default)]
125 made: u64,
126}
127
128#[derive(Deserialize)]
130struct State {
131 #[serde(default)]
132 messages: Vec<Arc<Message>>,
133 #[serde(default)]
134 meta: Vec<Meta>,
135 #[serde(default)]
136 asides: Vec<(AsideId, Aside)>,
137 #[serde(default)]
138 halted: Option<Halt>,
139 #[serde(default)]
140 next_id: u64,
141 #[serde(default)]
142 next_aside: u64,
143}
144
145impl TryFrom<State> for Conversation {
146 type Error = ApplyError;
147
148 fn try_from(state: State) -> Result<Self, ApplyError> {
149 if state.meta.len() != state.messages.len() {
150 return Err(mismatch(format!(
151 "{} messages but {} ids",
152 state.messages.len(),
153 state.meta.len()
154 )));
155 }
156 let mut ids = HashSet::new();
157 if let Some(meta) = state
158 .meta
159 .iter()
160 .find(|meta| meta.id.0 >= state.next_id || !ids.insert(meta.id))
161 {
162 return Err(mismatch(format!("message id {} reused", meta.id.0)));
163 }
164 let mut asides = HashSet::new();
165 if let Some((id, _)) = state
166 .asides
167 .iter()
168 .find(|(id, _)| id.0 >= state.next_aside || !asides.insert(*id))
169 {
170 return Err(mismatch(format!("aside id {} reused", id.0)));
171 }
172 Ok(Self {
173 messages: state.messages,
174 meta: state.meta,
175 asides: state.asides,
176 halted: state.halted,
177 next_id: state.next_id,
178 next_aside: state.next_aside,
179 revision: 0,
180 lineage: Lineage::DETACHED,
181 unsaved: Vec::new(),
182 clock: None,
183 })
184 }
185}
186
187fn saved_asides<S: Serializer>(
188 asides: &[(AsideId, Aside)],
189 serializer: S,
190) -> Result<S::Ok, S::Error> {
191 serializer.collect_seq(asides.iter().filter(|(_, aside)| aside.saved))
192}
193
194impl PartialEq for Conversation {
195 fn eq(&self, other: &Self) -> bool {
196 let saved = |(_, aside): &&(AsideId, Aside)| aside.saved;
197 let same = |a: &Meta, b: &Meta| a.id == b.id && a.author == b.author;
198 self.messages == other.messages
199 && self.meta.len() == other.meta.len()
200 && self.meta.iter().zip(&other.meta).all(|(a, b)| same(a, b))
201 && self.halted == other.halted
202 && self
203 .asides
204 .iter()
205 .filter(saved)
206 .eq(other.asides.iter().filter(saved))
207 }
208}
209
210#[derive(
214 Clone, Copy, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize,
215)]
216#[serde(transparent)]
217pub struct MessageId(u64);
218
219impl MessageId {
220 pub const fn get(self) -> u64 {
222 self.0
223 }
224}
225
226impl fmt::Display for MessageId {
228 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
229 write!(f, "m{}", self.0)
230 }
231}
232
233impl FromStr for MessageId {
235 type Err = ParseIdError;
236
237 fn from_str(text: &str) -> Result<Self, ParseIdError> {
238 parse_id(text, 'm').map(Self)
239 }
240}
241
242#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
244#[error("{0:?} is not an id")]
245pub struct ParseIdError(pub String);
246
247fn parse_id(text: &str, prefix: char) -> Result<u64, ParseIdError> {
249 let number = text.strip_prefix(prefix).unwrap_or(text);
250 number.parse().map_err(|_| ParseIdError(text.to_owned()))
251}
252
253#[derive(
258 Clone, Copy, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize,
259)]
260pub struct MessageKey {
261 pub made: u64,
263 pub id: MessageId,
265}
266
267#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
272pub struct Entry {
273 pub message: Arc<Message>,
275 #[serde(default, skip_serializing_if = "Option::is_none")]
278 pub author: Option<Author>,
279 #[serde(default, skip_serializing_if = "Option::is_none")]
281 pub id: Option<MessageId>,
282 #[serde(default, skip_serializing_if = "Option::is_none")]
286 pub made: Option<u64>,
287}
288
289impl Entry {
290 pub fn new(message: impl Into<Arc<Message>>) -> Self {
292 Self {
293 message: message.into(),
294 author: None,
295 id: None,
296 made: None,
297 }
298 }
299
300 pub fn is_stop_marker(&self) -> bool {
303 is_stop_marker(&self.message, self.author.as_ref())
304 }
305
306 #[must_use]
308 pub fn by(self, author: Author) -> Self {
309 Self {
310 author: Some(author),
311 ..self
312 }
313 }
314}
315
316impl From<Message> for Entry {
317 fn from(message: Message) -> Self {
318 Self::new(message)
319 }
320}
321
322impl From<Arc<Message>> for Entry {
323 fn from(message: Arc<Message>) -> Self {
324 Self::new(message)
325 }
326}
327
328#[derive(Clone, Copy, Debug)]
330pub struct EntryRef<'c> {
331 pub index: usize,
333 pub id: MessageId,
335 pub message: &'c Message,
337 pub author: Option<&'c Author>,
339 pub made: u64,
341}
342
343impl EntryRef<'_> {
344 pub fn key(&self) -> MessageKey {
346 MessageKey {
347 made: self.made,
348 id: self.id,
349 }
350 }
351
352 pub fn is_users_own(&self) -> bool {
356 let typed = match self.message {
357 Message::User { content } => content
358 .iter()
359 .any(|item| !matches!(item, UserContent::ToolResult(_))),
360 _ => false,
361 };
362 let users = self
363 .author
364 .is_none_or(|author| author.kind == AuthorKind::User && author.title.is_none());
365 typed && users
366 }
367
368 pub fn is_request(&self) -> bool {
372 let asks = match self.message {
373 Message::User { content } => content
374 .iter()
375 .any(|item| !matches!(item, UserContent::ToolResult(_))),
376 _ => false,
377 };
378 asks && self
379 .author
380 .is_none_or(|author| author.kind != AuthorKind::Rig)
381 }
382
383 pub fn is_stop_marker(&self) -> bool {
386 is_stop_marker(self.message, self.author)
387 }
388}
389
390fn is_stop_marker(message: &Message, author: Option<&Author>) -> bool {
392 let by_rig = author.is_some_and(|author| author.kind == AuthorKind::Rig);
393 by_rig
394 && matches!(message, Message::User { content }
395 if matches!(content.as_slice(), [UserContent::Text(text)] if text.text == STOPPED))
396}
397
398pub const STOPPED: &str = "[The user stopped the request above before it was fully answered; \
402 any reply to it above was cut off there. \
403 Do not carry it out unless asked again.]";
404
405#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
408#[serde(rename_all = "snake_case")]
409pub enum Halt {
410 Stopped,
413 #[default]
416 Kept,
417}
418
419#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
422#[serde(tag = "type", rename_all = "snake_case")]
423pub enum Change {
424 Push {
426 entries: Vec<Entry>,
428 },
429 Splice {
433 from: usize,
435 to: usize,
437 removed: Vec<MessageId>,
439 with: Vec<Entry>,
441 },
442 Halt {
444 halt: Option<Halt>,
446 },
447 Aside {
449 id: AsideId,
451 aside: Option<Aside>,
453 },
454 Reset {
458 state: Box<Conversation>,
460 },
461}
462
463#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
465pub struct ChangeRecord {
466 #[serde(flatten)]
468 pub change: Change,
469 #[serde(default, skip_serializing_if = "Option::is_none")]
472 pub by: Option<Author>,
473 #[serde(default, skip_serializing_if = "Option::is_none")]
475 pub at: Option<u64>,
476}
477
478#[derive(Clone, Debug, Default, PartialEq, Eq)]
480pub struct Spliced {
481 pub removed: Vec<MessageId>,
483 pub changed: Vec<MessageId>,
485 pub restored: Vec<MessageId>,
487}
488
489#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
492pub enum ApplyError {
493 #[error("{0}")]
495 Mismatch(String),
496}
497
498fn mismatch(why: String) -> ApplyError {
499 ApplyError::Mismatch(why)
500}
501
502pub fn short_id(id: &str) -> &str {
504 id.get(..8).unwrap_or(id)
505}
506
507impl Conversation {
508 pub fn new() -> Self {
510 Self::default()
511 }
512
513 pub fn from_messages(messages: impl IntoIterator<Item = impl Into<Arc<Message>>>) -> Self {
516 let mut conversation = Self::new();
517 conversation.extend(messages);
518 conversation
519 }
520
521 pub fn messages(&self) -> &[Arc<Message>] {
525 &self.messages
526 }
527
528 pub fn into_messages(self) -> Vec<Arc<Message>> {
531 self.messages
532 }
533
534 pub fn entries(&self) -> impl DoubleEndedIterator<Item = EntryRef<'_>> + ExactSizeIterator {
536 let entries = self.messages.iter().zip(&self.meta).enumerate();
537 entries.map(|(index, (message, meta))| EntryRef {
538 index,
539 id: meta.id,
540 message,
541 author: meta.author.as_ref(),
542 made: meta.made,
543 })
544 }
545
546 pub fn entry(&self, at: usize) -> Option<Entry> {
549 let meta = self.meta.get(at)?;
550 Some(Entry {
551 message: self.messages.get(at)?.clone(),
552 author: meta.author.clone(),
553 id: Some(meta.id),
554 made: Some(meta.made),
555 })
556 }
557
558 pub fn id(&self, at: usize) -> Option<MessageId> {
560 self.meta.get(at).map(|meta| meta.id)
561 }
562
563 pub fn key(&self, at: usize) -> Option<MessageKey> {
565 let meta = self.meta.get(at)?;
566 Some(MessageKey {
567 made: meta.made,
568 id: meta.id,
569 })
570 }
571
572 pub fn index_of_key(&self, key: MessageKey) -> Option<usize> {
575 let at = self.index_of(key.id)?;
576 (self.meta.get(at)?.made == key.made).then_some(at)
577 }
578
579 pub fn author(&self, at: usize) -> Option<&Author> {
582 self.meta.get(at)?.author.as_ref()
583 }
584
585 pub fn index_of(&self, id: MessageId) -> Option<usize> {
587 self.meta.iter().position(|meta| meta.id == id)
588 }
589
590 pub fn start_of(&self, id: MessageId) -> usize {
595 self.index_of(id).unwrap_or(if id.0 >= self.next_id {
596 self.messages.len()
597 } else {
598 0
599 })
600 }
601
602 pub fn turn_start(&self) -> MessageId {
605 let trailing = self.messages.iter().rev();
606 let trailing = trailing
607 .take_while(|message| matches!(&***message, Message::User { .. }))
608 .count();
609 let first = self.messages.len().saturating_sub(trailing);
610 let answered = (trailing > 0).then(|| self.id(first)).flatten();
611 answered.unwrap_or(MessageId(self.next_id))
612 }
613
614 pub fn asked(&self, start: MessageId) -> impl Iterator<Item = EntryRef<'_>> {
620 let entries = self.entries().skip(self.start_of(start));
621 entries.take_while(|entry| matches!(entry.message, Message::User { .. }))
622 }
623
624 pub fn request(&self, start: MessageId) -> Option<EntryRef<'_>> {
628 self.asked(start).find(EntryRef::is_request)
629 }
630
631 pub fn halted(&self) -> Option<Halt> {
634 self.halted
635 }
636
637 pub fn awaits_model(&self) -> bool {
642 let read = self.messages.iter().rev().map(Arc::as_ref).find(|message| {
643 !matches!(message, Message::Assistant(turn)
644 if turn.stop.as_ref().is_some_and(StopReason::is_failure))
645 });
646 self.halted.is_none() && matches!(read, Some(Message::User { .. }))
647 }
648
649 pub fn pending_calls(&self) -> Vec<ToolCall> {
652 pairing::pending_calls(&self.messages)
653 }
654
655 pub fn validate(&self) -> Result<(), PairingError> {
658 pairing::validate(&self.messages)
659 }
660
661 pub fn asides(&self) -> impl DoubleEndedIterator<Item = (AsideId, &Aside)> {
663 self.asides.iter().map(|(id, aside)| (*id, aside))
664 }
665
666 pub fn aside(&self, id: AsideId) -> Option<&Aside> {
668 let mut asides = self.asides.iter();
669 asides
670 .find(|(known, _)| *known == id)
671 .map(|(_, aside)| aside)
672 }
673
674 pub fn aside_position(&self, aside: &Aside) -> usize {
678 let Some(after) = aside.after else {
679 return 0;
680 };
681 match self.index_of(after) {
682 Some(index) => index.saturating_add(1),
683 None => self
684 .messages
685 .len()
686 .min(self.start_of(after).saturating_add(1)),
687 }
688 }
689
690 pub fn push(&mut self, entry: impl Into<Entry>) -> MessageId {
697 let entry = entry.into();
698 let by = entry.author.clone();
699 let mut ids = self.push_entries(vec![entry], by);
700 ids.pop().unwrap_or(MessageId(self.next_id))
701 }
702
703 pub fn extend(&mut self, messages: impl IntoIterator<Item = impl Into<Arc<Message>>>) {
706 let entries = messages.into_iter().map(Entry::new).collect();
707 self.push_entries(entries, None);
708 }
709
710 fn push_entries(&mut self, entries: Vec<Entry>, by: Option<Author>) -> Vec<MessageId> {
711 if entries.is_empty() {
712 return Vec::new();
713 }
714 let stop = (self.halted == Some(Halt::Stopped))
715 .then(|| Entry::new(Message::user(STOPPED)).by(Author::rig()));
716 let mut next = self.next_id;
717 let mut ids = Vec::with_capacity(entries.len());
718 let mut fill = |mut entry: Entry| {
719 let id = MessageId(next);
720 next = next.saturating_add(1);
721 entry.id = Some(id);
722 entry.made = None;
723 (entry, id)
724 };
725 let stop = stop.map(|entry| fill(entry).0);
726 let entries = entries.into_iter().map(|entry| {
727 let (entry, id) = fill(entry);
728 ids.push(id);
729 entry
730 });
731 let entries = stop.into_iter().chain(entries).collect();
732 self.record(Change::Push { entries }, by);
733 self.clear_halt();
734 ids
735 }
736
737 pub fn splice(
744 &mut self,
745 range: impl RangeBounds<usize>,
746 with: impl IntoIterator<Item = Entry>,
747 by: Author,
748 ) -> Spliced {
749 self.replace(range, with.into_iter().collect(), by, false)
750 }
751
752 pub fn restore(
777 &mut self,
778 range: impl RangeBounds<usize>,
779 with: impl IntoIterator<Item = Entry>,
780 by: Author,
781 ) -> Spliced {
782 self.replace(range, with.into_iter().collect(), by, true)
783 }
784
785 pub fn set_entries(&mut self, entries: Vec<Entry>, by: Author) -> Spliced {
790 let same = |entry: &Entry, at: usize| {
791 entry.id.is_some()
792 && entry.id == self.id(at)
793 && entry.author.as_ref() == self.author(at)
794 && Some(&entry.message) == self.messages.get(at)
795 };
796 let (len, count) = (self.messages.len(), entries.len());
797 let start = entries
798 .iter()
799 .enumerate()
800 .take_while(|(at, entry)| same(entry, *at))
801 .count();
802 let end = entries
803 .iter()
804 .rev()
805 .zip((0..len).rev())
806 .take(len.min(count).saturating_sub(start))
807 .take_while(|(entry, at)| same(entry, *at))
808 .count();
809 if start == len && start == count {
810 return Spliced::default();
811 }
812 let with = entries
813 .into_iter()
814 .skip(start)
815 .take(count.saturating_sub(start).saturating_sub(end));
816 self.restore(start..len.saturating_sub(end), with, by)
817 }
818
819 fn replace(
822 &mut self,
823 range: impl RangeBounds<usize>,
824 mut with: Vec<Entry>,
825 by: Author,
826 restores: bool,
827 ) -> Spliced {
828 let len = self.messages.len();
829 let to = match range.end_bound() {
830 Bound::Included(&end) => end.saturating_add(1),
831 Bound::Excluded(&end) => end,
832 Bound::Unbounded => len,
833 }
834 .min(len);
835 let from = match range.start_bound() {
836 Bound::Included(&start) => start,
837 Bound::Excluded(&start) => start.saturating_add(1),
838 Bound::Unbounded => 0,
839 }
840 .min(to);
841 if from == to && with.is_empty() {
842 return Spliced::default();
843 }
844 let replaced = self.meta.get(from..to).unwrap_or_default();
845 let removed: Vec<MessageId> = replaced.iter().map(|meta| meta.id).collect();
846 let in_range: HashSet<MessageId> = removed.iter().copied().collect();
847 let mut outside = None;
848 let mut taken = HashSet::new();
849 let mut restored = Vec::new();
850 let mut next = self.next_id;
851 for entry in &mut with {
852 let id = entry.id.filter(|id| !taken.contains(id));
853 if let Some(id) = id.filter(|id| in_range.contains(id)) {
854 taken.insert(id);
855 entry.made = None;
856 continue;
857 }
858 let back = id.filter(|id| {
859 restores
860 && id.0 < self.next_id
861 && !self.outside(from..to, &mut outside).contains(id)
862 });
863 if let Some(id) = back {
864 taken.insert(id);
865 restored.push(id);
866 continue;
867 }
868 entry.id = Some(MessageId(next));
869 entry.made = None;
870 next = next.saturating_add(1);
871 }
872 let changed = with.iter().filter_map(|entry| entry.id).collect();
873 let gone = removed.iter().filter(|id| !taken.contains(id)).copied();
874 let spliced = Spliced {
875 removed: gone.collect(),
876 changed,
877 restored,
878 };
879 let change = Change::Splice {
880 from,
881 to,
882 removed,
883 with,
884 };
885 self.record(change, Some(by));
886 if to == len {
887 self.clear_halt();
888 }
889 spliced
890 }
891
892 fn outside<'s>(
895 &self,
896 range: Range<usize>,
897 outside: &'s mut Option<HashSet<MessageId>>,
898 ) -> &'s HashSet<MessageId> {
899 outside.get_or_insert_with(|| {
900 let before = self.meta.get(..range.start).unwrap_or_default();
901 let after = self.meta.get(range.end..).unwrap_or_default();
902 before.iter().chain(after).map(|meta| meta.id).collect()
903 })
904 }
905
906 pub fn edit_each(
931 &mut self,
932 by: Author,
933 mut edit: impl FnMut(EntryRef<'_>) -> Option<Message>,
934 ) -> Vec<MessageId> {
935 let rewritten: Vec<(usize, MessageId, Message)> = self
936 .entries()
937 .filter_map(|entry| {
938 let (at, id) = (entry.index, entry.id);
939 edit(entry).map(|message| (at, id, message))
940 })
941 .collect();
942 let (Some(&(first, ..)), Some(&(last, ..))) = (rewritten.first(), rewritten.last()) else {
943 return Vec::new();
944 };
945 let ids = rewritten.iter().map(|(_, id, _)| *id).collect();
946 let mut rewritten = rewritten.into_iter().peekable();
947 let entries: Vec<Entry> = (first..=last)
948 .filter_map(|at| match rewritten.next_if(|(next, ..)| *next == at) {
949 Some((_, id, message)) => Some(Entry {
950 message: Arc::new(message),
951 author: self.author(at).cloned(),
952 id: Some(id),
953 made: None,
954 }),
955 None => self.entry(at),
956 })
957 .collect();
958 self.splice(first..=last, entries, by);
959 ids
960 }
961
962 pub fn normalize(&mut self, by: Author) -> Normalized {
969 let entries = (0..self.messages.len()).filter_map(|at| self.entry(at));
970 let normalized = pairing::normalize(entries.collect());
971 let into: HashMap<MessageId, MessageId> = normalized
972 .merged
973 .iter()
974 .map(|(kept, gone)| (*gone, *kept))
975 .collect();
976 let dropped: HashSet<MessageId> = normalized.dropped.iter().copied().collect();
977 let anchor = |id: MessageId| -> Option<MessageId> {
980 let before = self.meta.iter().rev().skip_while(|meta| meta.id != id);
981 before
982 .map(|meta| meta.id)
983 .find_map(|id| match into.get(&id) {
984 Some(kept) => Some(*kept),
985 None => (!dropped.contains(&id)).then_some(id),
986 })
987 };
988 let moves: Vec<(AsideId, Option<MessageId>)> = self
989 .asides
990 .iter()
991 .filter_map(|(at, aside)| {
992 let after = aside.after?;
993 let moves = into.contains_key(&after) || dropped.contains(&after);
994 moves.then(|| (*at, anchor(after)))
995 })
996 .collect();
997 self.set_entries(normalized.entries.clone(), by.clone());
998 for (id, after) in moves {
999 self.move_aside(id, after, &by);
1000 }
1001 normalized
1002 }
1003
1004 pub fn reread(&mut self, from_log: Conversation) -> Result<(), ApplyError> {
1015 if !self.unsaved.is_empty() || !from_log.unsaved.is_empty() {
1016 return Err(mismatch(
1017 "a value with unsaved changes is not reread".to_owned(),
1018 ));
1019 }
1020 let mut asides = from_log.asides;
1021 asides.retain(|(_, aside)| aside.saved);
1022 let unsaved = self.asides.iter().filter(|(_, aside)| !aside.saved);
1023 if let Some((id, _)) = unsaved
1024 .clone()
1025 .find(|(id, _)| asides.iter().any(|(saved, _)| saved == id))
1026 {
1027 return Err(mismatch(format!("aside id {} is saved in the log", id.0)));
1028 }
1029 let next_aside = unsaved
1030 .clone()
1031 .map(|(id, _)| id.0.saturating_add(1))
1032 .fold(from_log.next_aside, u64::max);
1033 asides.extend(unsaved.cloned());
1034 asides.sort_by_key(|(id, _)| *id);
1035 *self = Self {
1036 messages: from_log.messages,
1037 meta: from_log.meta,
1038 asides,
1039 halted: from_log.halted,
1040 next_id: from_log.next_id,
1041 next_aside,
1042 revision: from_log.revision,
1043 lineage: from_log.lineage,
1044 unsaved: Vec::new(),
1045 clock: self.clock,
1046 };
1047 Ok(())
1048 }
1049
1050 pub fn halt(&mut self, halt: Halt) -> bool {
1053 let halts = self.awaits_model();
1054 if halts {
1055 self.record(Change::Halt { halt: Some(halt) }, None);
1056 }
1057 halts
1058 }
1059
1060 fn clear_halt(&mut self) {
1061 if self.halted.is_some() {
1062 self.record(Change::Halt { halt: None }, None);
1063 }
1064 }
1065
1066 pub fn add_aside(&mut self, aside: Aside) -> AsideId {
1069 let id = AsideId(self.next_aside);
1070 if aside.saved {
1071 let by = Some(aside.author.clone());
1072 let aside = Some(aside);
1073 self.record(Change::Aside { id, aside }, by);
1074 } else {
1075 self.place_aside(id, aside);
1076 }
1077 id
1078 }
1079
1080 pub fn replace_aside(&mut self, id: AsideId, aside: Aside) -> bool {
1084 let Some(was_saved) = self.aside(id).map(|old| old.saved) else {
1085 return false;
1086 };
1087 if aside.saved {
1088 let by = Some(aside.author.clone());
1089 let aside = Some(aside);
1090 self.record(Change::Aside { id, aside }, by);
1091 } else {
1092 self.place_aside(id, aside);
1094 if was_saved {
1095 self.log(Change::Aside { id, aside: None }, None);
1096 }
1097 }
1098 true
1099 }
1100
1101 pub fn remove_aside(&mut self, id: AsideId) -> Option<Aside> {
1103 let at = self.asides.iter().position(|(known, _)| *known == id)?;
1104 let (_, aside) = self.asides.remove(at);
1105 if aside.saved {
1106 self.log(Change::Aside { id, aside: None }, None);
1107 }
1108 Some(aside)
1109 }
1110
1111 fn move_aside(&mut self, id: AsideId, after: Option<MessageId>, by: &Author) {
1114 let Some(aside) = self.aside(id).cloned() else {
1115 return;
1116 };
1117 let aside = aside.after(after);
1118 if aside.saved {
1119 let aside = Some(aside);
1120 self.record(Change::Aside { id, aside }, Some(by.clone()));
1121 } else {
1122 self.place_aside(id, aside);
1123 }
1124 }
1125
1126 fn place_aside(&mut self, id: AsideId, aside: Aside) {
1129 self.next_aside = self.next_aside.max(id.0.saturating_add(1));
1130 match self.asides.iter_mut().find(|(known, _)| *known == id) {
1131 Some((_, slot)) => *slot = aside,
1132 None => self.asides.push((id, aside)),
1133 }
1134 }
1135
1136 pub fn apply(&mut self, change: &ChangeRecord) -> Result<(), ApplyError> {
1142 let at = change.at.unwrap_or_else(|| self.now());
1143 self.change(&change.change, at)?;
1144 self.revision = self.revision.saturating_add(1);
1145 Ok(())
1146 }
1147
1148 pub fn dated<R>(&mut self, at: u64, edit: impl FnOnce(&mut Self) -> R) -> R {
1166 let outer = self.clock.replace(at);
1167 let made = edit(self);
1168 self.clock = outer;
1169 made
1170 }
1171
1172 fn now(&self) -> u64 {
1175 self.clock.unwrap_or_else(crate::now_ms)
1176 }
1177
1178 pub fn unsaved(&self) -> &[ChangeRecord] {
1180 &self.unsaved
1181 }
1182
1183 pub fn take_unsaved(&mut self) -> Vec<ChangeRecord> {
1185 std::mem::take(&mut self.unsaved)
1186 }
1187
1188 pub fn revision(&self) -> u64 {
1193 self.revision
1194 }
1195
1196 pub fn lineage(&self) -> Lineage {
1200 self.lineage
1201 }
1202
1203 pub fn continues(&self, lineage: Lineage, changes: u64) -> bool {
1209 let base = self.revision.saturating_sub(self.unsaved.len() as u64);
1210 base == changes
1211 && (self.lineage == lineage || (changes == 0 && self.lineage == Lineage::EMPTY))
1212 }
1213
1214 pub fn continue_log(&mut self, lineage: Lineage, changes: u64) {
1218 self.lineage = lineage;
1219 self.revision = changes.saturating_add(self.unsaved.len() as u64);
1220 }
1221
1222 pub fn view(&self) -> Self {
1225 Self {
1226 asides: self.asides.clone(),
1227 ..self.request_view()
1228 }
1229 }
1230
1231 pub fn request_view(&self) -> Self {
1234 Self {
1235 messages: self.messages.clone(),
1236 meta: self.meta.clone(),
1237 asides: Vec::new(),
1238 halted: self.halted,
1239 next_id: self.next_id,
1240 next_aside: self.next_aside,
1241 revision: self.revision,
1242 lineage: self.lineage,
1243 unsaved: Vec::new(),
1244 clock: None,
1245 }
1246 }
1247
1248 fn record(&mut self, change: Change, by: Option<Author>) {
1250 let at = self.now();
1251 match self.change(&change, at) {
1252 Ok(()) => self.log_at(change, by, at),
1253 Err(error) => tracing::error!(%error, "a conversation edit did not fit; dropped"),
1254 }
1255 }
1256
1257 fn log(&mut self, change: Change, by: Option<Author>) {
1259 let at = self.now();
1260 self.log_at(change, by, at);
1261 }
1262
1263 fn log_at(&mut self, change: Change, by: Option<Author>, at: u64) {
1264 self.revision = self.revision.saturating_add(1);
1265 self.unsaved.push(ChangeRecord {
1266 change,
1267 by,
1268 at: Some(at),
1269 });
1270 }
1271
1272 fn change(&mut self, change: &Change, at: u64) -> Result<(), ApplyError> {
1276 let meta = |entry: &Entry, made: u64| Meta {
1277 id: entry.id.unwrap_or_default(),
1278 author: entry.author.clone(),
1279 made,
1280 };
1281 match change {
1282 Change::Push { entries } => {
1283 let end = self.meta.len();
1284 let next = self.new_ids(entries, &HashSet::new(), end..end)?;
1285 for entry in entries {
1286 self.messages.push(entry.message.clone());
1287 self.meta.push(meta(entry, entry.made.unwrap_or(at)));
1288 }
1289 self.next_id = next;
1290 }
1291 Change::Splice {
1292 from,
1293 to,
1294 removed,
1295 with,
1296 } => {
1297 let (from, to) = (*from, *to);
1298 let replaced = self.meta.get(from..to).ok_or_else(|| {
1299 mismatch(format!(
1300 "splice of {from}..{to} in {} entries",
1301 self.meta.len()
1302 ))
1303 })?;
1304 if !replaced.iter().map(|meta| &meta.id).eq(removed) {
1305 return Err(mismatch(format!("splice of {from}..{to} names other ids")));
1306 }
1307 let kept: HashMap<MessageId, u64> =
1308 replaced.iter().map(|meta| (meta.id, meta.made)).collect();
1309 let next = self.new_ids(with, &removed.iter().copied().collect(), from..to)?;
1310 let metas: Vec<Meta> = with
1311 .iter()
1312 .map(|entry| {
1313 let made = entry.id.and_then(|id| kept.get(&id).copied());
1314 meta(entry, made.or(entry.made).unwrap_or(at))
1315 })
1316 .collect();
1317 self.messages
1318 .splice(from..to, with.iter().map(|entry| entry.message.clone()));
1319 self.meta.splice(from..to, metas);
1320 self.next_id = next;
1321 }
1322 Change::Halt { halt } => self.halted = *halt,
1323 Change::Aside {
1324 id,
1325 aside: Some(aside),
1326 } => self.place_aside(*id, aside.clone()),
1327 Change::Aside { id, aside: None } => {
1328 let at = self.asides.iter().position(|(known, _)| known == id);
1329 let at = at.ok_or_else(|| mismatch(format!("no aside {}", id.0)))?;
1330 self.asides.remove(at);
1331 }
1332 Change::Reset { state } => {
1335 let (unsaved, revision) = (std::mem::take(&mut self.unsaved), self.revision);
1336 let lineage = self.lineage;
1337 *self = state.view();
1338 self.unsaved = unsaved;
1339 self.revision = revision;
1340 self.lineage = lineage;
1341 }
1342 }
1343 Ok(())
1344 }
1345
1346 fn new_ids(
1351 &self,
1352 entries: &[Entry],
1353 reused: &HashSet<MessageId>,
1354 range: Range<usize>,
1355 ) -> Result<u64, ApplyError> {
1356 let mut next = self.next_id;
1357 let mut seen = HashSet::new();
1358 let mut outside = None;
1359 for entry in entries {
1360 let id = entry
1361 .id
1362 .ok_or_else(|| mismatch("a recorded entry without an id".to_owned()))?;
1363 if id.0 >= next {
1364 next = id.0.saturating_add(1);
1365 continue;
1366 }
1367 let fits = reused.contains(&id)
1368 || (id.0 < self.next_id
1369 && !self.outside(range.clone(), &mut outside).contains(&id));
1370 if !fits || !seen.insert(id) {
1371 return Err(mismatch(format!("message id {} reused", id.0)));
1372 }
1373 }
1374 Ok(next)
1375 }
1376}
1377
1378#[cfg(test)]
1379mod tests;