use std::path::PathBuf;
use std::sync::Arc;
use agent_client_protocol_schema::{ContentBlock, McpServer, SessionId, StopReason, ToolCallId};
use futures::future::BoxFuture;
use crate::error::BoxError;
use crate::event::{AgentEvent, PermissionResolution};
use crate::fs::FsBackend;
use crate::llm::{
Message, ModelCandidate, ModelInfo, ProviderError, ProviderInfo, ReasoningEffort,
};
use crate::shell::ShellBackend;
use crate::tool::{Tool, ToolSchema};
mod background;
mod capabilities;
mod context;
mod default;
mod events;
mod goal;
mod history;
mod permissions;
mod prompt;
mod tool_registry;
mod turn;
pub use background::{
BackgroundOutcome, BackgroundProgressConfig, BackgroundResult, BackgroundTasks, BlockKind,
ProgressBlock, TaskHandle, TaskSnapshot, TaskStatus, format_background_outcome,
};
pub use capabilities::{
ResolvedSessionCapabilities, SessionCapabilitiesConfig, WebSearchCapabilityConfig,
WebSearchCapabilityMode,
};
pub use context::{Frontend, RunningContext};
pub use default::{DefaultAgentCore, DefaultAgentCoreBuilder, DefaultSession, new_session_id};
pub use events::EventEmitter;
pub use goal::GoalState;
pub use history::VecHistory;
pub use permissions::PermissionGate;
pub use prompt::{load_project_prompt, resolve_system_prompt};
pub use tool_registry::{
AllowlistMatch, CompositeRegistry, StaticToolRegistry, StaticToolRegistryBuilder,
filter_registry_by_allowlist, match_tool_allowlist,
};
pub(crate) use turn::RequestAuditTracker;
pub use turn::{
BasePromptConfig, CompactionSlot, PromptConfig, TurnConfig, TurnRequestLimit, TurnRunner,
};
pub trait AgentCore: Send + Sync {
fn create_session(
&self,
id: SessionId,
cwd: PathBuf,
mcp_servers: Vec<McpServer>,
fs: Arc<dyn FsBackend>,
shell: Arc<dyn ShellBackend>,
frontend: Frontend,
) -> BoxFuture<'_, Result<Arc<dyn Session>, AgentError>>;
fn load_session(
&self,
id: SessionId,
fs: Arc<dyn FsBackend>,
shell: Arc<dyn ShellBackend>,
frontend: Frontend,
) -> BoxFuture<'_, Result<Arc<dyn Session>, AgentError>>;
fn session(&self, id: &SessionId) -> Option<Arc<dyn Session>>;
}
pub trait SessionLoader: Send + Sync {
fn load_session(&self, id: SessionId) -> BoxFuture<'_, Result<LoadedSession, BoxError>>;
}
pub trait SessionToolFactory: Send + Sync {
fn build_registry(
&self,
cwd: PathBuf,
mcp_servers: Vec<McpServer>,
) -> BoxFuture<'_, Result<Arc<dyn ToolRegistry>, BoxError>>;
}
pub trait SessionObserver: Send + Sync {
fn on_session_created(
&self,
session: Arc<dyn Session>,
info: SessionCreateInfo,
) -> Result<(), BoxError>;
}
#[derive(Debug, Clone)]
pub struct ModeDescriptor {
pub id: String,
pub name: String,
pub description: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ModelSelection {
pub provider: String,
pub model: String,
}
pub trait Session: Send + Sync {
fn id(&self) -> &SessionId;
fn provider_info(&self) -> ProviderInfo;
fn current_model(&self) -> String;
fn list_models(&self) -> BoxFuture<'_, Result<Vec<ModelInfo>, ProviderError>>;
fn list_candidates(&self) -> BoxFuture<'_, Result<Vec<ModelCandidate>, ProviderError>>;
fn set_model(&self, selection: ModelSelection) -> BoxFuture<'_, Result<(), ProviderError>>;
fn current_mode(&self) -> Option<String>;
fn available_modes(&self) -> Vec<ModeDescriptor>;
fn set_mode(&self, mode_id: String) -> Result<(), AgentError>;
fn current_reasoning_effort(&self) -> Option<ReasoningEffort>;
fn set_reasoning_effort(&self, effort: Option<ReasoningEffort>);
fn subscribe(&self) -> EventStream;
fn history_snapshot(&self) -> Vec<Message>;
fn run_turn(&self, prompt: Vec<ContentBlock>) -> BoxFuture<'_, Result<StopReason, TurnError>>;
fn cancel_turn(&self);
fn resolve_permission(&self, id: ToolCallId, outcome: PermissionResolution);
fn context_status(&self) -> ContextStatus;
fn compact_now(&self) -> BoxFuture<'_, Result<Option<CompactionReport>, TurnError>>;
}
pub type EventStream = futures::stream::BoxStream<'static, AgentEvent>;
#[derive(Debug, Clone)]
pub struct SessionCreateInfo {
pub id: SessionId,
pub cwd: PathBuf,
pub mcp_servers: Vec<McpServer>,
}
#[derive(Debug, Clone)]
pub struct LoadedSession {
pub info: SessionCreateInfo,
pub history: Vec<Message>,
}
pub trait History: Send + Sync {
fn append(&self, msg: Message);
fn snapshot(&self) -> Vec<Message>;
fn replace(&self, messages: Vec<Message>);
fn splice_prefix(&self, drop_count: usize, summary: Message) -> usize;
fn len(&self) -> usize;
fn is_empty(&self) -> bool {
self.len() == 0
}
fn truncate(&self, len: usize);
fn record_input_tokens(&self, tokens: u64);
fn token_estimate(&self) -> Option<u64>;
}
#[derive(Debug, Clone, Copy)]
pub struct CompactionReport {
pub tokens_before: u64,
pub tokens_after: u64,
}
#[derive(Debug, Clone, Copy)]
pub struct ContextStatus {
pub used_tokens: Option<u64>,
pub context_window: Option<u64>,
pub ratio: Option<f64>,
}
#[non_exhaustive]
#[derive(Debug, thiserror::Error)]
pub enum AgentError {
#[error("invalid working directory: {0}")]
InvalidCwd(PathBuf),
#[error("mcp startup failed for {server}: {source}")]
McpStartup {
server: String,
#[source]
source: BoxError,
},
#[error("session id already in use: {0}")]
DuplicateSessionId(SessionId),
#[error("session observer failed: {0}")]
Observer(#[source] BoxError),
#[error("session not found in storage: {0}")]
SessionNotFound(SessionId),
#[error("permission mode not found: {0}")]
ModeNotFound(String),
#[error("session restore failed: {0}")]
Restore(#[source] BoxError),
#[error(transparent)]
Init(#[from] SessionInitError),
#[error(transparent)]
Other(#[from] BoxError),
}
#[non_exhaustive]
#[derive(Debug)]
pub enum SessionInitError {
CapabilityUnsatisfied {
capability: &'static str,
provider: String,
},
}
impl std::fmt::Display for SessionInitError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::CapabilityUnsatisfied {
capability,
provider,
} => {
writeln!(
f,
"{capability} capability is unsatisfied: provider `{provider}` does not support hosted {capability}."
)?;
writeln!(f)?;
writeln!(f, "To fix this, choose one of:")?;
writeln!(f, " 1. Disable hosted {capability} for this provider:")?;
writeln!(f, " [providers.{provider}.capabilities.{capability}]")?;
writeln!(f, " mode = \"disabled\"")?;
writeln!(
f,
" 2. Change global default to `disabled` and only delegate where supported:"
)?;
writeln!(f, " [capabilities.{capability}]")?;
writeln!(f, " mode = \"disabled\"")?;
writeln!(
f,
" [providers.<hosted-supported>.capabilities.{capability}]"
)?;
write!(f, " mode = \"delegate\"")
}
}
}
}
impl std::error::Error for SessionInitError {}
#[non_exhaustive]
#[derive(Debug, thiserror::Error)]
pub enum TurnError {
#[error("turn already in progress for this session")]
TurnInProgress,
#[error(transparent)]
Provider(#[from] ProviderError),
#[error("internal turn error: {0}")]
Internal(#[source] BoxError),
}
pub trait ToolRegistry: Send + Sync {
fn schemas(&self) -> Vec<ToolSchema>;
fn get(&self, name: &str) -> Option<Arc<dyn Tool>>;
}