agent-workspace-adapter-local 0.3.0

Secure local filesystem Workspace adapter for Agent Infra
Documentation
use super::LocalWorkspace;
use agent_workspace_contract::{
    PreviewLifecycleProvider, ProvisionedWorkspace, StoredPreview, Workspace, WorkspaceConfig,
    WorkspaceProviderAdapter, WorkspaceProviderRegistry, WorkspaceRecord, WorkspaceResourceUsage,
};
use anyhow::{Context, Result};
use async_trait::async_trait;
use std::path::PathBuf;
use std::sync::Arc;

#[derive(Debug, Clone)]
pub struct LocalWorkspaceProvider {
    base_root: PathBuf,
}

impl LocalWorkspaceProvider {
    pub fn for_local_development(base_root: PathBuf) -> Result<Self> {
        std::fs::create_dir_all(&base_root)?;
        Ok(Self {
            base_root: std::fs::canonicalize(base_root)?,
        })
    }
    fn workspace_root(&self, id: &str) -> Result<PathBuf> {
        anyhow::ensure!(
            !id.is_empty()
                && id.len() <= 128
                && id
                    .bytes()
                    .all(|b| b.is_ascii_alphanumeric() || matches!(b, b'-' | b'_')),
            "invalid workspace id"
        );
        Ok(self.base_root.join(id))
    }
    fn checked_backend_root(&self, id: &str) -> Result<PathBuf> {
        let path = PathBuf::from(id);
        let parent = path
            .parent()
            .context("workspace backend root has no parent")?;
        anyhow::ensure!(
            std::fs::canonicalize(parent)? == self.base_root,
            "workspace backend escapes provider root"
        );
        Ok(path)
    }
}

#[async_trait]
impl WorkspaceProviderAdapter for LocalWorkspaceProvider {
    async fn provision(
        &self,
        _tenant_id: &str,
        workspace_id: &str,
        _config: &WorkspaceConfig,
        _key: &str,
    ) -> Result<ProvisionedWorkspace> {
        let root = self.workspace_root(workspace_id)?;
        tokio::fs::create_dir_all(&root).await?;
        let root = tokio::fs::canonicalize(root).await?;
        Ok(ProvisionedWorkspace {
            durable_backend_id: root.display().to_string(),
            root: root.display().to_string(),
        })
    }
    async fn reconnect(&self, id: &str) -> Result<()> {
        let root = self.checked_backend_root(id)?;
        anyhow::ensure!(
            tokio::fs::metadata(root).await?.is_dir(),
            "workspace backend is unavailable"
        );
        Ok(())
    }
    async fn suspend(&self, id: &str, _key: &str) -> Result<()> {
        self.reconnect(id).await
    }
    async fn resume(&self, id: &str, _key: &str) -> Result<()> {
        self.reconnect(id).await
    }
    async fn inspect(&self, record: &WorkspaceRecord) -> Result<WorkspaceResourceUsage> {
        LocalWorkspace::new(self.checked_backend_root(&record.durable_backend_id)?)
            .resource_usage()
            .await
    }
    async fn delete(&self, id: &str, key: &str) -> Result<()> {
        let root = self.checked_backend_root(id)?;
        if !tokio::fs::try_exists(&root).await? {
            return Ok(());
        }
        let trash = self.base_root.join(format!(
            ".trash-{}",
            agent_workspace_contract::content_revision(key.as_bytes())
                .trim_start_matches("sha256:")
        ));
        if !tokio::fs::try_exists(&trash).await? {
            tokio::fs::rename(root, trash).await?;
        }
        Ok(())
    }
    async fn cancel_command(&self, _id: &str, _command_id: &str) -> Result<()> {
        anyhow::bail!("local provider command cancellation is handled by LocalHostExecutor")
    }
}

#[derive(Debug, Clone)]
pub struct LocalWorkspaceProviderRegistry {
    provider: Arc<LocalWorkspaceProvider>,
}
impl LocalWorkspaceProviderRegistry {
    pub fn new(provider: LocalWorkspaceProvider) -> Self {
        Self {
            provider: Arc::new(provider),
        }
    }
}
#[async_trait]
impl WorkspaceProviderRegistry for LocalWorkspaceProviderRegistry {
    async fn provider(&self, backend: &str) -> Result<Arc<dyn WorkspaceProviderAdapter>> {
        anyhow::ensure!(
            matches!(backend, "local" | "local-test"),
            "unsupported local workspace backend"
        );
        Ok(self.provider.clone())
    }
}

#[derive(Debug, Clone, Default)]
pub struct LocalPreviewProvider;
#[async_trait]
impl PreviewLifecycleProvider for LocalPreviewProvider {
    async fn create_preview(
        &self,
        backend_id: &str,
        request_id: &str,
        port: u16,
        ttl_ms: u64,
    ) -> Result<StoredPreview> {
        anyhow::ensure!(
            !backend_id.trim().is_empty(),
            "workspace backend id is required"
        );
        Ok(StoredPreview {
            id: request_id.into(),
            tenant_id: String::new(),
            workspace_id: String::new(),
            provider_ref: format!("local:{backend_id}:{port}"),
            url: format!("http://localhost:{port}"),
            state: "ready".into(),
            expires_at_ms: ttl_ms,
            version: 1,
            request_digest: String::new(),
        })
    }
    async fn stop_preview(&self, provider_ref: &str) -> Result<()> {
        anyhow::ensure!(
            provider_ref.starts_with("local:"),
            "invalid local preview reference"
        );
        Ok(())
    }
}