1use std::{collections::HashMap, sync::Arc};
12
13use identity::IdentityStore;
14use observe::Observer;
15use tokio::sync::Mutex;
16
17pub mod error;
18pub mod graph;
19pub mod server;
20pub(crate) mod session;
21pub mod types;
22
23pub use error::TerminalError;
24pub use graph::{MirrorError, TerminalGraphHandles, TerminalGraphSink};
25pub use server::TerminalSvc;
26pub use types::{SessionId, SessionMeta, TermSize};
27
28use session::Session;
29
30pub const DEFAULT_PORT: u16 = 19793;
32
33pub mod pb {
35 tonic::include_proto!("brain.terminal.v1");
36}
37
38#[derive(Default)]
42pub struct SessionRegistry {
43 inner: Mutex<HashMap<SessionId, Arc<Session>>>,
44}
45
46impl SessionRegistry {
47 pub fn new() -> Self {
48 Self {
49 inner: Mutex::new(HashMap::new()),
50 }
51 }
52
53 pub async fn len(&self) -> usize {
54 self.inner.lock().await.len()
55 }
56
57 pub async fn is_empty(&self) -> bool {
58 self.inner.lock().await.is_empty()
59 }
60
61 pub async fn meta(&self, id: &SessionId) -> Option<SessionMeta> {
62 self.inner.lock().await.get(id).map(|s| s.meta.clone())
63 }
64
65 pub async fn list(&self) -> Vec<SessionMeta> {
66 self.inner
67 .lock()
68 .await
69 .values()
70 .map(|s| s.meta.clone())
71 .collect()
72 }
73
74 pub(crate) async fn get(&self, id: &SessionId) -> Option<Arc<Session>> {
75 self.inner.lock().await.get(id).cloned()
76 }
77
78 pub(crate) async fn insert(&self, session: Arc<Session>) {
79 let id = session.meta.session_id.clone();
80 self.inner.lock().await.insert(id, session);
81 }
82
83 pub(crate) async fn remove(&self, id: &SessionId) -> Option<Arc<Session>> {
84 self.inner.lock().await.remove(id)
85 }
86}
87
88#[derive(Clone)]
94pub struct TerminalAuth {
95 pub identity: Arc<dyn IdentityStore>,
96 pub api_keys: Arc<Vec<brain::ApiKeyConfig>>,
97}
98
99impl TerminalAuth {
100 pub fn new(identity: Arc<dyn IdentityStore>, api_keys: Vec<brain::ApiKeyConfig>) -> Self {
101 Self {
102 identity,
103 api_keys: Arc::new(api_keys),
104 }
105 }
106}
107
108#[derive(Clone)]
114pub struct TerminalBridge {
115 sessions: Arc<SessionRegistry>,
116 auth: Option<TerminalAuth>,
117 observer: Option<Arc<dyn Observer>>,
118 graph_sink: Option<Arc<dyn TerminalGraphSink>>,
119}
120
121impl TerminalBridge {
122 pub fn new() -> Self {
123 Self {
124 sessions: Arc::new(SessionRegistry::new()),
125 auth: None,
126 observer: None,
127 graph_sink: None,
128 }
129 }
130
131 pub fn sessions(&self) -> &Arc<SessionRegistry> {
132 &self.sessions
133 }
134
135 pub fn with_auth(mut self, auth: TerminalAuth) -> Self {
139 self.auth = Some(auth);
140 self
141 }
142
143 pub fn with_observer(mut self, observer: Arc<dyn Observer>) -> Self {
147 self.observer = Some(observer);
148 self
149 }
150
151 pub fn with_graph_sink(mut self, sink: Arc<dyn TerminalGraphSink>) -> Self {
155 self.graph_sink = Some(sink);
156 self
157 }
158
159 pub fn into_server(self) -> pb::terminal_session_server::TerminalSessionServer<TerminalSvc> {
162 pb::terminal_session_server::TerminalSessionServer::new(TerminalSvc::new(
163 self.sessions,
164 self.auth,
165 self.observer,
166 self.graph_sink,
167 ))
168 }
169
170 pub fn svc(&self) -> TerminalSvc {
174 TerminalSvc::new(
175 self.sessions.clone(),
176 self.auth.clone(),
177 self.observer.clone(),
178 self.graph_sink.clone(),
179 )
180 }
181}
182
183impl Default for TerminalBridge {
184 fn default() -> Self {
185 Self::new()
186 }
187}
188
189#[cfg(test)]
190mod tests {
191 use super::*;
192
193 #[tokio::test]
194 async fn registry_starts_empty() {
195 let bridge = TerminalBridge::new();
196 assert!(bridge.sessions().is_empty().await);
197 assert_eq!(bridge.sessions().len().await, 0);
198 }
199
200 #[test]
201 fn default_port_matches_spec() {
202 assert_eq!(DEFAULT_PORT, 19793);
203 }
204}