Skip to main content

everruns_capability/
spec.rs

1//! The open conversion contract from application values to capability
2//! registrations.
3
4use crate::reference::CapabilityRef;
5
6/// Convert an application value into one capability registration.
7///
8/// This trait is intentionally public and non-sealed. Third-party crates can
9/// implement it without depending on `everruns-core` or host internals:
10/// return a [`CapabilitySpec`] built from a stable [`CapabilityRef`]. The
11/// consuming boundary (e.g. the Framework's `AgentBuilder::build`) validates
12/// identifiers, JSON configuration, duplicates, and implementation
13/// collisions. Conversion itself is infallible and performs no registration.
14///
15/// # Example
16///
17/// ```
18/// use everruns_capability::{CapabilityRef, CapabilitySpec, IntoCapability};
19/// use serde_json::json;
20///
21/// struct VendorSearch {
22///     index: String,
23/// }
24///
25/// impl IntoCapability for VendorSearch {
26///     fn into_capability(self) -> CapabilitySpec {
27///         CapabilityRef::new("vendor.search")
28///             .config(json!({ "index": self.index }))
29///             .into()
30///     }
31/// }
32/// ```
33pub trait IntoCapability {
34    /// Consume the value and return its normalized specification.
35    fn into_capability(self) -> CapabilitySpec;
36}
37
38/// The normalized value produced by [`IntoCapability`].
39///
40/// A spec always activates exactly one [`CapabilityRef`]. With the
41/// `definition` feature it may also carry the matching code-defined
42/// [`Definition`](crate::definition::Definition) that the host must register.
43/// Applications normally construct specs by converting a [`CapabilityRef`], a
44/// typed built-in value, or a `Definition`.
45///
46/// Duplicate IDs are never merged and later registrations never overwrite
47/// earlier ones. The consuming boundary rejects duplicates after resolving
48/// built-in aliases, including a reference paired with a code-defined
49/// implementation and an implementation that would shadow a built-in.
50#[derive(Clone, Debug)]
51pub struct CapabilitySpec {
52    reference: CapabilityRef,
53    #[cfg(feature = "definition")]
54    definition: Option<crate::definition::Definition>,
55}
56
57impl CapabilitySpec {
58    /// Normalize a dynamic capability reference.
59    pub fn reference(reference: CapabilityRef) -> Self {
60        Self {
61            reference,
62            #[cfg(feature = "definition")]
63            definition: None,
64        }
65    }
66
67    /// Normalize a code-defined capability and activate its stable ID.
68    #[cfg(feature = "definition")]
69    pub fn definition(definition: crate::definition::Definition) -> Self {
70        Self {
71            reference: CapabilityRef::new(definition.id()),
72            definition: Some(definition),
73        }
74    }
75
76    /// The reference that will be activated for the agent.
77    pub fn capability_ref(&self) -> &CapabilityRef {
78        &self.reference
79    }
80
81    /// Split the spec into its reference and optional code-defined
82    /// implementation for host consumption.
83    pub fn into_parts(self) -> CapabilitySpecParts {
84        CapabilitySpecParts {
85            reference: self.reference,
86            #[cfg(feature = "definition")]
87            definition: self.definition,
88        }
89    }
90}
91
92/// The decomposed contents of a [`CapabilitySpec`].
93#[derive(Clone, Debug)]
94#[non_exhaustive]
95pub struct CapabilitySpecParts {
96    /// The reference activated for the agent.
97    pub reference: CapabilityRef,
98    /// The code-defined implementation to register, when present.
99    #[cfg(feature = "definition")]
100    pub definition: Option<crate::definition::Definition>,
101}
102
103impl From<CapabilityRef> for CapabilitySpec {
104    fn from(reference: CapabilityRef) -> Self {
105        Self::reference(reference)
106    }
107}
108
109impl IntoCapability for CapabilitySpec {
110    fn into_capability(self) -> CapabilitySpec {
111        self
112    }
113}
114
115impl IntoCapability for CapabilityRef {
116    fn into_capability(self) -> CapabilitySpec {
117        self.into()
118    }
119}
120
121impl IntoCapability for &str {
122    fn into_capability(self) -> CapabilitySpec {
123        CapabilityRef::new(self).into()
124    }
125}
126
127impl IntoCapability for String {
128    fn into_capability(self) -> CapabilitySpec {
129        CapabilityRef::new(self).into()
130    }
131}
132
133impl IntoCapability for &String {
134    fn into_capability(self) -> CapabilitySpec {
135        CapabilityRef::new(self.as_str()).into()
136    }
137}
138
139#[cfg(feature = "definition")]
140impl From<crate::definition::Definition> for CapabilitySpec {
141    fn from(definition: crate::definition::Definition) -> Self {
142        Self::definition(definition)
143    }
144}
145
146#[cfg(feature = "definition")]
147impl IntoCapability for crate::definition::Definition {
148    fn into_capability(self) -> CapabilitySpec {
149        self.into()
150    }
151}