agent-workspace-contract 0.5.1

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

/// Options for directory listing and recursive search operations.
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
pub struct FileVisibilityOptions {
    #[serde(default)]
    pub include_hidden: bool,
    #[serde(default)]
    pub include_ignored: bool,
}

/// Opaque reference resolved only by a trusted adapter.  The secret value is
/// deliberately impossible to serialize or expose through `Debug`.
#[derive(Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub struct SecretRef {
    pub provider: String,
    pub key: String,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub version: Option<String>,
}

impl Debug for SecretRef {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter
            .debug_struct("SecretRef")
            .field("provider", &self.provider)
            .field("key", &"[REDACTED]")
            .field("version", &self.version.as_ref().map(|_| "[REDACTED]"))
            .finish()
    }
}

/// Provider-neutral provisioning input. Provider-specific SDK/configuration
/// belongs to an adapter crate; the contract contains only a backend kind,
/// opaque settings and secret references.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub struct WorkspaceConfig {
    pub backend: String,
    #[serde(default)]
    pub settings: BTreeMap<String, String>,
    #[serde(default)]
    pub secrets: BTreeMap<String, SecretRef>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub durable_backend_id: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub working_root: Option<PathBuf>,
}

impl Default for WorkspaceConfig {
    fn default() -> Self {
        Self {
            backend: "local".into(),
            settings: BTreeMap::new(),
            secrets: BTreeMap::new(),
            durable_backend_id: None,
            working_root: Some(std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."))),
        }
    }
}

/// Resolved durable backend ID discovered while initializing a workspace.
/// This remains provider-neutral on the wire.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(transparent)]
pub struct ResolvedBackendId(pub String);

impl ResolvedBackendId {
    pub fn as_str(&self) -> &str {
        &self.0
    }
}

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

    #[test]
    fn secret_ref_never_debugs_secret_coordinates() {
        let secret = SecretRef {
            provider: "vault".into(),
            key: "prod/workspace/api-token".into(),
            version: Some("42".into()),
        };
        let debug = format!("{secret:?}");
        assert!(!debug.contains("prod/workspace/api-token"));
        assert!(!debug.contains("42"));
        let json = serde_json::to_string(&secret).unwrap();
        assert!(!json.contains("token-value"));
    }

    #[test]
    fn provider_config_contains_references_not_plaintext_credentials() {
        let json = r#"{"backend":"daytona","settings":{"apiUrl":"https://provider"},"secrets":{"credential":{"provider":"vault","key":"workspace/daytona"}}}"#;
        let config: WorkspaceConfig = serde_json::from_str(json).unwrap();
        assert_eq!(config.backend, "daytona");
        assert_eq!(config.secrets["credential"].provider, "vault");
    }
}