Skip to main content

rig_core/conversation/
turn.rs

1//! Turn records: where each turn of a conversation began and how it ended,
2//! kept as asides, so a saved session tells a stopped or failed turn from
3//! an answered one without the app that ran it.
4
5use serde::{Deserialize, Serialize};
6
7use super::{Aside, AsideId, Author, Conversation, MessageId};
8
9/// The kind of an aside holding a [`TurnRecord`].
10pub const TURN: &str = "rig.turn";
11
12/// One turn of a conversation, from the message that asked for it to how
13/// it ended. Kept as an aside of kind [`TURN`] by [`Author::rig`], put
14/// after the last entry when the turn begins and replaced when it ends.
15#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
16pub struct TurnRecord {
17    /// How many turns began before this one.
18    pub index: u64,
19    /// Where it begins ([`Conversation::turn_start`]):
20    /// [`asked`](Conversation::asked) is what it answers,
21    /// [`request`](Conversation::request) the message that asked for it.
22    pub start: MessageId,
23    /// How it ended; `None` while it runs, and when a crash or an exit cut
24    /// it short.
25    #[serde(default, skip_serializing_if = "Option::is_none")]
26    pub end: Option<TurnEnd>,
27}
28
29/// How a turn ended.
30#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
31#[serde(tag = "how", rename_all = "snake_case")]
32pub enum TurnEnd {
33    /// The model answered.
34    Answered {
35        /// The reply entry the turn ended on; `None` when the last entry was
36        /// no reply.
37        #[serde(default, rename = "by", skip_serializing_if = "Option::is_none")]
38        answer: Option<MessageId>,
39    },
40    /// The turn failed, for `why`.
41    Failed {
42        /// Why.
43        why: String,
44    },
45    /// The turn was stopped before an answer: by the user when `why` is
46    /// `None`.
47    Stopped {
48        /// Why, when it was not the user.
49        #[serde(default, skip_serializing_if = "Option::is_none")]
50        why: Option<String>,
51    },
52}
53
54impl TurnRecord {
55    /// The record `aside` holds, when it is one: of kind [`TURN`], its
56    /// value a record.
57    pub fn of(aside: &Aside) -> Option<Self> {
58        (aside.kind == TURN)
59            .then(|| Self::deserialize(&aside.value).ok())
60            .flatten()
61    }
62}
63
64impl Conversation {
65    /// The turn records, oldest first, with their asides' ids; the newest
66    /// is `turns().next_back()`, found from the end.
67    pub fn turns(&self) -> impl DoubleEndedIterator<Item = (AsideId, TurnRecord)> + '_ {
68        self.asides()
69            .filter_map(|(id, aside)| Some((id, TurnRecord::of(aside)?)))
70    }
71
72    /// Records a turn that begins now, numbered after the last record and
73    /// starting at [`turn_start`](Self::turn_start). Returns the new record.
74    pub fn open_turn(&mut self) -> TurnRecord {
75        let last = self.turns().next_back().map(|(_, record)| record.index);
76        let record = TurnRecord {
77            index: last.map_or(0, |index| index.saturating_add(1)),
78            start: self.turn_start(),
79            end: None,
80        };
81        let after = self.entries().next_back().map(|entry| entry.id);
82        let value = serde_json::to_value(&record).unwrap_or_default();
83        self.add_aside(Aside::new(TURN, Author::rig(), value).after(after));
84        record
85    }
86
87    /// Ends the last turn record, when it is open, with `end`. Returns
88    /// whether it was open.
89    pub fn end_turn(&mut self, end: TurnEnd) -> bool {
90        let Some((id, record)) = self.turns().next_back() else {
91            return false;
92        };
93        let Some(aside) = self.aside(id).filter(|_| record.end.is_none()).cloned() else {
94            return false;
95        };
96        let ended = TurnRecord {
97            end: Some(end),
98            ..record
99        };
100        let value = serde_json::to_value(ended).unwrap_or_default();
101        self.replace_aside(id, Aside { value, ..aside })
102    }
103}