Skip to main content

oxi_sdk/
builder.rs

1//! OxiBuilder and Oxi — SDK entry point
2
3use anyhow::Result;
4use std::sync::Arc;
5
6use oxi_agent::{ProviderResolver, ToolRegistry};
7use oxi_ai::{Model, ModelRegistry, Provider, ProviderRegistry};
8
9use crate::agent_builder::AgentBuilder;
10use crate::multi_provider::{MultiProviderBuilder, RoutingConfig};
11
12/// Oxi AI engine instance — holds isolated provider and model registries.
13///
14/// Created via [`OxiBuilder`]. Provides access to providers, models,
15/// provider creation, and agent building.
16///
17/// Implements [`ProviderResolver`] so it can be passed directly to
18/// [`oxi_agent::Agent::new_with_resolver`] for fully isolated operation.
19pub struct Oxi {
20    providers: Arc<ProviderRegistry>,
21    models: Arc<ModelRegistry>,
22    tools: Arc<ToolRegistry>,
23    /// Whether to include built-in provider resolution (from create_builtin_provider).
24    include_builtins: bool,
25}
26
27impl Oxi {
28    /// Create an agent builder with the given config.
29    pub fn agent(&self, config: oxi_agent::AgentConfig) -> AgentBuilder<'_> {
30        AgentBuilder::new(self, config)
31    }
32
33    /// Get the provider registry.
34    pub fn providers(&self) -> &ProviderRegistry {
35        &self.providers
36    }
37
38    /// Get the model registry.
39    pub fn models(&self) -> &ModelRegistry {
40        &self.models
41    }
42
43    /// Get the shared tool registry.
44    pub fn tools(&self) -> Arc<ToolRegistry> {
45        Arc::clone(&self.tools)
46    }
47
48    /// Resolve a model ID to a Model.
49    ///
50    /// Accepts `"provider/model"` or bare `"model"` (defaults to "anthropic").
51    pub fn resolve_model(&self, model_id: &str) -> Result<Model> {
52        let parts: Vec<&str> = model_id.splitn(2, '/').collect();
53        let (provider, model) = if parts.len() == 2 {
54            (parts[0], parts[1])
55        } else {
56            ("anthropic", parts[0])
57        };
58        self.models
59            .lookup(provider, model)
60            .ok_or_else(|| anyhow::anyhow!("Model '{}' not found", model_id))
61    }
62
63    /// Create a provider instance for a given provider name.
64    ///
65    /// Checks the local `ProviderRegistry` first, then falls back
66    /// to built-in providers (if `with_builtins()` was called).
67    pub fn create_provider(&self, name: &str) -> Result<Arc<dyn Provider>> {
68        // 1. Check custom providers registered via OxiBuilder::provider()
69        if let Some(p) = self.providers.get_custom(name) {
70            return Ok(p);
71        }
72        // 2. Fall back to built-in providers (stateless creation)
73        if self.include_builtins {
74            if let Some(p) = oxi_ai::create_builtin_provider(name) {
75                return Ok(Arc::from(p));
76            }
77        }
78        Err(anyhow::anyhow!("Provider '{}' not found", name))
79    }
80
81    /// Get the provider registry (Arc clone).
82    pub fn providers_arc(&self) -> Arc<ProviderRegistry> {
83        Arc::clone(&self.providers)
84    }
85
86    /// Get the model registry (Arc clone).
87    pub fn models_arc(&self) -> Arc<ModelRegistry> {
88        Arc::clone(&self.models)
89    }
90
91    /// Check whether built-in providers are enabled.
92    pub fn has_builtins(&self) -> bool {
93        self.include_builtins
94    }
95}
96
97/// Implement ProviderResolver so Oxi can be used as Agent's resolver.
98impl ProviderResolver for Oxi {
99    fn resolve_provider(&self, name: &str) -> Option<Arc<dyn Provider>> {
100        self.create_provider(name).ok()
101    }
102
103    fn resolve_model(&self, model_id: &str) -> Option<Model> {
104        self.resolve_model(model_id).ok()
105    }
106}
107
108/// Builder for creating an Oxi instance.
109pub struct OxiBuilder {
110    providers: ProviderRegistry,
111    models: ModelRegistry,
112    tools: ToolRegistry,
113    include_builtins: bool,
114}
115
116impl OxiBuilder {
117    /// Create a new empty builder (no builtins, no providers, no models).
118    pub fn new() -> Self {
119        Self {
120            providers: ProviderRegistry::new(),
121            models: ModelRegistry::new(),
122            tools: ToolRegistry::new(),
123            include_builtins: false,
124        }
125    }
126
127    /// Register all built-in models and enable built-in provider creation.
128    ///
129    /// This loads 50+ model definitions from the oxi-ai static database
130    /// and enables `create_builtin_provider()` fallback in [`Oxi::create_provider`].
131    pub fn with_builtins(mut self) -> Self {
132        self.models = ModelRegistry::from_static();
133        self.include_builtins = true;
134        self
135    }
136
137    /// Register a custom provider.
138    pub fn provider(self, name: &str, p: impl Provider + 'static) -> Self {
139        self.providers.register(name, p);
140        self
141    }
142
143    /// Register a custom tool in the shared tool registry.
144    pub fn tool(self, tool: impl oxi_agent::AgentTool + 'static) -> Self {
145        self.tools.register(tool);
146        self
147    }
148
149    /// Register a provider factory — a closure that lazily creates a provider.
150    ///
151    /// Unlike [`Self::provider()`], which takes an already-constructed instance,
152    /// this stores a factory closure. The factory is invoked the **first time**
153    /// `Oxi::create_provider(name)` is called, and the resulting provider is
154    /// cached for subsequent calls.
155    ///
156    /// This is useful when provider construction requires credential resolution
157    /// or network configuration that should happen at first use, not at build time.
158    ///
159    /// # Example
160    ///
161    /// ```ignore
162    /// let oxi = OxiBuilder::new()
163    ///     .with_builtins()
164    ///     .provider_factory("zai", || {
165    ///         let api_key = resolve_key("zai");
166    ///         let base_url = env::var("ZAI_BASE_URL").unwrap_or_default();
167    ///         Ok(Arc::new(OpenAiProvider::with_base_url_and_key(&base_url, api_key)))
168    ///     })
169    ///     .build();
170    /// ```
171    pub fn provider_factory(
172        self,
173        name: &str,
174        factory: impl Fn() -> anyhow::Result<Arc<dyn Provider>> + Send + Sync + 'static,
175    ) -> Self {
176        self.providers.register_factory(name, factory);
177        self
178    }
179
180    /// Register a custom model.
181    pub fn model(self, model: Model) -> Self {
182        self.models.register(model);
183        self
184    }
185
186    /// Enable multi-provider routing with automatic complexity-based model selection.
187    ///
188    /// This registers a [`MultiProvider`] that routes requests based on task complexity,
189    /// with configurable fallback chains and circuit breaker protection.
190    ///
191    /// # Arguments
192    ///
193    /// * `config` - Routing configuration (use [`RoutingConfig::new()`] for defaults)
194    ///
195    /// # Example
196    ///
197    /// ```ignore
198    /// use oxi_sdk::{RoutingConfig, create_builtin_provider};
199    ///
200    /// let oxi = OxiBuilder::new()
201    ///     .with_builtins()
202    ///     .enable_routing(RoutingConfig::new().prefer_cost_efficient(true))
203    ///     .build();
204    /// ```
205    pub fn enable_routing(self, config: RoutingConfig) -> Self {
206        // Collect providers before consuming self
207        let provider_names: Vec<String> = self.providers.names();
208        let mut providers_to_add: Vec<(String, Arc<dyn Provider>)> = Vec::new();
209        for name in &provider_names {
210            if let Some(provider) = self.providers.get_custom(name) {
211                providers_to_add.push((name.clone(), provider));
212            }
213        }
214
215        // Build multi-provider with registered providers and routing config
216        let mut mp_builder = MultiProviderBuilder::new();
217
218        // Apply routing config
219        if config.auto_routing {
220            mp_builder = mp_builder.enable_auto_routing();
221        }
222        if config.prefer_cost_efficient {
223            mp_builder = mp_builder.prefer_cost_efficient();
224        }
225        if let Some(router) = config.router {
226            mp_builder = mp_builder.with_router_boxed(router);
227        }
228
229        // Add collected providers
230        for (name, provider) in providers_to_add {
231            mp_builder = mp_builder.provider(&name, provider);
232        }
233
234        // Build and register the multi-provider
235        let built = mp_builder.build();
236        if let Ok(mp) = built {
237            self.providers.register_arc("multi", mp);
238        }
239        self
240    }
241
242    /// Build the Oxi engine instance.
243    pub fn build(self) -> Oxi {
244        Oxi {
245            providers: Arc::new(self.providers),
246            models: Arc::new(self.models),
247            tools: Arc::new(self.tools),
248            include_builtins: self.include_builtins,
249        }
250    }
251}
252
253impl Default for OxiBuilder {
254    fn default() -> Self {
255        Self::new()
256    }
257}