agent-workspace-contract 0.1.0

Transport-neutral contracts for Agent Infra workspace APIs
Documentation
use super::*;

pub const WORKSPACE_VERSION_CONFLICT_CODE: &str = "VERSION_CONFLICT";

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct WorkspaceVersionConflict {
    pub expected: String,
    pub actual: Option<String>,
}

impl fmt::Display for WorkspaceVersionConflict {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            formatter,
            "workspace file revision conflict: expected {}, actual {}",
            self.expected,
            self.actual.as_deref().unwrap_or("<missing>")
        )
    }
}

impl std::error::Error for WorkspaceVersionConflict {}

/// Collision-resistant content revision shared by CAS and idempotency paths.
pub fn content_revision(bytes: &[u8]) -> String {
    use sha2::{Digest as _, Sha256};
    format!("sha256:{:x}", Sha256::digest(bytes))
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct VersionedFile {
    pub bytes: Vec<u8>,
    pub revision: String,
}

/// A directory entry returned by `list_dir`.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct DirEntry {
    pub name: String,
    pub is_dir: bool,
    pub size: Option<u64>,
}

#[cfg(test)]
mod revision_tests {
    use super::*;

    #[test]
    fn revision_is_sha256_and_separates_known_fnv_collision_inputs() {
        // "costarring" and "liquid" are a well-known FNV-1a-32 collision.
        // Security revisions no longer use any FNV width or accept its output.
        let left = content_revision(b"costarring");
        let right = content_revision(b"liquid");
        assert_ne!(left, right);
        for revision in [left, right] {
            let digest = revision.strip_prefix("sha256:").unwrap();
            assert_eq!(digest.len(), 64);
            assert!(digest.bytes().all(|byte| byte.is_ascii_hexdigit()));
        }
    }
}

/// Output of a command execution.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CmdOutput {
    pub exit_code: i32,
    pub stdout: String,
    pub stderr: String,
    pub stdout_truncated: bool,
    pub stderr_truncated: bool,
}

/// Public preview URL for a port exposed by a workspace backend.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PortUrl {
    pub port: u16,
    pub url: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub host: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    /// Opaque credential reference; preview bearer values never enter the
    /// resource JSON or Debug output.
    pub token: Option<SecretRef>,
    #[serde(default)]
    pub signed: bool,
    /// Effective lifetime returned by a signed-preview provider.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub expires_in_seconds: Option<u64>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct StaticPreviewServer {
    pub id: String,
    pub port: u16,
}

/// A grep match result.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct GrepMatch {
    pub file: String,
    pub line_number: usize,
    pub line: String,
}