Skip to content

feat: add basilica-sdk-rust high-level Rust SDK#1

Open
echobt wants to merge 2 commits intomainfrom
feat/basilica-sdk-rust
Open

feat: add basilica-sdk-rust high-level Rust SDK#1
echobt wants to merge 2 commits intomainfrom
feat/basilica-sdk-rust

Conversation

@echobt
Copy link

@echobt echobt commented Feb 5, 2026

Summary

This PR introduces a new high-level Rust SDK crate (basilica-sdk-rust) that mirrors the functionality of the Python SDK (basilica-sdk-python), providing an ergonomic API for Rust developers.

Features

BasilicaClient

  • deploy() - Deploy applications with source code, images, GPU/CPU resources
  • deploy_vllm() - vLLM inference server deployment template
  • deploy_sglang() - SGLang inference server deployment template
  • get() / list() - Deployment retrieval
  • Node listing and secure cloud rentals (GPU/CPU)
  • SSH key management
  • Balance and usage history

Deployment Facade

  • status() - Get current deployment status with phase information
  • wait_until_ready() - Poll until deployment is ready (with DNS/HTTP verification)
  • logs() - Retrieve deployment logs
  • delete() - Remove deployment
  • refresh() - Update cached state
  • Full async support

Supporting Components

  • DeploymentSpec - Immutable deployment configuration
  • Volume - Persistent storage references
  • SourcePackager - Source code packaging with framework detection (FastAPI, Flask, Django)
  • model_size module - GPU requirements estimation from model names
  • Comprehensive error types (BasilicaError hierarchy)

CI/CD Integration

  • Added basilica-sdk-rust to workspace Cargo.toml
  • Added change detection for the new crate
  • Added clippy linting in lint-complex job
  • Created dedicated test-rust-sdk job with:
    • Format checking (cargo fmt)
    • Clippy linting with -D warnings
    • Full build
    • Unit tests via nextest
    • Doc tests
  • Integrated into ci-success final status check

Testing

  • 62 unit tests passing
  • 18 doc tests passing
  • All clippy warnings resolved
  • Code formatted with cargo fmt

Quick Example

use basilica_sdk_rust::{BasilicaClient, VllmConfig};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = BasilicaClient::builder()
        .with_api_key("your-api-key")
        .build()?;

    // Deploy a vLLM inference server
    let deployment = client.deploy_vllm(VllmConfig {
        model: "Qwen/Qwen3-0.6B".to_string(),
        trust_remote_code: true,
        ..Default::default()
    }).await?;

    println!("Deployment ready at: {}", deployment.url());
    Ok(())
}

Checklist

  • New crate basilica-sdk-rust created under crates/ directory
  • BasilicaClient with deploy(), deploy_vllm(), deploy_sglang(), get(), list() methods
  • Deployment facade with status(), wait_until_ready(), logs(), delete(), refresh() methods
  • Volume and DeploymentSpec types implemented
  • SourcePackager with framework detection
  • Model size estimation module
  • Comprehensive error types matching Python SDK hierarchy
  • Crate added to workspace Cargo.toml
  • CI/CD workflow updated with build and test jobs
  • Code passes cargo fmt, cargo clippy, and cargo test

Summary by CodeRabbit

  • New Features

    • Rust SDK now available for building applications on the Basilica platform
    • Deploy custom applications, vLLM, and SGLang models with configurable GPU/CPU resources
    • Full deployment lifecycle management: monitor status, retrieve logs, await readiness
    • SSH key registration and secure GPU/CPU rental capabilities
    • Automatic GPU requirements estimation for models
  • Chores

    • Added CI/CD pipeline for Rust SDK testing and validation

Add new crate with the following modules:
- error.rs: Comprehensive error hierarchy (BasilicaError, DeploymentError)
- volume.rs: Volume type for persistent storage
- spec.rs: DeploymentSpec for deployment configuration
- model_size.rs: GPU requirements estimation from model names
- source.rs: SourcePackager for Python code packaging

All tests pass (62 unit tests + 18 doc tests).
- Add basilica-sdk-rust to CI change detection in paths-filter
- Add clippy check for basilica-sdk-rust in lint-complex job
- Create dedicated test-rust-sdk job with:
  - Format checking
  - Clippy linting with -D warnings
  - Full build
  - Unit tests via nextest
  - Doc tests
