Skip to main content

rig_core/conversation/
author.rs

1//! Who wrote an entry or an aside of a conversation, or made a change to it.
2
3use serde::{Deserialize, Serialize};
4
5use super::short_id;
6
7/// The id of a request to an agent, which the reply to it names, such as
8/// the tool call that asked. Unique per sender, so it doubles as an
9/// idempotency key.
10#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
11#[serde(transparent)]
12pub struct RequestId(pub String);
13
14/// Who wrote it.
15#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
16#[serde(rename_all = "snake_case")]
17pub enum AuthorKind {
18    /// The user, typing.
19    #[default]
20    User,
21    /// An agent, such as a subagent answering or an agent handing out a
22    /// task.
23    Agent,
24    /// The plugin named.
25    Plugin(String),
26    /// rig itself: the stopped note, restart answers, the kernel's edits.
27    Rig,
28}
29
30/// Who wrote an entry or an aside, or made a change: the kind of writer,
31/// the id of the agent it came from, the request it answers or makes, and
32/// what it is about. An entry without one is the user's own text, the
33/// model's reply or a tool result.
34#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
35pub struct Author {
36    /// Who wrote it.
37    #[serde(default)]
38    pub kind: AuthorKind,
39    /// The id of the agent it came from.
40    #[serde(default, skip_serializing_if = "Option::is_none")]
41    pub from: Option<String>,
42    /// The request it answers or makes. Kept for correlation, such as a
43    /// report to the call that asked; not shown in its header.
44    #[serde(default, skip_serializing_if = "Option::is_none")]
45    pub request: Option<RequestId>,
46    /// What it is about, such as a subagent's task title, shown in its
47    /// header.
48    #[serde(default, skip_serializing_if = "Option::is_none")]
49    pub title: Option<String>,
50}
51
52impl Author {
53    /// The user, typing.
54    pub fn user() -> Self {
55        Self::default()
56    }
57
58    /// The agent whose id is `from`, answering or making `request`.
59    pub fn agent(from: impl Into<String>, request: Option<RequestId>) -> Self {
60        Self {
61            kind: AuthorKind::Agent,
62            from: Some(from.into()),
63            request,
64            title: None,
65        }
66    }
67
68    /// The plugin `name`.
69    pub fn plugin(name: impl Into<String>) -> Self {
70        Self {
71            kind: AuthorKind::Plugin(name.into()),
72            ..Self::default()
73        }
74    }
75
76    /// rig itself.
77    pub fn rig() -> Self {
78        Self {
79            kind: AuthorKind::Rig,
80            ..Self::default()
81        }
82    }
83
84    /// This author, writing about `title`, such as a task's.
85    #[must_use]
86    pub fn titled(self, title: impl Into<String>) -> Self {
87        Self {
88            title: Some(title.into()),
89            ..self
90        }
91    }
92
93    /// Who wrote it, for views: such as `agent 1a2b3c4d "Fix the parser"`.
94    /// The request id is left out; it stays in [`Author::request`].
95    pub fn label(&self) -> String {
96        let who = match &self.kind {
97            AuthorKind::User => "you".to_owned(),
98            AuthorKind::Agent => {
99                format!("agent {}", self.from.as_deref().map_or("?", short_id))
100            }
101            AuthorKind::Plugin(name) => format!("plugin {name}"),
102            AuthorKind::Rig => "rig".to_owned(),
103        };
104        match &self.title {
105            Some(title) => format!("{who} \"{title}\""),
106            None => who,
107        }
108    }
109
110    /// The line that heads text by this author for the model, or `None`
111    /// for the user's own: it names the writer and says the text is not
112    /// the user's words.
113    pub fn header(&self) -> Option<String> {
114        match self.kind {
115            AuthorKind::User => None,
116            _ => Some(format!(
117                "[Output of {}, not the user's words]",
118                self.label()
119            )),
120        }
121    }
122}