1use std::collections::HashMap;
7use std::sync::Arc;
8
9use tokio::sync::RwLock;
10
11use synwire_core::agents::plugin::Plugin;
12use synwire_core::agents::signal::{Action, SignalKind, SignalRoute};
13use synwire_core::tools::Tool;
14
15use crate::client::{DapClient, DapSessionState};
16use crate::config::{DapAdapterConfig, DapPluginConfig};
17use crate::error::DapError;
18use crate::registry::DebugAdapterRegistry;
19use crate::tools::create_tools;
20use crate::transport::EventHandler;
21
22pub struct DapPlugin {
28 clients: Arc<RwLock<HashMap<String, Arc<DapClient>>>>,
29 registry: Arc<DebugAdapterRegistry>,
30 config: DapPluginConfig,
31 tools: Vec<Arc<dyn Tool>>,
32}
33
34impl DapPlugin {
35 #[must_use]
40 pub fn new(config: DapPluginConfig) -> Self {
41 Self {
42 clients: Arc::new(RwLock::new(HashMap::new())),
43 registry: Arc::new(DebugAdapterRegistry::with_builtins()),
44 config,
45 tools: Vec::new(),
46 }
47 }
48
49 #[must_use]
51 pub fn with_registry(config: DapPluginConfig, registry: DebugAdapterRegistry) -> Self {
52 Self {
53 clients: Arc::new(RwLock::new(HashMap::new())),
54 registry: Arc::new(registry),
55 config,
56 tools: Vec::new(),
57 }
58 }
59
60 pub async fn start_adapter(
69 &mut self,
70 session_id: &str,
71 adapter_config: &DapAdapterConfig,
72 ) -> Result<Arc<DapClient>, DapError> {
73 {
75 let clients = self.clients.read().await;
76 if let Some(existing) = clients.get(session_id) {
77 let _ = existing.disconnect().await;
78 }
79 }
80
81 let clients_ref = Arc::clone(&self.clients);
82 let session_id_owned = session_id.to_string();
83
84 let event_handler: EventHandler = Arc::new(move |event: serde_json::Value| {
86 let event_name = event
87 .get("event")
88 .and_then(serde_json::Value::as_str)
89 .unwrap_or("")
90 .to_string();
91
92 let clients_ref = Arc::clone(&clients_ref);
93 let session_id_owned = session_id_owned.clone();
94
95 drop(tokio::spawn(async move {
97 let clients = clients_ref.read().await;
98 if let Some(client) = clients.get(&session_id_owned) {
99 match event_name.as_str() {
100 "stopped" => {
101 client.set_status(DapSessionState::Stopped).await;
102 tracing::debug!(
103 session_id = %session_id_owned,
104 "Debug session stopped (breakpoint/step)"
105 );
106 }
107 "terminated" => {
108 client.set_status(DapSessionState::Terminated).await;
109 tracing::debug!(
110 session_id = %session_id_owned,
111 "Debug session terminated"
112 );
113 }
114 "continued" => {
115 client.set_status(DapSessionState::Running).await;
116 }
117 _ => {}
118 }
119 }
120 }));
121 });
122
123 let client = DapClient::start(adapter_config, event_handler)?;
124 client.initialize().await?;
125
126 let client = Arc::new(client);
127
128 {
130 let mut clients = self.clients.write().await;
131 let _ = clients.insert(session_id.to_string(), Arc::clone(&client));
132 }
133
134 let new_tools = create_tools(Arc::clone(&client))
136 .map_err(|e| DapError::InitializationFailed(format!("failed to create tools: {e}")))?;
137 self.tools = new_tools;
138
139 tracing::debug!(session_id, "DAP adapter started and initialized");
140
141 Ok(client)
142 }
143
144 pub async fn client(&self, session_id: &str) -> Option<Arc<DapClient>> {
146 let clients = self.clients.read().await;
147 clients.get(session_id).cloned()
148 }
149
150 pub async fn disconnect_all(&self) {
152 let clients = self.clients.read().await;
153 for (session_id, client) in clients.iter() {
154 if let Err(e) = client.disconnect().await {
155 tracing::warn!(
156 session_id,
157 error = %e,
158 "Failed to disconnect DAP session"
159 );
160 }
161 }
162 }
163
164 #[must_use]
166 pub fn registry(&self) -> &DebugAdapterRegistry {
167 &self.registry
168 }
169
170 #[must_use]
172 pub const fn config(&self) -> &DapPluginConfig {
173 &self.config
174 }
175}
176
177impl Plugin for DapPlugin {
178 #[allow(clippy::unnecessary_literal_bound)] fn name(&self) -> &str {
180 "dap"
181 }
182
183 fn tools(&self) -> Vec<Arc<dyn Tool>> {
184 self.tools.clone()
185 }
186
187 fn signal_routes(&self) -> Vec<SignalRoute> {
188 vec![
189 SignalRoute::new(
190 SignalKind::Custom("dap_stopped".into()),
191 Action::Continue,
192 0,
193 ),
194 SignalRoute::new(
195 SignalKind::Custom("dap_terminated".into()),
196 Action::Continue,
197 0,
198 ),
199 ]
200 }
201}