- Add test-rust-sdk to ci-success final status check

Clippy fixes:
- Use next_back() instead of last() for DoubleEndedIterator
- Remove unnecessary Ok wrapping with ? operator
- Use div_ceil() instead of manual implementation
@coderabbitai
Copy link

coderabbitai bot commented Feb 5, 2026

📝 Walkthrough

Walkthrough

Introduces a comprehensive Rust SDK for Basilica to the workspace, featuring high-level client APIs, deployment management, error handling, GPU requirement estimation, and Python source packaging. Updates CI/CD pipeline to test the new Rust SDK.

Changes

Cohort / File(s) Summary
CI/CD Integration
.github/workflows/ci.yml
Adds test-rust-sdk job with Rust toolchain setup, caching, protoc configuration, formatting checks (rustfmt), linting (clippy), build, and test execution. Configures path-based triggering for basilica-sdk-rust or workspace changes and integrates results into final CI success gate.
Workspace Configuration
Cargo.toml
Adds crates/basilica-sdk-rust as a new workspace member.
Rust SDK Manifest
crates/basilica-sdk-rust/Cargo.toml
Declares new crate with version 0.17.0, metadata, and dependencies on internal SDK crates (basilica-sdk, basilica-common) plus external crates (tokio, serde, thiserror, reqwest, etc.). Includes development dependencies for testing (wiremock, tempfile).
High-Level Client & Configurations
crates/basilica-sdk-rust/src/client.rs
Implements BasilicaClient with builder pattern for authentication and configuration. Exposes deployment operations (deploy, deploy_vllm, deploy_sglang, get, list), health/status checks, SSH key management, and secure cloud/CPU rental operations. Includes DeployConfig, VllmConfig, SglangConfig, and StorageConfig types with sensible defaults.
Deployment Management
crates/basilica-sdk-rust/src/deployment.rs
Provides Deployment struct wrapping low-level client with async operations: status retrieval, log fetching, readiness waiting (with optional progress callbacks and timeout), deletion, and refresh. Includes DeploymentStatus and ProgressInfo types for tracking deployment state and long-running operations.
Error Hierarchy
crates/basilica-sdk-rust/src/error.rs
Defines BasilicaError enum with variants (Authentication, Authorization, Validation, Deployment, Resource, Storage, Network, RateLimit, Source, Api) and DeploymentError enum (NotFound, Timeout, Failed). Provides constructors, retryability checks, and client-error classification.
Library Exports & Configuration
crates/basilica-sdk-rust/src/lib.rs
Establishes crate module structure (client, deployment, error, model_size, source, spec, volume) and re-exports public API surface from modules and underlying basilica_sdk for ergonomic access. Defines VERSION constant.
GPU Requirements Estimation
crates/basilica-sdk-rust/src/model_size.rs
Implements GpuRequirements struct and estimate_gpu_requirements() function that extracts parameter counts from model names via regex, derives memory requirements from parameters or model-family heuristics, calculates GPU count, and recommends GPU models based on memory requirements.
Python Source Packaging
crates/basilica-sdk-rust/src/source.rs
Provides SourcePackager type that auto-detects or explicitly handles file paths vs inline Python code. Includes framework detection (FastAPI, Flask, Django), command generation with optional pip package installation, and tilde expansion for file paths.
Deployment Specification
crates/basilica-sdk-rust/src/spec.rs
Introduces DeploymentSpec struct with default configuration and builder-style methods (with_gpu, with_replicas, with_env, with_pip_packages, with_health_check, etc.). Supports GPU settings, environment variables, volumes, pip packages, ttl, timeout, and health checks. Includes serialization support and comprehensive test coverage.
Volume Configuration
crates/basilica-sdk-rust/src/volume.rs
Defines Volume struct for persistent storage with fields (name, create_if_missing) and convenience constructors (from_name, existing, new). Includes serialization support and equality semantics.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🦫 A Rust SDK hops into view,
Type-safe deployments, shiny and new!
From GPU counts to health checks that glow,
Watch Basilica's Rust foundations grow! 🚀

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately describes the main change: introducing a new high-level Rust SDK crate for Basilica, which is the primary focus of all file modifications.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/basilica-sdk-rust

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In `@crates/basilica-sdk-rust/src/client.rs`:
- Around line 774-779: The GPU requirements block currently drops the entire
GpuRequirementsSpec when config.gpu_count is None, which can silently ignore
GPU-related fields; update the logic around config.gpu_count and
GpuRequirementsSpec to validate or infer a count: if config.gpu_count.is_none()
but any of config.gpu_models, config.min_cuda_version, or
config.min_gpu_memory_gb is set, either return an error (propagate a Result) or
set a sensible default (e.g., count = 1) before constructing GpuRequirementsSpec
so the GPU constraints are not silently discarded; locate the code building
gpu_spec (the map on config.gpu_count and the GpuRequirementsSpec construction)
and add the validation/inference there.

