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}