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/// How a text file attached to a user's message starts, followed by its
55/// label and `">`.
56pub const FILE_OPEN: &str = "<file path=\"";
57
58/// How a text file attached to a user's message ends.
59pub const FILE_CLOSE: &str = "</file>";
60
61/// The label and the contents of an attached text file, when `text` is
62/// one, so a view can show it without the markup around it.
63pub 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/// A conversation: its entries (each a message with an id and, when it is
69/// not the user's own or the model's, an author), its asides, why its last
70/// user message is unanswered, and the changes made to it that nobody has
71/// saved yet. Every edit builds a [`Change`], applies it with the same code
72/// a fold ([`apply`](Self::apply)) uses, and keeps it in the outbox
73/// ([`unsaved`](Self::unsaved)).
74///
75/// Two conversations are equal when their entries (messages, ids and
76/// authors), saved asides and halt are: the outbox, the revision, unsaved
77/// asides and when entries were made are not compared, so a value folded
78/// from a log equals the live one and two values built alike are equal.
79///
80/// Its serde form is its state: entries, saved asides, halt and the next
81/// ids, without the outbox, the revision or the lineage. A value read back
82/// from it is [`Lineage::DETACHED`].
83#[derive(Clone, Debug, Default, Serialize, Deserialize)]
84#[serde(try_from = "State")]
85pub struct Conversation {
86    /// What a request sends, so [`messages`](Self::messages) is a slice;
87    /// shared with the requests and records made from it.
88    messages: Vec<Arc<Message>>,
89    /// Parallel to `messages`.
90    meta: Vec<Meta>,
91    /// In the order put.
92    #[serde(serialize_with = "saved_asides")]
93    asides: Vec<(AsideId, Aside)>,
94    #[serde(skip_serializing_if = "Option::is_none")]
95    halted: Option<Halt>,
96    /// The id the next new entry gets; only rises, but for a reset to another value.
97    next_id: u64,
98    /// The id the next new aside gets; only rises, but for a reset to another value.
99    next_aside: u64,
100    /// The changes behind this value: those of the log it continues, then
101    /// its own.
102    #[serde(skip)]
103    revision: u64,
104    /// The log it continues.
105    #[serde(skip)]
106    lineage: Lineage,
107    #[serde(skip)]
108    unsaved: Vec<ChangeRecord>,
109    /// The time edits are made at, set by [`dated`](Self::dated); now when
110    /// `None`.
111    #[serde(skip)]
112    clock: Option<u64>,
113}
114
115/// An entry's id, author and when it was made, beside its message. Its
116/// serde form carries `made`, so a reset and a value read back keep it;
117/// equality leaves it out.
118#[derive(Clone, Debug, Serialize, Deserialize)]
119struct Meta {
120    id: MessageId,
121    #[serde(default, skip_serializing_if = "Option::is_none")]
122    author: Option<Author>,
123    /// Milliseconds since the Unix epoch: when the entry was first made.
124    #[serde(default)]
125    made: u64,
126}
127
128/// A conversation as saved, checked before it becomes one.
129#[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/// A message's identity in its conversation: kept while entries around it
211/// are added, cut or replaced, while it is spliced back changed, across a
212/// save and a fork. Ids rise in the order messages were first made.
213#[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    /// Its number: ids rise from 0 in the order messages were first made.
221    pub const fn get(self) -> u64 {
222        self.0
223    }
224}
225
226/// Its plain form, `m` and its number (`m4`): how commands name a message.
227impl fmt::Display for MessageId {
228    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
229        write!(f, "m{}", self.0)
230    }
231}
232
233/// Reads its plain form, `m4`, or the number alone.
234impl 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/// A [`MessageId`] or [`AsideId`] that could not be read from its plain form.
243#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
244#[error("{0:?} is not an id")]
245pub struct ParseIdError(pub String);
246
247/// The number of `text`, written with or without `prefix`.
248fn 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/// A message's identity beyond its conversation: when it was made and its
254/// id. Equal for a message and its copy in a fork (a fork copies ids and
255/// made-times), different for one made since, unless both were made in
256/// the same millisecond with the same id.
257#[derive(
258    Clone, Copy, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize,
259)]
260pub struct MessageKey {
261    /// When it was first made, in milliseconds since the Unix epoch.
262    pub made: u64,
263    /// Its id in its conversation.
264    pub id: MessageId,
265}
266
267/// A message as it goes into a conversation: the message, who wrote it,
268/// and, for a message taken out with [`Conversation::entry`], its id,
269/// which a [`splice`](Conversation::splice) keeps. A recorded change
270/// always carries every id.
271#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
272pub struct Entry {
273    /// The message, shared with the conversation it goes into.
274    pub message: Arc<Message>,
275    /// Who wrote it; `None` for the user's own text, the model's reply and
276    /// tool results.
277    #[serde(default, skip_serializing_if = "Option::is_none")]
278    pub author: Option<Author>,
279    /// Its id in the conversation, if it has one.
280    #[serde(default, skip_serializing_if = "Option::is_none")]
281    pub id: Option<MessageId>,
282    /// When it was first made, in milliseconds since the Unix epoch: set by
283    /// [`Conversation::entry`], kept by an entry put back as it was. A
284    /// recorded change carries it only for such an entry.
285    #[serde(default, skip_serializing_if = "Option::is_none")]
286    pub made: Option<u64>,
287}
288
289impl Entry {
290    /// A new entry without an author.
291    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    /// Whether it is the [`STOPPED`] note, as
301    /// [`EntryRef::is_stop_marker`].
302    pub fn is_stop_marker(&self) -> bool {
303        is_stop_marker(&self.message, self.author.as_ref())
304    }
305
306    /// This entry, written by `author`.
307    #[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/// Entry `index` of a conversation, borrowed.
329#[derive(Clone, Copy, Debug)]
330pub struct EntryRef<'c> {
331    /// Where it is.
332    pub index: usize,
333    /// Its id.
334    pub id: MessageId,
335    /// Its message.
336    pub message: &'c Message,
337    /// Who wrote it, when it is not the user's own or the model's.
338    pub author: Option<&'c Author>,
339    /// When it was first made, in milliseconds since the Unix epoch.
340    pub made: u64,
341}
342
343impl EntryRef<'_> {
344    /// Its identity beyond its conversation.
345    pub fn key(&self) -> MessageKey {
346        MessageKey {
347            made: self.made,
348            id: self.id,
349        }
350    }
351
352    /// Whether it is a user message the user typed: text or attachments,
353    /// not only tool results, with no author or [`AuthorKind::User`] with
354    /// no title.
355    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    /// Whether it asks something of the model: a user message with text
369    /// or attachments, not only tool results, that is not rig's own (the
370    /// [`STOPPED`] note). The user's, an agent's or a plugin's.
371    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    /// Whether it is the [`STOPPED`] note rig puts between a request the
384    /// user stopped and what came next.
385    pub fn is_stop_marker(&self) -> bool {
386        is_stop_marker(self.message, self.author)
387    }
388}
389
390/// Whether `message` by `author` is the [`STOPPED`] note.
391fn 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
398/// The text of the entry that goes between a request the user stopped and
399/// the next entry, so the model does not carry the request out. rig pushes
400/// it as its own entry, by [`Author::rig`].
401pub 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/// Why the user's last message is left without an answer, so a restore
406/// does not answer it. How a turn ended is its [`TurnRecord`].
407#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
408#[serde(rename_all = "snake_case")]
409pub enum Halt {
410    /// The user stopped the turn answering it: the next entry pushed goes
411    /// after [`STOPPED`].
412    Stopped,
413    /// It is left for the next turn: a note to an idle agent, a request a
414    /// plugin stopped, or another author's message a failed turn left.
415    #[default]
416    Kept,
417}
418
419/// One edit of a conversation. Its serde form is the conversation part of
420/// the saved format.
421#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
422#[serde(tag = "type", rename_all = "snake_case")]
423pub enum Change {
424    /// Entries added at the end, each with its id.
425    Push {
426        /// The entries.
427        entries: Vec<Entry>,
428    },
429    /// Entries `from..to`, whose ids were `removed`, replaced by `with`
430    /// (each with its id). `removed` doubles as a check: applying it where
431    /// the range holds other ids fails.
432    Splice {
433        /// The first entry replaced.
434        from: usize,
435        /// The end of the range replaced.
436        to: usize,
437        /// The ids of entries `from..to` before the splice.
438        removed: Vec<MessageId>,
439        /// What replaces them.
440        with: Vec<Entry>,
441    },
442    /// The halt set, or with `None` cleared.
443    Halt {
444        /// The halt.
445        halt: Option<Halt>,
446    },
447    /// Aside `id` put or replaced (`Some`), or removed (`None`).
448    Aside {
449        /// The aside's id.
450        id: AsideId,
451        /// The aside.
452        aside: Option<Aside>,
453    },
454    /// The whole value replaced: a component inserted over another, or a
455    /// conversation built elsewhere and saved onto a log it was not read
456    /// from.
457    Reset {
458        /// The new value.
459        state: Box<Conversation>,
460    },
461}
462
463/// A change as kept in the outbox and saved: who made it and when.
464#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
465pub struct ChangeRecord {
466    /// The change.
467    #[serde(flatten)]
468    pub change: Change,
469    /// Who made it: a push's entry author, a splice's `by`, an aside's
470    /// author; `None` for rig's own.
471    #[serde(default, skip_serializing_if = "Option::is_none")]
472    pub by: Option<Author>,
473    /// Milliseconds since the Unix epoch; the writer stamps it when `None`.
474    #[serde(default, skip_serializing_if = "Option::is_none")]
475    pub at: Option<u64>,
476}
477
478/// What a splice changed.
479#[derive(Clone, Debug, Default, PartialEq, Eq)]
480pub struct Spliced {
481    /// The ids no longer in the conversation.
482    pub removed: Vec<MessageId>,
483    /// The ids of the entries it put in, kept, put back or new.
484    pub changed: Vec<MessageId>,
485    /// The ids [`restore`](Conversation::restore) put back.
486    pub restored: Vec<MessageId>,
487}
488
489/// Why a recorded change could not be applied: it does not fit the
490/// conversation, such as a splice of a range holding other ids.
491#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
492pub enum ApplyError {
493    /// The change does not match the conversation.
494    #[error("{0}")]
495    Mismatch(String),
496}
497
498fn mismatch(why: String) -> ApplyError {
499    ApplyError::Mismatch(why)
500}
501
502/// The first characters of the agent id `id`, enough to tell agents apart.
503pub fn short_id(id: &str) -> &str {
504    id.get(..8).unwrap_or(id)
505}
506
507impl Conversation {
508    /// An empty conversation.
509    pub fn new() -> Self {
510        Self::default()
511    }
512
513    /// A conversation of `messages`, each its own entry, recorded so that
514    /// saving it writes them (an import, a library user's history).
515    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    // Reading.
522
523    /// The messages, oldest first: what a request sends.
524    pub fn messages(&self) -> &[Arc<Message>] {
525        &self.messages
526    }
527
528    /// The messages, oldest first, taken out of the conversation: what a
529    /// request sends, without a copy.
530    pub fn into_messages(self) -> Vec<Arc<Message>> {
531        self.messages
532    }
533
534    /// The entries, oldest first.
535    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    /// Entry `at`, with its author, id and when it was made, to splice
547    /// back changed.
548    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    /// The id of entry `at`.
559    pub fn id(&self, at: usize) -> Option<MessageId> {
560        self.meta.get(at).map(|meta| meta.id)
561    }
562
563    /// The identity of entry `at` beyond this conversation.
564    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    /// Where the entry with identity `key` is now; `None` when it is not
573    /// here, also when an entry with its id was made at another time.
574    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    /// Who wrote entry `at`; `None` for the user's own text, the model's
580    /// reply, a tool result, or no such entry.
581    pub fn author(&self, at: usize) -> Option<&Author> {
582        self.meta.get(at)?.author.as_ref()
583    }
584
585    /// Where entry `id` is now; `None` once an edit took it out.
586    pub fn index_of(&self, id: MessageId) -> Option<usize> {
587        self.meta.iter().position(|meta| meta.id == id)
588    }
589
590    /// Where a span that began at entry `id` begins now, such as a turn:
591    /// its index while it is there; the end for an entry not made yet; 0
592    /// for one an edit took out, as a summary then stands for what came
593    /// before it.
594    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    /// Where a turn starting now begins: the first of the user messages at
603    /// the end, which it answers, else the first entry it adds.
604    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    /// The entries a turn that began at `start` (its
615    /// [`turn_start`](Self::turn_start)) answers: the user messages in a
616    /// row from where it begins now ([`start_of`](Self::start_of)). After a
617    /// stopped turn they begin with that turn's tool results and the
618    /// [`STOPPED`] note, before what was sent next.
619    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    /// The message that asked for the turn that began at `start`: the
625    /// first of the entries it answers ([`asked`](Self::asked)) that is a
626    /// request ([`EntryRef::is_request`]).
627    pub fn request(&self, start: MessageId) -> Option<EntryRef<'_>> {
628        self.asked(start).find(EntryRef::is_request)
629    }
630
631    /// Why the user's last message is left unanswered, if it was halted.
632    /// Cleared when an entry is pushed or a splice reaches the end.
633    pub fn halted(&self) -> Option<Halt> {
634        self.halted
635    }
636
637    /// Whether the model owes an answer: the last message a model reads is
638    /// the user's, and it was not halted. A failed or stopped reply after it
639    /// is no answer ([`StopReason::is_failure`]), so the answer is still
640    /// owed.
641    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    /// The tool calls of the last assistant message that nothing answers
650    /// yet. See [`pairing::pending_calls`].
651    pub fn pending_calls(&self) -> Vec<ToolCall> {
652        pairing::pending_calls(&self.messages)
653    }
654
655    /// Whether the messages pair every tool call with its result. See
656    /// [`pairing::validate`].
657    pub fn validate(&self) -> Result<(), PairingError> {
658        pairing::validate(&self.messages)
659    }
660
661    /// The asides, in the order put.
662    pub fn asides(&self) -> impl DoubleEndedIterator<Item = (AsideId, &Aside)> {
663        self.asides.iter().map(|(id, aside)| (*id, aside))
664    }
665
666    /// Aside `id`.
667    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    /// Where `aside` is drawn: before entry `position`, after entry
675    /// `position - 1`. While an edit took out the entry it follows, after
676    /// the entry that stands for it now ([`start_of`](Self::start_of)).
677    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    // Editing: each builds a change with every id filled in, applies it and
691    // records it.
692
693    /// Pushes `entry`, after a [`STOPPED`] entry by [`Author::rig`] in the
694    /// same change when the user stopped the last request; a halt is
695    /// cleared. Returns the new entry's id; a pushed entry never reuses one.
696    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    /// Pushes each of `messages` as its own entry without an author, in one
704    /// change, as [`push`](Self::push) does.
705    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    /// Replaces entries in `range`, clamped to the conversation, with
738    /// `with`, recorded as made by `by`. An entry of `with` keeps its id
739    /// when that id was in the range and no earlier entry of `with` kept
740    /// it; any other gets a new id. Nothing merges. A splice reaching the
741    /// end clears the halt. An empty range replaced by nothing changes and
742    /// records nothing.
743    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    /// As [`splice`](Self::splice), and an entry of `with` whose id this
753    /// conversation made (below the next id), that no entry outside the
754    /// range holds and no earlier entry of `with` took, keeps that id and
755    /// when it was made ([`Entry::made`], else now): entries put back as
756    /// they were, such as a summary's originals or an edit undone. Asides that followed such an entry follow it again.
757    /// Only for entries this conversation held: an entry from elsewhere
758    /// that carries an id would take one that meant another message here.
759    ///
760    /// ```
761    /// use rig_core::completion::Message;
762    /// use rig_core::conversation::{Author, Conversation, Entry};
763    ///
764    /// let mut conversation = Conversation::from_messages([
765    ///     Message::user("one"),
766    ///     Message::assistant("two"),
767    /// ]);
768    /// let ids: Vec<_> = conversation.entries().map(|entry| entry.id).collect();
769    /// let originals: Vec<Entry> = (0..2).filter_map(|at| conversation.entry(at)).collect();
770    /// let summary = Entry::new(Message::user("a summary"));
771    /// conversation.splice(.., [summary], Author::plugin("compact"));
772    /// let undone = conversation.restore(.., originals, Author::plugin("undo"));
773    /// assert_eq!(undone.restored, ids);
774    /// assert!(conversation.entries().map(|entry| entry.id).eq(ids));
775    /// ```
776    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    /// Makes the conversation hold `entries`, as made by `by`: one
786    /// [`restore`](Self::restore) of the range between the longest start
787    /// and end it shares with them (the same messages, ids and authors), so
788    /// entries taken from it keep their ids. Nothing when they are equal.
789    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    /// Replaces entries in `range` with `with`: [`splice`](Self::splice),
820    /// or with `restores`, [`restore`](Self::restore).
821    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    /// The ids of the entries outside `range`, gathered into `outside` the
893    /// first time they are asked for.
894    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    /// Rewrites entries in place, as made by `by`: `edit` sees each entry,
907    /// oldest first, and returns the message to put in its place, or
908    /// `None` to leave it. The entries it rewrote keep their ids and
909    /// authors, and the whole edit is one change: a [`splice`](Self::splice)
910    /// of the entries from the first rewritten to the last. Nothing is
911    /// recorded when it rewrote none. Returns the ids of the entries it
912    /// rewrote.
913    ///
914    /// ```
915    /// use rig_core::completion::Message;
916    /// use rig_core::conversation::{Author, Conversation};
917    ///
918    /// let mut conversation = Conversation::from_messages([
919    ///     Message::user("one"),
920    ///     Message::user("two"),
921    /// ]);
922    /// let before = conversation.id(1);
923    /// let changed = conversation.edit_each(Author::plugin("shout"), |entry| {
924    ///     (entry.index == 1).then(|| Message::user("TWO"))
925    /// });
926    /// assert_eq!(changed, before.into_iter().collect::<Vec<_>>());
927    /// assert_eq!(conversation.messages().get(1).map(|message| &**message), Some(&Message::user("TWO")));
928    /// assert_eq!(conversation.id(1), before);
929    /// ```
930    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    /// Repairs the entries as [`pairing::normalize`] does, saved with
963    /// [`set_entries`](Self::set_entries) as made by `by`, so merged
964    /// entries keep the first's id, author and made-time. An aside that
965    /// followed an entry merged into another follows that one; one that
966    /// followed a dropped entry follows the entry before it (each move
967    /// recorded, by `by`). Returns what it repaired.
968    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        // Where an aside that followed `id` goes: the entry it went into,
978        // or for a dropped one, the entry before it that stays.
979        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    /// Takes everything a log holds from `from_log`, a value read from that
1005    /// log: entries, saved asides, halt, next ids, lineage and revision.
1006    /// This value's unsaved asides stay, with their ids. For a host whose
1007    /// log was rewritten under the value it holds.
1008    ///
1009    /// # Errors
1010    ///
1011    /// [`ApplyError::Mismatch`], changing nothing, while either value has
1012    /// unsaved changes, or when an unsaved aside's id is a saved one's in
1013    /// `from_log`.
1014    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    /// Leaves the user's last message unanswered, for `halt`. Whether it
1051    /// was halted now: not when the model owes no answer.
1052    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    /// Puts `aside` after the asides already put and returns its id. An
1067    /// unsaved aside is not recorded.
1068    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    /// Replaces aside `id` with `aside`, in its place; whether there was
1081    /// one. Replacing a saved aside with an unsaved one records its
1082    /// removal.
1083    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            // The saved value loses it; the live one keeps it in its place.
1093            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    /// Removes aside `id` and returns it, or `None` if there was none.
1102    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    /// Makes aside `id` follow `after`, recorded as made by `by` when it is
1112    /// saved.
1113    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    /// Puts `aside` as `id` without recording it: in place of the aside
1127    /// with that id, else last.
1128    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    // The record.
1137
1138    /// Applies a recorded change verbatim, such as one read from a log or
1139    /// sent by a process that mirrors this conversation. It is not put in
1140    /// the outbox. Fails, changing nothing, when it does not fit.
1141    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    /// Makes the edits of `edit` as made at `at`, in milliseconds since the
1149    /// Unix epoch, such as messages imported at the times their source
1150    /// logged them: their records carry that time, which a writer keeps,
1151    /// and the entries they make were made then. An edit dated inside, by a
1152    /// nested call, keeps its own time.
1153    ///
1154    /// ```
1155    /// use rig_core::conversation::Conversation;
1156    /// use rig_core::message::Message;
1157    ///
1158    /// let mut conversation = Conversation::new();
1159    /// conversation.dated(1_790_848_800_000, |conversation| {
1160    ///     conversation.push(Message::user("Imported"))
1161    /// });
1162    /// let at = conversation.unsaved().first().and_then(|recorded| recorded.at);
1163    /// assert_eq!(at, Some(1_790_848_800_000));
1164    /// ```
1165    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    /// The time an edit is made at: the [`dated`](Self::dated) clock, else
1173    /// now.
1174    fn now(&self) -> u64 {
1175        self.clock.unwrap_or_else(crate::now_ms)
1176    }
1177
1178    /// The changes made that nobody has saved yet, oldest first.
1179    pub fn unsaved(&self) -> &[ChangeRecord] {
1180        &self.unsaved
1181    }
1182
1183    /// Takes the changes made that nobody has saved yet, to save them.
1184    pub fn take_unsaved(&mut self) -> Vec<ChangeRecord> {
1185        std::mem::take(&mut self.unsaved)
1186    }
1187
1188    /// The changes behind this value, one per record: those of the log it
1189    /// continues ([`lineage`](Self::lineage)) when it was last loaded or
1190    /// saved, then each applied or made since. An unsaved aside counts
1191    /// none.
1192    pub fn revision(&self) -> u64 {
1193        self.revision
1194    }
1195
1196    /// The saved log this value continues: [`Lineage::EMPTY`] when it was
1197    /// begun here from nothing and never saved, [`Lineage::DETACHED`] when
1198    /// it was read back from its serde form.
1199    pub fn lineage(&self) -> Lineage {
1200        self.lineage
1201    }
1202
1203    /// Whether this value's unsaved changes, appended to the log of
1204    /// `lineage` that holds `changes` changes, give this value: it
1205    /// continues that log, holding all of its changes and since then only
1206    /// its unsaved ones. A value begun here from nothing continues any
1207    /// empty log.
1208    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    /// Marks this value as holding the `changes` changes of the log of
1215    /// `lineage`, then its unsaved ones: what a store does once it loaded
1216    /// the value from that log or saved it there.
1217    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    /// A copy with an empty outbox, for edits never saved (a preview, a
1223    /// reset's state).
1224    pub fn view(&self) -> Self {
1225        Self {
1226            asides: self.asides.clone(),
1227            ..self.request_view()
1228        }
1229    }
1230
1231    /// A [`view`](Self::view) without the asides, for a model request's
1232    /// copy: a request never sends them, and one is built per model call.
1233    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    /// Applies `change`, built live so it fits, as made now, and records it.
1249    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    /// Records `change`, already applied, as made now.
1258    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    /// Applies `change`, made at `at`, taking its ids verbatim, after
1273    /// checking it fits. An entry that keeps its id keeps when it was made;
1274    /// any other was made when its record says, else at `at`.
1275    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            // Exactly the new value, its next ids too: the live value goes
1333            // on numbering from them, so a fold must as well.
1334            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    /// Checks that each of `entries`, which replace `range`, has an id
1347    /// that is, at most once, one of `reused` or an earlier id no entry
1348    /// outside `range` holds (put back), or else new: rising from the next
1349    /// id. Returns the next id after them.
1350    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;