In `@crates/basilica-sdk-rust/src/deployment.rs`:
- Around line 392-395: In is_http_ready(), remove the insecure
`.danger_accept_invalid_certs(true)` call on the reqwest::Client::builder() used
to construct `client` so TLS certificate validation is enforced; update the
builder chain to omit that method and keep
`.timeout(HTTP_READY_TIMEOUT).build()` and handle the resulting Result from
`build()` as before (propagate or map the error from `client` creation) so
readiness checks validate certificates properly instead of bypassing TLS
verification.

In `@crates/basilica-sdk-rust/src/source.rs`:
- Around line 274-276: The heredoc uses a fixed delimiter "PYCODE" which can
collide with self.code; change the logic in the method that builds the python
command so it generates a unique delimiter (e.g.,
"PYCODE_<random/uuid/timestamp>") and ensure it does not appear in self.code
(loop/regenerate while self.code.contains(&delimiter)). Then use that delimiter
when constructing heredoc (the variable currently named heredoc) before pushing
it into parts; add any small dependency (uuid or rand) or use a timestamp-based
string to create the unique suffix.

In `@crates/basilica-sdk-rust/src/spec.rs`:
- Around line 3-16: The docs claim DeploymentSpec is "immutable" but its public
struct fields allow external mutation; update either the docs or the struct:
make the fields of the DeploymentSpec struct private (change pub fields to
non-pub) and provide read-only accessor methods (e.g., impl DeploymentSpec { pub
fn name(&self) -> &str { &self.name } } or similar for other fields) or
alternatively change the module-level doc comment to remove/soften the
"immutable" wording to reflect that fields are public and mutable; reference the
DeploymentSpec type and its public fields in src/spec.rs when making this change
so callers keep using the new getters or updated docs consistently.
🧹 Nitpick comments (1)
crates/basilica-sdk-rust/src/client.rs (1)

278-283: Avoid :latest image tags for reproducible deployments.

Pin to a known tag or expose the image/tag in VllmConfig/SglangConfig to avoid unexpected changes.

Also applies to: 409-414

