Skip to main content

rig_core/conversation/
mod.rs

1//! One conversation model for all of rig: [`Conversation`], its edits as
2//! [`Change`]s, [`TurnRecord`]s kept as asides, and [`pairing`] of tool
3//! calls with their results.
4//!
5//! ```
6//! use rig_core::conversation::{Author, Conversation, Entry, pairing};
7//! use rig_core::message::Message;
8//!
9//! let mut conversation = Conversation::new();
10//! let hello = conversation.push(Message::user("Hello"));
11//! conversation.push(Message::assistant("Hi"));
12//! // A plugin rewrites the greeting; the entry keeps its id.
13//! let mut greeting = conversation.entry(0).ok_or("no entry")?;
14//! greeting.message = Message::user("Hello there").into();
15//! conversation.splice(0..1, [greeting], Author::plugin("polite"));
16//! assert_eq!(conversation.index_of(hello), Some(0));
17//!
18//! // The outbox folds into the same value.
19//! let mut folded = Conversation::new();
20//! for change in conversation.take_unsaved() {
21//!     folded.apply(&change)?;
22//! }
23//! assert_eq!(folded, conversation);
24//! assert!(pairing::pending_calls(conversation.messages()).is_empty());
25//! # Ok::<(), Box<dyn std::error::Error>>(())
26//! ```
27
28use 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
54/// A conversation: its entries (each a message with an id and, when it is
55/// not the user's own or the model's, an author), its asides, why its last
56/// user message is unanswered, and the changes made to it that nobody has
57/// saved yet. Every edit builds a [`Change`], applies it with the same code
58/// a fold ([`apply`](Self::apply)) uses, and keeps it in the outbox
59/// ([`unsaved`](Self::unsaved)).
60///
61/// Two conversations are equal when their entries (messages, ids and
62/// authors), saved asides and halt are: the outbox, the revision, unsaved
63/// asides and when entries were made are not compared, so a value folded
64/// from a log equals the live one and two values built alike are equal.
65///
66/// Its serde form is its state: entries, saved asides, halt and the next
67/// ids, without the outbox, the revision or the lineage. A value read back
68/// from it is [`Lineage::DETACHED`].
69#[derive(Clone, Debug, Default, Serialize, Deserialize)]
70#[serde(try_from = "State")]
71pub struct Conversation {
72    /// What a request sends, so [`messages`](Self::messages) is a slice;
73    /// shared with the requests and records made from it.
74    messages: Vec<Arc<Message>>,
75    /// Parallel to `messages`.
76    meta: Vec<Meta>,
77    /// In the order put.
78    #[serde(serialize_with = "saved_asides")]
79    asides: Vec<(AsideId, Aside)>,
80    #[serde(skip_serializing_if = "Option::is_none")]
81    halted: Option<Halt>,
82    /// The id the next new entry gets; only rises, but for a reset to another value.
83    next_id: u64,
84    /// The id the next new aside gets; only rises, but for a reset to another value.
85    next_aside: u64,
86    /// The changes behind this value: those of the log it continues, then
87    /// its own.
88    #[serde(skip)]
89    revision: u64,
90    /// The log it continues.
91    #[serde(skip)]
92    lineage: Lineage,
93    #[serde(skip)]
94    unsaved: Vec<ChangeRecord>,
95    /// The time edits are made at, set by [`dated`](Self::dated); now when
96    /// `None`.
97    #[serde(skip)]
98    clock: Option<u64>,
99}
100
101/// An entry's id, author and when it was made, beside its message. Its
102/// serde form carries `made`, so a reset and a value read back keep it;
103/// equality leaves it out.
104#[derive(Clone, Debug, Serialize, Deserialize)]
105struct Meta {
106    id: MessageId,
107    #[serde(default, skip_serializing_if = "Option::is_none")]
108    author: Option<Author>,
109    /// Milliseconds since the Unix epoch: when the entry was first made.
110    #[serde(default)]
111    made: u64,
112}
113
114/// A conversation as saved, checked before it becomes one.
115#[derive(Deserialize)]
116struct State {
117    #[serde(default)]
118    messages: Vec<Arc<Message>>,
119    #[serde(default)]
120    meta: Vec<Meta>,
121    #[serde(default)]
122    asides: Vec<(AsideId, Aside)>,
123    #[serde(default)]
124    halted: Option<Halt>,
125    #[serde(default)]
126    next_id: u64,
127    #[serde(default)]
128    next_aside: u64,
129}
130
131impl TryFrom<State> for Conversation {
132    type Error = ApplyError;
133
134    fn try_from(state: State) -> Result<Self, ApplyError> {
135        if state.meta.len() != state.messages.len() {
136            return Err(mismatch(format!(
137                "{} messages but {} ids",
138                state.messages.len(),
139                state.meta.len()
140            )));
141        }
142        let mut ids = HashSet::new();
143        if let Some(meta) = state
144            .meta
145            .iter()
146            .find(|meta| meta.id.0 >= state.next_id || !ids.insert(meta.id))
147        {
148            return Err(mismatch(format!("message id {} reused", meta.id.0)));
149        }
150        let mut asides = HashSet::new();
151        if let Some((id, _)) = state
152            .asides
153            .iter()
154            .find(|(id, _)| id.0 >= state.next_aside || !asides.insert(*id))
155        {
156            return Err(mismatch(format!("aside id {} reused", id.0)));
157        }
158        Ok(Self {
159            messages: state.messages,
160            meta: state.meta,
161            asides: state.asides,
162            halted: state.halted,
163            next_id: state.next_id,
164            next_aside: state.next_aside,
165            revision: 0,
166            lineage: Lineage::DETACHED,
167            unsaved: Vec::new(),
168            clock: None,
169        })
170    }
171}
172
173fn saved_asides<S: Serializer>(
174    asides: &[(AsideId, Aside)],
175    serializer: S,
176) -> Result<S::Ok, S::Error> {
177    serializer.collect_seq(asides.iter().filter(|(_, aside)| aside.saved))
178}
179
180impl PartialEq for Conversation {
181    fn eq(&self, other: &Self) -> bool {
182        let saved = |(_, aside): &&(AsideId, Aside)| aside.saved;
183        let same = |a: &Meta, b: &Meta| a.id == b.id && a.author == b.author;
184        self.messages == other.messages
185            && self.meta.len() == other.meta.len()
186            && self.meta.iter().zip(&other.meta).all(|(a, b)| same(a, b))
187            && self.halted == other.halted
188            && self
189                .asides
190                .iter()
191                .filter(saved)
192                .eq(other.asides.iter().filter(saved))
193    }
194}
195
196/// A message's identity in its conversation: kept while entries around it
197/// are added, cut or replaced, while it is spliced back changed, across a
198/// save and a fork. Ids rise in the order messages were first made.
199#[derive(
200    Clone, Copy, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize,
201)]
202#[serde(transparent)]
203pub struct MessageId(u64);
204
205impl MessageId {
206    /// Its number: ids rise from 0 in the order messages were first made.
207    pub const fn get(self) -> u64 {
208        self.0
209    }
210}
211
212/// Its plain form, `m` and its number (`m4`): how commands name a message.
213impl fmt::Display for MessageId {
214    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
215        write!(f, "m{}", self.0)
216    }
217}
218
219/// Reads its plain form, `m4`, or the number alone.
220impl FromStr for MessageId {
221    type Err = ParseIdError;
222
223    fn from_str(text: &str) -> Result<Self, ParseIdError> {
224        parse_id(text, 'm').map(Self)
225    }
226}
227
228/// A [`MessageId`] or [`AsideId`] that could not be read from its plain form.
229#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
230#[error("{0:?} is not an id")]
231pub struct ParseIdError(pub String);
232
233/// The number of `text`, written with or without `prefix`.
234fn parse_id(text: &str, prefix: char) -> Result<u64, ParseIdError> {
235    let number = text.strip_prefix(prefix).unwrap_or(text);
236    number.parse().map_err(|_| ParseIdError(text.to_owned()))
237}
238
239/// A message's identity beyond its conversation: when it was made and its
240/// id. Equal for a message and its copy in a fork (a fork copies ids and
241/// made-times), different for one made since, unless both were made in
242/// the same millisecond with the same id.
243#[derive(
244    Clone, Copy, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize,
245)]
246pub struct MessageKey {
247    /// When it was first made, in milliseconds since the Unix epoch.
248    pub made: u64,
249    /// Its id in its conversation.
250    pub id: MessageId,
251}
252
253/// A message as it goes into a conversation: the message, who wrote it,
254/// and, for a message taken out with [`Conversation::entry`], its id,
255/// which a [`splice`](Conversation::splice) keeps. A recorded change
256/// always carries every id.
257#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
258pub struct Entry {
259    /// The message, shared with the conversation it goes into.
260    pub message: Arc<Message>,
261    /// Who wrote it; `None` for the user's own text, the model's reply and
262    /// tool results.
263    #[serde(default, skip_serializing_if = "Option::is_none")]
264    pub author: Option<Author>,
265    /// Its id in the conversation, if it has one.
266    #[serde(default, skip_serializing_if = "Option::is_none")]
267    pub id: Option<MessageId>,
268    /// When it was first made, in milliseconds since the Unix epoch: set by
269    /// [`Conversation::entry`], kept by an entry put back as it was. A
270    /// recorded change carries it only for such an entry.
271    #[serde(default, skip_serializing_if = "Option::is_none")]
272    pub made: Option<u64>,
273}
274
275impl Entry {
276    /// A new entry without an author.
277    pub fn new(message: impl Into<Arc<Message>>) -> Self {
278        Self {
279            message: message.into(),
280            author: None,
281            id: None,
282            made: None,
283        }
284    }
285
286    /// This entry, written by `author`.
287    #[must_use]
288    pub fn by(self, author: Author) -> Self {
289        Self {
290            author: Some(author),
291            ..self
292        }
293    }
294}
295
296impl From<Message> for Entry {
297    fn from(message: Message) -> Self {
298        Self::new(message)
299    }
300}
301
302impl From<Arc<Message>> for Entry {
303    fn from(message: Arc<Message>) -> Self {
304        Self::new(message)
305    }
306}
307
308/// Entry `index` of a conversation, borrowed.
309#[derive(Clone, Copy, Debug)]
310pub struct EntryRef<'c> {
311    /// Where it is.
312    pub index: usize,
313    /// Its id.
314    pub id: MessageId,
315    /// Its message.
316    pub message: &'c Message,
317    /// Who wrote it, when it is not the user's own or the model's.
318    pub author: Option<&'c Author>,
319    /// When it was first made, in milliseconds since the Unix epoch.
320    pub made: u64,
321}
322
323impl EntryRef<'_> {
324    /// Its identity beyond its conversation.
325    pub fn key(&self) -> MessageKey {
326        MessageKey {
327            made: self.made,
328            id: self.id,
329        }
330    }
331
332    /// Whether it is a user message the user typed: text or attachments,
333    /// not only tool results, with no author or [`AuthorKind::User`] with
334    /// no title.
335    pub fn is_users_own(&self) -> bool {
336        let typed = match self.message {
337            Message::User { content } => content
338                .iter()
339                .any(|item| !matches!(item, UserContent::ToolResult(_))),
340            _ => false,
341        };
342        let users = self
343            .author
344            .is_none_or(|author| author.kind == AuthorKind::User && author.title.is_none());
345        typed && users
346    }
347
348    /// Whether it asks something of the model: a user message with text
349    /// or attachments, not only tool results, that is not rig's own (the
350    /// [`STOPPED`] note). The user's, an agent's or a plugin's.
351    pub fn is_request(&self) -> bool {
352        let asks = match self.message {
353            Message::User { content } => content
354                .iter()
355                .any(|item| !matches!(item, UserContent::ToolResult(_))),
356            _ => false,
357        };
358        asks && self
359            .author
360            .is_none_or(|author| author.kind != AuthorKind::Rig)
361    }
362
363    /// Whether it is the [`STOPPED`] note rig puts between a request the
364    /// user stopped and what came next.
365    pub fn is_stop_marker(&self) -> bool {
366        let by_rig = self
367            .author
368            .is_some_and(|author| author.kind == AuthorKind::Rig);
369        by_rig
370            && matches!(self.message, Message::User { content }
371                if matches!(content.as_slice(), [UserContent::Text(text)] if text.text == STOPPED))
372    }
373}
374
375/// The text of the entry that goes between a request the user stopped and
376/// the next entry, so the model does not carry the request out. rig pushes
377/// it as its own entry, by [`Author::rig`].
378pub const STOPPED: &str = "[The request above was stopped by the user before it was answered. \
379                           Do not carry it out unless asked again.]";
380
381/// Why the user's last message is left without an answer, so a restore
382/// does not answer it. How a turn ended is its [`TurnRecord`].
383#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
384#[serde(rename_all = "snake_case")]
385pub enum Halt {
386    /// The user stopped the turn answering it: the next entry pushed goes
387    /// after [`STOPPED`].
388    Stopped,
389    /// It is left for the next turn: a note to an idle agent, or a request
390    /// whose turn failed or a plugin stopped.
391    #[default]
392    Kept,
393}
394
395/// One edit of a conversation. Its serde form is the conversation part of
396/// the saved format.
397#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
398#[serde(tag = "type", rename_all = "snake_case")]
399pub enum Change {
400    /// Entries added at the end, each with its id.
401    Push {
402        /// The entries.
403        entries: Vec<Entry>,
404    },
405    /// Entries `from..to`, whose ids were `removed`, replaced by `with`
406    /// (each with its id). `removed` doubles as a check: applying it where
407    /// the range holds other ids fails.
408    Splice {
409        /// The first entry replaced.
410        from: usize,
411        /// The end of the range replaced.
412        to: usize,
413        /// The ids of entries `from..to` before the splice.
414        removed: Vec<MessageId>,
415        /// What replaces them.
416        with: Vec<Entry>,
417    },
418    /// The halt set, or with `None` cleared.
419    Halt {
420        /// The halt.
421        halt: Option<Halt>,
422    },
423    /// Aside `id` put or replaced (`Some`), or removed (`None`).
424    Aside {
425        /// The aside's id.
426        id: AsideId,
427        /// The aside.
428        aside: Option<Aside>,
429    },
430    /// The whole value replaced: a component inserted over another, or a
431    /// conversation built elsewhere and saved onto a log it was not read
432    /// from.
433    Reset {
434        /// The new value.
435        state: Box<Conversation>,
436    },
437}
438
439/// A change as kept in the outbox and saved: who made it and when.
440#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
441pub struct ChangeRecord {
442    /// The change.
443    #[serde(flatten)]
444    pub change: Change,
445    /// Who made it: a push's entry author, a splice's `by`, an aside's
446    /// author; `None` for rig's own.
447    #[serde(default, skip_serializing_if = "Option::is_none")]
448    pub by: Option<Author>,
449    /// Milliseconds since the Unix epoch; the writer stamps it when `None`.
450    #[serde(default, skip_serializing_if = "Option::is_none")]
451    pub at: Option<u64>,
452}
453
454/// What a splice changed.
455#[derive(Clone, Debug, Default, PartialEq, Eq)]
456pub struct Spliced {
457    /// The ids no longer in the conversation.
458    pub removed: Vec<MessageId>,
459    /// The ids of the entries it put in, kept, put back or new.
460    pub changed: Vec<MessageId>,
461    /// The ids [`restore`](Conversation::restore) put back.
462    pub restored: Vec<MessageId>,
463}
464
465/// Why a recorded change could not be applied: it does not fit the
466/// conversation, such as a splice of a range holding other ids.
467#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
468pub enum ApplyError {
469    /// The change does not match the conversation.
470    #[error("{0}")]
471    Mismatch(String),
472}
473
474fn mismatch(why: String) -> ApplyError {
475    ApplyError::Mismatch(why)
476}
477
478/// The first characters of the agent id `id`, enough to tell agents apart.
479pub fn short_id(id: &str) -> &str {
480    id.get(..8).unwrap_or(id)
481}
482
483impl Conversation {
484    /// An empty conversation.
485    pub fn new() -> Self {
486        Self::default()
487    }
488
489    /// A conversation of `messages`, each its own entry, recorded so that
490    /// saving it writes them (an import, a library user's history).
491    pub fn from_messages(messages: impl IntoIterator<Item = impl Into<Arc<Message>>>) -> Self {
492        let mut conversation = Self::new();
493        conversation.extend(messages);
494        conversation
495    }
496
497    // Reading.
498
499    /// The messages, oldest first: what a request sends.
500    pub fn messages(&self) -> &[Arc<Message>] {
501        &self.messages
502    }
503
504    /// The messages, oldest first, taken out of the conversation: what a
505    /// request sends, without a copy.
506    pub fn into_messages(self) -> Vec<Arc<Message>> {
507        self.messages
508    }
509
510    /// The entries, oldest first.
511    pub fn entries(&self) -> impl DoubleEndedIterator<Item = EntryRef<'_>> + ExactSizeIterator {
512        let entries = self.messages.iter().zip(&self.meta).enumerate();
513        entries.map(|(index, (message, meta))| EntryRef {
514            index,
515            id: meta.id,
516            message,
517            author: meta.author.as_ref(),
518            made: meta.made,
519        })
520    }
521
522    /// Entry `at`, with its author, id and when it was made, to splice
523    /// back changed.
524    pub fn entry(&self, at: usize) -> Option<Entry> {
525        let meta = self.meta.get(at)?;
526        Some(Entry {
527            message: self.messages.get(at)?.clone(),
528            author: meta.author.clone(),
529            id: Some(meta.id),
530            made: Some(meta.made),
531        })
532    }
533
534    /// The id of entry `at`.
535    pub fn id(&self, at: usize) -> Option<MessageId> {
536        self.meta.get(at).map(|meta| meta.id)
537    }
538
539    /// The identity of entry `at` beyond this conversation.
540    pub fn key(&self, at: usize) -> Option<MessageKey> {
541        let meta = self.meta.get(at)?;
542        Some(MessageKey {
543            made: meta.made,
544            id: meta.id,
545        })
546    }
547
548    /// Where the entry with identity `key` is now; `None` when it is not
549    /// here, also when an entry with its id was made at another time.
550    pub fn index_of_key(&self, key: MessageKey) -> Option<usize> {
551        let at = self.index_of(key.id)?;
552        (self.meta.get(at)?.made == key.made).then_some(at)
553    }
554
555    /// Who wrote entry `at`; `None` for the user's own text, the model's
556    /// reply, a tool result, or no such entry.
557    pub fn author(&self, at: usize) -> Option<&Author> {
558        self.meta.get(at)?.author.as_ref()
559    }
560
561    /// Where entry `id` is now; `None` once an edit took it out.
562    pub fn index_of(&self, id: MessageId) -> Option<usize> {
563        self.meta.iter().position(|meta| meta.id == id)
564    }
565
566    /// Where a span that began at entry `id` begins now, such as a turn:
567    /// its index while it is there; the end for an entry not made yet; 0
568    /// for one an edit took out, as a summary then stands for what came
569    /// before it.
570    pub fn start_of(&self, id: MessageId) -> usize {
571        self.index_of(id).unwrap_or(if id.0 >= self.next_id {
572            self.messages.len()
573        } else {
574            0
575        })
576    }
577
578    /// Where a turn starting now begins: the first of the user messages at
579    /// the end, which it answers, else the first entry it adds.
580    pub fn turn_start(&self) -> MessageId {
581        let trailing = self.messages.iter().rev();
582        let trailing = trailing
583            .take_while(|message| matches!(&***message, Message::User { .. }))
584            .count();
585        let first = self.messages.len().saturating_sub(trailing);
586        let answered = (trailing > 0).then(|| self.id(first)).flatten();
587        answered.unwrap_or(MessageId(self.next_id))
588    }
589
590    /// The entries a turn that began at `start` (its
591    /// [`turn_start`](Self::turn_start)) answers: the user messages in a
592    /// row from where it begins now ([`start_of`](Self::start_of)). After a
593    /// stopped turn they begin with that turn's tool results and the
594    /// [`STOPPED`] note, before what was sent next.
595    pub fn asked(&self, start: MessageId) -> impl Iterator<Item = EntryRef<'_>> {
596        let entries = self.entries().skip(self.start_of(start));
597        entries.take_while(|entry| matches!(entry.message, Message::User { .. }))
598    }
599
600    /// The message that asked for the turn that began at `start`: the
601    /// first of the entries it answers ([`asked`](Self::asked)) that is a
602    /// request ([`EntryRef::is_request`]).
603    pub fn request(&self, start: MessageId) -> Option<EntryRef<'_>> {
604        self.asked(start).find(EntryRef::is_request)
605    }
606
607    /// Why the user's last message is left unanswered, if it was halted.
608    /// Cleared when an entry is pushed or a splice reaches the end.
609    pub fn halted(&self) -> Option<Halt> {
610        self.halted
611    }
612
613    /// Whether the model owes an answer: the last message a model reads is
614    /// the user's, and it was not halted. A failed or stopped reply after it
615    /// is never read ([`StopReason::is_failure`]), so the answer is still
616    /// owed.
617    pub fn awaits_model(&self) -> bool {
618        let read = self.messages.iter().rev().map(Arc::as_ref).find(|message| {
619            !matches!(message, Message::Assistant(turn)
620                if turn.stop.as_ref().is_some_and(StopReason::is_failure))
621        });
622        self.halted.is_none() && matches!(read, Some(Message::User { .. }))
623    }
624
625    /// The tool calls of the last assistant message that nothing answers
626    /// yet. See [`pairing::pending_calls`].
627    pub fn pending_calls(&self) -> Vec<ToolCall> {
628        pairing::pending_calls(&self.messages)
629    }
630
631    /// Whether the messages pair every tool call with its result. See
632    /// [`pairing::validate`].
633    pub fn validate(&self) -> Result<(), PairingError> {
634        pairing::validate(&self.messages)
635    }
636
637    /// The asides, in the order put.
638    pub fn asides(&self) -> impl DoubleEndedIterator<Item = (AsideId, &Aside)> {
639        self.asides.iter().map(|(id, aside)| (*id, aside))
640    }
641
642    /// Aside `id`.
643    pub fn aside(&self, id: AsideId) -> Option<&Aside> {
644        let mut asides = self.asides.iter();
645        asides
646            .find(|(known, _)| *known == id)
647            .map(|(_, aside)| aside)
648    }
649
650    /// Where `aside` is drawn: before entry `position`, after entry
651    /// `position - 1`. While an edit took out the entry it follows, after
652    /// the entry that stands for it now ([`start_of`](Self::start_of)).
653    pub fn aside_position(&self, aside: &Aside) -> usize {
654        let Some(after) = aside.after else {
655            return 0;
656        };
657        match self.index_of(after) {
658            Some(index) => index.saturating_add(1),
659            None => self
660                .messages
661                .len()
662                .min(self.start_of(after).saturating_add(1)),
663        }
664    }
665
666    // Editing: each builds a change with every id filled in, applies it and
667    // records it.
668
669    /// Pushes `entry`, after a [`STOPPED`] entry by [`Author::rig`] in the
670    /// same change when the user stopped the last request; a halt is
671    /// cleared. Returns the new entry's id; a pushed entry never reuses one.
672    pub fn push(&mut self, entry: impl Into<Entry>) -> MessageId {
673        let entry = entry.into();
674        let by = entry.author.clone();
675        let mut ids = self.push_entries(vec![entry], by);
676        ids.pop().unwrap_or(MessageId(self.next_id))
677    }
678
679    /// Pushes each of `messages` as its own entry without an author, in one
680    /// change, as [`push`](Self::push) does.
681    pub fn extend(&mut self, messages: impl IntoIterator<Item = impl Into<Arc<Message>>>) {
682        let entries = messages.into_iter().map(Entry::new).collect();
683        self.push_entries(entries, None);
684    }
685
686    fn push_entries(&mut self, entries: Vec<Entry>, by: Option<Author>) -> Vec<MessageId> {
687        if entries.is_empty() {
688            return Vec::new();
689        }
690        let stop = (self.halted == Some(Halt::Stopped))
691            .then(|| Entry::new(Message::user(STOPPED)).by(Author::rig()));
692        let mut next = self.next_id;
693        let mut ids = Vec::with_capacity(entries.len());
694        let mut fill = |mut entry: Entry| {
695            let id = MessageId(next);
696            next = next.saturating_add(1);
697            entry.id = Some(id);
698            entry.made = None;
699            (entry, id)
700        };
701        let stop = stop.map(|entry| fill(entry).0);
702        let entries = entries.into_iter().map(|entry| {
703            let (entry, id) = fill(entry);
704            ids.push(id);
705            entry
706        });
707        let entries = stop.into_iter().chain(entries).collect();
708        self.record(Change::Push { entries }, by);
709        self.clear_halt();
710        ids
711    }
712
713    /// Replaces entries in `range`, clamped to the conversation, with
714    /// `with`, recorded as made by `by`. An entry of `with` keeps its id
715    /// when that id was in the range and no earlier entry of `with` kept
716    /// it; any other gets a new id. Nothing merges. A splice reaching the
717    /// end clears the halt. An empty range replaced by nothing changes and
718    /// records nothing.
719    pub fn splice(
720        &mut self,
721        range: impl RangeBounds<usize>,
722        with: impl IntoIterator<Item = Entry>,
723        by: Author,
724    ) -> Spliced {
725        self.replace(range, with.into_iter().collect(), by, false)
726    }
727
728    /// As [`splice`](Self::splice), and an entry of `with` whose id this
729    /// conversation made (below the next id), that no entry outside the
730    /// range holds and no earlier entry of `with` took, keeps that id and
731    /// when it was made ([`Entry::made`], else now): entries put back as
732    /// they were, such as a summary's originals or an edit undone. Asides that followed such an entry follow it again.
733    /// Only for entries this conversation held: an entry from elsewhere
734    /// that carries an id would take one that meant another message here.
735    ///
736    /// ```
737    /// use rig_core::completion::Message;
738    /// use rig_core::conversation::{Author, Conversation, Entry};
739    ///
740    /// let mut conversation = Conversation::from_messages([
741    ///     Message::user("one"),
742    ///     Message::assistant("two"),
743    /// ]);
744    /// let ids: Vec<_> = conversation.entries().map(|entry| entry.id).collect();
745    /// let originals: Vec<Entry> = (0..2).filter_map(|at| conversation.entry(at)).collect();
746    /// let summary = Entry::new(Message::user("a summary"));
747    /// conversation.splice(.., [summary], Author::plugin("compact"));
748    /// let undone = conversation.restore(.., originals, Author::plugin("undo"));
749    /// assert_eq!(undone.restored, ids);
750    /// assert!(conversation.entries().map(|entry| entry.id).eq(ids));
751    /// ```
752    pub fn restore(
753        &mut self,
754        range: impl RangeBounds<usize>,
755        with: impl IntoIterator<Item = Entry>,
756        by: Author,
757    ) -> Spliced {
758        self.replace(range, with.into_iter().collect(), by, true)
759    }
760
761    /// Makes the conversation hold `entries`, as made by `by`: one
762    /// [`restore`](Self::restore) of the range between the longest start
763    /// and end it shares with them (the same messages, ids and authors), so
764    /// entries taken from it keep their ids. Nothing when they are equal.
765    pub fn set_entries(&mut self, entries: Vec<Entry>, by: Author) -> Spliced {
766        let same = |entry: &Entry, at: usize| {
767            entry.id.is_some()
768                && entry.id == self.id(at)
769                && entry.author.as_ref() == self.author(at)
770                && Some(&entry.message) == self.messages.get(at)
771        };
772        let (len, count) = (self.messages.len(), entries.len());
773        let start = entries
774            .iter()
775            .enumerate()
776            .take_while(|(at, entry)| same(entry, *at))
777            .count();
778        let end = entries
779            .iter()
780            .rev()
781            .zip((0..len).rev())
782            .take(len.min(count).saturating_sub(start))
783            .take_while(|(entry, at)| same(entry, *at))
784            .count();
785        if start == len && start == count {
786            return Spliced::default();
787        }
788        let with = entries
789            .into_iter()
790            .skip(start)
791            .take(count.saturating_sub(start).saturating_sub(end));
792        self.restore(start..len.saturating_sub(end), with, by)
793    }
794
795    /// Replaces entries in `range` with `with`: [`splice`](Self::splice),
796    /// or with `restores`, [`restore`](Self::restore).
797    fn replace(
798        &mut self,
799        range: impl RangeBounds<usize>,
800        mut with: Vec<Entry>,
801        by: Author,
802        restores: bool,
803    ) -> Spliced {
804        let len = self.messages.len();
805        let to = match range.end_bound() {
806            Bound::Included(&end) => end.saturating_add(1),
807            Bound::Excluded(&end) => end,
808            Bound::Unbounded => len,
809        }
810        .min(len);
811        let from = match range.start_bound() {
812            Bound::Included(&start) => start,
813            Bound::Excluded(&start) => start.saturating_add(1),
814            Bound::Unbounded => 0,
815        }
816        .min(to);
817        if from == to && with.is_empty() {
818            return Spliced::default();
819        }
820        let replaced = self.meta.get(from..to).unwrap_or_default();
821        let removed: Vec<MessageId> = replaced.iter().map(|meta| meta.id).collect();
822        let in_range: HashSet<MessageId> = removed.iter().copied().collect();
823        let mut outside = None;
824        let mut taken = HashSet::new();
825        let mut restored = Vec::new();
826        let mut next = self.next_id;
827        for entry in &mut with {
828            let id = entry.id.filter(|id| !taken.contains(id));
829            if let Some(id) = id.filter(|id| in_range.contains(id)) {
830                taken.insert(id);
831                entry.made = None;
832                continue;
833            }
834            let back = id.filter(|id| {
835                restores
836                    && id.0 < self.next_id
837                    && !self.outside(from..to, &mut outside).contains(id)
838            });
839            if let Some(id) = back {
840                taken.insert(id);
841                restored.push(id);
842                continue;
843            }
844            entry.id = Some(MessageId(next));
845            entry.made = None;
846            next = next.saturating_add(1);
847        }
848        let changed = with.iter().filter_map(|entry| entry.id).collect();
849        let gone = removed.iter().filter(|id| !taken.contains(id)).copied();
850        let spliced = Spliced {
851            removed: gone.collect(),
852            changed,
853            restored,
854        };
855        let change = Change::Splice {
856            from,
857            to,
858            removed,
859            with,
860        };
861        self.record(change, Some(by));
862        if to == len {
863            self.clear_halt();
864        }
865        spliced
866    }
867
868    /// The ids of the entries outside `range`, gathered into `outside` the
869    /// first time they are asked for.
870    fn outside<'s>(
871        &self,
872        range: Range<usize>,
873        outside: &'s mut Option<HashSet<MessageId>>,
874    ) -> &'s HashSet<MessageId> {
875        outside.get_or_insert_with(|| {
876            let before = self.meta.get(..range.start).unwrap_or_default();
877            let after = self.meta.get(range.end..).unwrap_or_default();
878            before.iter().chain(after).map(|meta| meta.id).collect()
879        })
880    }
881
882    /// Rewrites entries in place, as made by `by`: `edit` sees each entry,
883    /// oldest first, and returns the message to put in its place, or
884    /// `None` to leave it. The entries it rewrote keep their ids and
885    /// authors, and the whole edit is one change: a [`splice`](Self::splice)
886    /// of the entries from the first rewritten to the last. Nothing is
887    /// recorded when it rewrote none. Returns the ids of the entries it
888    /// rewrote.
889    ///
890    /// ```
891    /// use rig_core::completion::Message;
892    /// use rig_core::conversation::{Author, Conversation};
893    ///
894    /// let mut conversation = Conversation::from_messages([
895    ///     Message::user("one"),
896    ///     Message::user("two"),
897    /// ]);
898    /// let before = conversation.id(1);
899    /// let changed = conversation.edit_each(Author::plugin("shout"), |entry| {
900    ///     (entry.index == 1).then(|| Message::user("TWO"))
901    /// });
902    /// assert_eq!(changed, before.into_iter().collect::<Vec<_>>());
903    /// assert_eq!(conversation.messages().get(1).map(|message| &**message), Some(&Message::user("TWO")));
904    /// assert_eq!(conversation.id(1), before);
905    /// ```
906    pub fn edit_each(
907        &mut self,
908        by: Author,
909        mut edit: impl FnMut(EntryRef<'_>) -> Option<Message>,
910    ) -> Vec<MessageId> {
911        let rewritten: Vec<(usize, MessageId, Message)> = self
912            .entries()
913            .filter_map(|entry| {
914                let (at, id) = (entry.index, entry.id);
915                edit(entry).map(|message| (at, id, message))
916            })
917            .collect();
918        let (Some(&(first, ..)), Some(&(last, ..))) = (rewritten.first(), rewritten.last()) else {
919            return Vec::new();
920        };
921        let ids = rewritten.iter().map(|(_, id, _)| *id).collect();
922        let mut rewritten = rewritten.into_iter().peekable();
923        let entries: Vec<Entry> = (first..=last)
924            .filter_map(|at| match rewritten.next_if(|(next, ..)| *next == at) {
925                Some((_, id, message)) => Some(Entry {
926                    message: Arc::new(message),
927                    author: self.author(at).cloned(),
928                    id: Some(id),
929                    made: None,
930                }),
931                None => self.entry(at),
932            })
933            .collect();
934        self.splice(first..=last, entries, by);
935        ids
936    }
937
938    /// Repairs the entries as [`pairing::normalize`] does, saved with
939    /// [`set_entries`](Self::set_entries) as made by `by`, so merged
940    /// entries keep the first's id, author and made-time. An aside that
941    /// followed an entry merged into another follows that one; one that
942    /// followed a dropped entry follows the entry before it (each move
943    /// recorded, by `by`). Returns what it repaired.
944    pub fn normalize(&mut self, by: Author) -> Normalized {
945        let entries = (0..self.messages.len()).filter_map(|at| self.entry(at));
946        let normalized = pairing::normalize(entries.collect());
947        let into: HashMap<MessageId, MessageId> = normalized
948            .merged
949            .iter()
950            .map(|(kept, gone)| (*gone, *kept))
951            .collect();
952        let dropped: HashSet<MessageId> = normalized.dropped.iter().copied().collect();
953        // Where an aside that followed `id` goes: the entry it went into,
954        // or for a dropped one, the entry before it that stays.
955        let anchor = |id: MessageId| -> Option<MessageId> {
956            let before = self.meta.iter().rev().skip_while(|meta| meta.id != id);
957            before
958                .map(|meta| meta.id)
959                .find_map(|id| match into.get(&id) {
960                    Some(kept) => Some(*kept),
961                    None => (!dropped.contains(&id)).then_some(id),
962                })
963        };
964        let moves: Vec<(AsideId, Option<MessageId>)> = self
965            .asides
966            .iter()
967            .filter_map(|(at, aside)| {
968                let after = aside.after?;
969                let moves = into.contains_key(&after) || dropped.contains(&after);
970                moves.then(|| (*at, anchor(after)))
971            })
972            .collect();
973        self.set_entries(normalized.entries.clone(), by.clone());
974        for (id, after) in moves {
975            self.move_aside(id, after, &by);
976        }
977        normalized
978    }
979
980    /// Takes everything a log holds from `from_log`, a value read from that
981    /// log: entries, saved asides, halt, next ids, lineage and revision.
982    /// This value's unsaved asides stay, with their ids. For a host whose
983    /// log was rewritten under the value it holds.
984    ///
985    /// # Errors
986    ///
987    /// [`ApplyError::Mismatch`], changing nothing, while either value has
988    /// unsaved changes, or when an unsaved aside's id is a saved one's in
989    /// `from_log`.
990    pub fn reread(&mut self, from_log: Conversation) -> Result<(), ApplyError> {
991        if !self.unsaved.is_empty() || !from_log.unsaved.is_empty() {
992            return Err(mismatch(
993                "a value with unsaved changes is not reread".to_owned(),
994            ));
995        }
996        let mut asides = from_log.asides;
997        asides.retain(|(_, aside)| aside.saved);
998        let unsaved = self.asides.iter().filter(|(_, aside)| !aside.saved);
999        if let Some((id, _)) = unsaved
1000            .clone()
1001            .find(|(id, _)| asides.iter().any(|(saved, _)| saved == id))
1002        {
1003            return Err(mismatch(format!("aside id {} is saved in the log", id.0)));
1004        }
1005        let next_aside = unsaved
1006            .clone()
1007            .map(|(id, _)| id.0.saturating_add(1))
1008            .fold(from_log.next_aside, u64::max);
1009        asides.extend(unsaved.cloned());
1010        asides.sort_by_key(|(id, _)| *id);
1011        *self = Self {
1012            messages: from_log.messages,
1013            meta: from_log.meta,
1014            asides,
1015            halted: from_log.halted,
1016            next_id: from_log.next_id,
1017            next_aside,
1018            revision: from_log.revision,
1019            lineage: from_log.lineage,
1020            unsaved: Vec::new(),
1021            clock: self.clock,
1022        };
1023        Ok(())
1024    }
1025
1026    /// Leaves the user's last message unanswered, for `halt`. Whether it
1027    /// was halted now: not when the model owes no answer.
1028    pub fn halt(&mut self, halt: Halt) -> bool {
1029        let halts = self.awaits_model();
1030        if halts {
1031            self.record(Change::Halt { halt: Some(halt) }, None);
1032        }
1033        halts
1034    }
1035
1036    /// Asks the model again for an answer to the user's last message, also
1037    /// a halted one; whether the last message is the user's.
1038    pub fn resume(&mut self) -> bool {
1039        self.clear_halt();
1040        self.awaits_model()
1041    }
1042
1043    fn clear_halt(&mut self) {
1044        if self.halted.is_some() {
1045            self.record(Change::Halt { halt: None }, None);
1046        }
1047    }
1048
1049    /// Puts `aside` after the asides already put and returns its id. An
1050    /// unsaved aside is not recorded.
1051    pub fn add_aside(&mut self, aside: Aside) -> AsideId {
1052        let id = AsideId(self.next_aside);
1053        if aside.saved {
1054            let by = Some(aside.author.clone());
1055            let aside = Some(aside);
1056            self.record(Change::Aside { id, aside }, by);
1057        } else {
1058            self.place_aside(id, aside);
1059        }
1060        id
1061    }
1062
1063    /// Replaces aside `id` with `aside`, in its place; whether there was
1064    /// one. Replacing a saved aside with an unsaved one records its
1065    /// removal.
1066    pub fn replace_aside(&mut self, id: AsideId, aside: Aside) -> bool {
1067        let Some(was_saved) = self.aside(id).map(|old| old.saved) else {
1068            return false;
1069        };
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            // The saved value loses it; the live one keeps it in its place.
1076            self.place_aside(id, aside);
1077            if was_saved {
1078                self.log(Change::Aside { id, aside: None }, None);
1079            }
1080        }
1081        true
1082    }
1083
1084    /// Removes aside `id` and returns it, or `None` if there was none.
1085    pub fn remove_aside(&mut self, id: AsideId) -> Option<Aside> {
1086        let at = self.asides.iter().position(|(known, _)| *known == id)?;
1087        let (_, aside) = self.asides.remove(at);
1088        if aside.saved {
1089            self.log(Change::Aside { id, aside: None }, None);
1090        }
1091        Some(aside)
1092    }
1093
1094    /// Makes aside `id` follow `after`, recorded as made by `by` when it is
1095    /// saved.
1096    fn move_aside(&mut self, id: AsideId, after: Option<MessageId>, by: &Author) {
1097        let Some(aside) = self.aside(id).cloned() else {
1098            return;
1099        };
1100        let aside = aside.after(after);
1101        if aside.saved {
1102            let aside = Some(aside);
1103            self.record(Change::Aside { id, aside }, Some(by.clone()));
1104        } else {
1105            self.place_aside(id, aside);
1106        }
1107    }
1108
1109    /// Puts `aside` as `id` without recording it: in place of the aside
1110    /// with that id, else last.
1111    fn place_aside(&mut self, id: AsideId, aside: Aside) {
1112        self.next_aside = self.next_aside.max(id.0.saturating_add(1));
1113        match self.asides.iter_mut().find(|(known, _)| *known == id) {
1114            Some((_, slot)) => *slot = aside,
1115            None => self.asides.push((id, aside)),
1116        }
1117    }
1118
1119    // The record.
1120
1121    /// Applies a recorded change verbatim, such as one read from a log or
1122    /// sent by a process that mirrors this conversation. It is not put in
1123    /// the outbox. Fails, changing nothing, when it does not fit.
1124    pub fn apply(&mut self, change: &ChangeRecord) -> Result<(), ApplyError> {
1125        let at = change.at.unwrap_or_else(|| self.now());
1126        self.change(&change.change, at)?;
1127        self.revision = self.revision.saturating_add(1);
1128        Ok(())
1129    }
1130
1131    /// Makes the edits of `edit` as made at `at`, in milliseconds since the
1132    /// Unix epoch, such as messages imported at the times their source
1133    /// logged them: their records carry that time, which a writer keeps,
1134    /// and the entries they make were made then. An edit dated inside, by a
1135    /// nested call, keeps its own time.
1136    ///
1137    /// ```
1138    /// use rig_core::conversation::Conversation;
1139    /// use rig_core::message::Message;
1140    ///
1141    /// let mut conversation = Conversation::new();
1142    /// conversation.dated(1_790_848_800_000, |conversation| {
1143    ///     conversation.push(Message::user("Imported"))
1144    /// });
1145    /// let at = conversation.unsaved().first().and_then(|recorded| recorded.at);
1146    /// assert_eq!(at, Some(1_790_848_800_000));
1147    /// ```
1148    pub fn dated<R>(&mut self, at: u64, edit: impl FnOnce(&mut Self) -> R) -> R {
1149        let outer = self.clock.replace(at);
1150        let made = edit(self);
1151        self.clock = outer;
1152        made
1153    }
1154
1155    /// The time an edit is made at: the [`dated`](Self::dated) clock, else
1156    /// now.
1157    fn now(&self) -> u64 {
1158        self.clock.unwrap_or_else(crate::now_ms)
1159    }
1160
1161    /// The changes made that nobody has saved yet, oldest first.
1162    pub fn unsaved(&self) -> &[ChangeRecord] {
1163        &self.unsaved
1164    }
1165
1166    /// Takes the changes made that nobody has saved yet, to save them.
1167    pub fn take_unsaved(&mut self) -> Vec<ChangeRecord> {
1168        std::mem::take(&mut self.unsaved)
1169    }
1170
1171    /// The changes behind this value, one per record: those of the log it
1172    /// continues ([`lineage`](Self::lineage)) when it was last loaded or
1173    /// saved, then each applied or made since. An unsaved aside counts
1174    /// none.
1175    pub fn revision(&self) -> u64 {
1176        self.revision
1177    }
1178
1179    /// The saved log this value continues: [`Lineage::EMPTY`] when it was
1180    /// begun here from nothing and never saved, [`Lineage::DETACHED`] when
1181    /// it was read back from its serde form.
1182    pub fn lineage(&self) -> Lineage {
1183        self.lineage
1184    }
1185
1186    /// Whether this value's unsaved changes, appended to the log of
1187    /// `lineage` that holds `changes` changes, give this value: it
1188    /// continues that log, holding all of its changes and since then only
1189    /// its unsaved ones. A value begun here from nothing continues any
1190    /// empty log.
1191    pub fn continues(&self, lineage: Lineage, changes: u64) -> bool {
1192        let base = self.revision.saturating_sub(self.unsaved.len() as u64);
1193        base == changes
1194            && (self.lineage == lineage || (changes == 0 && self.lineage == Lineage::EMPTY))
1195    }
1196
1197    /// Marks this value as holding the `changes` changes of the log of
1198    /// `lineage`, then its unsaved ones: what a store does once it loaded
1199    /// the value from that log or saved it there.
1200    pub fn continue_log(&mut self, lineage: Lineage, changes: u64) {
1201        self.lineage = lineage;
1202        self.revision = changes.saturating_add(self.unsaved.len() as u64);
1203    }
1204
1205    /// A copy with an empty outbox, for edits never saved (a preview, a
1206    /// reset's state).
1207    pub fn view(&self) -> Self {
1208        Self {
1209            asides: self.asides.clone(),
1210            ..self.request_view()
1211        }
1212    }
1213
1214    /// A [`view`](Self::view) without the asides, for a model request's
1215    /// copy: a request never sends them, and one is built per model call.
1216    pub fn request_view(&self) -> Self {
1217        Self {
1218            messages: self.messages.clone(),
1219            meta: self.meta.clone(),
1220            asides: Vec::new(),
1221            halted: self.halted,
1222            next_id: self.next_id,
1223            next_aside: self.next_aside,
1224            revision: self.revision,
1225            lineage: self.lineage,
1226            unsaved: Vec::new(),
1227            clock: None,
1228        }
1229    }
1230
1231    /// Applies `change`, built live so it fits, as made now, and records it.
1232    fn record(&mut self, change: Change, by: Option<Author>) {
1233        let at = self.now();
1234        match self.change(&change, at) {
1235            Ok(()) => self.log_at(change, by, at),
1236            Err(error) => tracing::error!(%error, "a conversation edit did not fit; dropped"),
1237        }
1238    }
1239
1240    /// Records `change`, already applied, as made now.
1241    fn log(&mut self, change: Change, by: Option<Author>) {
1242        let at = self.now();
1243        self.log_at(change, by, at);
1244    }
1245
1246    fn log_at(&mut self, change: Change, by: Option<Author>, at: u64) {
1247        self.revision = self.revision.saturating_add(1);
1248        self.unsaved.push(ChangeRecord {
1249            change,
1250            by,
1251            at: Some(at),
1252        });
1253    }
1254
1255    /// Applies `change`, made at `at`, taking its ids verbatim, after
1256    /// checking it fits. An entry that keeps its id keeps when it was made;
1257    /// any other was made when its record says, else at `at`.
1258    fn change(&mut self, change: &Change, at: u64) -> Result<(), ApplyError> {
1259        let meta = |entry: &Entry, made: u64| Meta {
1260            id: entry.id.unwrap_or_default(),
1261            author: entry.author.clone(),
1262            made,
1263        };
1264        match change {
1265            Change::Push { entries } => {
1266                let end = self.meta.len();
1267                let next = self.new_ids(entries, &HashSet::new(), end..end)?;
1268                for entry in entries {
1269                    self.messages.push(entry.message.clone());
1270                    self.meta.push(meta(entry, entry.made.unwrap_or(at)));
1271                }
1272                self.next_id = next;
1273            }
1274            Change::Splice {
1275                from,
1276                to,
1277                removed,
1278                with,
1279            } => {
1280                let (from, to) = (*from, *to);
1281                let replaced = self.meta.get(from..to).ok_or_else(|| {
1282                    mismatch(format!(
1283                        "splice of {from}..{to} in {} entries",
1284                        self.meta.len()
1285                    ))
1286                })?;
1287                if !replaced.iter().map(|meta| &meta.id).eq(removed) {
1288                    return Err(mismatch(format!("splice of {from}..{to} names other ids")));
1289                }
1290                let kept: HashMap<MessageId, u64> =
1291                    replaced.iter().map(|meta| (meta.id, meta.made)).collect();
1292                let next = self.new_ids(with, &removed.iter().copied().collect(), from..to)?;
1293                let metas: Vec<Meta> = with
1294                    .iter()
1295                    .map(|entry| {
1296                        let made = entry.id.and_then(|id| kept.get(&id).copied());
1297                        meta(entry, made.or(entry.made).unwrap_or(at))
1298                    })
1299                    .collect();
1300                self.messages
1301                    .splice(from..to, with.iter().map(|entry| entry.message.clone()));
1302                self.meta.splice(from..to, metas);
1303                self.next_id = next;
1304            }
1305            Change::Halt { halt } => self.halted = *halt,
1306            Change::Aside {
1307                id,
1308                aside: Some(aside),
1309            } => self.place_aside(*id, aside.clone()),
1310            Change::Aside { id, aside: None } => {
1311                let at = self.asides.iter().position(|(known, _)| known == id);
1312                let at = at.ok_or_else(|| mismatch(format!("no aside {}", id.0)))?;
1313                self.asides.remove(at);
1314            }
1315            // Exactly the new value, its next ids too: the live value goes
1316            // on numbering from them, so a fold must as well.
1317            Change::Reset { state } => {
1318                let (unsaved, revision) = (std::mem::take(&mut self.unsaved), self.revision);
1319                let lineage = self.lineage;
1320                *self = state.view();
1321                self.unsaved = unsaved;
1322                self.revision = revision;
1323                self.lineage = lineage;
1324            }
1325        }
1326        Ok(())
1327    }
1328
1329    /// Checks that each of `entries`, which replace `range`, has an id
1330    /// that is, at most once, one of `reused` or an earlier id no entry
1331    /// outside `range` holds (put back), or else new: rising from the next
1332    /// id. Returns the next id after them.
1333    fn new_ids(
1334        &self,
1335        entries: &[Entry],
1336        reused: &HashSet<MessageId>,
1337        range: Range<usize>,
1338    ) -> Result<u64, ApplyError> {
1339        let mut next = self.next_id;
1340        let mut seen = HashSet::new();
1341        let mut outside = None;
1342        for entry in entries {
1343            let id = entry
1344                .id
1345                .ok_or_else(|| mismatch("a recorded entry without an id".to_owned()))?;
1346            if id.0 >= next {
1347                next = id.0.saturating_add(1);
1348                continue;
1349            }
1350            let fits = reused.contains(&id)
1351                || (id.0 < self.next_id
1352                    && !self.outside(range.clone(), &mut outside).contains(&id));
1353            if !fits || !seen.insert(id) {
1354                return Err(mismatch(format!("message id {} reused", id.0)));
1355            }
1356        }
1357        Ok(next)
1358    }
1359}
1360
1361#[cfg(test)]
1362mod tests;