Skip to main content

feagi_api/endpoints/
physiology.rs

1// Copyright 2025 Neuraville Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4/*!
5 * FEAGI v1 Physiology API
6 *
7 * Endpoints to read/update physiology parameters in the active genome
8 * Maps to Python: feagi/api/v1/physiology.py
9 */
10
11use crate::common::ApiState;
12use crate::common::{ApiError, ApiResult, Json, State};
13// Removed - using crate::common::State instead
14use serde_json::{json, Value};
15use std::collections::HashMap;
16
17// ============================================================================
18// PHYSIOLOGY CONFIGURATION
19// ============================================================================
20
21/// Get current physiology parameters including simulation timestep and neural aging settings.
22#[utoipa::path(
23    get,
24    path = "/v1/physiology/",
25    tag = "physiology",
26    responses(
27        (status = 200, description = "Physiology parameters", body = HashMap<String, serde_json::Value>),
28        (status = 500, description = "Internal server error")
29    )
30)]
31pub async fn get_physiology(
32    State(state): State<ApiState>,
33) -> ApiResult<Json<HashMap<String, Value>>> {
34    // Get physiology parameters from genome via ConnectomeService
35    let _connectome_service = state.connectome_service.as_ref();
36
37    // Get current simulation timestep from runtime service
38    let runtime_service = state.runtime_service.as_ref();
39    let status = runtime_service
40        .get_status()
41        .await
42        .map_err(|e| ApiError::internal(format!("Failed to get runtime status: {}", e)))?;
43
44    let simulation_timestep = if status.frequency_hz > 0.0 {
45        1.0 / status.frequency_hz
46    } else {
47        0.0
48    };
49
50    // TODO: Add get_genome_physiology to ConnectomeService for other parameters
51    let physiology = json!({
52        "simulation_timestep": simulation_timestep,
53        "max_age": 0,
54        "evolution_burst_count": 0,
55        "ipu_idle_threshold": 0,
56        "plasticity_queue_depth": 0,
57        "lifespan_mgmt_interval": 0,
58        "sleep_trigger_inactivity_window": 0,
59        "sleep_trigger_neural_activity_max": 0.0
60    });
61
62    let mut response = HashMap::new();
63    response.insert("physiology".to_string(), physiology);
64
65    Ok(Json(response))
66}
67
68/// Update physiology parameters in the active genome including timestep and aging settings.
69#[utoipa::path(
70    put,
71    path = "/v1/physiology/",
72    tag = "physiology",
73    responses(
74        (status = 200, description = "Physiology updated", body = HashMap<String, serde_json::Value>),
75        (status = 500, description = "Internal server error")
76    )
77)]
78pub async fn put_physiology(
79    State(state): State<ApiState>,
80    Json(request): Json<HashMap<String, Value>>,
81) -> ApiResult<Json<HashMap<String, Value>>> {
82    // Whitelist of allowed physiology keys
83    let allowed_keys = [
84        "simulation_timestep",
85        "max_age",
86        "evolution_burst_count",
87        "ipu_idle_threshold",
88        "plasticity_queue_depth",
89        "lifespan_mgmt_interval",
90        "sleep_trigger_inactivity_window",
91        "sleep_trigger_neural_activity_max",
92    ];
93
94    // Extract and filter physiology updates
95    let updates = request
96        .get("physiology")
97        .and_then(|v| v.as_object())
98        .map(|obj| {
99            obj.iter()
100                .filter(|(k, _)| allowed_keys.contains(&k.as_str()))
101                .map(|(k, v)| (k.clone(), v.clone()))
102                .collect::<HashMap<String, Value>>()
103        })
104        .unwrap_or_default();
105
106    if updates.is_empty() {
107        return Ok(Json(HashMap::from([
108            ("success".to_string(), json!(false)),
109            ("updated".to_string(), json!({})),
110            (
111                "message".to_string(),
112                json!("No valid physiology parameters provided"),
113            ),
114        ])));
115    }
116
117    // Apply simulation_timestep if provided
118    if let Some(timestep) = updates.get("simulation_timestep").and_then(|v| v.as_f64()) {
119        let runtime_service = state.runtime_service.as_ref();
120        if timestep > 0.0 {
121            let frequency = 1.0 / timestep;
122            runtime_service
123                .set_frequency(frequency)
124                .await
125                .map_err(|e| ApiError::internal(format!("Failed to set timestep: {}", e)))?;
126            tracing::info!(target: "feagi-api", "Updated simulation timestep to {:.6}s ({:.2} Hz)",
127                timestep, frequency);
128        }
129    }
130
131    // TODO: Apply other physiology parameters to genome
132    tracing::info!(target: "feagi-api", "Physiology parameters updated: {:?}",
133        updates.keys().collect::<Vec<_>>());
134
135    let mut response = HashMap::new();
136    response.insert("success".to_string(), json!(true));
137    response.insert("updated".to_string(), json!(updates));
138
139    Ok(Json(response))
140}