Comment on lines +774 to +779
let gpu_spec = config.gpu_count.map(|count| GpuRequirementsSpec {
count,
model: config.gpu_models.clone().unwrap_or_default(),
min_cuda_version: config.min_cuda_version.clone(),
min_gpu_memory_gb: config.min_gpu_memory_gb,
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

GPU constraints can be silently ignored when gpu_count is None.

If gpu_models/min_cuda_version/min_gpu_memory_gb are set without gpu_count, the GPU spec is dropped and a CPU deployment results. Consider validating or inferring a default count.

✅ Add validation to avoid silent misconfiguration
+        if config.gpu_count.is_none()
+            && (config.gpu_models.is_some()
+                || config.min_cuda_version.is_some()
+                || config.min_gpu_memory_gb.is_some())
+        {
+            return Err(BasilicaError::Validation {
+                message: "gpu_count is required when specifying GPU constraints.".to_string(),
+                field: Some("gpu_count".to_string()),
+                value: None,
+            });
+        }
+
         let gpu_spec = config.gpu_count.map(|count| GpuRequirementsSpec {
             count,
             model: config.gpu_models.clone().unwrap_or_default(),
             min_cuda_version: config.min_cuda_version.clone(),
             min_gpu_memory_gb: config.min_gpu_memory_gb,
         });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let gpu_spec = config.gpu_count.map(|count| GpuRequirementsSpec {
count,
model: config.gpu_models.clone().unwrap_or_default(),
min_cuda_version: config.min_cuda_version.clone(),
min_gpu_memory_gb: config.min_gpu_memory_gb,
});
if config.gpu_count.is_none()
&& (config.gpu_models.is_some()
|| config.min_cuda_version.is_some()
|| config.min_gpu_memory_gb.is_some())
{
return Err(BasilicaError::Validation {
message: "gpu_count is required when specifying GPU constraints.".to_string(),
field: Some("gpu_count".to_string()),
value: None,
});
}
let gpu_spec = config.gpu_count.map(|count| GpuRequirementsSpec {
count,
model: config.gpu_models.clone().unwrap_or_default(),
min_cuda_version: config.min_cuda_version.clone(),
min_gpu_memory_gb: config.min_gpu_memory_gb,
});
🤖 Prompt for AI Agents
In `@crates/basilica-sdk-rust/src/client.rs` around lines 774 - 779, The GPU
requirements block currently drops the entire GpuRequirementsSpec when
config.gpu_count is None, which can silently ignore GPU-related fields; update
the logic around config.gpu_count and GpuRequirementsSpec to validate or infer a
count: if config.gpu_count.is_none() but any of config.gpu_models,
config.min_cuda_version, or config.min_gpu_memory_gb is set, either return an
error (propagate a Result) or set a sensible default (e.g., count = 1) before
constructing GpuRequirementsSpec so the GPU constraints are not silently
discarded; locate the code building gpu_spec (the map on config.gpu_count and
the GpuRequirementsSpec construction) and add the validation/inference there.

Comment on lines +392 to +395
let client = reqwest::Client::builder()
.timeout(HTTP_READY_TIMEOUT)
.danger_accept_invalid_certs(true)
.build();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's examine the file and context around lines 392-395
wc -l crates/basilica-sdk-rust/src/deployment.rs

Repository: PlatformNetwork/basilica

Length of output: 114


🏁 Script executed:

# Read the specific section with context
sed -n '380,410p' crates/basilica-sdk-rust/src/deployment.rs

Repository: PlatformNetwork/basilica

Length of output: 952


🏁 Script executed:

# Search for the function containing this code and any related comments
ast-grep --pattern 'let client = reqwest::Client::builder()'

Repository: PlatformNetwork/basilica

Length of output: 50


🏁 Script executed:

# Check if there are environment variables or config flags related to TLS
rg -i 'danger_accept_invalid_certs|tls|certificate|https' crates/basilica-sdk-rust/src/deployment.rs -A 2 -B 2

Repository: PlatformNetwork/basilica

Length of output: 226


🏁 Script executed:

# Search for where is_http_ready is called
rg 'is_http_ready' crates/basilica-sdk-rust/src/

Repository: PlatformNetwork/basilica

Length of output: 250


🏁 Script executed:

# Look for any configuration or environment variables that might control TLS validation
rg -i 'insecure|skip.*cert|tls.*skip' crates/basilica-sdk-rust/ --type rust

Repository: PlatformNetwork/basilica

Length of output: 50


🏁 Script executed:

# Check if there's documentation about the security model
find crates/basilica-sdk-rust -name '*.md' -o -name 'README*' | head -20

Repository: PlatformNetwork/basilica

Length of output: 50


🏁 Script executed:

# Look at the broader Deployment struct definition
sed -n '1,100p' crates/basilica-sdk-rust/src/deployment.rs

Repository: PlatformNetwork/basilica

Length of output: 3377


🏁 Script executed:

# Check if there are other HTTP client usages for comparison
rg 'reqwest::Client' crates/basilica-sdk-rust/src/ -B 2 -A 5

Repository: PlatformNetwork/basilica

Length of output: 696


Disable TLS verification bypass in is_http_ready() method.

The .danger_accept_invalid_certs(true) setting leaves the readiness check vulnerable to MITM attacks, where an attacker could forge responses and provide false positive signals that the service is ready. Remove this line to enforce proper TLS validation; self-signed certificates should be handled through proper infrastructure configuration, not by disabling validation in the SDK.

Proposed fix
         let client = reqwest::Client::builder()
             .timeout(HTTP_READY_TIMEOUT)
-            .danger_accept_invalid_certs(true)
             .build();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let client = reqwest::Client::builder()
.timeout(HTTP_READY_TIMEOUT)
.danger_accept_invalid_certs(true)
.build();
let client = reqwest::Client::builder()
.timeout(HTTP_READY_TIMEOUT)
.build();
🤖 Prompt for AI Agents
In `@crates/basilica-sdk-rust/src/deployment.rs` around lines 392 - 395, In
is_http_ready(), remove the insecure `.danger_accept_invalid_certs(true)` call
on the reqwest::Client::builder() used to construct `client` so TLS certificate
validation is enforced; update the builder chain to omit that method and keep
`.timeout(HTTP_READY_TIMEOUT).build()` and handle the resulting Result from
`build()` as before (propagate or map the error from `client` creation) so
readiness checks validate certificates properly instead of bypassing TLS
verification.

Comment on lines +274 to +276
// Build the python command with heredoc
let heredoc = format!("python3 - <<'PYCODE'\n{}\nPYCODE\n", self.code);
parts.push(heredoc);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Guard against heredoc delimiter collisions.

If the code contains a line equal to PYCODE, the heredoc will terminate early and truncate execution. Pick a delimiter that doesn't appear in the code.

🔧 Safer heredoc delimiter
-        let heredoc = format!("python3 - <<'PYCODE'\n{}\nPYCODE\n", self.code);
+        let mut delimiter = "PYCODE".to_string();
+        while self.code.contains(&delimiter) {
+            delimiter.push('_');
+        }
+        let heredoc = format!(
+            "python3 - <<'{d}'\n{code}\n{d}\n",
+            d = delimiter,
+            code = self.code
+        );
🤖 Prompt for AI Agents
In `@crates/basilica-sdk-rust/src/source.rs` around lines 274 - 276, The heredoc
uses a fixed delimiter "PYCODE" which can collide with self.code; change the
logic in the method that builds the python command so it generates a unique
delimiter (e.g., "PYCODE_<random/uuid/timestamp>") and ensure it does not appear
in self.code (loop/regenerate while self.code.contains(&delimiter)). Then use
that delimiter when constructing heredoc (the variable currently named heredoc)
before pushing it into parts; add any small dependency (uuid or rand) or use a
timestamp-based string to create the unique suffix.

Comment on lines +3 to +16
//! This module provides [`DeploymentSpec`], an immutable configuration
//! for defining how deployments should be created and configured.

use crate::Volume;
use basilica_sdk::HealthCheckConfig;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

/// Immutable specification for a deployment.
///
/// `DeploymentSpec` defines all the configuration needed to create a deployment
/// on the Basilica GPU cloud platform. It includes container settings, resource
/// requirements, networking, and optional GPU specifications.
///
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Docs say immutable but fields are public.

Public fields allow mutation, so the "immutable" wording is misleading. Either adjust the docs or make fields private with setters.

📝 Suggested doc tweak
-//! This module provides [`DeploymentSpec`], an immutable configuration
+//! This module provides [`DeploymentSpec`], a configuration
@@
-/// Immutable specification for a deployment.
+/// Specification for a deployment.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
//! This module provides [`DeploymentSpec`], an immutable configuration
//! for defining how deployments should be created and configured.
use crate::Volume;
use basilica_sdk::HealthCheckConfig;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
/// Immutable specification for a deployment.
///
/// `DeploymentSpec` defines all the configuration needed to create a deployment
/// on the Basilica GPU cloud platform. It includes container settings, resource
/// requirements, networking, and optional GPU specifications.
///
//! This module provides [`DeploymentSpec`], a configuration
//! for defining how deployments should be created and configured.
use crate::Volume;
use basilica_sdk::HealthCheckConfig;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
/// Specification for a deployment.
///
/// `DeploymentSpec` defines all the configuration needed to create a deployment
/// on the Basilica GPU cloud platform. It includes container settings, resource
/// requirements, networking, and optional GPU specifications.
///
🤖 Prompt for AI Agents
In `@crates/basilica-sdk-rust/src/spec.rs` around lines 3 - 16, The docs claim
DeploymentSpec is "immutable" but its public struct fields allow external
mutation; update either the docs or the struct: make the fields of the
DeploymentSpec struct private (change pub fields to non-pub) and provide
read-only accessor methods (e.g., impl DeploymentSpec { pub fn name(&self) ->
&str { &self.name } } or similar for other fields) or alternatively change the
module-level doc comment to remove/soften the "immutable" wording to reflect
that fields are public and mutable; reference the DeploymentSpec type and its
public fields in src/spec.rs when making this change so callers keep using the
new getters or updated docs consistently.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant