vendor: update cargo-cxai-sdk-0.1.0

This commit is contained in:
cx-git-agent
2026-04-26 16:48:14 +00:00
committed by GitHub
parent 09027db52b
commit dae5a214a6
31 changed files with 4363 additions and 3 deletions
+6
View File
@@ -0,0 +1,6 @@
{
"git": {
"sha1": "d878deb8441897ecdd416011b49d2d2f6112e867"
},
"path_in_vcs": "crates/cxai-sdk"
}
Generated
+1926
View File
File diff suppressed because it is too large Load Diff
+86
View File
@@ -0,0 +1,86 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2024"
name = "cxai-sdk"
version = "0.1.0"
authors = ["CxAI-LLM <agent@cxai-studio.com>"]
build = false
autolib = false
autobins = false
autoexamples = false
autotests = false
autobenches = false
description = "Core SDK for the CxAI Platform — typed HTTP clients, models, resilience, and configuration"
homepage = "https://cxllm.io"
readme = false
license = "MIT"
repository = "https://git.cxllm-studio.com/CxAI-LLM/CxAI.Rust"
resolver = "2"
[lib]
name = "cxai_sdk"
path = "src/lib.rs"
[dependencies.anyhow]
version = "1"
[dependencies.async-trait]
version = "0.1"
[dependencies.backon]
version = "1"
[dependencies.chrono]
version = "0.4"
features = ["serde"]
[dependencies.reqwest]
version = "0.12"
features = [
"json",
"rustls-tls",
"stream",
]
default-features = false
[dependencies.serde]
version = "1"
features = ["derive"]
[dependencies.serde_json]
version = "1"
[dependencies.thiserror]
version = "2"
[dependencies.tokio]
version = "1"
features = ["full"]
[dependencies.tracing]
version = "0.1"
[dependencies.uuid]
version = "1"
features = [
"v4",
"serde",
]
[dev-dependencies.tokio]
version = "1"
features = [
"full",
"test-util",
"macros",
]
+25
View File
@@ -0,0 +1,25 @@
[package]
name = "cxai-sdk"
description = "Core SDK for the CxAI Platform — typed HTTP clients, models, resilience, and configuration"
version.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
homepage.workspace = true
authors.workspace = true
[dependencies]
tokio = { workspace = true }
reqwest = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
thiserror = { workspace = true }
anyhow = { workspace = true }
tracing = { workspace = true }
chrono = { workspace = true }
uuid = { workspace = true }
backon = { workspace = true }
async-trait = { workspace = true }
[dev-dependencies]
tokio = { workspace = true, features = ["test-util", "macros"] }
-3
View File
@@ -1,3 +0,0 @@
# cargo-cxai-sdk-0.1.0
Cargo crate: cxai-sdk-0.1.0
+66
View File
@@ -0,0 +1,66 @@
use std::sync::Arc;
use crate::config::PlatformConfig;
use crate::error::{CxError, CxResult};
use crate::models::common::HealthResponse;
/// App service client — application gateway, auth, chat, inference.
#[derive(Clone)]
pub struct AppClient {
http: reqwest::Client,
config: Arc<PlatformConfig>,
}
impl AppClient {
pub fn new(http: reqwest::Client, config: Arc<PlatformConfig>) -> Self {
Self { http, config }
}
fn url(&self, path: &str) -> String {
format!(
"{}/api/app{}",
self.config.service_url(self.config.app_port),
path
)
}
pub async fn health(&self) -> CxResult<HealthResponse> {
let resp = self.http.get(self.url("/healthz")).send().await?;
if !resp.status().is_success() {
return Err(CxError::from_response(resp).await);
}
Ok(resp.json().await?)
}
pub async fn chat(
&self,
message: &str,
session_id: Option<&str>,
) -> CxResult<serde_json::Value> {
let mut body = serde_json::json!({ "message": message });
if let Some(sid) = session_id {
body["sessionId"] = serde_json::json!(sid);
}
let resp = self.http.post(self.url("/chat")).json(&body).send().await?;
if !resp.status().is_success() {
return Err(CxError::from_response(resp).await);
}
Ok(resp.json().await?)
}
pub async fn infer(
&self,
request: &crate::models::inference::CxInferenceRequest,
) -> CxResult<crate::models::inference::InferenceResponse> {
let resp = self
.http
.post(self.url("/infer"))
.json(request)
.send()
.await?;
if !resp.status().is_success() {
return Err(CxError::from_response(resp).await);
}
Ok(resp.json().await?)
}
}
+87
View File
@@ -0,0 +1,87 @@
use std::sync::Arc;
use crate::config::PlatformConfig;
use crate::error::{CxError, CxResult};
/// Bridge service client — C++ IPC gateway, JSON-RPC, pub/sub.
#[derive(Clone)]
pub struct BridgeClient {
http: reqwest::Client,
config: Arc<PlatformConfig>,
}
impl BridgeClient {
pub fn new(http: reqwest::Client, config: Arc<PlatformConfig>) -> Self {
Self { http, config }
}
fn url(&self, path: &str) -> String {
format!(
"{}/api/bridge{}",
self.config.service_url(self.config.bridge_port),
path
)
}
/// Invoke an RPC method on the C++ backend.
pub async fn rpc(
&self,
method: &str,
params: Option<serde_json::Value>,
) -> CxResult<serde_json::Value> {
let body = serde_json::json!({
"method": method,
"params": params,
});
let resp = self.http.post(self.url("/rpc")).json(&body).send().await?;
if !resp.status().is_success() {
return Err(CxError::from_response(resp).await);
}
Ok(resp.json().await?)
}
/// Publish a message to a topic.
pub async fn publish(&self, topic: &str, message: serde_json::Value) -> CxResult<()> {
let body = serde_json::json!({
"topic": topic,
"message": message,
});
let resp = self
.http
.post(self.url("/publish"))
.json(&body)
.send()
.await?;
if !resp.status().is_success() {
return Err(CxError::from_response(resp).await);
}
Ok(())
}
/// Discover available C++ modules and methods.
pub async fn discover(&self) -> CxResult<serde_json::Value> {
let resp = self.http.get(self.url("/discover")).send().await?;
if !resp.status().is_success() {
return Err(CxError::from_response(resp).await);
}
Ok(resp.json().await?)
}
/// Get bridge connection status.
pub async fn status(&self) -> CxResult<serde_json::Value> {
let resp = self.http.get(self.url("/status")).send().await?;
if !resp.status().is_success() {
return Err(CxError::from_response(resp).await);
}
Ok(resp.json().await?)
}
/// Get RPC call metrics.
pub async fn metrics(&self) -> CxResult<serde_json::Value> {
let resp = self.http.get(self.url("/metrics")).send().await?;
if !resp.status().is_success() {
return Err(CxError::from_response(resp).await);
}
Ok(resp.json().await?)
}
}
+67
View File
@@ -0,0 +1,67 @@
use std::sync::Arc;
use crate::config::PlatformConfig;
use crate::error::{CxError, CxResult};
/// Codegen service client — MCP server, code intelligence.
#[derive(Clone)]
pub struct CodegenClient {
http: reqwest::Client,
config: Arc<PlatformConfig>,
}
impl CodegenClient {
pub fn new(http: reqwest::Client, config: Arc<PlatformConfig>) -> Self {
Self { http, config }
}
fn url(&self, path: &str) -> String {
format!(
"{}/api/codegen{}",
self.config.service_url(self.config.codegen_port),
path
)
}
pub async fn analyze(&self, code: &str, language: &str) -> CxResult<serde_json::Value> {
let body = serde_json::json!({
"code": code,
"language": language,
});
let resp = self
.http
.post(self.url("/analyze"))
.json(&body)
.send()
.await?;
if !resp.status().is_success() {
return Err(CxError::from_response(resp).await);
}
Ok(resp.json().await?)
}
pub async fn generate(&self, prompt: &str, language: &str) -> CxResult<serde_json::Value> {
let body = serde_json::json!({
"prompt": prompt,
"language": language,
});
let resp = self
.http
.post(self.url("/generate"))
.json(&body)
.send()
.await?;
if !resp.status().is_success() {
return Err(CxError::from_response(resp).await);
}
Ok(resp.json().await?)
}
pub async fn list_resources(&self) -> CxResult<serde_json::Value> {
let resp = self.http.get(self.url("/resources")).send().await?;
if !resp.status().is_success() {
return Err(CxError::from_response(resp).await);
}
Ok(resp.json().await?)
}
}
+94
View File
@@ -0,0 +1,94 @@
use std::sync::Arc;
use crate::config::PlatformConfig;
use crate::error::{CxError, CxResult};
use crate::models::commissioning::*;
/// Commissioning service client — BESS lifecycle management.
#[derive(Clone)]
pub struct CommissioningClient {
http: reqwest::Client,
config: Arc<PlatformConfig>,
}
impl CommissioningClient {
pub fn new(http: reqwest::Client, config: Arc<PlatformConfig>) -> Self {
Self { http, config }
}
fn url(&self, path: &str) -> String {
format!(
"{}/api/commissioning{}",
self.config.service_url(self.config.commissioning_port),
path
)
}
pub async fn list_projects(&self) -> CxResult<Vec<CommissioningProject>> {
let resp = self.http.get(self.url("/projects")).send().await?;
if !resp.status().is_success() {
return Err(CxError::from_response(resp).await);
}
Ok(resp.json().await?)
}
pub async fn get_project(&self, id: &str) -> CxResult<CommissioningProject> {
let resp = self
.http
.get(self.url(&format!("/projects/{id}")))
.send()
.await?;
if !resp.status().is_success() {
return Err(CxError::from_response(resp).await);
}
Ok(resp.json().await?)
}
pub async fn list_steps(&self, project_id: &str) -> CxResult<Vec<WorkflowStep>> {
let resp = self
.http
.get(self.url(&format!("/projects/{project_id}/steps")))
.send()
.await?;
if !resp.status().is_success() {
return Err(CxError::from_response(resp).await);
}
Ok(resp.json().await?)
}
pub async fn list_equipment(&self, project_id: &str) -> CxResult<Vec<Equipment>> {
let resp = self
.http
.get(self.url(&format!("/projects/{project_id}/equipment")))
.send()
.await?;
if !resp.status().is_success() {
return Err(CxError::from_response(resp).await);
}
Ok(resp.json().await?)
}
pub async fn list_incidents(&self, project_id: &str) -> CxResult<Vec<Incident>> {
let resp = self
.http
.get(self.url(&format!("/projects/{project_id}/incidents")))
.send()
.await?;
if !resp.status().is_success() {
return Err(CxError::from_response(resp).await);
}
Ok(resp.json().await?)
}
pub async fn list_compliance(&self, project_id: &str) -> CxResult<Vec<ComplianceRecord>> {
let resp = self
.http
.get(self.url(&format!("/projects/{project_id}/compliance")))
.send()
.await?;
if !resp.status().is_success() {
return Err(CxError::from_response(resp).await);
}
Ok(resp.json().await?)
}
}
+62
View File
@@ -0,0 +1,62 @@
use std::sync::Arc;
use crate::config::PlatformConfig;
use crate::error::{CxError, CxResult};
/// CxDocs service client — document engine.
#[derive(Clone)]
pub struct CxDocsClient {
http: reqwest::Client,
config: Arc<PlatformConfig>,
}
impl CxDocsClient {
pub fn new(http: reqwest::Client, config: Arc<PlatformConfig>) -> Self {
Self { http, config }
}
fn url(&self, path: &str) -> String {
format!(
"{}/api/cxdocs{}",
self.config.service_url(self.config.cxdocs_port),
path
)
}
pub async fn list_documents(&self) -> CxResult<serde_json::Value> {
let resp = self.http.get(self.url("/documents")).send().await?;
if !resp.status().is_success() {
return Err(CxError::from_response(resp).await);
}
Ok(resp.json().await?)
}
pub async fn get_document(&self, id: &str) -> CxResult<serde_json::Value> {
let resp = self
.http
.get(self.url(&format!("/documents/{id}")))
.send()
.await?;
if !resp.status().is_success() {
return Err(CxError::from_response(resp).await);
}
Ok(resp.json().await?)
}
pub async fn generate(&self, template: &str, data: serde_json::Value) -> CxResult<Vec<u8>> {
let body = serde_json::json!({
"template": template,
"data": data,
});
let resp = self
.http
.post(self.url("/generate"))
.json(&body)
.send()
.await?;
if !resp.status().is_success() {
return Err(CxError::from_response(resp).await);
}
Ok(resp.bytes().await?.to_vec())
}
}
+121
View File
@@ -0,0 +1,121 @@
use std::sync::Arc;
use crate::config::PlatformConfig;
use crate::error::{CxError, CxResult};
use crate::models::energy::*;
/// Energy service client — ERCOT market data, SCADA, ML predictions.
#[derive(Clone)]
pub struct EnergyClient {
http: reqwest::Client,
config: Arc<PlatformConfig>,
}
impl EnergyClient {
pub fn new(http: reqwest::Client, config: Arc<PlatformConfig>) -> Self {
Self { http, config }
}
fn url(&self, path: &str) -> String {
format!(
"{}/api/energy{}",
self.config.service_url(self.config.energy_port),
path
)
}
pub async fn dam_prices(&self, settlement_point: &str) -> CxResult<Vec<DamPrice>> {
let resp = self
.http
.get(self.url("/dam/prices"))
.query(&[("settlementPoint", settlement_point)])
.send()
.await?;
if !resp.status().is_success() {
return Err(CxError::from_response(resp).await);
}
Ok(resp.json().await?)
}
pub async fn rt_prices(&self, settlement_point: &str) -> CxResult<Vec<RtPrice>> {
let resp = self
.http
.get(self.url("/rt/prices"))
.query(&[("settlementPoint", settlement_point)])
.send()
.await?;
if !resp.status().is_success() {
return Err(CxError::from_response(resp).await);
}
Ok(resp.json().await?)
}
pub async fn system_demand(&self) -> CxResult<SystemDemand> {
let resp = self.http.get(self.url("/demand")).send().await?;
if !resp.status().is_success() {
return Err(CxError::from_response(resp).await);
}
Ok(resp.json().await?)
}
pub async fn wind_generation(&self) -> CxResult<WindGeneration> {
let resp = self.http.get(self.url("/wind")).send().await?;
if !resp.status().is_success() {
return Err(CxError::from_response(resp).await);
}
Ok(resp.json().await?)
}
pub async fn solar_generation(&self) -> CxResult<SolarGeneration> {
let resp = self.http.get(self.url("/solar")).send().await?;
if !resp.status().is_success() {
return Err(CxError::from_response(resp).await);
}
Ok(resp.json().await?)
}
pub async fn market_summary(&self) -> CxResult<MarketSummary> {
let resp = self.http.get(self.url("/summary")).send().await?;
if !resp.status().is_success() {
return Err(CxError::from_response(resp).await);
}
Ok(resp.json().await?)
}
pub async fn scada_snapshot(&self, device_id: &str) -> CxResult<ScadaSnapshot> {
let resp = self
.http
.get(self.url(&format!("/scada/{device_id}")))
.send()
.await?;
if !resp.status().is_success() {
return Err(CxError::from_response(resp).await);
}
Ok(resp.json().await?)
}
pub async fn predict(
&self,
prediction_type: &str,
model: &str,
target_date: &str,
target_hour: u8,
) -> CxResult<ErcotMlPrediction> {
let body = serde_json::json!({
"predictionType": prediction_type,
"model": model,
"targetDate": target_date,
"targetHour": target_hour,
});
let resp = self
.http
.post(self.url("/predict"))
.json(&body)
.send()
.await?;
if !resp.status().is_success() {
return Err(CxError::from_response(resp).await);
}
Ok(resp.json().await?)
}
}
+127
View File
@@ -0,0 +1,127 @@
use std::sync::Arc;
use crate::config::PlatformConfig;
use crate::error::{CxError, CxResult};
use crate::models::forma::*;
/// Forma service client — Autodesk Forma/ACC integration.
#[derive(Clone)]
pub struct FormaClient {
http: reqwest::Client,
config: Arc<PlatformConfig>,
}
impl FormaClient {
pub fn new(http: reqwest::Client, config: Arc<PlatformConfig>) -> Self {
Self { http, config }
}
fn url(&self, path: &str) -> String {
format!(
"{}/api/forma{}",
self.config.service_url(self.config.forma_port),
path
)
}
pub async fn list_projects(&self, token: &str) -> CxResult<Vec<FormaProjectInfo>> {
let resp = self
.http
.get(self.url("/projects"))
.bearer_auth(token)
.send()
.await?;
if !resp.status().is_success() {
return Err(CxError::from_response(resp).await);
}
Ok(resp.json().await?)
}
pub async fn list_elements(
&self,
token: &str,
project_id: &str,
) -> CxResult<Vec<FormaElementInfo>> {
let resp = self
.http
.get(self.url(&format!("/projects/{project_id}/elements")))
.bearer_auth(token)
.send()
.await?;
if !resp.status().is_success() {
return Err(CxError::from_response(resp).await);
}
Ok(resp.json().await?)
}
pub async fn trigger_analysis(
&self,
token: &str,
project_id: &str,
analysis_type: &str,
parameters: serde_json::Value,
) -> CxResult<FormaAnalysisResult> {
let body = serde_json::json!({
"analysisType": analysis_type,
"parameters": parameters,
});
let resp = self
.http
.post(self.url(&format!("/projects/{project_id}/analysis")))
.bearer_auth(token)
.json(&body)
.send()
.await?;
if !resp.status().is_success() {
return Err(CxError::from_response(resp).await);
}
Ok(resp.json().await?)
}
pub async fn check_compliance(
&self,
token: &str,
element_urn: &str,
) -> CxResult<FormaComplianceResult> {
let resp = self
.http
.get(self.url(&format!("/compliance/{element_urn}")))
.bearer_auth(token)
.send()
.await?;
if !resp.status().is_success() {
return Err(CxError::from_response(resp).await);
}
Ok(resp.json().await?)
}
pub async fn list_issues(&self, token: &str, container_id: &str) -> CxResult<Vec<AccIssue>> {
let resp = self
.http
.get(self.url(&format!("/acc/issues/{container_id}")))
.bearer_auth(token)
.send()
.await?;
if !resp.status().is_success() {
return Err(CxError::from_response(resp).await);
}
Ok(resp.json().await?)
}
pub async fn list_checklists(
&self,
token: &str,
project_id: &str,
) -> CxResult<Vec<AccChecklist>> {
let resp = self
.http
.get(self.url(&format!("/acc/checklists/{project_id}")))
.bearer_auth(token)
.send()
.await?;
if !resp.status().is_success() {
return Err(CxError::from_response(resp).await);
}
Ok(resp.json().await?)
}
}
+149
View File
@@ -0,0 +1,149 @@
use std::sync::Arc;
use crate::config::PlatformConfig;
use crate::error::{CxError, CxResult};
/// ML service client — model catalog, prediction, anomaly detection.
#[derive(Clone)]
pub struct MlClient {
http: reqwest::Client,
config: Arc<PlatformConfig>,
}
impl MlClient {
pub fn new(http: reqwest::Client, config: Arc<PlatformConfig>) -> Self {
Self { http, config }
}
fn url(&self, path: &str) -> String {
format!(
"{}/api/ml{}",
self.config.service_url(self.config.ml_port),
path
)
}
/// Single model prediction.
pub async fn predict(
&self,
model: &str,
features: serde_json::Value,
) -> CxResult<serde_json::Value> {
let body = serde_json::json!({
"model": model,
"features": features,
});
let resp = self
.http
.post(self.url("/predict"))
.json(&body)
.send()
.await?;
if !resp.status().is_success() {
return Err(CxError::from_response(resp).await);
}
Ok(resp.json().await?)
}
/// Batch prediction across multiple models.
pub async fn predict_batch(
&self,
requests: Vec<serde_json::Value>,
) -> CxResult<serde_json::Value> {
let resp = self
.http
.post(self.url("/predict/batch"))
.json(&requests)
.send()
.await?;
if !resp.status().is_success() {
return Err(CxError::from_response(resp).await);
}
Ok(resp.json().await?)
}
/// Anomaly detection using IsolationForest.
pub async fn detect_anomalies(
&self,
features: serde_json::Value,
) -> CxResult<serde_json::Value> {
let body = serde_json::json!({ "features": features });
let resp = self
.http
.post(self.url("/detect-anomalies"))
.json(&body)
.send()
.await?;
if !resp.status().is_success() {
return Err(CxError::from_response(resp).await);
}
Ok(resp.json().await?)
}
/// Classify price regime (4-class).
pub async fn classify_regime(
&self,
features: serde_json::Value,
) -> CxResult<serde_json::Value> {
let body = serde_json::json!({ "features": features });
let resp = self
.http
.post(self.url("/classify-regime"))
.json(&body)
.send()
.await?;
if !resp.status().is_success() {
return Err(CxError::from_response(resp).await);
}
Ok(resp.json().await?)
}
/// List all 26 models in the catalog.
pub async fn list_models(&self) -> CxResult<serde_json::Value> {
let resp = self.http.get(self.url("/models")).send().await?;
if !resp.status().is_success() {
return Err(CxError::from_response(resp).await);
}
Ok(resp.json().await?)
}
/// List model domains.
pub async fn list_domains(&self) -> CxResult<serde_json::Value> {
let resp = self.http.get(self.url("/domains")).send().await?;
if !resp.status().is_success() {
return Err(CxError::from_response(resp).await);
}
Ok(resp.json().await?)
}
/// ERCOT price forecast.
pub async fn price_forecast(
&self,
settlement_point: &str,
hours_ahead: u32,
) -> CxResult<serde_json::Value> {
let body = serde_json::json!({
"settlementPoint": settlement_point,
"hoursAhead": hours_ahead,
});
let resp = self
.http
.post(self.url("/intelligence/price-forecast"))
.json(&body)
.send()
.await?;
if !resp.status().is_success() {
return Err(CxError::from_response(resp).await);
}
Ok(resp.json().await?)
}
/// Backend health check.
pub async fn backend_health(&self) -> CxResult<serde_json::Value> {
let resp = self.http.get(self.url("/backend-health")).send().await?;
if !resp.status().is_success() {
return Err(CxError::from_response(resp).await);
}
Ok(resp.json().await?)
}
}
+25
View File
@@ -0,0 +1,25 @@
mod app;
mod bridge;
mod codegen;
mod commissioning;
mod cxdocs;
mod energy;
mod forma;
mod ml;
mod nemo;
mod nvidia;
mod studio;
mod supabase;
pub use app::AppClient;
pub use bridge::BridgeClient;
pub use codegen::CodegenClient;
pub use commissioning::CommissioningClient;
pub use cxdocs::CxDocsClient;
pub use energy::EnergyClient;
pub use forma::FormaClient;
pub use ml::MlClient;
pub use nemo::NemoClient;
pub use nvidia::NvidiaClient;
pub use studio::StudioClient;
pub use supabase::SupabaseClient;
+126
View File
@@ -0,0 +1,126 @@
use std::sync::Arc;
use crate::config::PlatformConfig;
use crate::error::{CxError, CxResult};
use crate::models::inference::*;
use crate::models::nemo::*;
/// Nemo service client — AI agent orchestration.
#[derive(Clone)]
pub struct NemoClient {
http: reqwest::Client,
config: Arc<PlatformConfig>,
}
impl NemoClient {
pub fn new(http: reqwest::Client, config: Arc<PlatformConfig>) -> Self {
Self { http, config }
}
fn url(&self, path: &str) -> String {
format!(
"{}/api/nemo{}",
self.config.service_url(self.config.nemo_port),
path
)
}
pub async fn run_agent(&self, request: &AgentRunRequest) -> CxResult<AgentRunResponse> {
let resp = self
.http
.post(self.url("/agent/run"))
.json(request)
.send()
.await?;
if !resp.status().is_success() {
return Err(CxError::from_response(resp).await);
}
Ok(resp.json().await?)
}
pub async fn chat(&self, request: &NemoChatRequest) -> CxResult<NemoChatResponse> {
let resp = self
.http
.post(self.url("/chat"))
.json(request)
.send()
.await?;
if !resp.status().is_success() {
return Err(CxError::from_response(resp).await);
}
Ok(resp.json().await?)
}
pub async fn list_sessions(&self) -> CxResult<Vec<NemoSessionSummary>> {
let resp = self.http.get(self.url("/sessions")).send().await?;
if !resp.status().is_success() {
return Err(CxError::from_response(resp).await);
}
Ok(resp.json().await?)
}
pub async fn get_session(&self, session_id: &str) -> CxResult<NemoSession> {
let resp = self
.http
.get(self.url(&format!("/sessions/{session_id}")))
.send()
.await?;
if !resp.status().is_success() {
return Err(CxError::from_response(resp).await);
}
Ok(resp.json().await?)
}
pub async fn list_tools(&self) -> CxResult<Vec<ToolDefinition>> {
let resp = self.http.get(self.url("/tools")).send().await?;
if !resp.status().is_success() {
return Err(CxError::from_response(resp).await);
}
Ok(resp.json().await?)
}
pub async fn execute_tool(
&self,
request: &ToolExecutionRequest,
) -> CxResult<ToolExecutionResult> {
let resp = self
.http
.post(self.url("/tools/execute"))
.json(request)
.send()
.await?;
if !resp.status().is_success() {
return Err(CxError::from_response(resp).await);
}
Ok(resp.json().await?)
}
pub async fn list_profiles(&self) -> CxResult<Vec<InferenceProfile>> {
let resp = self.http.get(self.url("/profiles")).send().await?;
if !resp.status().is_success() {
return Err(CxError::from_response(resp).await);
}
Ok(resp.json().await?)
}
pub async fn route(&self, request: &RouteRequest) -> CxResult<RouteResponse> {
let resp = self
.http
.post(self.url("/route"))
.json(request)
.send()
.await?;
if !resp.status().is_success() {
return Err(CxError::from_response(resp).await);
}
Ok(resp.json().await?)
}
pub async fn metrics(&self) -> CxResult<NemoMetrics> {
let resp = self.http.get(self.url("/metrics")).send().await?;
if !resp.status().is_success() {
return Err(CxError::from_response(resp).await);
}
Ok(resp.json().await?)
}
}
+143
View File
@@ -0,0 +1,143 @@
use std::sync::Arc;
use crate::config::PlatformConfig;
use crate::error::{CxError, CxResult};
use crate::models::ChatMessage;
use crate::models::inference::*;
/// NVIDIA NIM inference client — chat, embedding, reranking, vision, guardrails.
#[derive(Clone)]
pub struct NvidiaClient {
http: reqwest::Client,
config: Arc<PlatformConfig>,
}
impl NvidiaClient {
pub fn new(http: reqwest::Client, config: Arc<PlatformConfig>) -> Self {
Self { http, config }
}
#[allow(clippy::misnamed_getters)]
fn base_url(&self) -> &str {
&self.config.nvidia_base_url
}
fn api_key(&self) -> CxResult<&str> {
self.config
.nvidia_api_key
.as_deref()
.ok_or_else(|| CxError::Config("NVIDIA_API_KEY not set".into()))
}
/// Run chat completion.
pub async fn chat(&self, request: &InferenceRequest) -> CxResult<InferenceResponse> {
let resp = self
.http
.post(format!("{}/chat/completions", self.base_url()))
.bearer_auth(self.api_key()?)
.json(request)
.send()
.await?;
if !resp.status().is_success() {
return Err(CxError::from_response(resp).await);
}
Ok(resp.json().await?)
}
/// Generate embeddings for text.
pub async fn embed(&self, text: &str) -> CxResult<Vec<f32>> {
let body = serde_json::json!({
"input": text,
"model": "nvidia/nv-embedqa-e5-v5"
});
let resp = self
.http
.post(format!("{}/embeddings", self.base_url()))
.bearer_auth(self.api_key()?)
.json(&body)
.send()
.await?;
if !resp.status().is_success() {
return Err(CxError::from_response(resp).await);
}
let data: serde_json::Value = resp.json().await?;
let embedding = data["data"][0]["embedding"]
.as_array()
.ok_or_else(|| CxError::Api {
status: 500,
message: "missing embedding data".into(),
})?
.iter()
.filter_map(|v| v.as_f64().map(|f| f as f32))
.collect();
Ok(embedding)
}
/// Rerank documents against a query.
pub async fn rerank(&self, query: &str, documents: &[String]) -> CxResult<InferenceResponse> {
let body = serde_json::json!({
"model": "nvidia/nv-rerankqa-mistral-4b-v3",
"query": { "text": query },
"passages": documents.iter().map(|d| serde_json::json!({"text": d})).collect::<Vec<_>>()
});
let resp = self
.http
.post(format!("{}/ranking", self.base_url()))
.bearer_auth(self.api_key()?)
.json(&body)
.send()
.await?;
if !resp.status().is_success() {
return Err(CxError::from_response(resp).await);
}
Ok(resp.json().await?)
}
/// Vision analysis on an image URL.
pub async fn vision(&self, prompt: &str, image_url: &str) -> CxResult<InferenceResponse> {
let request = InferenceRequest {
model: "meta/llama-4-maverick-17b-128e-instruct".into(),
messages: vec![ChatMessage {
role: "user".into(),
content: format!(
r#"[{{"type":"text","text":"{prompt}"}},{{"type":"image_url","image_url":{{"url":"{image_url}"}}}}]"#
),
timestamp: None,
}],
temperature: Some(0.2),
max_tokens: Some(1024),
stream: Some(false),
top_p: None,
};
self.chat(&request).await
}
/// Run guardrail safety check.
pub async fn guardrail(&self, text: &str) -> CxResult<GuardrailResult> {
let body = serde_json::json!({
"model": "nvidia/llama-3.1-nemoguard-8b-content-safety",
"messages": [{"role": "user", "content": text}]
});
let resp = self
.http
.post(format!("{}/chat/completions", self.base_url()))
.bearer_auth(self.api_key()?)
.json(&body)
.send()
.await?;
if !resp.status().is_success() {
return Err(CxError::from_response(resp).await);
}
Ok(resp.json().await?)
}
}
+67
View File
@@ -0,0 +1,67 @@
use std::sync::Arc;
use crate::config::PlatformConfig;
use crate::error::{CxError, CxResult};
use crate::models::common::*;
use crate::models::studio::*;
/// Studio service client — platform hub, service registry, deployments.
#[derive(Clone)]
pub struct StudioClient {
http: reqwest::Client,
config: Arc<PlatformConfig>,
}
impl StudioClient {
pub fn new(http: reqwest::Client, config: Arc<PlatformConfig>) -> Self {
Self { http, config }
}
fn url(&self, path: &str) -> String {
format!(
"{}/api/studio{}",
self.config.service_url(self.config.studio_port),
path
)
}
pub async fn health(&self) -> CxResult<HealthResponse> {
let resp = self.http.get(self.url("/healthz")).send().await?;
if !resp.status().is_success() {
return Err(CxError::from_response(resp).await);
}
Ok(resp.json().await?)
}
pub async fn info(&self) -> CxResult<PlatformInfo> {
let resp = self.http.get(self.url("/info")).send().await?;
if !resp.status().is_success() {
return Err(CxError::from_response(resp).await);
}
Ok(resp.json().await?)
}
pub async fn list_services(&self) -> CxResult<Vec<Service>> {
let resp = self.http.get(self.url("/services")).send().await?;
if !resp.status().is_success() {
return Err(CxError::from_response(resp).await);
}
Ok(resp.json().await?)
}
pub async fn list_deployments(&self) -> CxResult<Vec<Deployment>> {
let resp = self.http.get(self.url("/deployments")).send().await?;
if !resp.status().is_success() {
return Err(CxError::from_response(resp).await);
}
Ok(resp.json().await?)
}
pub async fn list_conversations(&self) -> CxResult<Vec<Conversation>> {
let resp = self.http.get(self.url("/conversations")).send().await?;
if !resp.status().is_success() {
return Err(CxError::from_response(resp).await);
}
Ok(resp.json().await?)
}
}
+166
View File
@@ -0,0 +1,166 @@
use std::sync::Arc;
use crate::config::PlatformConfig;
use crate::error::{CxError, CxResult};
/// Supabase client — PostgreSQL queries and Storage operations.
#[derive(Clone)]
pub struct SupabaseClient {
http: reqwest::Client,
config: Arc<PlatformConfig>,
}
impl SupabaseClient {
pub fn new(http: reqwest::Client, config: Arc<PlatformConfig>) -> Self {
Self { http, config }
}
fn base_url(&self) -> CxResult<&str> {
self.config
.supabase_url
.as_deref()
.ok_or_else(|| CxError::Config("SUPABASE_URL not set".into()))
}
fn service_key(&self) -> CxResult<&str> {
self.config
.supabase_service_role_key
.as_deref()
.ok_or_else(|| CxError::Config("SUPABASE_SERVICE_ROLE_KEY not set".into()))
}
/// Query rows from a table.
pub async fn query<T: serde::de::DeserializeOwned>(
&self,
table: &str,
select: Option<&str>,
filter: Option<&str>,
) -> CxResult<Vec<T>> {
let mut url = format!("{}/rest/v1/{}", self.base_url()?, table);
let mut params = vec![];
if let Some(s) = select {
params.push(format!("select={s}"));
}
if let Some(f) = filter {
params.push(f.to_string());
}
if !params.is_empty() {
url = format!("{}?{}", url, params.join("&"));
}
let resp = self
.http
.get(&url)
.header("apikey", self.service_key()?)
.header("Authorization", format!("Bearer {}", self.service_key()?))
.send()
.await?;
if !resp.status().is_success() {
return Err(CxError::from_response(resp).await);
}
Ok(resp.json().await?)
}
/// Insert a row.
pub async fn insert<T: serde::Serialize + serde::de::DeserializeOwned>(
&self,
table: &str,
data: &T,
) -> CxResult<T> {
let url = format!("{}/rest/v1/{}", self.base_url()?, table);
let resp = self
.http
.post(&url)
.header("apikey", self.service_key()?)
.header("Authorization", format!("Bearer {}", self.service_key()?))
.header("Prefer", "return=representation")
.json(data)
.send()
.await?;
if !resp.status().is_success() {
return Err(CxError::from_response(resp).await);
}
let mut items: Vec<T> = resp.json().await?;
items.pop().ok_or_else(|| CxError::Api {
status: 500,
message: "empty insert response".into(),
})
}
/// Delete rows matching a filter.
pub async fn delete(&self, table: &str, filter: &str) -> CxResult<()> {
let url = format!("{}/rest/v1/{}?{}", self.base_url()?, table, filter);
let resp = self
.http
.delete(&url)
.header("apikey", self.service_key()?)
.header("Authorization", format!("Bearer {}", self.service_key()?))
.send()
.await?;
if !resp.status().is_success() {
return Err(CxError::from_response(resp).await);
}
Ok(())
}
/// Upload a file to Supabase Storage.
pub async fn upload_file(
&self,
bucket: &str,
path: &str,
data: Vec<u8>,
content_type: &str,
) -> CxResult<()> {
let url = format!("{}/storage/v1/object/{}/{}", self.base_url()?, bucket, path);
let resp = self
.http
.post(&url)
.header("apikey", self.service_key()?)
.header("Authorization", format!("Bearer {}", self.service_key()?))
.header("Content-Type", content_type)
.body(data)
.send()
.await?;
if !resp.status().is_success() {
return Err(CxError::from_response(resp).await);
}
Ok(())
}
/// Get a signed download URL.
pub async fn signed_url(&self, bucket: &str, path: &str, expires_in: u64) -> CxResult<String> {
let url = format!(
"{}/storage/v1/object/sign/{}/{}",
self.base_url()?,
bucket,
path
);
let body = serde_json::json!({ "expiresIn": expires_in });
let resp = self
.http
.post(&url)
.header("apikey", self.service_key()?)
.header("Authorization", format!("Bearer {}", self.service_key()?))
.json(&body)
.send()
.await?;
if !resp.status().is_success() {
return Err(CxError::from_response(resp).await);
}
let data: serde_json::Value = resp.json().await?;
data["signedURL"]
.as_str()
.map(|s| s.to_string())
.ok_or_else(|| CxError::Api {
status: 500,
message: "missing signedURL".into(),
})
}
}
+118
View File
@@ -0,0 +1,118 @@
use crate::error::{CxError, CxResult};
/// Platform-wide configuration.
#[derive(Debug, Clone)]
pub struct PlatformConfig {
pub base_url: String,
pub api_token: Option<String>,
pub timeout_secs: u64,
pub pool_size: usize,
// Service ports
pub studio_port: u16,
pub commissioning_port: u16,
pub energy_port: u16,
pub forma_port: u16,
pub cxdocs_port: u16,
pub app_port: u16,
pub nemo_port: u16,
pub codegen_port: u16,
pub bridge_port: u16,
pub ml_port: u16,
// NVIDIA
pub nvidia_api_key: Option<String>,
pub nvidia_base_url: String,
// Supabase
pub supabase_url: Option<String>,
pub supabase_service_role_key: Option<String>,
// ERCOT
pub ercot_username: Option<String>,
pub ercot_password: Option<String>,
pub ercot_subscription_key: Option<String>,
// MongoDB
pub mongodb_uri: Option<String>,
}
impl Default for PlatformConfig {
fn default() -> Self {
Self {
base_url: "http://localhost".into(),
api_token: None,
timeout_secs: 30,
pool_size: 10,
studio_port: 8080,
commissioning_port: 8081,
energy_port: 8082,
forma_port: 8083,
cxdocs_port: 8084,
app_port: 8090,
nemo_port: 18800,
codegen_port: 18900,
bridge_port: 9100,
ml_port: 8085,
nvidia_api_key: None,
nvidia_base_url: "https://integrate.api.nvidia.com/v1".into(),
supabase_url: None,
supabase_service_role_key: None,
ercot_username: None,
ercot_password: None,
ercot_subscription_key: None,
mongodb_uri: None,
}
}
}
impl PlatformConfig {
/// Load configuration from environment variables.
pub fn from_env() -> CxResult<Self> {
let env = |key: &str| std::env::var(key).ok();
Ok(Self {
base_url: env("CXAI_BASE_URL").unwrap_or_else(|| "http://localhost".into()),
api_token: env("CXAI_API_TOKEN"),
timeout_secs: env("CXAI_TIMEOUT_SECS")
.and_then(|v| v.parse().ok())
.unwrap_or(30),
pool_size: env("CXAI_POOL_SIZE")
.and_then(|v| v.parse().ok())
.unwrap_or(10),
studio_port: parse_port("CXAI_STUDIO_PORT", 8080)?,
commissioning_port: parse_port("CXAI_COMMISSIONING_PORT", 8081)?,
energy_port: parse_port("CXAI_ENERGY_PORT", 8082)?,
forma_port: parse_port("CXAI_FORMA_PORT", 8083)?,
cxdocs_port: parse_port("CXAI_CXDOCS_PORT", 8084)?,
app_port: parse_port("CXAI_APP_PORT", 8090)?,
nemo_port: parse_port("CXAI_NEMO_PORT", 18800)?,
codegen_port: parse_port("CXAI_CODEGEN_PORT", 18900)?,
bridge_port: parse_port("CXAI_BRIDGE_PORT", 9100)?,
ml_port: parse_port("CXAI_ML_PORT", 8085)?,
nvidia_api_key: env("NVIDIA_API_KEY"),
nvidia_base_url: env("NVIDIA_BASE_URL")
.unwrap_or_else(|| "https://integrate.api.nvidia.com/v1".into()),
supabase_url: env("SUPABASE_URL"),
supabase_service_role_key: env("SUPABASE_SERVICE_ROLE_KEY"),
ercot_username: env("ERCOT_USERNAME"),
ercot_password: env("ERCOT_PASSWORD"),
ercot_subscription_key: env("ERCOT_SUBSCRIPTION_KEY"),
mongodb_uri: env("MONGODB_URI"),
})
}
/// Build the full URL for a service.
pub fn service_url(&self, port: u16) -> String {
format!("{}:{}", self.base_url, port)
}
}
fn parse_port(key: &str, default: u16) -> CxResult<u16> {
match std::env::var(key) {
Ok(v) => v
.parse()
.map_err(|_| CxError::Config(format!("{key}: invalid port '{v}'"))),
Err(_) => Ok(default),
}
}
+48
View File
@@ -0,0 +1,48 @@
use thiserror::Error;
/// Platform SDK error type.
#[derive(Debug, Error)]
pub enum CxError {
#[error("HTTP error: {0}")]
Http(#[from] reqwest::Error),
#[error("API error ({status}): {message}")]
Api { status: u16, message: String },
#[error("JSON error: {0}")]
Json(#[from] serde_json::Error),
#[error("Configuration error: {0}")]
Config(String),
#[error("Not found: {0}")]
NotFound(String),
#[error("Unauthorized: {0}")]
Unauthorized(String),
#[error("Timeout after {0}s")]
Timeout(u64),
#[error("Circuit breaker open for {service}")]
CircuitOpen { service: String },
}
pub type CxResult<T> = Result<T, CxError>;
impl CxError {
/// Create from an HTTP response with non-2xx status.
pub async fn from_response(response: reqwest::Response) -> Self {
let status = response.status().as_u16();
let message = response
.text()
.await
.unwrap_or_else(|_| "unknown error".into());
match status {
401 | 403 => Self::Unauthorized(message),
404 => Self::NotFound(message),
_ => Self::Api { status, message },
}
}
}
+129
View File
@@ -0,0 +1,129 @@
pub mod clients;
pub mod config;
pub mod error;
pub mod models;
pub mod resilience;
pub use config::PlatformConfig;
pub use error::{CxError, CxResult};
use clients::*;
use std::sync::Arc;
/// Central SDK handle providing access to all platform service clients.
pub struct CxPlatform {
config: Arc<PlatformConfig>,
http: reqwest::Client,
}
impl CxPlatform {
/// Create a new platform SDK instance from configuration.
pub fn new(config: PlatformConfig) -> CxResult<Self> {
let mut headers = reqwest::header::HeaderMap::new();
if let Some(ref token) = config.api_token {
headers.insert(
reqwest::header::AUTHORIZATION,
reqwest::header::HeaderValue::from_str(&format!("Bearer {token}"))
.map_err(|e| CxError::Config(format!("invalid token: {e}")))?,
);
}
let http = reqwest::Client::builder()
.default_headers(headers)
.timeout(std::time::Duration::from_secs(config.timeout_secs))
.pool_max_idle_per_host(config.pool_size)
.build()
.map_err(|e| CxError::Config(format!("http client: {e}")))?;
Ok(Self {
config: Arc::new(config),
http,
})
}
/// Create from environment variables.
pub fn from_env() -> CxResult<Self> {
Self::new(PlatformConfig::from_env()?)
}
pub fn nvidia(&self) -> NvidiaClient {
NvidiaClient::new(self.http.clone(), self.config.clone())
}
pub fn supabase(&self) -> SupabaseClient {
SupabaseClient::new(self.http.clone(), self.config.clone())
}
pub fn studio(&self) -> StudioClient {
StudioClient::new(self.http.clone(), self.config.clone())
}
pub fn commissioning(&self) -> CommissioningClient {
CommissioningClient::new(self.http.clone(), self.config.clone())
}
pub fn energy(&self) -> EnergyClient {
EnergyClient::new(self.http.clone(), self.config.clone())
}
pub fn forma(&self) -> FormaClient {
FormaClient::new(self.http.clone(), self.config.clone())
}
pub fn cxdocs(&self) -> CxDocsClient {
CxDocsClient::new(self.http.clone(), self.config.clone())
}
pub fn app(&self) -> AppClient {
AppClient::new(self.http.clone(), self.config.clone())
}
pub fn nemo(&self) -> NemoClient {
NemoClient::new(self.http.clone(), self.config.clone())
}
pub fn codegen(&self) -> CodegenClient {
CodegenClient::new(self.http.clone(), self.config.clone())
}
pub fn bridge(&self) -> BridgeClient {
BridgeClient::new(self.http.clone(), self.config.clone())
}
pub fn ml(&self) -> MlClient {
MlClient::new(self.http.clone(), self.config.clone())
}
pub fn config(&self) -> &PlatformConfig {
&self.config
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn platform_from_default_config() {
let config = PlatformConfig::default();
let platform = CxPlatform::new(config).unwrap();
assert_eq!(platform.config().base_url, "http://localhost");
}
#[test]
fn platform_clients_are_constructible() {
let platform = CxPlatform::new(PlatformConfig::default()).unwrap();
let _ = platform.nvidia();
let _ = platform.studio();
let _ = platform.commissioning();
let _ = platform.energy();
let _ = platform.forma();
let _ = platform.cxdocs();
let _ = platform.app();
let _ = platform.nemo();
let _ = platform.codegen();
let _ = platform.bridge();
let _ = platform.ml();
let _ = platform.supabase();
}
}
+61
View File
@@ -0,0 +1,61 @@
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CommissioningProject {
pub id: String,
pub name: String,
pub phase: String,
pub status: String,
pub capacity_mw: Option<f64>,
pub manager: Option<String>,
pub created_at: DateTime<Utc>,
pub updated_at: Option<DateTime<Utc>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct WorkflowStep {
pub id: String,
pub project_id: String,
pub name: String,
pub status: String,
pub order: u32,
pub completed_at: Option<DateTime<Utc>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Equipment {
pub id: String,
pub project_id: String,
pub name: String,
pub equipment_type: String,
pub serial_number: Option<String>,
pub status: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Incident {
pub id: String,
pub project_id: String,
pub title: String,
pub severity: String,
pub status: String,
pub description: Option<String>,
pub created_at: DateTime<Utc>,
pub resolved_at: Option<DateTime<Utc>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ComplianceRecord {
pub id: String,
pub project_id: String,
pub standard: String,
pub status: String,
pub verified_at: Option<DateTime<Utc>>,
pub notes: Option<String>,
}
+117
View File
@@ -0,0 +1,117 @@
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
/// Generic API response wrapper matching .NET ApiResponse<T>.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ApiResponse<T> {
pub success: bool,
pub data: Option<T>,
pub error: Option<String>,
pub errors: Option<Vec<String>>,
}
impl<T> ApiResponse<T> {
pub fn ok(data: T) -> Self {
Self {
success: true,
data: Some(data),
error: None,
errors: None,
}
}
pub fn err(message: impl Into<String>) -> Self {
Self {
success: false,
data: None,
error: Some(message.into()),
errors: None,
}
}
}
/// Paginated response matching .NET PaginatedResponse<T>.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PaginatedResponse<T> {
pub items: Vec<T>,
pub total: i64,
pub page: i32,
pub per_page: i32,
}
/// Health status response from /healthz endpoints.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HealthResponse {
pub status: String,
pub version: String,
pub environment: String,
pub services: Option<HashMap<String, ServiceHealth>>,
}
/// Individual service health.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServiceHealth {
pub status: String,
pub latency_ms: Option<f64>,
pub message: Option<String>,
}
/// Platform info from /info endpoint.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PlatformInfo {
pub service: String,
pub version: String,
pub environment: String,
pub uptime: String,
pub runtime: String,
pub host: String,
}
/// Metrics from /metrics endpoint.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PlatformMetrics {
pub uptime_seconds: f64,
pub total_requests: u64,
pub total_errors: u64,
pub memory_mb: f64,
pub threads: u32,
pub timestamp: DateTime<Utc>,
}
/// Chat message matching .NET ChatMessage.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChatMessage {
pub role: String,
pub content: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub timestamp: Option<DateTime<Utc>>,
}
impl ChatMessage {
pub fn system(content: impl Into<String>) -> Self {
Self {
role: "system".into(),
content: content.into(),
timestamp: None,
}
}
pub fn user(content: impl Into<String>) -> Self {
Self {
role: "user".into(),
content: content.into(),
timestamp: None,
}
}
pub fn assistant(content: impl Into<String>) -> Self {
Self {
role: "assistant".into(),
content: content.into(),
timestamp: None,
}
}
}
+105
View File
@@ -0,0 +1,105 @@
use chrono::{DateTime, NaiveDate, Utc};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DamPrice {
pub settlement_point: String,
pub delivery_date: NaiveDate,
pub hour_ending: u8,
pub price: f64,
#[serde(skip_serializing_if = "Option::is_none")]
pub timestamp: Option<DateTime<Utc>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RtPrice {
pub settlement_point: String,
pub price: f64,
pub interval: String,
pub timestamp: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SystemDemand {
pub demand_mw: f64,
pub timestamp: DateTime<Utc>,
#[serde(skip_serializing_if = "Option::is_none")]
pub forecast_mw: Option<f64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct WindGeneration {
pub generation_mw: f64,
pub capacity_mw: f64,
pub timestamp: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SolarGeneration {
pub generation_mw: f64,
pub capacity_mw: f64,
pub timestamp: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MarketSummary {
pub avg_dam_price: f64,
pub avg_rt_price: f64,
pub peak_demand_mw: f64,
pub wind_generation_mw: f64,
pub solar_generation_mw: f64,
pub timestamp: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ErcotMlPrediction {
pub prediction_type: String,
pub model: String,
pub target_date: NaiveDate,
pub target_hour: u8,
pub predicted_value: f64,
pub confidence: f64,
#[serde(skip_serializing_if = "Option::is_none")]
pub lower_bound: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub upper_bound: Option<f64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ScadaSnapshot {
pub device_id: String,
pub soc_percent: f64,
pub soh_percent: f64,
pub power_kw: f64,
pub voltage_v: f64,
pub current_a: f64,
pub temperature_c: f64,
pub timestamp: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CongestionSnapshot {
pub constraint_name: String,
pub shadow_price: f64,
pub limit_mw: f64,
pub flow_mw: f64,
pub timestamp: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PriceVolatility {
pub settlement_point: String,
pub volatility: f64,
pub period_hours: u32,
pub timestamp: DateTime<Utc>,
}
+71
View File
@@ -0,0 +1,71 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FormaTokenResponse {
pub access_token: String,
pub token_type: String,
pub expires_in: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FormaProjectInfo {
pub id: String,
pub name: String,
pub status: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FormaElementInfo {
pub id: String,
pub name: String,
pub element_type: String,
pub project_id: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FormaAnalysisResult {
pub id: String,
pub analysis_type: String,
pub status: String,
pub result: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FormaComplianceResult {
pub compliant: bool,
pub violations: Vec<String>,
pub element_urn: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AccFolder {
pub id: String,
pub name: String,
pub parent_id: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AccIssue {
pub id: String,
pub title: String,
pub status: String,
pub priority: Option<String>,
pub assigned_to: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AccChecklist {
pub id: String,
pub name: String,
pub status: String,
pub items_total: u32,
pub items_completed: u32,
}
+121
View File
@@ -0,0 +1,121 @@
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
/// Inference request matching .NET InferenceRequest.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct InferenceRequest {
pub model: String,
pub messages: Vec<super::ChatMessage>,
#[serde(skip_serializing_if = "Option::is_none")]
pub temperature: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_tokens: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub stream: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub top_p: Option<f32>,
}
/// Inference response matching .NET InferenceResponse.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct InferenceResponse {
pub content: String,
pub model: String,
pub usage: Option<TokenUsage>,
pub finish_reason: Option<String>,
}
/// Token usage stats.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TokenUsage {
pub prompt_tokens: u32,
pub completion_tokens: u32,
pub total_tokens: u32,
}
/// CxModel record matching .NET CxModelRecord.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CxModelRecord {
pub id: String,
pub name: String,
pub provider: String,
pub model_id: String,
pub status: String,
pub capabilities: CxModelCapabilities,
#[serde(skip_serializing_if = "Option::is_none")]
pub created_at: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub updated_at: Option<DateTime<Utc>>,
}
/// Model capabilities.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CxModelCapabilities {
pub chat: bool,
pub streaming: bool,
pub embedding: bool,
pub vision: bool,
pub code_gen: bool,
pub function_calling: bool,
pub max_context_tokens: Option<u32>,
}
/// CxModel inference request with profile routing.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CxInferenceRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub model: Option<String>,
pub messages: Vec<super::ChatMessage>,
#[serde(skip_serializing_if = "Option::is_none")]
pub temperature: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_tokens: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub profile: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub task: Option<String>,
}
/// Inference profile matching .NET InferenceProfile.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct InferenceProfile {
pub id: String,
pub provider: String,
pub model: String,
pub display: String,
pub timeout_seconds: u64,
pub tier: String,
}
/// Model health report.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CxModelHealthReport {
pub status: String,
pub providers: Vec<ProviderHealth>,
}
/// Individual provider health.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ProviderHealth {
pub name: String,
pub status: String,
pub latency_ms: Option<f64>,
pub models_available: u32,
}
/// Guardrail result from NVIDIA safety checks.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GuardrailResult {
pub safe: bool,
pub categories: Vec<String>,
pub scores: Vec<f32>,
}
+11
View File
@@ -0,0 +1,11 @@
pub mod commissioning;
pub mod common;
pub mod energy;
pub mod forma;
pub mod inference;
pub mod nemo;
pub mod studio;
pub mod telemetry;
pub use common::*;
pub use inference::*;
+120
View File
@@ -0,0 +1,120 @@
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AgentRunRequest {
pub goal: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub profile: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub session_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_steps: Option<u32>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AgentRunResponse {
pub session_id: String,
pub status: String,
pub steps: Vec<AgentStep>,
pub result: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AgentStep {
pub step_number: u32,
pub action: String,
pub result: Option<String>,
pub duration_ms: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct NemoChatRequest {
pub message: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub session_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub profile: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct NemoChatResponse {
pub reply: String,
pub session_id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub profile: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct NemoSession {
pub id: String,
pub created_at: DateTime<Utc>,
pub messages: Vec<super::ChatMessage>,
pub profile: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct NemoSessionSummary {
pub id: String,
pub created_at: DateTime<Utc>,
pub message_count: u32,
pub last_message: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ToolDefinition {
pub name: String,
pub description: String,
pub parameters: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ToolExecutionRequest {
pub tool: String,
pub arguments: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ToolExecutionResult {
pub tool: String,
pub success: bool,
pub result: Option<serde_json::Value>,
pub error: Option<String>,
pub duration_ms: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RouteRequest {
pub message: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub task_type: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RouteResponse {
pub profile: super::InferenceProfile,
pub confidence: f64,
pub reasoning: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct NemoMetrics {
pub total_sessions: u64,
pub active_sessions: u64,
pub total_agent_runs: u64,
pub avg_steps_per_run: f64,
pub tool_executions: u64,
}
+44
View File
@@ -0,0 +1,44 @@
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct User {
pub id: String,
pub email: String,
pub name: Option<String>,
pub role: String,
pub created_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Conversation {
pub id: String,
pub user_id: String,
pub title: Option<String>,
pub message_count: u32,
pub created_at: DateTime<Utc>,
pub updated_at: Option<DateTime<Utc>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Deployment {
pub id: String,
pub service: String,
pub version: String,
pub status: String,
pub target: String,
pub created_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Service {
pub name: String,
pub port: u16,
pub status: String,
pub version: String,
pub health: String,
}
+21
View File
@@ -0,0 +1,21 @@
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TelemetryEvent {
pub event_type: String,
pub service: String,
pub timestamp: DateTime<Utc>,
pub data: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MemorySummary {
pub total_mb: f64,
pub used_mb: f64,
pub gc_gen0: u64,
pub gc_gen1: u64,
pub gc_gen2: u64,
}
+54
View File
@@ -0,0 +1,54 @@
use backon::{ExponentialBuilder, Retryable};
use std::future::Future;
use tracing::warn;
use crate::error::{CxError, CxResult};
/// Retry configuration matching .NET SDK retry/circuit-breaker policies.
#[derive(Debug, Clone)]
pub struct RetryPolicy {
pub max_attempts: u32,
pub base_delay_ms: u64,
pub max_delay_ms: u64,
}
impl Default for RetryPolicy {
fn default() -> Self {
Self {
max_attempts: 3,
base_delay_ms: 200,
max_delay_ms: 5000,
}
}
}
/// Execute an async operation with exponential backoff retry.
pub async fn with_retry<F, Fut, T>(policy: &RetryPolicy, operation: F) -> CxResult<T>
where
F: FnMut() -> Fut,
Fut: Future<Output = CxResult<T>>,
{
let backoff = ExponentialBuilder::default()
.with_min_delay(std::time::Duration::from_millis(policy.base_delay_ms))
.with_max_delay(std::time::Duration::from_millis(policy.max_delay_ms))
.with_max_times(policy.max_attempts as usize);
operation
.retry(backoff)
.notify(|err: &CxError, dur| {
warn!("Retrying after {dur:?} due to: {err}");
})
.await
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn retry_succeeds_first_try() {
let policy = RetryPolicy::default();
let result = with_retry(&policy, || async { Ok::<_, CxError>(42) }).await;
assert_eq!(result.unwrap(), 42);
}
}