pub mod agent_builder;
pub mod agent_group;
pub mod builder;
pub mod closure_tool;
pub mod kernel_bridge;
pub mod message_bus;
pub mod metrics;
pub mod multi_provider;
pub mod prelude;
pub mod tool_factory;
pub use agent_builder::AgentBuilder;
pub use agent_group::{AgentGroup, AgentGroupOutput, GroupResult, GroupStrategy};
pub use builder::{Oxi, OxiBuilder};
pub use closure_tool::ClosureTool;
pub use kernel_bridge::{KernelToolContext, KernelToolProvider};
pub use message_bus::{InterAgentMessage, MessageBus};
pub use metrics::{AgentMetrics, MetricsSnapshot};
pub use multi_provider::{MultiProviderBuilder, RoutingConfig};
pub use oxi_ai::circuit_breaker::{CircuitBreakerConfig, ProviderCircuitBreaker};
pub use oxi_ai::multi_provider::MultiProviderConfig;
pub use oxi_ai::provider_pool::{ProviderPool, RateLimitPolicy};
pub use oxi_ai::{
Api, CompactionStrategy, ContentBlock, Context, Cost, InputModality, Message, Model,
ModelRegistry, Provider, ProviderError, ProviderEvent, ProviderRegistry, StreamOptions,
UserMessage,
};
pub use oxi_ai::env_api_keys::{find_env_keys, get_all_env_keys, get_env_api_key, has_env_key};
pub use oxi_ai::model_db::{
get_all_models, get_cheapest_models, get_model_entry, get_provider_models, get_providers,
get_reasoning_models, get_vision_models, model_count, search_models, ModelEntry,
};
pub use oxi_ai::oauth::{
default_auth_path, load_auth_store, load_token, remove_token, save_auth_store, save_token,
AuthStore, OAuthError, TokenBundle,
};
pub use oxi_agent::{
Agent, AgentConfig, AgentError, AgentEvent, AgentHooks, AgentLoop, AgentLoopConfig, AgentState,
AgentTool, AgentToolResult, CompactionEvent, EditTool, FindTool, GetSearchResultsTool,
GrepTool, LsTool, OutputMode, ProviderResolver, ReadTool, SearchCache, SharedState,
StructuredOutput, StructuredOutputError, ToolContext, ToolError, ToolExecutionMode,
ToolRegistry, WebSearchTool, WriteTool,
};
#[cfg(test)]
mod tests {
use super::*;
use std::path::Path;
fn test_model(id: &str, provider: &str) -> Model {
Model::new(
id,
id,
Api::AnthropicMessages,
provider,
"https://api.example.com",
)
}
#[test]
fn test_oxi_builder_new() {
let oxi = OxiBuilder::new().build();
assert!(oxi
.resolve_model("anthropic/claude-sonnet-4-20250514")
.is_err());
}
#[test]
fn test_oxi_builder_with_builtins() {
let oxi = OxiBuilder::new().with_builtins().build();
assert!(oxi
.resolve_model("anthropic/claude-sonnet-4-20250514")
.is_ok());
assert!(oxi.resolve_model("openai/gpt-4o").is_ok());
}
#[test]
fn test_oxi_builder_custom_model() {
let oxi = OxiBuilder::new()
.model(test_model("test-model", "test-provider"))
.build();
assert!(oxi.resolve_model("test-provider/test-model").is_ok());
}
#[test]
fn test_oxi_provider_resolution() {
let oxi = OxiBuilder::new().with_builtins().build();
assert!(oxi.create_provider("anthropic").is_ok());
assert!(oxi.create_provider("nonexistent").is_err());
}
#[test]
fn test_agent_builder_workspace() {
let oxi = OxiBuilder::new().with_builtins().build();
let config = AgentConfig {
model_id: "anthropic/claude-sonnet-4-20250514".into(),
max_iterations: 10,
timeout_seconds: 30,
..Default::default()
};
let result = oxi.agent(config).workspace("/tmp/test-workspace").build();
assert!(result.is_ok() || result.is_err());
}
#[test]
fn test_agent_builder_coding_tools() {
let oxi = OxiBuilder::new().with_builtins().build();
let config = AgentConfig {
model_id: "anthropic/claude-sonnet-4-20250514".into(),
max_iterations: 10,
timeout_seconds: 30,
..Default::default()
};
let result = oxi.agent(config).workspace("/tmp").coding_tools().build();
if let Ok(agent) = result {
let tool_names = agent.tools().names();
assert!(tool_names.contains(&"read".to_string()));
assert!(tool_names.contains(&"write".to_string()));
assert!(tool_names.contains(&"edit".to_string()));
assert!(tool_names.contains(&"ls".to_string()));
}
}
#[test]
fn test_agent_builder_readonly_tools() {
let oxi = OxiBuilder::new().with_builtins().build();
let config = AgentConfig {
model_id: "anthropic/claude-sonnet-4-20250514".into(),
max_iterations: 10,
timeout_seconds: 30,
..Default::default()
};
let result = oxi.agent(config).workspace("/tmp").readonly_tools().build();
if let Ok(agent) = result {
let tool_names = agent.tools().names();
assert!(tool_names.contains(&"read".to_string()));
assert!(tool_names.contains(&"ls".to_string()));
assert!(!tool_names.contains(&"write".to_string()));
}
}
#[test]
fn test_model_registry_isolation() {
let oxi1 = OxiBuilder::new()
.model(test_model("unique-1", "test"))
.build();
let oxi2 = OxiBuilder::new().with_builtins().build();
assert!(oxi2.resolve_model("test/unique-1").is_err());
assert!(oxi1.resolve_model("test/unique-1").is_ok());
}
#[test]
fn test_tool_factory_coding_tools() {
let tools = crate::tool_factory::coding_tools(Path::new("/tmp"));
let names = tools.names();
assert!(names.contains(&"read".to_string()));
assert!(names.contains(&"write".to_string()));
assert!(names.contains(&"edit".to_string()));
assert!(names.contains(&"ls".to_string()));
assert_eq!(names.len(), 4);
}
#[test]
fn test_tool_factory_readonly_tools() {
let tools = crate::tool_factory::readonly_tools(Path::new("/tmp"));
let names = tools.names();
assert!(names.contains(&"read".to_string()));
assert!(names.contains(&"ls".to_string()));
assert_eq!(names.len(), 2);
}
#[test]
fn test_provider_resolver_trait_on_oxi() {
let oxi = OxiBuilder::new().with_builtins().build();
let resolver: &dyn ProviderResolver = &oxi;
assert!(resolver.resolve_provider("anthropic").is_some());
assert!(resolver.resolve_provider("nonexistent").is_none());
assert!(resolver
.resolve_model("anthropic/claude-sonnet-4-20250514")
.is_some());
assert!(resolver.resolve_model("nonexistent/model").is_none());
}
#[test]
fn test_agent_uses_resolver_for_switch_model() {
let oxi = OxiBuilder::new()
.model(test_model("test-model", "test-provider"))
.build();
let config = AgentConfig {
model_id: "test-provider/test-model".into(),
max_iterations: 1,
timeout_seconds: 5,
..Default::default()
};
let result = oxi.agent(config).build();
assert!(result.is_err());
}
#[test]
fn test_oxi_builder_without_builtins() {
let oxi = OxiBuilder::new().build();
assert!(oxi
.resolve_model("anthropic/claude-sonnet-4-20250514")
.is_err());
assert!(oxi.create_provider("anthropic").is_err());
assert!(!oxi.has_builtins());
}
#[test]
fn test_oxi_builder_with_builtins_creates_providers() {
let oxi = OxiBuilder::new().with_builtins().build();
assert!(oxi.has_builtins());
assert!(oxi.create_provider("anthropic").is_ok());
assert!(oxi.create_provider("openai").is_ok());
assert!(oxi.create_provider("deepseek").is_ok());
assert!(oxi.create_provider("unknown-provider").is_err());
}
#[test]
fn test_closure_tool_sync() {
let tool = crate::closure_tool::ClosureTool::new_sync(
"test_tool",
"A test tool",
serde_json::json!({
"type": "object",
"properties": {
"input": { "type": "string" }
}
}),
|params, _ctx| {
let input = params["input"].as_str().unwrap_or("default");
Ok(AgentToolResult::success(format!("processed: {}", input)))
},
);
assert_eq!(tool.name(), "test_tool");
assert_eq!(tool.description(), "A test tool");
let rt = tokio::runtime::Runtime::new().unwrap();
let result = rt
.block_on(tool.execute(
"call_1",
serde_json::json!({"input": "hello"}),
None,
&ToolContext::default(),
))
.unwrap();
assert!(result.success);
assert!(result.output.contains("processed: hello"));
}
#[test]
fn test_custom_tool_in_agent_builder() {
let oxi = OxiBuilder::new().with_builtins().build();
let config = AgentConfig {
model_id: "anthropic/claude-sonnet-4-20250514".into(),
max_iterations: 10,
timeout_seconds: 30,
..Default::default()
};
let result = oxi
.agent(config)
.workspace("/tmp")
.custom_tool(
"my_tool",
"My custom tool",
serde_json::json!({"type": "object", "properties": {"query": {"type": "string"}}}),
|params, _ctx| {
Ok(AgentToolResult::success(format!(
"result: {}",
params["query"]
)))
},
)
.build();
if let Ok(agent) = result {
let tool_names = agent.tools().names();
assert!(tool_names.contains(&"my_tool".to_string()));
}
}
#[test]
fn test_full_isolation_between_instances() {
let oxi1 = OxiBuilder::new()
.model(test_model("unique-alpha", "p1"))
.build();
let oxi2 = OxiBuilder::new().with_builtins().build();
assert!(oxi2.resolve_model("p1/unique-alpha").is_err());
assert!(oxi1
.resolve_model("anthropic/claude-sonnet-4-20250514")
.is_err());
assert!(oxi1.create_provider("anthropic").is_err());
assert!(oxi2.create_provider("anthropic").is_ok());
}
#[test]
fn test_agent_builder_system_prompt() {
let oxi = OxiBuilder::new().with_builtins().build();
let config = AgentConfig {
model_id: "anthropic/claude-sonnet-4-20250514".into(),
max_iterations: 1,
timeout_seconds: 5,
..Default::default()
};
let agent = oxi
.agent(config)
.workspace("/tmp")
.system_prompt("You are a test agent.")
.build()
.unwrap();
drop(agent);
}
}