systemprompt_models/bridge/
instance.rs1use serde::{Deserialize, Serialize};
15
16use crate::bridge::host::HostKind;
17
18pub const DISCOVERY_CONTRACT: &str = "1";
19
20#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
22pub struct BridgeDiscovery {
23 pub contract: String,
24 pub device_link_path: String,
25 pub identity_path: String,
26 #[serde(default)]
27 pub branding: InstanceBranding,
28 #[serde(default)]
29 pub enabled_hosts: Vec<HostKind>,
30}
31
32#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
34pub struct InstanceBranding {
35 #[serde(default, skip_serializing_if = "Option::is_none")]
36 pub display_name: Option<String>,
37 #[serde(default, skip_serializing_if = "Option::is_none")]
38 pub logo_url: Option<String>,
39 #[serde(default, skip_serializing_if = "Option::is_none")]
40 pub theme_css_url: Option<String>,
41 #[serde(default, skip_serializing_if = "Option::is_none")]
42 pub sign_in_hint: Option<String>,
43 #[serde(default, skip_serializing_if = "Option::is_none")]
44 pub docs_url: Option<String>,
45 #[serde(default, skip_serializing_if = "Option::is_none")]
46 pub contact_email: Option<String>,
47}
48
49impl InstanceBranding {
50 #[must_use]
51 pub fn is_empty(&self) -> bool {
52 *self == Self::default()
53 }
54}
55
56#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
59pub struct BridgeVersionWindow {
60 #[serde(default, skip_serializing_if = "Option::is_none")]
61 pub min: Option<semver::Version>,
62 #[serde(default, skip_serializing_if = "Option::is_none")]
63 pub max: Option<semver::Version>,
64 #[serde(default)]
65 pub enforce_max: bool,
66}
67
68#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
69#[serde(rename_all = "snake_case")]
70pub enum WindowStatus {
71 Below,
72 Within,
73 Above,
74 Unknown,
75}
76
77impl BridgeVersionWindow {
78 #[must_use]
79 pub const fn is_open(&self) -> bool {
80 self.min.is_none() && self.max.is_none()
81 }
82
83 #[must_use]
84 pub fn admits(&self, version: &semver::Version) -> bool {
85 self.min
86 .as_ref()
87 .is_none_or(|min| version.cmp_precedence(min).is_ge())
88 && self
89 .max
90 .as_ref()
91 .is_none_or(|max| version.cmp_precedence(max).is_le())
92 }
93
94 #[must_use]
95 pub fn status(&self, reported: &str) -> WindowStatus {
96 let Ok(version) = semver::Version::parse(reported) else {
97 return WindowStatus::Unknown;
98 };
99 if self
100 .min
101 .as_ref()
102 .is_some_and(|min| version.cmp_precedence(min).is_lt())
103 {
104 WindowStatus::Below
105 } else if self
106 .max
107 .as_ref()
108 .is_some_and(|max| version.cmp_precedence(max).is_gt())
109 {
110 WindowStatus::Above
111 } else {
112 WindowStatus::Within
113 }
114 }
115
116 #[must_use]
117 pub fn intersect(&self, other: &Self) -> Self {
118 let min = match (&self.min, &other.min) {
119 (Some(a), Some(b)) => Some(if a.cmp_precedence(b).is_ge() {
120 a.clone()
121 } else {
122 b.clone()
123 }),
124 (a, b) => a.clone().or_else(|| b.clone()),
125 };
126 let max = match (&self.max, &other.max) {
127 (Some(a), Some(b)) => Some(if a.cmp_precedence(b).is_le() {
128 a.clone()
129 } else {
130 b.clone()
131 }),
132 (a, b) => a.clone().or_else(|| b.clone()),
133 };
134 Self {
135 min,
136 max,
137 enforce_max: self.enforce_max || other.enforce_max,
138 }
139 }
140
141 #[must_use]
142 pub fn is_empty(&self) -> bool {
143 matches!((&self.min, &self.max), (Some(min), Some(max)) if min.cmp_precedence(max).is_gt())
144 }
145}
146
147#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
150#[serde(rename_all = "snake_case")]
151pub enum BridgeState {
152 Active,
153 Disabled,
154}
155
156impl BridgeState {
157 #[must_use]
158 pub const fn as_str(self) -> &'static str {
159 match self {
160 Self::Active => "active",
161 Self::Disabled => "disabled",
162 }
163 }
164}
165
166impl WindowStatus {
167 #[must_use]
168 pub const fn as_str(self) -> &'static str {
169 match self {
170 Self::Below => "below",
171 Self::Within => "within",
172 Self::Above => "above",
173 Self::Unknown => "unknown",
174 }
175 }
176}