limit-llm 0.0.29

Multi-provider LLM client for Rust with streaming support. Supports Anthropic Claude, OpenAI, and z.ai.
Documentation
use crate::client::AnthropicClient;
use crate::config::Config;
use crate::error::LlmError;
use crate::local_provider::LocalProvider;
use crate::openai_provider::OpenAiProvider;
use crate::providers::LlmProvider;
use crate::zai_provider::{ThinkingConfig, ZaiProvider};
use std::boxed::Box;

pub struct ProviderFactory;

impl ProviderFactory {
    pub fn create_provider(config: &Config) -> Result<Box<dyn LlmProvider>, LlmError> {
        let provider_config = config.providers.get(&config.provider).ok_or_else(|| {
            LlmError::ConfigError(format!(
                "Provider '{}' not found in config",
                config.provider
            ))
        })?;

        let api_key = provider_config.api_key_or_env(&config.provider)
            .ok_or_else(|| LlmError::ConfigError(format!("No API key found for provider '{}'. Set api_key in config or {}_API_KEY env var",
                config.provider, config.provider.to_uppercase())))?;

        match config.provider.as_str() {
            "anthropic" => Ok(Box::new(AnthropicClient::new(
                api_key,
                provider_config.base_url.as_deref(),
                provider_config.timeout,
                &provider_config.model,
                provider_config.max_tokens,
            ))),
            "openai" => Ok(Box::new(OpenAiProvider::new(
                api_key,
                provider_config.base_url.as_deref(),
                &provider_config.model,
                provider_config.max_tokens,
                provider_config.timeout,
            ))),
            "zai" => {
                let thinking_config = ThinkingConfig {
                    thinking_enabled: provider_config.thinking_enabled,
                    clear_thinking: provider_config.clear_thinking,
                };
                Ok(Box::new(ZaiProvider::new(
                    api_key,
                    provider_config.base_url.as_deref(),
                    &provider_config.model,
                    provider_config.max_tokens,
                    provider_config.timeout,
                    thinking_config,
                )))
            }
            // Local LLM providers (all use LocalProvider with different defaults)
            "local" | "ollama" | "lmstudio" | "vllm" => {
                // Local providers don't require API key, use placeholder if empty
                let _ = api_key; // Suppress unused warning
                Ok(Box::new(LocalProvider::new(
                    provider_config.base_url.as_deref(),
                    &provider_config.model,
                    provider_config.max_tokens,
                    provider_config.timeout,
                )))
            }
            _ => Err(LlmError::ConfigError(format!(
                "Unknown provider: {}",
                config.provider
            ))),
        }
    }
}

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

    #[test]
    fn test_zai_provider_factory() {
        let config_content = r#"
provider = "zai"

[providers.zai]
api_key = "test-zai-key"
model = "glm-4.7"
"#;
        let config: Config = toml::from_str(config_content).unwrap();
        let provider = ProviderFactory::create_provider(&config).unwrap();
        assert_eq!(provider.provider_name(), "zai");
        // Now uses ZaiProvider
    }
}