use std::future::Future;
use tools_kit::ToolsetBackend;
use providers_kit::ProviderBackend;
use storage_kit::{DatabaseBackend, StateStoreBackend};
use crate::backends::{codex, custom, opencode};
use crate::{
AgentId, DeleteWorkspaceRequest, IngestKnowledgeRequest, KitError, ModelInfo, NewWorkspace,
UpdateWorkspaceRequest, Workspace, WorkspaceId, WorkspaceInfo, WorkspaceUpdateReadiness,
};
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct AgentInfo {
id: AgentId,
display_name: Option<String>,
}
impl AgentInfo {
pub fn new(id: AgentId, display_name: Option<String>) -> Result<Self, KitError> {
if display_name
.as_deref()
.is_some_and(|value| value.is_empty() || value.trim() != value)
{
return Err(KitError::invalid_input(
"agent display name must be nonempty and unpadded when present",
));
}
Ok(Self { id, display_name })
}
pub fn id(&self) -> &AgentId {
&self.id
}
pub fn display_name(&self) -> Option<&str> {
self.display_name.as_deref()
}
}
pub trait AgentBackend: Send + Sync + 'static {
type Workspace: crate::WorkspaceBackend;
fn id(&self) -> AgentId;
fn info(&self) -> impl Future<Output = Result<AgentInfo, KitError>> + Send + '_;
fn new_workspace(
&self,
request: NewWorkspace,
) -> impl Future<Output = Result<Self::Workspace, KitError>> + Send + '_;
fn list_workspaces(
&self,
) -> impl Future<Output = Result<Vec<WorkspaceInfo>, KitError>> + Send + '_;
fn workspace_info(
&self,
id: WorkspaceId,
) -> impl Future<Output = Result<WorkspaceInfo, KitError>> + Send + '_;
fn get_workspace(
&self,
id: WorkspaceId,
) -> impl Future<Output = Result<Self::Workspace, KitError>> + Send + '_;
fn check_workspace_update(
&self,
request: &UpdateWorkspaceRequest,
) -> impl Future<Output = Result<WorkspaceUpdateReadiness, KitError>> + Send + '_;
fn update_workspace(
&self,
request: UpdateWorkspaceRequest,
) -> impl Future<Output = Result<WorkspaceInfo, KitError>> + Send + '_;
fn delete_workspace(
&self,
request: DeleteWorkspaceRequest,
) -> impl Future<Output = Result<(), KitError>> + Send + '_;
fn list_models(&self) -> impl Future<Output = Result<Vec<ModelInfo>, KitError>> + Send + '_;
fn ingest_knowledge(
&self,
request: IngestKnowledgeRequest,
) -> impl Future<Output = Result<(), KitError>> + Send + '_;
fn shutdown(self) -> impl Future<Output = Result<(), KitError>> + Send
where
Self: Sized;
}
pub struct Agent<
S: StateStoreBackend = storage_kit::StateStore,
D: DatabaseBackend = storage_kit::Database,
P: ProviderBackend = providers_kit::Provider,
T: ToolsetBackend = tools_kit::Toolset,
> {
inner: AgentInner<S, D, P, T>,
}
enum AgentInner<S: StateStoreBackend, D: DatabaseBackend, P: ProviderBackend, T: ToolsetBackend> {
Codex(Box<codex::agent::CodexAgent<S, D, T>>),
OpenCode(Box<opencode::agent::OpenCodeAgent<S, D>>),
Custom(Box<custom::CustomAgent<S, D, P, T>>),
}
impl<S, D, P, T> Agent<S, D, P, T>
where
S: StateStoreBackend,
D: DatabaseBackend,
P: ProviderBackend,
T: ToolsetBackend,
{
pub(crate) fn codex(backend: codex::agent::CodexAgent<S, D, T>) -> Self {
Self {
inner: AgentInner::Codex(Box::new(backend)),
}
}
pub(crate) fn opencode(backend: opencode::agent::OpenCodeAgent<S, D>) -> Self {
Self {
inner: AgentInner::OpenCode(Box::new(backend)),
}
}
pub(crate) fn custom(backend: custom::CustomAgent<S, D, P, T>) -> Self {
Self {
inner: AgentInner::Custom(Box::new(backend)),
}
}
pub(crate) fn retain_storage_claim(&mut self) {
match &mut self.inner {
AgentInner::Codex(backend) => backend.retain_storage_claim(),
AgentInner::OpenCode(backend) => backend.retain_storage_claim(),
AgentInner::Custom(backend) => backend.retain_storage_claim(),
}
}
}
impl<S, D, P, T> AgentBackend for AgentInner<S, D, P, T>
where
S: StateStoreBackend,
D: DatabaseBackend,
P: ProviderBackend,
T: ToolsetBackend,
{
type Workspace = Workspace<S, D, P, T>;
fn id(&self) -> AgentId {
match self {
AgentInner::Codex(backend) => backend.id(),
AgentInner::OpenCode(backend) => backend.id(),
AgentInner::Custom(backend) => backend.id(),
}
}
async fn info(&self) -> Result<AgentInfo, KitError> {
match self {
AgentInner::Codex(backend) => backend.info().await,
AgentInner::OpenCode(backend) => backend.info().await,
AgentInner::Custom(backend) => backend.info().await,
}
}
async fn new_workspace(
&self,
request: NewWorkspace,
) -> Result<Workspace<S, D, P, T>, KitError> {
match self {
AgentInner::Codex(backend) => {
backend.new_workspace(request).await.map(Workspace::codex)
}
AgentInner::OpenCode(backend) => backend
.new_workspace(request)
.await
.map(Workspace::opencode),
AgentInner::Custom(backend) => {
backend.new_workspace(request).await.map(Workspace::custom)
}
}
}
async fn list_workspaces(&self) -> Result<Vec<WorkspaceInfo>, KitError> {
match self {
AgentInner::Codex(backend) => backend.list_workspaces().await,
AgentInner::OpenCode(backend) => backend.list_workspaces().await,
AgentInner::Custom(backend) => backend.list_workspaces().await,
}
}
async fn workspace_info(&self, id: WorkspaceId) -> Result<WorkspaceInfo, KitError> {
match self {
AgentInner::Codex(backend) => backend.workspace_info(id).await,
AgentInner::OpenCode(backend) => backend.workspace_info(id).await,
AgentInner::Custom(backend) => backend.workspace_info(id).await,
}
}
async fn get_workspace(&self, id: WorkspaceId) -> Result<Workspace<S, D, P, T>, KitError> {
match self {
AgentInner::Codex(backend) => backend.get_workspace(id).await.map(Workspace::codex),
AgentInner::OpenCode(backend) => {
backend.get_workspace(id).await.map(Workspace::opencode)
}
AgentInner::Custom(backend) => backend.get_workspace(id).await.map(Workspace::custom),
}
}
fn check_workspace_update(
&self,
request: &UpdateWorkspaceRequest,
) -> impl Future<Output = Result<WorkspaceUpdateReadiness, KitError>> + Send + '_ {
let request = request.clone();
async move {
match self {
AgentInner::Codex(backend) => backend.check_workspace_update(&request).await,
AgentInner::OpenCode(backend) => backend.check_workspace_update(&request).await,
AgentInner::Custom(backend) => backend.check_workspace_update(&request).await,
}
}
}
async fn update_workspace(
&self,
request: UpdateWorkspaceRequest,
) -> Result<WorkspaceInfo, KitError> {
match self {
AgentInner::Codex(backend) => backend.update_workspace(request).await,
AgentInner::OpenCode(backend) => backend.update_workspace(request).await,
AgentInner::Custom(backend) => backend.update_workspace(request).await,
}
}
async fn delete_workspace(&self, request: DeleteWorkspaceRequest) -> Result<(), KitError> {
match self {
AgentInner::Codex(backend) => backend.delete_workspace(request).await,
AgentInner::OpenCode(backend) => backend.delete_workspace(request).await,
AgentInner::Custom(backend) => backend.delete_workspace(request).await,
}
}
async fn list_models(&self) -> Result<Vec<ModelInfo>, KitError> {
match self {
AgentInner::Codex(backend) => backend.list_models().await,
AgentInner::OpenCode(backend) => backend.list_models().await,
AgentInner::Custom(backend) => backend.list_models().await,
}
}
async fn ingest_knowledge(&self, request: IngestKnowledgeRequest) -> Result<(), KitError> {
match self {
AgentInner::Codex(backend) => backend.ingest_knowledge(request).await,
AgentInner::OpenCode(backend) => backend.ingest_knowledge(request).await,
AgentInner::Custom(backend) => backend.ingest_knowledge(request).await,
}
}
async fn shutdown(self) -> Result<(), KitError> {
match self {
AgentInner::Codex(backend) => (*backend).shutdown().await,
AgentInner::OpenCode(backend) => (*backend).shutdown().await,
AgentInner::Custom(backend) => (*backend).shutdown().await,
}
}
}
impl<S, D, P, T> AgentBackend for Agent<S, D, P, T>
where
S: StateStoreBackend,
D: DatabaseBackend,
P: ProviderBackend,
T: ToolsetBackend,
{
type Workspace = Workspace<S, D, P, T>;
fn id(&self) -> AgentId {
self.inner.id()
}
async fn info(&self) -> Result<AgentInfo, KitError> {
self.inner.info().await.map_err(|error| {
error
.at_operation(crate::error::Operation::AgentInfo)
.in_phase(crate::error::ErrorPhase::StateRead)
})
}
async fn new_workspace(
&self,
request: NewWorkspace,
) -> Result<Workspace<S, D, P, T>, KitError> {
self.inner.new_workspace(request).await.map_err(|error| {
error
.at_operation(crate::error::Operation::NewWorkspace)
.in_phase(crate::error::ErrorPhase::Admission)
})
}
async fn list_workspaces(&self) -> Result<Vec<WorkspaceInfo>, KitError> {
self.inner.list_workspaces().await.map_err(|error| {
error
.at_operation(crate::error::Operation::ListWorkspaces)
.in_phase(crate::error::ErrorPhase::StateRead)
})
}
async fn workspace_info(&self, id: WorkspaceId) -> Result<WorkspaceInfo, KitError> {
self.inner.workspace_info(id).await.map_err(|error| {
error
.at_operation(crate::error::Operation::WorkspaceInfo)
.in_phase(crate::error::ErrorPhase::StateRead)
})
}
async fn get_workspace(&self, id: WorkspaceId) -> Result<Workspace<S, D, P, T>, KitError> {
self.inner.get_workspace(id).await.map_err(|error| {
error
.at_operation(crate::error::Operation::GetWorkspace)
.in_phase(crate::error::ErrorPhase::Admission)
})
}
fn check_workspace_update(
&self,
request: &UpdateWorkspaceRequest,
) -> impl Future<Output = Result<WorkspaceUpdateReadiness, KitError>> + Send + '_ {
let check = self.inner.check_workspace_update(request);
async move {
check.await.map_err(|error| {
error
.at_operation(crate::error::Operation::CheckWorkspaceUpdate)
.in_phase(crate::error::ErrorPhase::InputValidation)
})
}
}
async fn update_workspace(
&self,
request: UpdateWorkspaceRequest,
) -> Result<WorkspaceInfo, KitError> {
self.inner.update_workspace(request).await.map_err(|error| {
error
.at_operation(crate::error::Operation::UpdateWorkspace)
.in_phase(crate::error::ErrorPhase::Admission)
})
}
async fn delete_workspace(&self, request: DeleteWorkspaceRequest) -> Result<(), KitError> {
self.inner.delete_workspace(request).await.map_err(|error| {
error
.at_operation(crate::error::Operation::DeleteWorkspace)
.in_phase(crate::error::ErrorPhase::Admission)
})
}
async fn list_models(&self) -> Result<Vec<ModelInfo>, KitError> {
self.inner.list_models().await.map_err(|error| {
error
.at_operation(crate::error::Operation::ListModels)
.in_phase(crate::error::ErrorPhase::InventoryVerification)
})
}
async fn ingest_knowledge(&self, request: IngestKnowledgeRequest) -> Result<(), KitError> {
self.inner.ingest_knowledge(request).await.map_err(|error| {
error
.at_operation(crate::error::Operation::IngestKnowledge)
.in_phase(crate::error::ErrorPhase::KnowledgeWrite)
})
}
async fn shutdown(self) -> Result<(), KitError> {
self.inner.shutdown().await.map_err(|error| {
error
.at_operation(crate::error::Operation::Shutdown)
.in_phase(crate::error::ErrorPhase::Lifecycle)
})
}
}