use anyhow::Result;
use std::sync::Arc;
use super::builder::AgentBuilder;
use super::runtime_agent::RuntimeAgent;
use crate::llm::LlmProvider;
use crate::tools::{AsyncTool, Tool};
pub trait AgentQuick {
fn quick(
model: impl Into<String>,
prompt: impl Into<String>,
tools: Vec<Arc<dyn Tool>>,
) -> Result<Self>
where
Self: Sized;
}
impl AgentQuick for RuntimeAgent {
fn quick(
model: impl Into<String>,
prompt: impl Into<String>,
tools: Vec<Arc<dyn Tool>>,
) -> Result<Self> {
let model_str = model.into();
let name = format!("agent-{}", extract_model_name(&model_str));
let provider = detect_provider(&model_str);
let mut builder = AgentBuilder::new(name)
.provider(provider)
.model(model_str)
.system_prompt(prompt.into())
.with_tools(tools)
.temperature(0.7)
.max_tokens(4096)
.top_p(0.9);
builder = builder.retry(crate::llm::anthropic::RetryConfig {
max_retries: 3,
initial_backoff_ms: 1000,
max_backoff_ms: 30000,
backoff_multiplier: 2.0,
jitter: true,
});
builder.build()
}
}
pub struct Agent;
impl Agent {
pub fn quick(
model: impl Into<String>,
prompt: impl Into<String>,
tools: Vec<Arc<dyn crate::tools::Tool>>,
) -> Result<RuntimeAgent> {
RuntimeAgent::quick(model, prompt, tools)
}
pub fn quick_with<T: crate::tools::Tool + 'static>(
model: impl Into<String>,
prompt: impl Into<String>,
tools: Vec<T>,
) -> Result<RuntimeAgent> {
let wrapped_tools: Vec<Arc<dyn crate::tools::Tool>> = tools
.into_iter()
.map(|t| Arc::new(t) as Arc<dyn crate::tools::Tool>)
.collect();
RuntimeAgent::quick(model, prompt, wrapped_tools)
}
#[allow(clippy::new_ret_no_self)]
pub fn new(name: impl Into<String>, model: impl Into<String>) -> AgentBuilder {
let model_str = model.into();
let provider = detect_provider(&model_str);
AgentBuilder::new(name).provider(provider).model(model_str)
}
}
pub fn detect_provider(model: &str) -> LlmProvider {
let model_lower = model.to_lowercase();
if model_lower.starts_with("openrouter/") {
return LlmProvider::OpenRouterResponses;
}
if model_lower.starts_with("anthropic/") || model_lower.starts_with("claude-") {
return LlmProvider::Anthropic;
}
if model_lower.starts_with("openai-codex/") {
return LlmProvider::OpenAICodex;
}
if model_lower.starts_with("openai/")
|| model_lower.starts_with("gpt-")
|| model_lower.starts_with("o1-")
|| model_lower.starts_with("o3-")
{
return LlmProvider::OpenAI;
}
if model_lower.starts_with("vertex/")
|| model_lower.starts_with("gemini-")
|| model_lower.starts_with("google/gemini")
{
return LlmProvider::Vertex;
}
LlmProvider::OpenRouterResponses
}
pub fn extract_model_name(model: &str) -> String {
if let Some((_provider, name)) = model.split_once('/') {
name.to_string()
} else {
model.to_string()
}
}
impl AgentBuilder {
pub fn prompt(self, prompt: impl Into<String>) -> Self {
self.system_prompt(prompt)
}
pub fn tools(mut self, tools: Vec<Arc<dyn Tool>>) -> Self {
for tool in tools {
self = self.with_tool(tool);
}
self
}
pub fn tool_dyn(self, tool: Arc<dyn Tool>) -> Self {
self.with_tool(tool)
}
pub fn async_tool_dyn(self, tool: Arc<dyn AsyncTool>) -> Self {
self.with_async_tool(tool)
}
}
pub trait AgentBuilderToolExt {
fn tool<T: Tool + 'static>(self, tool: T) -> Self;
}
impl AgentBuilderToolExt for AgentBuilder {
fn tool<T: Tool + 'static>(self, tool: T) -> Self {
self.with_tool(Arc::new(tool))
}
}
pub trait AgentBuilderAsyncToolExt {
fn async_tool<T: AsyncTool + 'static>(self, tool: T) -> Self;
}
impl AgentBuilderAsyncToolExt for AgentBuilder {
fn async_tool<T: AsyncTool + 'static>(self, tool: T) -> Self {
self.with_async_tool(Arc::new(tool))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_detect_provider_anthropic() {
assert!(matches!(
detect_provider("anthropic/claude-sonnet-4-5"),
LlmProvider::Anthropic
));
assert!(matches!(
detect_provider("claude-sonnet-4-5"),
LlmProvider::Anthropic
));
assert!(matches!(
detect_provider("claude-opus-4"),
LlmProvider::Anthropic
));
}
#[test]
fn test_detect_provider_openai() {
assert!(matches!(
detect_provider("openai/gpt-4o"),
LlmProvider::OpenAI
));
assert!(matches!(detect_provider("gpt-4o"), LlmProvider::OpenAI));
assert!(matches!(detect_provider("gpt-4"), LlmProvider::OpenAI));
assert!(matches!(detect_provider("o1-preview"), LlmProvider::OpenAI));
assert!(matches!(detect_provider("o3-mini"), LlmProvider::OpenAI));
}
#[test]
fn test_detect_provider_openai_codex() {
assert!(matches!(
detect_provider("openai-codex/gpt-5.4"),
LlmProvider::OpenAICodex
));
}
#[test]
fn test_detect_provider_openrouter() {
assert!(matches!(
detect_provider("openrouter/anthropic/claude-sonnet-4-5"),
LlmProvider::OpenRouterResponses
));
}
#[test]
fn test_detect_provider_vertex() {
assert!(matches!(
detect_provider("gemini-2.5-flash"),
LlmProvider::Vertex
));
assert!(matches!(
detect_provider("vertex/gemini-2.5-pro"),
LlmProvider::Vertex
));
assert!(matches!(
detect_provider("google/gemini-2.5-flash"),
LlmProvider::Vertex
));
}
#[test]
fn test_detect_provider_default() {
assert!(matches!(
detect_provider("some-unknown-model"),
LlmProvider::OpenRouterResponses
));
}
#[test]
fn test_extract_model_name() {
assert_eq!(
extract_model_name("anthropic/claude-sonnet-4-5"),
"claude-sonnet-4-5"
);
assert_eq!(extract_model_name("openai/gpt-4o"), "gpt-4o");
assert_eq!(extract_model_name("gpt-4"), "gpt-4");
assert_eq!(
extract_model_name("openrouter/anthropic/claude"),
"anthropic/claude"
);
}
}