@@ -116,12 +116,15 @@ use futures::{
116116 SinkExt , StreamExt ,
117117 channel:: mpsc:: { self } ,
118118} ;
119- use sacp:: role:: { ConductorToAgent , ConductorToClient , ConductorToProxy } ;
120119use sacp:: schema:: {
121120 McpConnectRequest , McpConnectResponse , McpDisconnectNotification , McpOverAcpMessage ,
122121 SuccessorMessage ,
123122} ;
124123use sacp:: { Agent , Client , Component , Error , JrMessage } ;
124+ use sacp:: {
125+ ChainResponder , JrResponder , NullResponder ,
126+ role:: { ConductorToAgent , ConductorToClient , ConductorToProxy } ,
127+ } ;
125128use sacp:: {
126129 HasDefaultEndpoint , JrConnectionBuilder , JrConnectionCx , JrNotification , JrRequest ,
127130 JrRequestCx , JrResponse , JrRole , MessageCx , UntypedMessage ,
@@ -156,12 +159,12 @@ pub struct Conductor {
156159
157160impl Conductor {
158161 pub fn new (
159- name : String ,
162+ name : impl ToString ,
160163 component_list : impl ComponentList + ' static ,
161164 mcp_bridge_mode : crate :: McpBridgeMode ,
162165 ) -> Self {
163166 Conductor {
164- name,
167+ name : name . to_string ( ) ,
165168 component_list : Box :: new ( component_list) ,
166169 mcp_bridge_mode,
167170 trace_writer : None ,
@@ -193,11 +196,15 @@ impl Conductor {
193196
194197 pub fn into_connection_builder (
195198 self ,
196- ) -> JrConnectionBuilder < ConductorMessageHandler , impl sacp:: JrResponder < ConductorToClient > >
197- {
198- let ( mut conductor_tx, mut conductor_rx) = mpsc:: channel ( 128 /* chosen arbitrarily */ ) ;
199+ ) -> JrConnectionBuilder <
200+ ConductorMessageHandler ,
201+ ChainResponder < NullResponder , ConductorResponder > ,
202+ > {
203+ let ( conductor_tx, conductor_rx) = mpsc:: channel ( 128 /* chosen arbitrarily */ ) ;
199204
200- let mut state = ConductorHandlerState {
205+ let responder = ConductorResponder {
206+ conductor_rx,
207+ conductor_tx : conductor_tx. clone ( ) ,
201208 component_list : Some ( self . component_list ) ,
202209 bridge_listeners : Default :: default ( ) ,
203210 bridge_connections : Default :: default ( ) ,
@@ -208,23 +215,9 @@ impl Conductor {
208215 pending_requests : Default :: default ( ) ,
209216 } ;
210217
211- JrConnectionBuilder :: new_with ( ConductorMessageHandler {
212- conductor_tx : conductor_tx. clone ( ) ,
213- } )
214- . name ( self . name )
215- . with_spawned ( async move |cx| {
216- // Components are now spawned lazily in forward_initialize_request
217- // when the first Initialize request is received.
218-
219- // This is the "central actor" of the conductor. Most other things forward messages
220- // via `conductor_tx` into this loop. This lets us serialize the conductor's activity.
221- while let Some ( message) = conductor_rx. next ( ) . await {
222- state
223- . handle_conductor_message ( & cx, message, & mut conductor_tx)
224- . await ?;
225- }
226- Ok ( ( ) )
227- } )
218+ JrConnectionBuilder :: new_with ( ConductorMessageHandler { conductor_tx } )
219+ . name ( self . name )
220+ . with_responder ( responder)
228221 }
229222
230223 /// Convenience method to run the conductor with a transport.
@@ -254,43 +247,6 @@ pub struct ConductorMessageHandler {
254247 conductor_tx : mpsc:: Sender < ConductorMessage > ,
255248}
256249
257- /// The conductor manages the proxy chain lifecycle and message routing.
258- ///
259- /// It maintains connections to all components in the chain and routes messages
260- /// bidirectionally between the editor, components, and agent.
261- ///
262- struct ConductorHandlerState {
263- /// Manages the TCP listeners for MCP connections that will be proxied over ACP.
264- bridge_listeners : McpBridgeListeners ,
265-
266- /// Manages active connections to MCP clients.
267- bridge_connections : HashMap < String , McpBridgeConnection > ,
268-
269- /// The component list for lazy initialization.
270- /// Set to None after components are instantiated.
271- component_list : Option < Box < dyn ComponentList > > ,
272-
273- /// The chain of proxies before the agent (if any).
274- ///
275- /// Populated lazily when the first Initialize request is received.
276- proxies : Vec < JrConnectionCx < ConductorToProxy > > ,
277-
278- /// If the conductor is operating in agent mode, this will be the agent.
279- /// If the conductor is operating in proxy mode, this will be None.
280- ///
281- /// Populated lazily when the first Initialize request is received.
282- agent : Option < JrConnectionCx < ConductorToAgent > > ,
283-
284- /// Mode for the MCP bridge (determines how to spawn bridge processes).
285- mcp_bridge_mode : crate :: McpBridgeMode ,
286-
287- /// Optional trace writer for sequence diagram visualization.
288- trace_writer : Option < crate :: trace:: TraceWriter > ,
289-
290- /// Tracks pending requests for response tracing: id -> (from, to)
291- pending_requests : HashMap < String , ( String , String ) > ,
292- }
293-
294250impl JrMessageHandler for ConductorMessageHandler {
295251 type Role = ConductorToClient ;
296252
@@ -336,7 +292,65 @@ impl JrMessageHandler for ConductorMessageHandler {
336292 }
337293}
338294
339- impl ConductorHandlerState {
295+ /// The conductor manages the proxy chain lifecycle and message routing.
296+ ///
297+ /// It maintains connections to all components in the chain and routes messages
298+ /// bidirectionally between the editor, components, and agent.
299+ ///
300+ pub struct ConductorResponder {
301+ conductor_rx : mpsc:: Receiver < ConductorMessage > ,
302+
303+ conductor_tx : mpsc:: Sender < ConductorMessage > ,
304+
305+ /// Manages the TCP listeners for MCP connections that will be proxied over ACP.
306+ bridge_listeners : McpBridgeListeners ,
307+
308+ /// Manages active connections to MCP clients.
309+ bridge_connections : HashMap < String , McpBridgeConnection > ,
310+
311+ /// The component list for lazy initialization.
312+ /// Set to None after components are instantiated.
313+ component_list : Option < Box < dyn ComponentList > > ,
314+
315+ /// The chain of proxies before the agent (if any).
316+ ///
317+ /// Populated lazily when the first Initialize request is received.
318+ proxies : Vec < JrConnectionCx < ConductorToProxy > > ,
319+
320+ /// If the conductor is operating in agent mode, this will be the agent.
321+ /// If the conductor is operating in proxy mode, this will be None.
322+ ///
323+ /// Populated lazily when the first Initialize request is received.
324+ agent : Option < JrConnectionCx < ConductorToAgent > > ,
325+
326+ /// Mode for the MCP bridge (determines how to spawn bridge processes).
327+ mcp_bridge_mode : crate :: McpBridgeMode ,
328+
329+ /// Optional trace writer for sequence diagram visualization.
330+ trace_writer : Option < crate :: trace:: TraceWriter > ,
331+
332+ /// Tracks pending requests for response tracing: id -> (from, to)
333+ pending_requests : HashMap < String , ( String , String ) > ,
334+ }
335+
336+ impl JrResponder < ConductorToClient > for ConductorResponder {
337+ async fn run ( mut self , cx : JrConnectionCx < ConductorToClient > ) -> Result < ( ) , sacp:: Error > {
338+ // Components are now spawned lazily in forward_initialize_request
339+ // when the first Initialize request is received.
340+
341+ let mut conductor_tx = self . conductor_tx . clone ( ) ;
342+
343+ // This is the "central actor" of the conductor. Most other things forward messages
344+ // via `conductor_tx` into this loop. This lets us serialize the conductor's activity.
345+ while let Some ( message) = self . conductor_rx . next ( ) . await {
346+ self . handle_conductor_message ( & cx, message, & mut conductor_tx)
347+ . await ?;
348+ }
349+ Ok ( ( ) )
350+ }
351+ }
352+
353+ impl ConductorResponder {
340354 /// Convert a component index to a trace-friendly name.
341355 fn component_name ( & self , index : usize ) -> String {
342356 if self . is_agent_component ( index) {
0 commit comments