anyclaw-sdk-runtime 0.1.0

SDK for building anyclaw runtime extensions (container + proxy + network)
Documentation
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

/// Parameters sent to a runtime during the `initialize` handshake.
#[derive(Debug, Clone, Deserialize)]
pub struct RuntimeInitializeParams {
    /// Deployment namespace for scoping resources (networks, volumes).
    pub namespace: String,
    /// Runtime-specific configuration from `anyclaw.yaml`.
    #[serde(default)]
    pub options: HashMap<String, serde_json::Value>,
}

/// Result returned by a runtime after successful initialization.
#[derive(Debug, Clone, Serialize)]
pub struct RuntimeInitializeResult {
    /// Environment variables that should be set on exec'd processes.
    /// E.g., `HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY`.
    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
    pub env: HashMap<String, String>,
    /// Workspace root path inside the runtime environment.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub workspace_root: Option<String>,
}

/// Request to execute a process inside the runtime environment.
#[derive(Debug, Clone, Deserialize)]
pub struct ExecRequest {
    /// Command and arguments to execute.
    pub cmd: Vec<String>,
    /// Environment variables to set on the process (merged with runtime env).
    #[serde(default)]
    pub env: HashMap<String, String>,
    /// Working directory inside the environment.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub working_dir: Option<String>,
}

/// Result of starting a process inside the runtime.
#[derive(Debug, Clone, Serialize)]
pub struct ExecResult {
    /// Opaque process identifier (runtime-specific).
    pub process_id: String,
    /// Unix socket path for bidirectional stdio (stdin + stdout).
    /// The supervisor connects to this socket to communicate with the process.
    pub stdio_addr: String,
    /// Unix socket path for stderr stream.
    pub stderr_addr: String,
}

/// Health status reported by a runtime.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RuntimeHealthStatus {
    /// Current health state.
    pub status: RuntimeHealth,
    /// Optional detail message.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub message: Option<String>,
}

/// Possible health states for a runtime.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum RuntimeHealth {
    /// Runtime environment is fully operational.
    Healthy,
    /// Runtime is operational but experiencing issues.
    Degraded,
    /// Runtime is not operational.
    Unhealthy,
}

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

    #[rstest]
    fn when_init_params_deserialized_then_fields_present() {
        let json = r#"{"namespace": "anyclaw-prod", "options": {"image": "ubuntu:22.04"}}"#;
        let params: RuntimeInitializeParams = serde_json::from_str(json).unwrap();
        assert_eq!(params.namespace, "anyclaw-prod");
        assert_eq!(
            params.options.get("image").and_then(|v| v.as_str()),
            Some("ubuntu:22.04")
        );
    }

    #[rstest]
    fn when_exec_request_deserialized_then_cmd_present() {
        let json = r#"{"cmd": ["/usr/bin/agent", "--stdio"], "env": {"FOO": "bar"}}"#;
        let req: ExecRequest = serde_json::from_str(json).unwrap();
        assert_eq!(req.cmd, vec!["/usr/bin/agent", "--stdio"]);
        assert_eq!(req.env.get("FOO").map(String::as_str), Some("bar"));
    }

    #[rstest]
    fn when_exec_result_serialized_then_contains_addrs() {
        let result = ExecResult {
            process_id: "proc-1".into(),
            stdio_addr: "/tmp/anyclaw/proc-1.sock".into(),
            stderr_addr: "/tmp/anyclaw/proc-1-err.sock".into(),
        };
        let json = serde_json::to_value(&result).unwrap();
        assert_eq!(json["process_id"], "proc-1");
        assert_eq!(json["stdio_addr"], "/tmp/anyclaw/proc-1.sock");
    }

    #[rstest]
    fn when_health_status_serialized_then_snake_case() {
        let status = RuntimeHealthStatus {
            status: RuntimeHealth::Healthy,
            message: None,
        };
        let json = serde_json::to_value(&status).unwrap();
        assert_eq!(json["status"], "healthy");
    }
}