use std::path::PathBuf;
use std::time::Duration;
use serde::{Deserialize, Serialize};
use crate::async_trait;
use crate::caps::{AgentCaps, ToolPolicy};
use crate::secret::Secret;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct McpServer {
pub name: String,
pub url: String,
pub bearer_token_env: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum AgentProgress {
TextDelta(String),
Text(String),
ToolUse(String),
}
#[derive(Clone)]
pub struct ProgressFn(pub std::sync::Arc<dyn Fn(AgentProgress) + Send + Sync>);
impl ProgressFn {
pub fn new(f: impl Fn(AgentProgress) + Send + Sync + 'static) -> Self {
Self(std::sync::Arc::new(f))
}
pub fn report(&self, progress: AgentProgress) {
(self.0)(progress);
}
}
impl std::fmt::Debug for ProgressFn {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("ProgressFn")
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Resume {
pub backend: String,
pub session: String,
pub prompt: String,
}
#[derive(Debug, Clone)]
pub struct AgentTurn {
pub system: String,
pub prompt: String,
pub tools: ToolPolicy,
pub mcp: Vec<McpServer>,
pub env: Vec<(String, Secret<String>)>,
pub workdir: PathBuf,
pub timeout: Duration,
pub resume: Option<Resume>,
pub progress: Option<ProgressFn>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
pub struct TokenUsage {
pub input: u64,
pub output: u64,
pub reasoning: u64,
pub cache_read: u64,
pub cache_write: u64,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AgentReply {
pub text: String,
pub backend: String,
pub model: Option<String>,
pub session_id: Option<String>,
pub usage: Option<TokenUsage>,
pub duration: Duration,
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum AgentError {
#[error("agent unavailable: {0}")]
Unavailable(String),
#[error("agent cannot handle this turn: {0}")]
Unsupported(String),
#[error("agent timed out after {0:?}")]
Timeout(Duration),
#[error("agent failed: {message}")]
Failed {
message: String,
retryable: bool,
},
}
impl AgentError {
pub fn should_fall_back(&self) -> bool {
match self {
Self::Unavailable(_) | Self::Unsupported(_) | Self::Timeout(_) => true,
Self::Failed { retryable, .. } => *retryable,
}
}
}
#[async_trait]
pub trait AgentBackend: Send + Sync {
fn name(&self) -> &str;
fn caps(&self) -> AgentCaps;
async fn run(&self, turn: AgentTurn) -> Result<AgentReply, AgentError>;
}