use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use crate::amalgamation;
use crate::common::ApiState;
use crate::common::{ApiError, ApiResult, Json, State};
#[allow(non_snake_case)] #[derive(Debug, Serialize, Deserialize, utoipa::ToSchema)]
pub struct FatigueInfo {
pub fatigue_index: Option<u8>,
pub fatigue_active: Option<bool>,
pub regular_neuron_util: Option<u8>,
pub memory_neuron_util: Option<u8>,
pub synapse_util: Option<u8>,
}
#[allow(non_snake_case)] #[derive(Debug, Serialize, Deserialize, utoipa::ToSchema)]
pub struct HealthCheckResponse {
pub burst_engine: bool,
pub connected_agents: Option<i32>,
pub influxdb_availability: bool,
pub neuron_count_max: i64,
pub synapse_count_max: i64,
pub latest_changes_saved_externally: bool,
pub genome_availability: bool,
pub genome_validity: Option<bool>,
pub brain_readiness: bool,
pub feagi_session: Option<i64>,
pub fitness: Option<f64>,
pub cortical_area_count: Option<i32>,
pub neuron_count: Option<i64>,
pub memory_neuron_count: Option<i64>,
pub regular_neuron_count: Option<i64>,
pub synapse_count: Option<i64>,
pub estimated_brain_size_in_MB: Option<f64>,
pub genome_num: Option<i32>,
pub genome_timestamp: Option<i64>,
pub simulation_timestep: Option<f64>,
pub memory_area_stats: Option<HashMap<String, HashMap<String, serde_json::Value>>>,
pub amalgamation_pending: Option<HashMap<String, serde_json::Value>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub brain_regions_hash: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cortical_areas_hash: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub brain_geometry_hash: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub morphologies_hash: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cortical_mappings_hash: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub agent_data_hash: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub brain_regions_root: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub fatigue: Option<FatigueInfo>,
}
#[utoipa::path(
get,
path = "/v1/system/health_check",
responses(
(status = 200, description = "System health retrieved successfully", body = HealthCheckResponse),
(status = 500, description = "Internal server error")
),
tag = "system"
)]
pub async fn get_health_check(
State(state): State<ApiState>,
) -> ApiResult<Json<HealthCheckResponse>> {
let analytics_service = state.analytics_service.as_ref();
let health = analytics_service
.get_system_health()
.await
.map_err(|e| ApiError::internal(format!("Failed to get system health: {}", e)))?;
let runtime_status = state.runtime_service.get_status().await.ok();
let burst_engine_active = runtime_status
.as_ref()
.map(|status| status.is_running)
.unwrap_or(false);
let _burst_count = state.runtime_service.get_burst_count().await.ok();
let connected_agents = if let Some(agent_service) = state.agent_service.as_ref() {
agent_service
.list_agents()
.await
.ok()
.map(|agents| agents.len() as i32)
} else {
None
};
let synapse_count = analytics_service
.get_total_synapse_count()
.await
.ok()
.map(|count| count as i64);
let regular_neuron_count = analytics_service
.get_regular_neuron_count()
.await
.ok()
.map(|count| count as i64);
let memory_neuron_count = analytics_service
.get_memory_neuron_count()
.await
.ok()
.map(|count| count as i64);
let genome_info = state.genome_service.get_genome_info().await.ok();
let runtime_timestep = runtime_status.as_ref().map(|status| {
if status.frequency_hz > 0.0 {
1.0 / status.frequency_hz
} else {
0.0
}
});
let simulation_timestep =
runtime_timestep.or_else(|| genome_info.as_ref().map(|info| info.simulation_timestep));
let genome_num = genome_info.as_ref().and_then(|info| info.genome_num);
let genome_timestamp = genome_info.as_ref().and_then(|info| info.genome_timestamp);
#[allow(non_snake_case)] let estimated_brain_size_in_MB = {
let neuron_bytes = health.neuron_count * 64;
let synapse_bytes = synapse_count.unwrap_or(0) as usize * 16;
let metadata_bytes = health.cortical_area_count * 512; let total_bytes = neuron_bytes + synapse_bytes + metadata_bytes;
Some((total_bytes as f64) / (1024.0 * 1024.0))
};
let neuron_count_max = health.neuron_capacity as i64;
let synapse_count_max = health.synapse_capacity as i64;
let influxdb_availability = false; let latest_changes_saved_externally = false; let genome_availability = health.cortical_area_count > 0;
let genome_validity = Some(health.brain_readiness);
let feagi_session = Some(state.feagi_session_timestamp);
let fitness = None;
let (memory_area_stats, memory_neuron_count_from_cache) = state
.memory_stats_cache
.as_ref()
.map(|cache| {
let snapshot = feagi_npu_plasticity::memory_stats_cache::get_stats_snapshot(cache);
let total = snapshot
.values()
.map(|s| s.neuron_count as i64)
.sum::<i64>();
let per_area = snapshot
.into_iter()
.map(|(name, stats)| {
let mut inner_map = HashMap::new();
inner_map.insert(
"neuron_count".to_string(),
serde_json::json!(stats.neuron_count),
);
inner_map.insert(
"created_total".to_string(),
serde_json::json!(stats.created_total),
);
inner_map.insert(
"deleted_total".to_string(),
serde_json::json!(stats.deleted_total),
);
inner_map.insert(
"last_updated".to_string(),
serde_json::json!(stats.last_updated),
);
(name, inner_map)
})
.collect::<HashMap<String, HashMap<String, serde_json::Value>>>();
(Some(per_area), Some(total))
})
.unwrap_or((None, None));
let memory_neuron_count = memory_neuron_count_from_cache.or(memory_neuron_count);
let amalgamation_pending = state.amalgamation_state.read().pending.as_ref().map(|p| {
let v = amalgamation::pending_summary_to_health_json(&p.summary);
v.as_object()
.cloned()
.unwrap_or_default()
.into_iter()
.collect::<HashMap<String, serde_json::Value>>()
});
#[cfg(feature = "services")]
let brain_regions_root = feagi_brain_development::ConnectomeManager::instance()
.read()
.get_root_region_id();
#[cfg(not(feature = "services"))]
let brain_regions_root = None;
#[cfg(feature = "services")]
let fatigue = {
use feagi_state_manager::StateManager;
match StateManager::instance().try_read() {
Some(state_manager) => {
let core_state = state_manager.get_core_state();
Some(FatigueInfo {
fatigue_index: Some(core_state.get_fatigue_index()),
fatigue_active: Some(core_state.is_fatigue_active()),
regular_neuron_util: Some(core_state.get_regular_neuron_util()),
memory_neuron_util: Some(core_state.get_memory_neuron_util()),
synapse_util: Some(core_state.get_synapse_util()),
})
}
None => {
tracing::warn!(target: "feagi-api", "StateManager is locked, cannot read fatigue data");
None
}
}
};
#[cfg(not(feature = "services"))]
let fatigue = {
tracing::debug!(target: "feagi-api", "Services feature not enabled, fatigue data unavailable");
None
};
let (
brain_regions_hash,
cortical_areas_hash,
brain_geometry_hash,
morphologies_hash,
cortical_mappings_hash,
agent_data_hash,
) = {
let state_manager = feagi_state_manager::StateManager::instance();
let state_manager = state_manager.read();
(
Some(state_manager.get_brain_regions_hash()),
Some(state_manager.get_cortical_areas_hash()),
Some(state_manager.get_brain_geometry_hash()),
Some(state_manager.get_morphologies_hash()),
Some(state_manager.get_cortical_mappings_hash()),
Some(state_manager.get_agent_data_hash()),
)
};
Ok(Json(HealthCheckResponse {
burst_engine: burst_engine_active,
connected_agents,
influxdb_availability,
neuron_count_max,
synapse_count_max,
latest_changes_saved_externally,
genome_availability,
genome_validity,
brain_readiness: health.brain_readiness,
feagi_session,
fitness,
cortical_area_count: Some(health.cortical_area_count as i32),
neuron_count: Some(health.neuron_count as i64),
memory_neuron_count,
regular_neuron_count,
synapse_count,
estimated_brain_size_in_MB,
genome_num,
genome_timestamp,
simulation_timestep,
memory_area_stats,
amalgamation_pending,
brain_regions_hash,
cortical_areas_hash,
brain_geometry_hash,
morphologies_hash,
cortical_mappings_hash,
agent_data_hash,
brain_regions_root, fatigue,
}))
}
#[utoipa::path(
get,
path = "/v1/system/cortical_area_visualization_skip_rate",
responses(
(status = 200, description = "Skip rate retrieved successfully", body = i32),
(status = 500, description = "Internal server error")
),
tag = "system"
)]
pub async fn get_cortical_area_visualization_skip_rate(
State(_state): State<ApiState>,
) -> ApiResult<Json<i32>> {
Ok(Json(1))
}
#[utoipa::path(
put,
path = "/v1/system/cortical_area_visualization_skip_rate",
request_body = i32,
responses(
(status = 200, description = "Skip rate updated successfully"),
(status = 500, description = "Internal server error")
),
tag = "system"
)]
pub async fn set_cortical_area_visualization_skip_rate(
State(_state): State<ApiState>,
Json(skip_rate): Json<i32>,
) -> ApiResult<Json<serde_json::Value>> {
Ok(Json(serde_json::json!({
"message": format!("Skip rate set to {}", skip_rate)
})))
}
#[utoipa::path(
get,
path = "/v1/system/cortical_area_visualization_suppression_threshold",
responses(
(status = 200, description = "Threshold retrieved successfully", body = i32),
(status = 500, description = "Internal server error")
),
tag = "system"
)]
pub async fn get_cortical_area_visualization_suppression_threshold(
State(_state): State<ApiState>,
) -> ApiResult<Json<i32>> {
Ok(Json(0))
}
#[utoipa::path(
put,
path = "/v1/system/cortical_area_visualization_suppression_threshold",
request_body = i32,
responses(
(status = 200, description = "Threshold updated successfully"),
(status = 500, description = "Internal server error")
),
tag = "system"
)]
pub async fn set_cortical_area_visualization_suppression_threshold(
State(_state): State<ApiState>,
Json(threshold): Json<i32>,
) -> ApiResult<Json<serde_json::Value>> {
Ok(Json(serde_json::json!({
"message": format!("Suppression threshold set to {}", threshold)
})))
}
#[utoipa::path(
get,
path = "/v1/system/version",
tag = "system",
responses(
(status = 200, description = "Version string", body = String)
)
)]
pub async fn get_version(State(_state): State<ApiState>) -> ApiResult<Json<String>> {
Ok(Json(env!("CARGO_PKG_VERSION").to_string()))
}
#[utoipa::path(
get,
path = "/v1/system/versions",
tag = "system",
responses(
(status = 200, description = "Version information", body = HashMap<String, String>)
)
)]
pub async fn get_versions(
State(state): State<ApiState>,
) -> ApiResult<Json<HashMap<String, String>>> {
match state.system_service.get_version().await {
Ok(version_info) => {
let mut versions = version_info.crates.clone();
versions.insert("rust".to_string(), version_info.rust_version);
versions.insert("build_timestamp".to_string(), version_info.build_timestamp);
Ok(Json(versions))
}
Err(e) => {
tracing::warn!(
"Failed to get version from system service: {}, using fallback",
e
);
let mut versions = HashMap::new();
versions.insert(
"error".to_string(),
"system service unavailable".to_string(),
);
Ok(Json(versions))
}
}
}
#[utoipa::path(
get,
path = "/v1/system/configuration",
tag = "system",
responses(
(status = 200, description = "System configuration", body = HashMap<String, serde_json::Value>)
)
)]
pub async fn get_configuration(
State(state): State<ApiState>,
) -> ApiResult<Json<HashMap<String, serde_json::Value>>> {
let health = state
.analytics_service
.get_system_health()
.await
.map_err(|e| ApiError::internal(format!("Failed to get system health: {}", e)))?;
let mut config = HashMap::new();
config.insert("api_host".to_string(), serde_json::json!("0.0.0.0"));
config.insert("api_port".to_string(), serde_json::json!(8000));
config.insert(
"max_neurons".to_string(),
serde_json::json!(health.neuron_capacity),
);
config.insert(
"max_synapses".to_string(),
serde_json::json!(health.synapse_capacity),
);
Ok(Json(config))
}
#[utoipa::path(
get,
path = "/v1/system/user_preferences",
tag = "system",
responses(
(status = 200, description = "User preferences", body = HashMap<String, serde_json::Value>)
)
)]
pub async fn get_user_preferences(
State(_state): State<ApiState>,
) -> ApiResult<Json<HashMap<String, serde_json::Value>>> {
let mut prefs = HashMap::new();
prefs.insert("adv_mode".to_string(), serde_json::json!(false));
prefs.insert("ui_magnification".to_string(), serde_json::json!(1.0));
prefs.insert(
"auto_pns_area_creation".to_string(),
serde_json::json!(true),
);
Ok(Json(prefs))
}
#[utoipa::path(
put,
path = "/v1/system/user_preferences",
tag = "system",
responses(
(status = 200, description = "Preferences updated", body = HashMap<String, String>)
)
)]
pub async fn put_user_preferences(
State(_state): State<ApiState>,
Json(_prefs): Json<HashMap<String, serde_json::Value>>,
) -> ApiResult<Json<HashMap<String, String>>> {
Ok(Json(HashMap::from([(
"message".to_string(),
"User preferences updated successfully".to_string(),
)])))
}
#[utoipa::path(
get,
path = "/v1/system/cortical_area_types",
tag = "system",
responses(
(status = 200, description = "Cortical area types", body = Vec<String>)
)
)]
pub async fn get_cortical_area_types_list(
State(_state): State<ApiState>,
) -> ApiResult<Json<Vec<String>>> {
Ok(Json(vec![
"Sensory".to_string(),
"Motor".to_string(),
"Custom".to_string(),
"Memory".to_string(),
"Core".to_string(),
]))
}
#[utoipa::path(
post,
path = "/v1/system/enable_visualization_fq_sampler",
tag = "system",
responses(
(status = 200, description = "FQ sampler enabled", body = HashMap<String, String>)
)
)]
pub async fn post_enable_visualization_fq_sampler(
State(state): State<ApiState>,
) -> ApiResult<Json<HashMap<String, String>>> {
let runtime_service = state.runtime_service.as_ref();
runtime_service
.set_fcl_sampler_config(None, Some(1))
.await
.map_err(|e| ApiError::internal(format!("Failed to enable FQ sampler: {}", e)))?;
Ok(Json(HashMap::from([(
"message".to_string(),
"Visualization FQ sampler enabled".to_string(),
)])))
}
#[utoipa::path(
post,
path = "/v1/system/disable_visualization_fq_sampler",
tag = "system",
responses(
(status = 200, description = "FQ sampler disabled", body = HashMap<String, String>)
)
)]
pub async fn post_disable_visualization_fq_sampler(
State(state): State<ApiState>,
) -> ApiResult<Json<HashMap<String, String>>> {
let runtime_service = state.runtime_service.as_ref();
runtime_service
.set_fcl_sampler_config(None, Some(0))
.await
.map_err(|e| ApiError::internal(format!("Failed to disable FQ sampler: {}", e)))?;
Ok(Json(HashMap::from([(
"message".to_string(),
"Visualization FQ sampler disabled".to_string(),
)])))
}
#[utoipa::path(
get,
path = "/v1/system/fcl_status",
tag = "system",
responses(
(status = 200, description = "FCL status", body = HashMap<String, serde_json::Value>)
)
)]
pub async fn get_fcl_status_system(
State(state): State<ApiState>,
) -> ApiResult<Json<HashMap<String, serde_json::Value>>> {
let runtime_service = state.runtime_service.as_ref();
let (frequency, consumer) = runtime_service
.get_fcl_sampler_config()
.await
.map_err(|e| ApiError::internal(format!("Failed to get FCL status: {}", e)))?;
let mut response = HashMap::new();
response.insert("available".to_string(), serde_json::json!(true));
response.insert("frequency".to_string(), serde_json::json!(frequency));
response.insert("consumer".to_string(), serde_json::json!(consumer));
response.insert("enabled".to_string(), serde_json::json!(consumer > 0));
Ok(Json(response))
}
#[utoipa::path(
post,
path = "/v1/system/fcl_reset",
tag = "system",
responses(
(status = 200, description = "FCL reset", body = HashMap<String, String>)
)
)]
pub async fn post_fcl_reset_system(
State(_state): State<ApiState>,
) -> ApiResult<Json<HashMap<String, String>>> {
tracing::info!(target: "feagi-api", "FCL reset requested");
Ok(Json(HashMap::from([(
"message".to_string(),
"FCL reset successfully".to_string(),
)])))
}
#[utoipa::path(
get,
path = "/v1/system/processes",
tag = "system",
responses(
(status = 200, description = "Active processes", body = HashMap<String, serde_json::Value>)
)
)]
pub async fn get_processes(
State(state): State<ApiState>,
) -> ApiResult<Json<HashMap<String, serde_json::Value>>> {
let runtime_service = state.runtime_service.as_ref();
let status = runtime_service
.get_status()
.await
.map_err(|e| ApiError::internal(format!("Failed to get processes: {}", e)))?;
let mut processes = HashMap::new();
processes.insert(
"burst_engine".to_string(),
serde_json::json!({
"active": status.is_running,
"paused": status.is_paused
}),
);
processes.insert(
"api_server".to_string(),
serde_json::json!({"active": true}),
);
Ok(Json(processes))
}
#[utoipa::path(
get,
path = "/v1/system/unique_logs",
tag = "system",
responses(
(status = 200, description = "Unique logs", body = HashMap<String, Vec<String>>)
)
)]
pub async fn get_unique_logs(
State(_state): State<ApiState>,
) -> ApiResult<Json<HashMap<String, Vec<String>>>> {
let mut response = HashMap::new();
response.insert("logs".to_string(), Vec::new());
Ok(Json(response))
}
#[utoipa::path(
post,
path = "/v1/system/logs",
tag = "system",
responses(
(status = 200, description = "Log config updated", body = HashMap<String, String>)
)
)]
pub async fn post_logs(
State(_state): State<ApiState>,
Json(_config): Json<HashMap<String, serde_json::Value>>,
) -> ApiResult<Json<HashMap<String, String>>> {
Ok(Json(HashMap::from([(
"message".to_string(),
"Log configuration updated".to_string(),
)])))
}
#[utoipa::path(
get,
path = "/v1/system/beacon/subscribers",
tag = "system",
responses(
(status = 200, description = "Beacon subscribers", body = Vec<String>)
)
)]
pub async fn get_beacon_subscribers(
State(_state): State<ApiState>,
) -> ApiResult<Json<Vec<String>>> {
Ok(Json(Vec::new()))
}
#[utoipa::path(
post,
path = "/v1/system/beacon/subscribe",
tag = "system",
responses(
(status = 200, description = "Subscribed", body = HashMap<String, String>)
)
)]
pub async fn post_beacon_subscribe(
State(_state): State<ApiState>,
Json(_request): Json<HashMap<String, String>>,
) -> ApiResult<Json<HashMap<String, String>>> {
Ok(Json(HashMap::from([(
"message".to_string(),
"Subscribed to beacon".to_string(),
)])))
}
#[utoipa::path(
delete,
path = "/v1/system/beacon/unsubscribe",
tag = "system",
responses(
(status = 200, description = "Unsubscribed", body = HashMap<String, String>)
)
)]
pub async fn delete_beacon_unsubscribe(
State(_state): State<ApiState>,
Json(_request): Json<HashMap<String, String>>,
) -> ApiResult<Json<HashMap<String, String>>> {
Ok(Json(HashMap::from([(
"message".to_string(),
"Unsubscribed from beacon".to_string(),
)])))
}
#[utoipa::path(
get,
path = "/v1/system/global_activity_visualization",
tag = "system",
responses(
(status = 200, description = "Global activity viz status", body = HashMap<String, serde_json::Value>)
)
)]
pub async fn get_global_activity_visualization(
State(_state): State<ApiState>,
) -> ApiResult<Json<HashMap<String, serde_json::Value>>> {
let mut response = HashMap::new();
response.insert("enabled".to_string(), serde_json::json!(false));
response.insert("frequency_hz".to_string(), serde_json::json!(30.0));
Ok(Json(response))
}
#[utoipa::path(
put,
path = "/v1/system/global_activity_visualization",
tag = "system",
responses(
(status = 200, description = "Configured", body = HashMap<String, String>)
)
)]
pub async fn put_global_activity_visualization(
State(_state): State<ApiState>,
Json(_config): Json<HashMap<String, serde_json::Value>>,
) -> ApiResult<Json<HashMap<String, String>>> {
Ok(Json(HashMap::from([(
"message".to_string(),
"Global activity visualization configured".to_string(),
)])))
}
#[utoipa::path(
post,
path = "/v1/system/circuit_library_path",
tag = "system",
responses(
(status = 200, description = "Path set", body = HashMap<String, String>)
)
)]
pub async fn post_circuit_library_path(
State(_state): State<ApiState>,
Json(_request): Json<HashMap<String, String>>,
) -> ApiResult<Json<HashMap<String, String>>> {
Ok(Json(HashMap::from([(
"message".to_string(),
"Circuit library path updated".to_string(),
)])))
}
#[utoipa::path(
get,
path = "/v1/system/db/influxdb/test",
tag = "system",
responses(
(status = 200, description = "Test result", body = HashMap<String, bool>)
)
)]
pub async fn get_influxdb_test(
State(_state): State<ApiState>,
) -> ApiResult<Json<HashMap<String, bool>>> {
let mut response = HashMap::new();
response.insert("connected".to_string(), false);
response.insert("available".to_string(), false);
Ok(Json(response))
}
#[utoipa::path(
post,
path = "/v1/system/register",
tag = "system",
responses(
(status = 200, description = "Registered", body = HashMap<String, String>)
)
)]
pub async fn post_register_system(
State(_state): State<ApiState>,
Json(_request): Json<HashMap<String, serde_json::Value>>,
) -> ApiResult<Json<HashMap<String, String>>> {
Ok(Json(HashMap::from([(
"message".to_string(),
"System component registered".to_string(),
)])))
}