Skip to main content

starweaver_context/
config.rs

1//! Model, tool, and security configuration carried by agent context.
2
3mod model;
4mod tool;
5
6use serde::{Deserialize, Serialize};
7
8pub use model::{ModelCapability, ModelConfig};
9pub use tool::{ToolAvailabilityPolicy, ToolConfig};
10
11/// Fixed-point ratio stored as parts per thousand.
12#[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
13pub struct PerThousandRatio {
14    #[serde(alias = "parts_per_thousand")]
15    per_thousand: u16,
16}
17
18impl PerThousandRatio {
19    /// Create a ratio from parts per thousand.
20    #[must_use]
21    pub const fn from_per_thousand(per_thousand: u16) -> Self {
22        Self { per_thousand }
23    }
24
25    /// Return the ratio as parts per thousand.
26    #[must_use]
27    pub const fn per_thousand(self) -> u16 {
28        self.per_thousand
29    }
30
31    /// Return the ratio as a floating point value for calculations.
32    #[must_use]
33    pub fn as_fraction(self) -> f64 {
34        f64::from(self.per_thousand) / 1000.0
35    }
36}
37
38impl Default for PerThousandRatio {
39    fn default() -> Self {
40        Self::from_per_thousand(1000)
41    }
42}
43
44#[cfg(test)]
45mod tests {
46    use super::PerThousandRatio;
47
48    #[test]
49    fn legacy_parts_per_thousand_deserializes_to_the_canonical_ratio()
50    -> Result<(), serde_json::Error> {
51        let ratio = serde_json::from_str::<PerThousandRatio>(r#"{"parts_per_thousand": 900}"#)?;
52
53        assert_eq!(ratio.per_thousand(), 900);
54        assert_eq!(
55            serde_json::to_value(ratio)?["per_thousand"],
56            serde_json::json!(900)
57        );
58        Ok(())
59    }
60}
61
62/// Shell review action for commands that require policy intervention.
63#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
64#[serde(rename_all = "snake_case")]
65pub enum ShellReviewAction {
66    /// Defer command execution for external approval.
67    #[default]
68    Defer,
69    /// Deny commands that need approval.
70    Deny,
71}
72
73/// Shell review risk threshold.
74#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
75#[serde(rename_all = "snake_case")]
76pub enum ShellReviewRiskLevel {
77    /// Low risk.
78    Low,
79    /// Medium risk.
80    Medium,
81    /// High risk.
82    #[default]
83    High,
84    /// Extra high risk.
85    ExtraHigh,
86}
87
88/// Shell command safety review configuration.
89#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
90pub struct ShellReviewConfig {
91    /// Whether shell review is enabled.
92    #[serde(default)]
93    pub enabled: bool,
94    /// Model identifier used for shell review.
95    #[serde(default, skip_serializing_if = "Option::is_none")]
96    pub model: Option<String>,
97    /// Action when a command needs approval.
98    #[serde(default)]
99    pub on_needs_approval: ShellReviewAction,
100    /// Risk level where intervention begins.
101    #[serde(default)]
102    pub risk_threshold: ShellReviewRiskLevel,
103    /// Optional custom system prompt.
104    #[serde(default, skip_serializing_if = "Option::is_none")]
105    pub system_prompt: Option<String>,
106}
107
108/// Security-related runtime configuration.
109#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
110pub struct SecurityConfig {
111    /// Optional shell command safety review configuration.
112    #[serde(default, skip_serializing_if = "Option::is_none")]
113    pub shell_review: Option<ShellReviewConfig>,
114}
115
116impl SecurityConfig {
117    /// Return whether no security config is active.
118    #[must_use]
119    pub fn is_default(&self) -> bool {
120        self == &Self::default()
121    }
122}