Aelio
PlatformsRust

Rust

Implement the Aelio SDK in Rust using the wire protocol — community guide.

Status: No official Rust SDK yet. Rust in the Aelio ecosystem is the Sunjet/Astrolobe storage engine, not a Convox SDK target. This guide shows how to implement the wire protocol in Rust for backends built with Actix, Axum, or Tokio.

Dependencies

# Cargo.toml
[dependencies]
tokio = { version = "1", features = ["full"] }
tokio-tungstenite = { version = "0.21", features = ["native-tls"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
futures-util = "0.3"

Step 1: Types

use serde::{Deserialize, Serialize};
use std::collections::HashMap;

#[derive(Serialize)]
struct RegisterMessage {
    #[serde(rename = "type")]
    msg_type: String,
    functions: Vec<FunctionDef>,
    language: String,
    sdkVersion: String,
}

#[derive(Serialize)]
struct FunctionDef {
    name: String,
    description: String,
    params: HashMap<String, serde_json::Value>,
    safety: String,
}

#[derive(Deserialize)]
struct IncomingMessage {
    #[serde(rename = "type")]
    msg_type: String,
    id: Option<String>,
    function: Option<String>,
    args: Option<HashMap<String, serde_json::Value>>,
    context: Option<HashMap<String, serde_json::Value>>,
}

#[derive(Serialize)]
struct ResultMessage {
    #[serde(rename = "type")]
    msg_type: String,
    id: String,
    ok: bool,
    #[serde(skip_serializing_if = "Option::is_none")]
    data: Option<serde_json::Value>,
    #[serde(skip_serializing_if = "Option::is_none")]
    error: Option<ErrorBody>,
}

#[derive(Serialize)]
struct ErrorBody {
    code: String,
    message: String,
    retryable: bool,
}

type Handler = Box<dyn Fn(HashMap<String, serde_json::Value>, HashMap<String, serde_json::Value>)
    -> Result<serde_json::Value, String>
    + Send
    + Sync>;

Step 2: SDK client

use tokio_tungstenite::{connect_async, tungstenite::client::IntoClientRequest};
use futures_util::{StreamExt, SinkExt};

pub struct AelioSDK {
    secret: String,
    url: String,
    handlers: HashMap<String, Handler>,
    functions: Vec<FunctionDef>,
}

impl AelioSDK {
    pub fn new(secret: &str, url: &str) -> Self {
        Self {
            secret: secret.to_string(),
            url: format!("{}/sdk", url.trim_end_matches('/')),
            handlers: HashMap::new(),
            functions: Vec::new(),
        }
    }

    pub fn expose<F>(&mut self, name: &str, description: &str, safety: &str, handler: F)
    where
        F: Fn(HashMap<String, serde_json::Value>, HashMap<String, serde_json::Value>)
            -> Result<serde_json::Value, String>
            + Send
            + Sync
            + 'static,
    {
        self.handlers.insert(name.to_string(), Box::new(handler));
        self.functions.push(FunctionDef {
            name: name.to_string(),
            description: description.to_string(),
            params: HashMap::from([(
                "orderId".to_string(),
                serde_json::json!({"type": "string"}),
            )]),
            safety: safety.to_string(),
        });
    }

    pub async fn listen(&self) -> Result<(), Box<dyn std::error::Error>> {
        let mut request = self.url.as_str().into_client_request()?;
        request.headers_mut().insert(
            "Authorization",
            format!("Bearer {}", self.secret).parse()?,
        );

        let (ws, _) = connect_async(request).await?;
        let (mut write, mut read) = ws.split();

        // Register
        let register = RegisterMessage {
            msg_type: "register".to_string(),
            functions: self.functions.clone(),
            language: "rust".to_string(),
            sdkVersion: "0.1.0".to_string(),
        };
        write.send(serde_json::to_string(&register)?.into()).await?;
        println!("Aelio SDK connected (Rust)");

        // Listen
        while let Some(msg) = read.next().await {
            let text = msg?.into_text()?;
            let incoming: IncomingMessage = serde_json::from_str(&text)?;

            match incoming.msg_type.as_str() {
                "invoke" => {
                    let id = incoming.id.unwrap_or_default();
                    let function = incoming.function.unwrap_or_default();
                    let args = incoming.args.unwrap_or_default();
                    let ctx = incoming.context.unwrap_or_default();

                    let result = if let Some(handler) = self.handlers.get(&function) {
                        match handler(args, ctx) {
                            Ok(data) => ResultMessage {
                                msg_type: "result".to_string(),
                                id: id.clone(),
                                ok: true,
                                data: Some(data),
                                error: None,
                            },
                            Err(e) => ResultMessage {
                                msg_type: "result".to_string(),
                                id: id.clone(),
                                ok: false,
                                data: None,
                                error: Some(ErrorBody {
                                    code: "HANDLER_ERROR".to_string(),
                                    message: e,
                                    retryable: false,
                                }),
                            },
                        }
                    } else {
                        ResultMessage {
                            msg_type: "result".to_string(),
                            id: id.clone(),
                            ok: false,
                            data: None,
                            error: Some(ErrorBody {
                                code: "FUNCTION_NOT_FOUND".to_string(),
                                message: function,
                                retryable: false,
                            }),
                        }
                    };

                    write.send(serde_json::to_string(&result)?.into()).await?;
                }
                "ping" => {
                    write.send(r#"{"type":"pong"}"#.into()).await?;
                }
                _ => {}
            }
        }

        Ok(())
    }
}

Step 3: Usage

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut aelio = AelioSDK::new(
        &std::env::var("AELIO_SDK_SECRET").unwrap_or_default(),
        &std::env::var("AELIO_SERVER_URL").unwrap_or_else(|_| "ws://127.0.0.1:3010".to_string()),
    );

    aelio.expose("getOrderStatus", "Get order status", "read", |args, ctx| {
        Ok(serde_json::json!({
            "orderId": args.get("orderId"),
            "customerId": ctx.get("customerId"),
            "status": "shipped"
        }))
    });

    aelio.listen().await
}

Step 4: Run

AELIO_SDK_SECRET=change-me-in-production \
AELIO_SERVER_URL=ws://127.0.0.1:3010 \
cargo run

Axum integration

Run the SDK in a background Tokio task alongside your Axum server:

#[tokio::main]
async fn main() {
    let mut aelio = AelioSDK::new(&secret, &url);
    // ... expose tools ...

    tokio::spawn(async move {
        aelio.listen().await.expect("SDK connection failed");
    });

    let app = Router::new().route("/health", get(|| async { "ok" }));
    axum::serve(listener, app).await.unwrap();
}

Protocol reference

See SDK Wire Protocol for complete message schemas.

Reference implementations

  • Python SDK: sdk/python/sdk.py — ~330 lines
  • Protocol types: packages/protocol/src/index.ts
  • Note: Rust is used for Sunjet/Astrolobe storage engine, not as a primary SDK language

On this page