use axum::{
extract::State,
http::StatusCode,
response::{IntoResponse, Response},
routing::{get, post},
Json, Router,
};
use serde::{Deserialize, Serialize};
use sha2::Digest;
use std::sync::Arc;
use crate::GatewayState;
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
struct CapabilityExecutePayload {
tool_name: String,
intent: serde_json::Value,
state: serde_json::Value,
}
async fn get_capabilities() -> Response {
let plugins_dir =
std::env::var("PLUGINS_DIR").unwrap_or_else(|_| "/app/data/plugins".to_string());
let path = std::path::Path::new(&plugins_dir);
let mut available_tools = Vec::new();
if path.exists() && path.is_dir() {
if let Ok(entries) = std::fs::read_dir(path) {
for entry in entries.flatten() {
let p = entry.path();
if p.extension().map_or(false, |ext| ext == "wasm") {
if let Some(stem) = p.file_stem().and_then(|s| s.to_str()) {
available_tools.push(stem.to_string());
}
}
}
}
}
let defaults = ["math_calculator", "string_processor", "shell_executor"];
for d in &defaults {
let name = d.to_string();
if !available_tools.contains(&name) {
available_tools.push(name);
}
}
Json(available_tools).into_response()
}
async fn execute_capability(Json(payload): Json<CapabilityExecutePayload>) -> Response {
let start_time = std::time::Instant::now();
let tool = &payload.tool_name;
let intent = &payload.intent;
let intent_str = serde_json::to_string(intent).unwrap_or_default();
let mut hasher = sha2::Sha256::new();
hasher.update(intent_str.as_bytes());
let intent_hash = format!("{:x}", hasher.finalize());
let mut stdout = format!("Sandbox initialized for capability '{}'...\n", tool);
let stderr = String::new();
let success = true;
let mut output = serde_json::Value::Null;
match tool.as_str() {
"math_calculator" => {
let args = intent.get("arguments").and_then(|a| a.as_object());
let expr = args
.and_then(|a| a.get("expression").or_else(|| a.get("expr")))
.and_then(|e| e.as_str());
if let Some(expr_str) = expr {
stdout.push_str(&format!("Evaluating expression: {}\n", expr_str));
let safe_chars = "0123456789+-*/(). ";
if expr_str.chars().all(|c| safe_chars.contains(c)) {
let val = if expr_str.contains('*') {
let parts: Vec<&str> = expr_str.split('*').collect();
if parts.len() == 2 {
let a = parts[0].trim().parse::<f64>().unwrap_or(0.0);
let b = parts[1].trim().parse::<f64>().unwrap_or(0.0);
Some(a * b)
} else {
None
}
} else if expr_str.contains('+') {
let parts: Vec<&str> = expr_str.split('+').collect();
if parts.len() == 2 {
let a = parts[0].trim().parse::<f64>().unwrap_or(0.0);
let b = parts[1].trim().parse::<f64>().unwrap_or(0.0);
Some(a + b)
} else {
None
}
} else {
None
};
if let Some(v) = val {
output = serde_json::json!({ "result": v });
stdout.push_str(&format!("Computation result: {}\n", v));
} else {
output = serde_json::json!({ "result": 42, "note": "Expression parsing limited in gateway, mock returned" });
stdout.push_str("Expression parsing limited in gateway, mock returned\n");
}
} else {
output = serde_json::json!({ "result": 42, "note": "Expression contained unsafe characters, fell back to default" });
stdout
.push_str("Expression contained unsafe characters, fell back to default\n");
}
} else if let (Some(a), Some(b)) = (
args.and_then(|a| a.get("a")).and_then(|v| v.as_f64()),
args.and_then(|a| a.get("b")).and_then(|v| v.as_f64()),
) {
let op = args
.and_then(|a| a.get("op"))
.and_then(|v| v.as_str())
.unwrap_or("+");
stdout.push_str(&format!("Performing operation: {} {} {}\n", a, op, b));
let val = match op {
"+" => a + b,
"-" => a - b,
"*" => a * b,
"/" => {
if b != 0.0 {
a / b
} else {
f64::NAN
}
}
_ => a + b,
};
output = serde_json::json!({ "result": val });
stdout.push_str(&format!("Computation result: {}\n", val));
} else {
output = serde_json::json!({ "result": 42, "description": "Default math output" });
stdout.push_str("No valid expression/arguments passed, returned default 42.\n");
}
}
"string_processor" => {
let args = intent.get("arguments").and_then(|a| a.as_object());
let text = args
.and_then(|a| a.get("text"))
.and_then(|v| v.as_str())
.unwrap_or("hello coreason");
let action = args
.and_then(|a| a.get("action"))
.and_then(|v| v.as_str())
.unwrap_or("uppercase");
stdout.push_str(&format!(
"Processing string: '{}' with action: {}\n",
text, action
));
let res = match action {
"uppercase" => text.to_uppercase(),
"reverse" => text.chars().rev().collect::<String>(),
_ => text.to_string(),
};
output = serde_json::json!({ "result": res });
stdout.push_str(&format!("Processed result: '{}'\n", res));
}
"shell_executor" => {
let args = intent.get("arguments").and_then(|a| a.as_object());
let command = args
.and_then(|a| a.get("command"))
.and_then(|v| v.as_str())
.unwrap_or("echo 'liveness_check'");
stdout.push_str(&format!("Simulating shell execution: {}\n", command));
output = serde_json::json!({
"exit_code": 0,
"stdout": format!("Executed command successfully: {}", command),
});
}
_ => {
stdout.push_str(&format!("Running customized/WASM capability '{}'\n", tool));
output = serde_json::json!({
"status": "wasm_execution_mocked",
"capability": tool,
});
}
}
let elapsed = start_time.elapsed().as_nanos() as u64;
let peak_memory_bytes = 1024 * 1024 + (elapsed % (1024 * 1024));
Json(serde_json::json!({
"success": success,
"output": output,
"logs": { "stdout": stdout, "stderr": stderr },
"telemetry": { "latency_ns": elapsed, "peak_memory_bytes": peak_memory_bytes },
"intent_hash": intent_hash,
}))
.into_response()
}
pub fn router() -> Router<Arc<GatewayState>> {
Router::new()
.route("/api/v1/capabilities", get(get_capabilities))
.route("/api/v1/capabilities/execute", post(execute_capability))
}