use async_trait::async_trait;
use everruns_core::session::ExecutionSession;
use everruns_platform::Agent;
use everruns_platform::Harness;
use everruns_platform::{PlatformCreateSessionRequest, PlatformMessage, PlatformStore};
use everruns_platform::{Session, SessionParticipant};
use everruns_provider::error::{AgentLoopError, Result};
use everruns_provider::typed_id::PrincipalId;
use everruns_provider::typed_id::{AgentId, HarnessId, SessionId};
use std::sync::Arc;
#[async_trait]
pub trait LocalSessionRunner: Send + Sync {
async fn routable_session_ids(&self) -> Result<Option<Vec<SessionId>>> {
Ok(None)
}
async fn create_session(
&self,
harness_id: HarnessId,
agent_id: Option<AgentId>,
title: Option<&str>,
locale: Option<&str>,
parent_session_id: Option<SessionId>,
) -> Result<ExecutionSession>;
async fn create_session_with_options(
&self,
request: PlatformCreateSessionRequest,
) -> Result<ExecutionSession> {
if request.forked_from_session_id.is_some()
|| request.budget_root_session_id.is_some()
|| request.seed != everruns_core::session::SessionSeedMode::Fresh
{
return Err(unsupported("create_session(seed)"));
}
let mut session = self
.create_session(
request.harness_id,
request.agent_id,
request.title.as_deref(),
request.locale.as_deref(),
request.parent_session_id,
)
.await?;
session.goal = request.goal;
Ok(session)
}
async fn send_message(&self, session_id: SessionId, content: &str) -> Result<()>;
async fn list_sessions(
&self,
limit: Option<usize>,
agent_id: Option<AgentId>,
) -> Result<Vec<ExecutionSession>>;
async fn get_session(&self, session_id: SessionId) -> Result<Option<ExecutionSession>>;
async fn get_messages(
&self,
session_id: SessionId,
limit: Option<usize>,
) -> Result<Vec<PlatformMessage>>;
async fn get_session_status(&self, session_id: SessionId) -> Result<Option<String>>;
}
fn unsupported(op: &str) -> AgentLoopError {
AgentLoopError::tool(format!(
"operation '{op}' is not supported by the local platform store; \
manage this entity in embedder code"
))
}
#[derive(Clone)]
pub struct LocalPlatformStore {
runner: Arc<dyn LocalSessionRunner>,
}
impl LocalPlatformStore {
pub fn new(runner: Arc<dyn LocalSessionRunner>) -> Self {
Self { runner }
}
fn lift(&self, session: ExecutionSession) -> Session {
Session::from_execution_session(session, PrincipalId::from_seed(1))
}
}
#[async_trait]
impl PlatformStore for LocalPlatformStore {
async fn create_session_with_options(
&self,
request: PlatformCreateSessionRequest,
) -> Result<Session> {
if request.blueprint_id.is_some() {
return Err(unsupported("create_session(blueprint)"));
}
Ok(self.lift(self.runner.create_session_with_options(request).await?))
}
async fn get_session_by_id(&self, id: SessionId) -> Result<Option<Session>> {
Ok(self
.runner
.get_session(id)
.await?
.map(|session| self.lift(session)))
}
async fn add_agent_session_participant(
&self,
_session_id: SessionId,
_agent_id: AgentId,
) -> Result<SessionParticipant> {
Err(unsupported("add_agent_session_participant"))
}
async fn send_message(&self, session_id: SessionId, content: &str) -> Result<()> {
self.runner.send_message(session_id, content).await
}
async fn get_messages(
&self,
session_id: SessionId,
limit: Option<usize>,
) -> Result<Vec<PlatformMessage>> {
self.runner.get_messages(session_id, limit).await
}
async fn wait_for_idle(
&self,
session_id: SessionId,
_timeout_secs: Option<u64>,
) -> Result<String> {
self.runner
.get_session_status(session_id)
.await?
.ok_or_else(|| AgentLoopError::session_not_found(session_id))
}
async fn get_harness(&self, _id: HarnessId) -> Result<Option<Harness>> {
Err(unsupported("get_harness"))
}
async fn get_agent_by_id(&self, _id: AgentId) -> Result<Option<Agent>> {
Err(unsupported("get_agent_by_id"))
}
}