use std::path::PathBuf;
use std::sync::Arc;
use oxi_agent::{
Agent, AgentConfig, AgentTool, AgentToolResult, ProviderResolver, ToolContext, ToolRegistry,
};
use crate::builder::Oxi;
pub(crate) struct OxiResolver {
oxi: Arc<OxiCore>,
}
pub(crate) struct OxiCore {
#[allow(clippy::type_complexity)]
resolve_provider_fn: Box<dyn Fn(&str) -> Option<Arc<dyn oxi_ai::Provider>> + Send + Sync>,
#[allow(clippy::type_complexity)]
resolve_model_fn: Box<dyn Fn(&str) -> Option<oxi_ai::Model> + Send + Sync>,
}
impl ProviderResolver for OxiResolver {
fn resolve_provider(&self, name: &str) -> Option<Arc<dyn oxi_ai::Provider>> {
(self.oxi.resolve_provider_fn)(name)
}
fn resolve_model(&self, model_id: &str) -> Option<oxi_ai::Model> {
(self.oxi.resolve_model_fn)(model_id)
}
}
#[allow(dead_code)]
pub struct AgentBuilder<'a> {
oxi: &'a Oxi,
config: AgentConfig,
tools: ToolRegistry,
workspace_dir: Option<PathBuf>,
system_prompt: Option<String>,
}
impl<'a> AgentBuilder<'a> {
pub fn new(oxi: &'a Oxi, config: AgentConfig) -> Self {
Self {
oxi,
config,
tools: ToolRegistry::new(),
workspace_dir: None,
system_prompt: None,
}
}
pub fn workspace(mut self, dir: impl Into<PathBuf>) -> Self {
self.workspace_dir = Some(dir.into());
self
}
pub fn system_prompt(mut self, prompt: impl Into<String>) -> Self {
self.system_prompt = Some(prompt.into());
self
}
pub fn coding_tools(self) -> Self {
let cwd = self
.workspace_dir
.clone()
.unwrap_or_else(|| std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")));
let tools = crate::tool_factory::coding_tools(&cwd);
for name in tools.names() {
if let Some(tool) = tools.get(&name) {
self.tools.register_arc(tool);
}
}
self
}
pub fn readonly_tools(self) -> Self {
let cwd = self
.workspace_dir
.clone()
.unwrap_or_else(|| std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")));
let tools = crate::tool_factory::readonly_tools(&cwd);
for name in tools.names() {
if let Some(tool) = tools.get(&name) {
self.tools.register_arc(tool);
}
}
self
}
pub fn tool(self, tool: impl AgentTool + 'static) -> Self {
self.tools.register(tool);
self
}
pub fn custom_tool(
self,
name: impl Into<String>,
description: impl Into<String>,
schema: serde_json::Value,
handler: impl Fn(serde_json::Value, &ToolContext) -> Result<AgentToolResult, oxi_agent::ToolError>
+ Send
+ Sync
+ 'static,
) -> Self {
self.tool(crate::closure_tool::ClosureTool::new_sync(
name,
description,
schema,
handler,
))
}
pub fn tools(self, tools: impl IntoIterator<Item = impl AgentTool + 'static>) -> Self {
for tool in tools {
self.tools.register(tool);
}
self
}
pub fn kernel_tools(
self,
provider: &dyn crate::KernelToolProvider,
context: &crate::KernelToolContext,
) -> Self {
provider.register_tools(&self.tools, context);
self
}
pub fn build(self) -> anyhow::Result<Agent> {
let model = self.oxi.resolve_model(&self.config.model_id)?;
let provider: Arc<dyn oxi_ai::Provider> = self.oxi.create_provider(&model.provider)?;
let mut config = self.config.clone();
config.workspace_dir = self.workspace_dir.or(config.workspace_dir);
if let Some(ref prompt) = self.system_prompt {
config.system_prompt = Some(prompt.clone());
}
let oxi_providers = self.oxi.providers_arc();
let oxi_models = self.oxi.models_arc();
let include_builtins = self.oxi.has_builtins();
let resolver: Arc<dyn ProviderResolver> = Arc::new(OxiResolver {
oxi: Arc::new(OxiCore {
resolve_provider_fn: Box::new(move |name: &str| {
if let Some(p) = oxi_providers.get_custom(name) {
return Some(p);
}
if include_builtins {
if let Some(p) = oxi_ai::create_builtin_provider(name) {
return Some(Arc::from(p));
}
}
None
}),
resolve_model_fn: Box::new(move |model_id: &str| {
let parts: Vec<&str> = model_id.splitn(2, '/').collect();
let (provider, model) = if parts.len() == 2 {
(parts[0], parts[1])
} else {
("anthropic", parts[0])
};
oxi_models.lookup(provider, model)
}),
}),
});
let agent = Agent::new_with_resolver(provider, config, Arc::new(self.tools), resolver);
Ok(agent)
}
}