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}