Skip to main content

Rust Client

A Rust client for interacting with the Pie server.

Installation

Add to your Cargo.toml:

[dependencies]
pie-client = "0.1"
tokio = { version = "1", features = ["full"] }
anyhow = "1"

Quick Start

use pie_client::{Client, InstanceEvent};
use anyhow::Result;

#[tokio::main]
async fn main() -> Result<()> {
// Connect to server
let client = Client::connect("ws://127.0.0.1:8080").await?;

// Authenticate
client.authenticate("username", &None).await?;

// Launch an inferlet from registry
let mut instance = client.launch_instance_from_registry(
"text-completion".to_string(),
vec!["--prompt".to_string(), "Hello, world!".to_string()],
false, // not detached
).await?;

// Receive output
loop {
match instance.recv().await? {
InstanceEvent::Stdout(text) => print!("{}", text),
InstanceEvent::Completed(_) => break,
InstanceEvent::Exception(msg) => {
eprintln!("Error: {}", msg);
break;
}
_ => {}
}
}

client.close()?;
Ok(())
}

Client

The main client struct for connecting to a Pie server.

Construction

let client = Client::connect("ws://127.0.0.1:8080").await?;

Methods

MethodReturnsDescription
connect(ws_host)Result<Client>Connect to server
close(self)Result<()>Close connection
authenticate(username, key)Result<()>Authenticate with server
internal_authenticate(token)Result<()>Internal token auth
upload_program(bytes)Result<()>Upload WASM program
program_exists(hash)Result<bool>Check if program exists
launch_instance(hash, args, detached)Result<Instance>Launch by hash
launch_instance_from_registry(name, args, detached)Result<Instance>Launch from registry
attach_instance(id)Result<Instance>Attach to existing
list_instances()Result<Vec<InstanceInfo>>List instances
terminate_instance(id)Result<()>Terminate instance
ping()Result<()>Check connectivity

Authentication

use pie_client::crypto::ParsedPrivateKey;

// With SSH key
let key = ParsedPrivateKey::from_file("~/.ssh/id_ed25519")?;
client.authenticate("username", &Some(key)).await?;

// Without key (when server auth is disabled)
client.authenticate("username", &None).await?;

Instance

Represents a running program instance.

Methods

MethodReturnsDescription
id()InstanceIdGet instance ID
send(message)Result<()>Send string message
upload_blob(bytes)Result<()>Upload binary data
recv()Result<InstanceEvent>Receive next event

Example

let mut instance = client.launch_instance_from_registry(
"chat".to_string(),
vec![],
false,
).await?;

// Send a message
instance.send("What is 2 + 2?")?;

// Receive response
match instance.recv().await? {
InstanceEvent::Message(text) => println!("{}", text),
InstanceEvent::Stdout(text) => print!("{}", text),
_ => {}
}

InstanceEvent

Events received from instances:

pub enum InstanceEvent {
/// Text message from instance
Message(String),
/// Instance completed successfully
Completed(String),
/// Instance was aborted
Aborted(String),
/// Instance raised an exception
Exception(String),
/// Server-side error
ServerError(String),
/// Resource limit reached
OutOfResources(String),
/// Binary data
Blob(Vec<u8>),
/// Streaming stdout
Stdout(String),
/// Streaming stderr
Stderr(String),
}

Pattern Matching

loop {
match instance.recv().await? {
InstanceEvent::Stdout(text) => {
print!("{}", text);
std::io::stdout().flush()?;
}
InstanceEvent::Stderr(text) => {
eprint!("{}", text);
}
InstanceEvent::Completed(msg) => {
println!("\nCompleted: {}", msg);
break;
}
InstanceEvent::Exception(err) => {
return Err(anyhow!("Inferlet error: {}", err));
}
InstanceEvent::ServerError(err) => {
return Err(anyhow!("Server error: {}", err));
}
_ => {}
}
}

Upload Custom Inferlet

use pie_client::hash_blob;

// Read and upload
let wasm_bytes = std::fs::read("my_inferlet.wasm")?;
client.upload_program(&wasm_bytes).await?;

// Get hash for launching
let program_hash = hash_blob(&wasm_bytes);

// Launch
let mut instance = client.launch_instance(
program_hash,
vec!["--arg".to_string(), "value".to_string()],
false,
).await?;

Detached Instances

// Launch detached
let instance = client.launch_instance_from_registry(
"long-task".to_string(),
vec![],
true, // detached
).await?;
let instance_id = instance.id();

// Later, reattach
let mut attached = client.attach_instance(&instance_id.to_string()).await?;
let event = attached.recv().await?;

Error Handling

use anyhow::{Result, Context};

async fn run() -> Result<()> {
let client = Client::connect("ws://127.0.0.1:8080")
.await
.context("Failed to connect to Pie server")?;

client.authenticate("username", &None)
.await
.context("Authentication failed")?;

// ...
Ok(())
}

Thread Safety

The Client is thread-safe and can be shared across tasks:

use std::sync::Arc;

let client = Arc::new(Client::connect("ws://127.0.0.1:8080").await?);

let c1 = client.clone();
let h1 = tokio::spawn(async move {
// Use c1
});

let c2 = client.clone();
let h2 = tokio::spawn(async move {
// Use c2
});

h1.await?;
h2.await?;