bamboo_server/app_state/
provider_api.rs1use super::*;
2
3use crate::tools::ToolSurface;
4
5impl AppState {
6 pub async fn get_provider(&self) -> Arc<dyn LLMProvider> {
32 self.provider_handle.clone()
35 }
36
37 pub fn get_provider_for_model_ref(
42 &self,
43 target: &bamboo_domain::ProviderModelRef,
44 ) -> Result<Arc<dyn LLMProvider>, AppError> {
45 self.provider_router
46 .route(target)
47 .map_err(|e| AppError::BadRequest(e.to_string()))
48 }
49
50 pub async fn get_provider_for_endpoint(
55 &self,
56 provider_name: &str,
57 ) -> Result<Arc<dyn LLMProvider>, AppError> {
58 let use_registry = {
59 let config = self.config.read().await;
60 config.features.provider_model_ref
61 };
62
63 if use_registry {
64 self.provider_registry.get(provider_name).ok_or_else(|| {
65 AppError::InternalError(anyhow::anyhow!(
66 "Provider '{}' not found in registry",
67 provider_name
68 ))
69 })
70 } else {
71 Ok(self.get_provider().await)
72 }
73 }
74
75 pub async fn shutdown(&self) {
84 tracing::info!("Shutting down MCP servers...");
85 self.mcp_proxy_shutdown.cancel();
88 self.mcp_manager.shutdown_all().await;
89 tracing::info!("MCP servers shut down complete");
90 }
91
92 pub fn tools_for(
97 &self,
98 surface: ToolSurface,
99 ) -> Arc<dyn bamboo_agent_core::tools::ToolExecutor> {
100 self.tool_factory.get(surface)
101 }
102
103 pub fn get_all_tool_schemas(&self) -> Vec<bamboo_agent_core::tools::ToolSchema> {
112 self.tool_factory.get(ToolSurface::Root).list_tools()
113 }
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119
120 async fn make_state() -> (tempfile::TempDir, AppState) {
122 let temp_dir = tempfile::tempdir().expect("temp dir");
123 let state = AppState::new(temp_dir.path().to_path_buf())
124 .await
125 .expect("app state");
126 (temp_dir, state)
127 }
128
129 #[tokio::test]
132 async fn get_provider_returns_a_provider() {
133 let (_temp, state) = make_state().await;
134 let provider = state.get_provider().await;
135 let models = provider.list_models().await;
136 assert!(
139 models.is_err(),
140 "Default provider should be UnconfiguredProvider"
141 );
142 }
143
144 #[tokio::test]
147 async fn endpoint_flag_off_returns_default_provider() {
148 let (_temp, state) = make_state().await;
149 {
151 let config = state.config.read().await;
152 assert!(!config.features.provider_model_ref);
153 }
154
155 let result = state.get_provider_for_endpoint("openai").await;
156 assert!(
157 result.is_ok(),
158 "Flag OFF should always return default provider"
159 );
160 }
161
162 #[tokio::test]
163 async fn endpoint_flag_on_unknown_provider_returns_error() {
164 let (_temp, state) = make_state().await;
165 {
166 let mut config = state.config.write().await;
167 config.features.provider_model_ref = true;
168 }
169
170 let result = state.get_provider_for_endpoint("nonexistent").await;
171 assert!(result.is_err(), "Flag ON with unknown provider should fail");
172 }
173
174 #[tokio::test]
175 async fn endpoint_flag_on_copilot_returns_provider() {
176 let (_temp, state) = make_state().await;
177 {
178 let mut config = state.config.write().await;
179 config.features.provider_model_ref = true;
180 }
181
182 let result = state.get_provider_for_endpoint("copilot").await;
184 assert!(result.is_ok(), "Flag ON with copilot should succeed");
185 }
186
187 #[tokio::test]
190 async fn model_ref_unknown_provider_returns_error() {
191 let (_temp, state) = make_state().await;
192 let target = bamboo_domain::ProviderModelRef::new("nonexistent", "some-model");
193 let result = state.get_provider_for_model_ref(&target);
194 assert!(result.is_err());
195 }
196
197 #[tokio::test]
198 async fn model_ref_copilot_returns_provider() {
199 let (_temp, state) = make_state().await;
200 let target = bamboo_domain::ProviderModelRef::new("copilot", "gpt-4o");
201 let result = state.get_provider_for_model_ref(&target);
202 assert!(result.is_ok(), "copilot provider should be routable");
203 }
204
205 #[tokio::test]
208 async fn tools_for_root_returns_tool_executor() {
209 let (_temp, state) = make_state().await;
210 let executor = state.tools_for(ToolSurface::Root);
211 let schemas = executor.list_tools();
212 assert!(!schemas.is_empty(), "Root tools should not be empty");
213 }
214
215 #[tokio::test]
218 async fn get_all_tool_schemas_includes_core_tools() {
219 let (_temp, state) = make_state().await;
220 let schemas = state.get_all_tool_schemas();
221 let names: std::collections::HashSet<&str> =
222 schemas.iter().map(|s| s.function.name.as_str()).collect();
223 assert!(names.contains("Task"));
224 assert!(names.contains("SubAgent"));
225 }
226}