symposium_acp_proxy/
lib.rs1use anyhow::Result;
19use sacp::link::{AgentToClient, ConductorToProxy, ProxyToConductor};
20use sacp::{Component, DynComponent};
21use sacp_conductor::{Conductor, McpBridgeMode};
22use std::path::PathBuf;
23
24struct SymposiumConfig {
26 ferris: Option<symposium_ferris::Ferris>,
27 cargo: bool,
28 sparkle: bool,
29 trace_dir: Option<PathBuf>,
30}
31
32impl SymposiumConfig {
33 fn new() -> Self {
34 SymposiumConfig {
35 sparkle: true,
36 ferris: Some(symposium_ferris::Ferris::default()),
37 cargo: true,
38 trace_dir: None,
39 }
40 }
41}
42
43pub struct Symposium {
48 config: SymposiumConfig,
49}
50
51impl Symposium {
52 pub fn new() -> Self {
53 Symposium {
54 config: SymposiumConfig::new(),
55 }
56 }
57
58 pub fn sparkle(mut self, enable: bool) -> Self {
59 self.config.sparkle = enable;
60 self
61 }
62
63 pub fn ferris(mut self, config: Option<symposium_ferris::Ferris>) -> Self {
65 self.config.ferris = config;
66 self
67 }
68
69 pub fn cargo(mut self, enable: bool) -> Self {
71 self.config.cargo = enable;
72 self
73 }
74
75 pub fn trace_dir(mut self, dir: impl Into<PathBuf>) -> Self {
78 self.config.trace_dir = Some(dir.into());
79 self
80 }
81
82 pub fn with_agent(self, agent: impl Component<AgentToClient>) -> SymposiumAgent {
84 let Symposium { config } = self;
85 SymposiumAgent::new(config, agent)
86 }
87}
88
89impl Component<ProxyToConductor> for Symposium {
90 async fn serve(self, client: impl Component<ConductorToProxy>) -> Result<(), sacp::Error> {
91 tracing::debug!("Symposium::serve starting (proxy mode)");
92 let Self { config } = self;
93
94 let ferris = config.ferris;
95 let cargo = config.cargo;
96 let sparkle = config.sparkle;
97 let trace_dir = config.trace_dir;
98
99 tracing::debug!("Creating conductor (proxy mode)");
100 let mut conductor = Conductor::new_proxy(
101 "symposium",
102 move |init_req| async move {
103 tracing::info!("Building proxy chain based on capabilities");
104
105 let mut proxies: Vec<DynComponent<ProxyToConductor>> = vec![];
106
107 if let Some(ferris_config) = ferris {
108 proxies.push(DynComponent::new(symposium_ferris::FerrisComponent::new(
109 ferris_config,
110 )));
111 }
112
113 if cargo {
114 proxies.push(DynComponent::new(symposium_cargo::CargoProxy));
115 }
116
117 if sparkle {
118 proxies.push(DynComponent::new(sparkle::SparkleComponent::new()));
119 }
120
121 Ok((init_req, proxies))
122 },
123 McpBridgeMode::default(),
124 );
125
126 if let Some(dir) = trace_dir {
128 std::fs::create_dir_all(&dir).map_err(sacp::Error::into_internal_error)?;
129 let timestamp = chrono::Utc::now().format("%Y%m%d-%H%M%S");
130 let trace_path = dir.join(format!("{}.jsons", timestamp));
131 conductor = conductor
132 .trace_to_path(&trace_path)
133 .map_err(sacp::Error::into_internal_error)?;
134 tracing::info!("Tracing to {}", trace_path.display());
135 }
136
137 tracing::debug!("Starting conductor.run()");
138 conductor.run(client).await
139 }
140}
141
142pub struct SymposiumAgent {
147 config: SymposiumConfig,
148 agent: DynComponent<AgentToClient>,
149}
150
151impl SymposiumAgent {
152 fn new<C: Component<AgentToClient>>(config: SymposiumConfig, agent: C) -> Self {
153 SymposiumAgent {
154 config,
155 agent: DynComponent::new(agent),
156 }
157 }
158}
159
160impl Component<AgentToClient> for SymposiumAgent {
161 async fn serve(
162 self,
163 client: impl Component<sacp::link::ClientToAgent>,
164 ) -> Result<(), sacp::Error> {
165 tracing::debug!("SymposiumAgent::serve starting (agent mode)");
166 let Self { config, agent } = self;
167
168 let ferris = config.ferris;
169 let cargo = config.cargo;
170 let sparkle = config.sparkle;
171 let trace_dir = config.trace_dir;
172
173 tracing::debug!("Creating conductor (agent mode)");
174 let mut conductor = Conductor::new_agent(
175 "symposium",
176 move |init_req| async move {
177 tracing::info!("Building proxy chain based on capabilities");
178
179 let mut proxies: Vec<DynComponent<ProxyToConductor>> = vec![];
180
181 if let Some(ferris_config) = ferris {
182 proxies.push(DynComponent::new(symposium_ferris::FerrisComponent::new(
183 ferris_config,
184 )));
185 }
186
187 if cargo {
188 proxies.push(DynComponent::new(symposium_cargo::CargoProxy));
189 }
190
191 if sparkle {
192 proxies.push(DynComponent::new(sparkle::SparkleComponent::new()));
193 }
194
195 Ok((init_req, proxies, agent))
196 },
197 McpBridgeMode::default(),
198 );
199
200 if let Some(dir) = trace_dir {
202 std::fs::create_dir_all(&dir).map_err(sacp::Error::into_internal_error)?;
203 let timestamp = chrono::Utc::now().format("%Y%m%d-%H%M%S");
204 let trace_path = dir.join(format!("{}.jsons", timestamp));
205 conductor = conductor
206 .trace_to_path(&trace_path)
207 .map_err(sacp::Error::into_internal_error)?;
208 tracing::info!("Tracing to {}", trace_path.display());
209 }
210
211 tracing::debug!("Starting conductor.run()");
212 conductor.run(client).await
213 }
214}