pub mod transport;
use crate::event::{AgentEvent, ScopedAgentEvent, StreamScopeFrame};
use crate::types::{RunResult, SessionId, Usage};
use crate::{
AgentToolDispatcher, BudgetLimits, HookRunOverrides, OutputSchema, PeerMeta, Provider, Session,
};
use crate::{EventStream, StreamError};
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use std::time::SystemTime;
use tokio::sync::mpsc;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InitialTurnPolicy {
RunImmediately,
Defer,
}
#[derive(Debug, thiserror::Error)]
pub enum SessionError {
#[error("session not found: {id}")]
NotFound { id: SessionId },
#[error("session is busy: {id}")]
Busy { id: SessionId },
#[error("session persistence is disabled")]
PersistenceDisabled,
#[error("session compaction is disabled")]
CompactionDisabled,
#[error("no turn running on session: {id}")]
NotRunning { id: SessionId },
#[error("store error: {0}")]
Store(#[source] Box<dyn std::error::Error + Send + Sync>),
#[error("agent error: {0}")]
Agent(#[from] crate::error::AgentError),
}
impl SessionError {
pub fn code(&self) -> &'static str {
match self {
Self::NotFound { .. } => "SESSION_NOT_FOUND",
Self::Busy { .. } => "SESSION_BUSY",
Self::PersistenceDisabled => "SESSION_PERSISTENCE_DISABLED",
Self::CompactionDisabled => "SESSION_COMPACTION_DISABLED",
Self::NotRunning { .. } => "SESSION_NOT_RUNNING",
Self::Store(_) => "SESSION_STORE_ERROR",
Self::Agent(_) => "AGENT_ERROR",
}
}
}
#[derive(Debug)]
pub struct CreateSessionRequest {
pub model: String,
pub prompt: String,
pub system_prompt: Option<String>,
pub max_tokens: Option<u32>,
pub event_tx: Option<mpsc::Sender<AgentEvent>>,
pub host_mode: bool,
pub skill_references: Option<Vec<crate::skills::SkillKey>>,
pub initial_turn: InitialTurnPolicy,
pub build: Option<SessionBuildOptions>,
}
#[derive(Clone)]
pub struct SessionBuildOptions {
pub provider: Option<Provider>,
pub output_schema: Option<OutputSchema>,
pub structured_output_retries: u32,
pub hooks_override: HookRunOverrides,
pub comms_name: Option<String>,
pub peer_meta: Option<PeerMeta>,
pub resume_session: Option<Session>,
pub budget_limits: Option<BudgetLimits>,
pub provider_params: Option<serde_json::Value>,
pub external_tools: Option<Arc<dyn AgentToolDispatcher>>,
pub llm_client_override: Option<Arc<dyn std::any::Any + Send + Sync>>,
pub scoped_event_tx: Option<mpsc::Sender<ScopedAgentEvent>>,
pub scoped_event_path: Option<Vec<StreamScopeFrame>>,
pub override_builtins: Option<bool>,
pub override_shell: Option<bool>,
pub override_subagents: Option<bool>,
pub override_memory: Option<bool>,
pub override_mob: Option<bool>,
pub preload_skills: Option<Vec<crate::skills::SkillId>>,
pub realm_id: Option<String>,
pub instance_id: Option<String>,
pub backend: Option<String>,
pub config_generation: Option<u64>,
pub checkpointer: Option<std::sync::Arc<dyn crate::checkpoint::SessionCheckpointer>>,
pub silent_comms_intents: Vec<String>,
pub max_inline_peer_notifications: Option<i32>,
}
impl Default for SessionBuildOptions {
fn default() -> Self {
Self {
provider: None,
output_schema: None,
structured_output_retries: 2,
hooks_override: HookRunOverrides::default(),
comms_name: None,
peer_meta: None,
resume_session: None,
budget_limits: None,
provider_params: None,
external_tools: None,
llm_client_override: None,
scoped_event_tx: None,
scoped_event_path: None,
override_builtins: None,
override_shell: None,
override_subagents: None,
override_memory: None,
override_mob: None,
preload_skills: None,
realm_id: None,
instance_id: None,
backend: None,
config_generation: None,
checkpointer: None,
silent_comms_intents: Vec::new(),
max_inline_peer_notifications: None,
}
}
}
impl std::fmt::Debug for SessionBuildOptions {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SessionBuildOptions")
.field("provider", &self.provider)
.field("output_schema", &self.output_schema.is_some())
.field("structured_output_retries", &self.structured_output_retries)
.field("hooks_override", &self.hooks_override)
.field("comms_name", &self.comms_name)
.field("peer_meta", &self.peer_meta)
.field("resume_session", &self.resume_session.is_some())
.field("budget_limits", &self.budget_limits)
.field("provider_params", &self.provider_params.is_some())
.field("external_tools", &self.external_tools.is_some())
.field("llm_client_override", &self.llm_client_override.is_some())
.field("scoped_event_tx", &self.scoped_event_tx.is_some())
.field("scoped_event_path", &self.scoped_event_path.is_some())
.field("override_builtins", &self.override_builtins)
.field("override_shell", &self.override_shell)
.field("override_subagents", &self.override_subagents)
.field("override_memory", &self.override_memory)
.field("override_mob", &self.override_mob)
.field("preload_skills", &self.preload_skills)
.field("realm_id", &self.realm_id)
.field("instance_id", &self.instance_id)
.field("backend", &self.backend)
.field("config_generation", &self.config_generation)
.field("checkpointer", &self.checkpointer.is_some())
.field("silent_comms_intents", &self.silent_comms_intents)
.field(
"max_inline_peer_notifications",
&self.max_inline_peer_notifications,
)
.finish()
}
}
#[derive(Debug)]
pub struct StartTurnRequest {
pub prompt: String,
pub event_tx: Option<mpsc::Sender<AgentEvent>>,
pub host_mode: bool,
pub skill_references: Option<Vec<crate::skills::SkillKey>>,
}
#[derive(Debug, Default)]
pub struct SessionQuery {
pub limit: Option<usize>,
pub offset: Option<usize>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SessionSummary {
pub session_id: SessionId,
pub created_at: SystemTime,
pub updated_at: SystemTime,
pub message_count: usize,
pub total_tokens: u64,
pub is_active: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SessionInfo {
pub session_id: SessionId,
pub created_at: SystemTime,
pub updated_at: SystemTime,
pub message_count: usize,
pub is_active: bool,
pub last_assistant_text: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SessionUsage {
pub total_tokens: u64,
pub usage: Usage,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SessionView {
pub state: SessionInfo,
pub billing: SessionUsage,
}
impl SessionView {
pub fn session_id(&self) -> &SessionId {
&self.state.session_id
}
}
#[async_trait]
pub trait SessionService: Send + Sync {
async fn create_session(&self, req: CreateSessionRequest) -> Result<RunResult, SessionError>;
async fn start_turn(
&self,
id: &SessionId,
req: StartTurnRequest,
) -> Result<RunResult, SessionError>;
async fn interrupt(&self, id: &SessionId) -> Result<(), SessionError>;
async fn read(&self, id: &SessionId) -> Result<SessionView, SessionError>;
async fn list(&self, query: SessionQuery) -> Result<Vec<SessionSummary>, SessionError>;
async fn archive(&self, id: &SessionId) -> Result<(), SessionError>;
async fn subscribe_session_events(&self, id: &SessionId) -> Result<EventStream, StreamError> {
Err(StreamError::NotFound(format!("session {}", id)))
}
}
impl dyn SessionService {
pub fn into_arc(self: Box<Self>) -> Arc<dyn SessionService> {
Arc::from(self)
}
}