bamboo-llm 2026.10.21

LLM provider layer (providers, protocol adapters, streaming, routing, model catalog) for the Bamboo agent framework
Documentation
//! Routes a [`ProviderModelRef`] to the correct [`LLMProvider`] from the registry.

use std::sync::Arc;

use bamboo_domain::ProviderModelRef;

use crate::provider::{LLMError, LLMProvider};
use crate::provider_registry::{ManagedProviderOrigin, ProviderRegistry};

/// Given a [`ProviderModelRef`], resolve the correct [`LLMProvider`].
pub struct ProviderModelRouter {
    registry: Arc<ProviderRegistry>,
}

impl ProviderModelRouter {
    pub fn new(registry: Arc<ProviderRegistry>) -> Self {
        Self { registry }
    }

    /// Resolve the provider for a given model reference.
    pub fn route(&self, target: &ProviderModelRef) -> Result<Arc<dyn LLMProvider>, LLMError> {
        self.registry.provider_for_model(target)
    }

    /// Route through this router's actual registry while checking the caller's
    /// live configuration against a managed provider generation.
    #[doc(hidden)]
    pub fn route_with_config(
        &self,
        config: &bamboo_config::Config,
        target: &ProviderModelRef,
    ) -> Result<Arc<dyn LLMProvider>, LLMError> {
        self.registry.provider_for_configured_model(config, target)
    }

    /// Select the same route as `route` and retain its managed source registry.
    /// Unmanaged selections retain the embedder's existing behavior.
    #[doc(hidden)]
    pub fn route_with_origin(
        &self,
        target: &ProviderModelRef,
    ) -> Result<(Arc<dyn LLMProvider>, Option<Arc<ManagedProviderOrigin>>), LLMError> {
        self.registry.provider_for_model_with_origin(target)
    }

    /// Resolve the canonical provider type for a given model reference.
    ///
    /// In multi-instance mode `target.provider` may be an instance id, so this
    /// consults registry metadata first and falls back to the raw provider key
    /// for backward compatibility.
    pub fn provider_type_for(&self, target: &ProviderModelRef) -> Option<String> {
        let provider = target.provider.trim();
        if provider.is_empty() {
            return None;
        }

        self.registry
            .get_metadata(provider)
            .map(|meta| meta.provider_type)
            .or_else(|| Some(provider.to_string()))
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use bamboo_config::Config;

    #[test]
    fn test_route_missing_provider() {
        let config = Config::default();
        let app_data_dir = tempfile::tempdir().unwrap();
        let rt = tokio::runtime::Builder::new_current_thread()
            .enable_all()
            .build()
            .unwrap();
        let registry = rt
            .block_on(ProviderRegistry::from_config(
                &config,
                app_data_dir.path().to_path_buf(),
            ))
            .unwrap();
        let router = ProviderModelRouter::new(Arc::new(registry));

        let target = ProviderModelRef::new("nonexistent", "some-model");
        let result = router.route(&target);
        assert!(result.is_err());
    }
}