| 1089 | /// (model, llm_client, session_store, …) |
| 1090 | #[pyo3(signature = (dir, workspace, options=None))] |
| 1091 | fn serve_agent_dir( |
| 1092 | &self, |
| 1093 | dir: String, |
| 1094 | workspace: String, |
| 1095 | options: Option<PySessionOptions>, |
| 1096 | ) -> PyResult<PyServeHandle> { |
| 1097 | let agent_dir = RustAgentDir::load(&dir) |
| 1098 | .map_err(|e| PyRuntimeError::new_err(format!("Failed to load agent dir: {e}")))?; |
| 1099 | let extra = match options { |
| 1100 | Some(o) => Some(build_rust_session_options(o)?), |
| 1101 | None => None, |
| 1102 | }; |
| 1103 | |
| 1104 | // The daemon runs until cancelled; spawn it on the shared runtime so the |
| 1105 | // call returns to Python immediately. The token, owned by the returned |
| 1106 | // ServeHandle, drives graceful shutdown. |
| 1107 | let cancel = CancellationToken::new(); |
| 1108 | let agent = self.inner.clone(); |
| 1109 | let handle_token = cancel.clone(); |
| 1110 | |
| 1111 | get_runtime().spawn(async move { |
| 1112 | // serve_agent_dir returns Result and never panics by construction; a |
| 1113 | // scheduling error is reported, not propagated (spawned task bodies |
| 1114 | // are not panic-safe). |
| 1115 | if let Err(e) = rust_serve_agent_dir(&agent, &agent_dir, workspace, extra, cancel).await |
| 1116 | { |
| 1117 | eprintln!("a3s-code: serve_agent_dir daemon exited with error: {e}"); |
| 1118 | } |
| 1119 | }); |
| 1120 | |
| 1121 | Ok(PyServeHandle { |
| 1122 | cancel: handle_token, |
| 1123 | }) |
| 1124 | } |
| 1125 | |
| 1126 | /// Re-fetch tool definitions from all connected global MCP servers and |
| 1127 | /// update the agent-level cache. |