Skip to main content

rig_core/completion/message/
identity.rs

1//! Identities a message carries: a tool call's one [`CallId`], a tool's
2//! [`ToolName`], and reasoning [`Sealed`] to the [`Issuer`] that produced it.
3
4use std::borrow::Cow;
5
6use serde::{Deserialize, Serialize};
7
8/// A tool name, never empty.
9///
10/// ```
11/// use rig_core::message::ToolName;
12///
13/// let name = ToolName::new("add")?;
14/// assert_eq!(name.as_str(), "add");
15/// assert!(ToolName::new("").is_err());
16/// # Ok::<(), rig_core::message::EmptyToolName>(())
17/// ```
18#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
19#[serde(try_from = "String", into = "String")]
20pub struct ToolName(String);
21
22/// A tool name was empty.
23#[derive(Clone, Copy, Debug, PartialEq, Eq, thiserror::Error)]
24#[error("a tool name cannot be empty")]
25pub struct EmptyToolName;
26
27impl ToolName {
28    /// `name`, or [`EmptyToolName`] when it is empty.
29    pub fn new(name: impl Into<String>) -> Result<Self, EmptyToolName> {
30        let name = name.into();
31        if name.is_empty() {
32            Err(EmptyToolName)
33        } else {
34            Ok(Self(name))
35        }
36    }
37
38    /// The name.
39    pub fn as_str(&self) -> &str {
40        &self.0
41    }
42}
43
44impl TryFrom<String> for ToolName {
45    type Error = EmptyToolName;
46
47    fn try_from(name: String) -> Result<Self, EmptyToolName> {
48        Self::new(name)
49    }
50}
51
52impl TryFrom<&str> for ToolName {
53    type Error = EmptyToolName;
54
55    fn try_from(name: &str) -> Result<Self, EmptyToolName> {
56        Self::new(name)
57    }
58}
59
60impl From<ToolName> for String {
61    fn from(name: ToolName) -> Self {
62        name.0
63    }
64}
65
66impl std::ops::Deref for ToolName {
67    type Target = str;
68
69    fn deref(&self) -> &str {
70        &self.0
71    }
72}
73
74impl AsRef<str> for ToolName {
75    fn as_ref(&self) -> &str {
76        &self.0
77    }
78}
79
80impl std::borrow::Borrow<str> for ToolName {
81    fn borrow(&self) -> &str {
82        &self.0
83    }
84}
85
86impl std::fmt::Display for ToolName {
87    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
88        f.write_str(&self.0)
89    }
90}
91
92impl PartialEq<str> for ToolName {
93    fn eq(&self, other: &str) -> bool {
94        self.0 == other
95    }
96}
97
98impl PartialEq<&str> for ToolName {
99    fn eq(&self, other: &&str) -> bool {
100        self.0 == *other
101    }
102}
103
104impl PartialEq<String> for ToolName {
105    fn eq(&self, other: &String) -> bool {
106        &self.0 == other
107    }
108}
109
110impl PartialEq<ToolName> for String {
111    fn eq(&self, other: &ToolName) -> bool {
112        *self == other.0
113    }
114}
115
116impl PartialEq<ToolName> for str {
117    fn eq(&self, other: &ToolName) -> bool {
118        self == other.0
119    }
120}
121
122/// A tool call's one identity: the id the provider issued, or one rig issued
123/// because the provider sent none.
124///
125/// A result copies the id of the call it answers ([`ToolCall::result`]), so
126/// the two always match.
127///
128/// ```
129/// use rig_core::message::{CallId, ProviderCallId};
130///
131/// let id = CallId::from(ProviderCallId::new("call_1").ok_or("empty id")?);
132/// assert_eq!(id.to_string(), "call_1");
133/// assert_eq!(id.provider().map(|provider| provider.call_id.as_str()), Some("call_1"));
134/// # Ok::<(), Box<dyn std::error::Error>>(())
135/// ```
136///
137/// [`ToolCall::result`]: super::ToolCall::result
138#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
139#[serde(rename_all = "snake_case")]
140pub enum CallId {
141    /// The provider's identifiers.
142    Provider(ProviderCallId),
143    /// An identifier rig issued for a call the provider sent without one.
144    Local(LocalCallId),
145}
146
147impl CallId {
148    /// The provider's id `call_id`, or a rig-issued id when it is empty.
149    pub fn from_wire(call_id: impl Into<String>) -> Self {
150        ProviderCallId::new(call_id).map_or_else(|| Self::Local(LocalCallId::new()), Self::Provider)
151    }
152
153    /// A dual-identifier wire's ids (OpenAI Responses): `call_id` is the
154    /// correlator and `item_id` the output item. A rig-issued id when
155    /// `call_id` is empty.
156    pub fn from_dual_wire(item_id: impl Into<String>, call_id: impl Into<String>) -> Self {
157        match ProviderCallId::new(call_id) {
158            Some(provider) => Self::Provider(provider.with_item_id(item_id)),
159            None => Self::Local(LocalCallId::new()),
160        }
161    }
162
163    /// The provider's identifiers, when the provider issued them.
164    pub fn provider(&self) -> Option<&ProviderCallId> {
165        match self {
166            Self::Provider(provider) => Some(provider),
167            Self::Local(_) => None,
168        }
169    }
170
171    /// Whether rig issued this id.
172    pub fn is_local(&self) -> bool {
173        matches!(self, Self::Local(_))
174    }
175
176    /// The id as a wire sends it: the provider's call id, or the rig-issued
177    /// UUID.
178    pub fn wire(&self) -> Cow<'_, str> {
179        match self {
180            Self::Provider(provider) => Cow::Borrowed(provider.call_id.as_str()),
181            Self::Local(local) => Cow::Owned(local.to_string()),
182        }
183    }
184}
185
186impl From<ProviderCallId> for CallId {
187    fn from(provider: ProviderCallId) -> Self {
188        Self::Provider(provider)
189    }
190}
191
192impl From<LocalCallId> for CallId {
193    fn from(local: LocalCallId) -> Self {
194        Self::Local(local)
195    }
196}
197
198impl std::fmt::Display for CallId {
199    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
200        f.write_str(&self.wire())
201    }
202}
203
204/// A call id rig issued: a random (v4) UUID.
205#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
206#[serde(transparent)]
207pub struct LocalCallId(uuid::Uuid);
208
209impl LocalCallId {
210    /// A fresh id.
211    pub fn new() -> Self {
212        Self(uuid::Uuid::new_v4())
213    }
214}
215
216impl Default for LocalCallId {
217    fn default() -> Self {
218        Self::new()
219    }
220}
221
222impl std::fmt::Display for LocalCallId {
223    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
224        self.0.fmt(f)
225    }
226}
227
228/// Wire shape for [`ProviderCallId`], so deserialization enforces the
229/// non-empty `call_id` invariant.
230#[derive(Deserialize)]
231struct ProviderCallIdWire {
232    call_id: String,
233    #[serde(default)]
234    item_id: Option<String>,
235}
236
237/// Provider-issued identifiers for replay. Single-id protocols use `call_id`;
238/// dual-id protocols also use `item_id`. Keep each identifier in its protocol
239/// slot.
240#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
241#[serde(try_from = "ProviderCallIdWire")]
242pub struct ProviderCallId {
243    /// The call-correlation identifier the provider expects echoed back.
244    pub call_id: String,
245    /// The output-item id issued alongside `call_id` on dual-identifier
246    /// wires (OpenAI Responses `fc_…`).
247    #[serde(default, skip_serializing_if = "Option::is_none")]
248    pub item_id: Option<String>,
249}
250
251/// A provider call id was empty.
252#[derive(Clone, Copy, Debug, PartialEq, Eq, thiserror::Error)]
253#[error("a provider call id cannot be empty")]
254pub struct EmptyCallId;
255
256impl ProviderCallId {
257    /// Adopt a provider-issued call identifier. `None` for the empty string:
258    /// absence is not an id.
259    pub fn new(call_id: impl Into<String>) -> Option<Self> {
260        let call_id = call_id.into();
261        (!call_id.is_empty()).then_some(Self {
262            call_id,
263            item_id: None,
264        })
265    }
266
267    /// Attach the dual-wire output-item id (empty strings are dropped).
268    pub fn with_item_id(mut self, item_id: impl Into<String>) -> Self {
269        let item_id = item_id.into();
270        self.item_id = (!item_id.is_empty()).then_some(item_id);
271        self
272    }
273}
274
275impl TryFrom<ProviderCallIdWire> for ProviderCallId {
276    type Error = EmptyCallId;
277
278    fn try_from(wire: ProviderCallIdWire) -> Result<Self, EmptyCallId> {
279        let provider = Self::new(wire.call_id).ok_or(EmptyCallId)?;
280        Ok(match wire.item_id {
281            Some(item_id) => provider.with_item_id(item_id),
282            None => provider,
283        })
284    }
285}
286
287/// The service that issued reasoning: the descriptor name of the wire that
288/// decoded it (`"anthropic"`), or the model vendor where a gateway relays
289/// several (`"openrouter/openai"`).
290///
291/// An issuer ending in `/` names a family: it accepts every issuer under it,
292/// so `openrouter/` accepts `openrouter/openai`.
293#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
294#[serde(transparent)]
295pub struct Issuer(Cow<'static, str>);
296
297impl Issuer {
298    /// The issuer named `name`.
299    pub fn new(name: impl Into<Cow<'static, str>>) -> Self {
300        Self(name.into())
301    }
302
303    /// The issuer named `name`, in a const context.
304    pub const fn from_static(name: &'static str) -> Self {
305        Self(Cow::Borrowed(name))
306    }
307
308    /// The issuer's name.
309    pub fn as_str(&self) -> &str {
310        &self.0
311    }
312
313    /// Whether a value `issued` by that issuer may be opened by this one:
314    /// the same issuer, or a family `issued` belongs to.
315    pub fn accepts(&self, issued: &Issuer) -> bool {
316        self.0 == issued.0 || (self.0.ends_with('/') && issued.0.starts_with(self.as_str()))
317    }
318}
319
320impl std::fmt::Display for Issuer {
321    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
322        f.write_str(&self.0)
323    }
324}
325
326impl From<&'static str> for Issuer {
327    fn from(name: &'static str) -> Self {
328        Self(Cow::Borrowed(name))
329    }
330}
331
332impl From<String> for Issuer {
333    fn from(name: String) -> Self {
334        Self(Cow::Owned(name))
335    }
336}
337
338/// A value only its issuer may read: reasoning, whose signatures, encrypted
339/// payloads and ids mean something only to the service that produced them.
340///
341/// A wire encoding a request opens each sealed value with the issuers it
342/// replays; a value another service issued does not open, so it is not
343/// sent.
344///
345/// ```
346/// use rig_core::message::{Issuer, Reasoning, Sealed};
347///
348/// let sealed = Sealed::new(Issuer::from("anthropic"), Reasoning::new("thinking"));
349/// assert!(sealed.open(&Issuer::from("anthropic")).is_some());
350/// assert!(sealed.open(&Issuer::from("openai")).is_none());
351/// ```
352#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
353pub struct Sealed<T> {
354    issuer: Issuer,
355    #[serde(flatten)]
356    value: T,
357}
358
359impl<T> Sealed<T> {
360    /// `value`, readable only by `issuer`.
361    pub fn new(issuer: impl Into<Issuer>, value: T) -> Self {
362        Self {
363            issuer: issuer.into(),
364            value,
365        }
366    }
367
368    /// Who issued the value.
369    pub fn issuer(&self) -> &Issuer {
370        &self.issuer
371    }
372
373    /// The value, when `to` accepts its issuer ([`Issuer::accepts`]).
374    pub fn open(&self, to: &Issuer) -> Option<&T> {
375        to.accepts(&self.issuer).then_some(&self.value)
376    }
377
378    /// The value, when any of `issuers` accepts its issuer.
379    pub fn open_for(&self, issuers: &[Issuer]) -> Option<&T> {
380        issuers
381            .iter()
382            .any(|to| to.accepts(&self.issuer))
383            .then_some(&self.value)
384    }
385
386    /// The same value, readable only by `issuer`.
387    pub(crate) fn reseal(self, issuer: impl Into<Issuer>) -> Self {
388        Self::new(issuer, self.value)
389    }
390
391    /// The value, for the completion writer that assembles it.
392    pub(crate) fn value(&self) -> &T {
393        &self.value
394    }
395}