Skip to main content

starweaver_runtime/capability/
spec.rs

1//! Capability identifiers, specs, and retry boundary metadata.
2
3use serde::{Deserialize, Serialize};
4use starweaver_core::Metadata;
5
6/// Runtime retry boundary observed by capability hooks.
7#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
8#[serde(rename_all = "snake_case")]
9pub enum RetryEventKind {
10    /// Output validation or output function validation requested another model turn.
11    Output,
12    /// A tool requested semantic retry through structured metadata.
13    Tool,
14}
15
16/// Stable capability identifier.
17#[derive(Clone, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
18pub struct CapabilityId(String);
19
20impl CapabilityId {
21    /// Build an identifier from a string.
22    #[must_use]
23    pub fn from_string(value: impl Into<String>) -> Self {
24        Self(value.into())
25    }
26
27    /// Return the identifier as a string slice.
28    #[must_use]
29    pub fn as_str(&self) -> &str {
30        &self.0
31    }
32}
33
34impl From<&str> for CapabilityId {
35    fn from(value: &str) -> Self {
36        Self::from_string(value)
37    }
38}
39
40impl From<String> for CapabilityId {
41    fn from(value: String) -> Self {
42        Self::from_string(value)
43    }
44}
45
46/// Capability ordering constraints.
47#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
48pub struct CapabilityOrdering {
49    /// Capability ids that must run before this capability.
50    #[serde(default, skip_serializing_if = "Vec::is_empty")]
51    pub after: Vec<CapabilityId>,
52    /// Capability ids that must run after this capability.
53    #[serde(default, skip_serializing_if = "Vec::is_empty")]
54    pub before: Vec<CapabilityId>,
55}
56
57impl CapabilityOrdering {
58    /// Require this capability to run after another capability.
59    #[must_use]
60    pub fn after(mut self, id: impl Into<CapabilityId>) -> Self {
61        self.after.push(id.into());
62        self
63    }
64
65    /// Require this capability to run before another capability.
66    #[must_use]
67    pub fn before(mut self, id: impl Into<CapabilityId>) -> Self {
68        self.before.push(id.into());
69        self
70    }
71}
72
73/// Stable capability specification used for ordering and reconstruction evidence.
74#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
75pub struct CapabilitySpec {
76    /// Stable capability id.
77    pub id: CapabilityId,
78    /// Human-readable description.
79    #[serde(default, skip_serializing_if = "Option::is_none")]
80    pub description: Option<String>,
81    /// Ordering constraints.
82    #[serde(default)]
83    pub ordering: CapabilityOrdering,
84    /// Whether the capability can be loaded on demand by a host registry.
85    #[serde(default)]
86    pub on_demand: bool,
87    /// Additional metadata.
88    #[serde(default, skip_serializing_if = "Metadata::is_empty")]
89    pub metadata: Metadata,
90}
91
92impl CapabilitySpec {
93    /// Build a capability spec from an id.
94    #[must_use]
95    pub fn new(id: impl Into<CapabilityId>) -> Self {
96        Self {
97            id: id.into(),
98            description: None,
99            ordering: CapabilityOrdering::default(),
100            on_demand: false,
101            metadata: Metadata::default(),
102        }
103    }
104
105    /// Attach a description.
106    #[must_use]
107    pub fn with_description(mut self, description: impl Into<String>) -> Self {
108        self.description = Some(description.into());
109        self
110    }
111
112    /// Attach ordering constraints.
113    #[must_use]
114    pub fn with_ordering(mut self, ordering: CapabilityOrdering) -> Self {
115        self.ordering = ordering;
116        self
117    }
118
119    /// Mark the capability as on-demand loadable.
120    #[must_use]
121    pub const fn with_on_demand(mut self, on_demand: bool) -> Self {
122        self.on_demand = on_demand;
123        self
124    }
125
126    /// Attach metadata.
127    #[must_use]
128    pub fn with_metadata(mut self, metadata: Metadata) -> Self {
129        self.metadata = metadata;
130        self
131    }
132}