use std::path::Path;
use anyhow::Result;
use oxo_flow_ai::agent::orchestrator::Orchestrator;
use oxo_flow_ai::agent::{AgentContext, ExternalSource};
use oxo_flow_ai::config::AiConfig;
use oxo_flow_ai::provider::AiProvider;
use oxo_flow_ai::session::AiSession;
use oxo_flow_ai::tools::{ToolRegistry, builtin};
pub struct AiRuntime {
pub provider: AiProvider,
pub config: AiConfig,
pub tool_registry: ToolRegistry,
pub orchestrator: Orchestrator,
}
impl AiRuntime {
pub fn new(
workflow_path: Option<&Path>,
project_dir: Option<&Path>,
cli_max_retries: Option<u32>,
) -> Result<Self> {
let provider = super::ai_template::resolve_ai_provider()?;
let global = load_global_config();
let project = project_dir.and_then(AiConfig::from_project_file);
let workflow = workflow_path.and_then(|p| {
std::fs::read_to_string(p).ok().and_then(|c| {
c.parse::<toml::Table>()
.ok()
.and_then(|t| AiConfig::from_workflow_toml(&t))
})
});
let cli_overrides = cli_max_retries.map(|n| AiConfig {
max_retries: n,
..Default::default()
});
let config = AiConfig::resolve_chain(
global.as_ref(),
project.as_ref(),
workflow.as_ref(),
None,
cli_overrides.as_ref(),
);
let mut tool_registry = ToolRegistry::new();
tool_registry.register(Box::new(builtin::ReadFileTool::new()));
tool_registry.register(Box::new(builtin::FetchUrlTool::new()));
register_discovered_tools(&mut tool_registry);
let orchestrator = Orchestrator::new(provider.clone(), config.max_retries);
Ok(Self {
provider,
config,
tool_registry,
orchestrator,
})
}
pub fn create_context(
&self,
command: &str,
intent: &str,
workflow_path: Option<&Path>,
workflow_content: Option<&str>,
external_sources: Vec<ExternalSource>,
) -> AgentContext {
AgentContext {
intent: intent.to_string(),
command: command.to_string(),
workflow_path: workflow_path.map(|p| p.to_path_buf()),
workflow_content: workflow_content.map(|s| s.to_string()),
external_sources,
max_rounds: self.config.max_retries,
tool_registry: ToolRegistry::new(),
session: AiSession::new(
command,
intent,
self.provider.name(),
&self.provider.model().unwrap_or_else(|| "default".into()),
),
}
}
}
fn load_global_config() -> Option<AiConfig> {
let path = oxo_flow_ai::provider::ai_config_path();
let content = std::fs::read_to_string(&path).ok()?;
let json: serde_json::Value = serde_json::from_str(&content).ok()?;
let provider_str = json["provider"].as_str().unwrap_or("");
let provider: oxo_flow_ai::provider::ProviderKind = provider_str.parse().ok()?;
Some(AiConfig {
enabled: true,
provider,
model: json["model"].as_str().map(String::from),
api_url: json["api_url"].as_str().map(String::from),
..Default::default()
})
}
fn register_discovered_tools(_registry: &mut ToolRegistry) {
let skills = oxo_flow_ai::skill::discover_skills(None);
for skill in &skills {
if skill.skill_type == "tool" {
tracing::info!("Discovered AI skill: {} v{}", skill.name, skill.version);
}
}
if !skills.is_empty() {
tracing::info!(
"Registered {} builtin tools + {} discovered skills",
2,
skills.len()
);
}
}