1use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10
11use crate::common::ApiState;
12use crate::common::{ApiError, ApiResult, Json, State};
13
14#[allow(non_snake_case)] #[derive(Debug, Serialize, Deserialize, utoipa::ToSchema)]
20pub struct HealthCheckResponse {
21 pub burst_engine: bool,
22 pub connected_agents: Option<i32>,
23 pub influxdb_availability: bool,
24 pub neuron_count_max: i64,
25 pub synapse_count_max: i64,
26 pub latest_changes_saved_externally: bool,
27 pub genome_availability: bool,
28 pub genome_validity: Option<bool>,
29 pub brain_readiness: bool,
30 pub feagi_session: Option<i64>,
31 pub fitness: Option<f64>,
32 pub cortical_area_count: Option<i32>,
33 pub neuron_count: Option<i64>,
34 pub memory_neuron_count: Option<i64>,
35 pub regular_neuron_count: Option<i64>,
36 pub synapse_count: Option<i64>,
37 pub estimated_brain_size_in_MB: Option<f64>,
38 pub genome_num: Option<i32>,
39 pub genome_timestamp: Option<i64>,
40 pub simulation_timestep: Option<f64>,
41 pub memory_area_stats: Option<HashMap<String, HashMap<String, serde_json::Value>>>,
42 pub amalgamation_pending: Option<HashMap<String, serde_json::Value>>,
43 #[serde(skip_serializing_if = "Option::is_none")]
45 pub brain_regions_root: Option<String>,
46}
47
48#[utoipa::path(
54 get,
55 path = "/v1/system/health_check",
56 responses(
57 (status = 200, description = "System health retrieved successfully", body = HealthCheckResponse),
58 (status = 500, description = "Internal server error")
59 ),
60 tag = "system"
61)]
62pub async fn get_health_check(
63 State(state): State<ApiState>,
64) -> ApiResult<Json<HealthCheckResponse>> {
65 let analytics_service = state.analytics_service.as_ref();
66
67 let health = analytics_service
69 .get_system_health()
70 .await
71 .map_err(|e| ApiError::internal(format!("Failed to get system health: {}", e)))?;
72
73 let burst_engine_active = state
75 .runtime_service
76 .get_status()
77 .await
78 .map(|status| status.is_running)
79 .unwrap_or(false);
80
81 let _burst_count = state.runtime_service.get_burst_count().await.ok();
82
83 let connected_agents = if let Some(agent_service) = state.agent_service.as_ref() {
85 agent_service
86 .list_agents()
87 .await
88 .ok()
89 .map(|agents| agents.len() as i32)
90 } else {
91 None
92 };
93
94 let synapse_count = analytics_service
96 .get_total_synapse_count()
97 .await
98 .ok()
99 .map(|count| count as i64);
100
101 let regular_neuron_count = analytics_service
103 .get_regular_neuron_count()
104 .await
105 .ok()
106 .map(|count| count as i64);
107
108 let memory_neuron_count = analytics_service
109 .get_memory_neuron_count()
110 .await
111 .ok()
112 .map(|count| count as i64);
113
114 let genome_info = state.genome_service.get_genome_info().await.ok();
116
117 let simulation_timestep = genome_info.as_ref().map(|info| info.simulation_timestep);
118 let genome_num = genome_info.as_ref().and_then(|info| info.genome_num);
119 let genome_timestamp = genome_info.as_ref().and_then(|info| info.genome_timestamp);
120
121 #[allow(non_snake_case)] let estimated_brain_size_in_MB = {
125 let neuron_bytes = health.neuron_count * 64;
126 let synapse_bytes = synapse_count.unwrap_or(0) as usize * 16;
127 let metadata_bytes = health.cortical_area_count * 512; let total_bytes = neuron_bytes + synapse_bytes + metadata_bytes;
129 Some((total_bytes as f64) / (1024.0 * 1024.0))
130 };
131
132 let neuron_count_max = health.neuron_capacity as i64;
134 let synapse_count_max = health.synapse_capacity as i64;
135
136 let influxdb_availability = false; let latest_changes_saved_externally = false; let genome_availability = health.cortical_area_count > 0;
140 let genome_validity = Some(health.brain_readiness);
141
142 let feagi_session = Some(state.feagi_session_timestamp);
144
145 let fitness = None; let memory_area_stats = None; let amalgamation_pending = None; #[cfg(feature = "services")]
152 let brain_regions_root = feagi_brain_development::ConnectomeManager::instance()
153 .read()
154 .get_root_region_id();
155 #[cfg(not(feature = "services"))]
156 let brain_regions_root = None; Ok(Json(HealthCheckResponse {
159 burst_engine: burst_engine_active,
160 connected_agents,
161 influxdb_availability,
162 neuron_count_max,
163 synapse_count_max,
164 latest_changes_saved_externally,
165 genome_availability,
166 genome_validity,
167 brain_readiness: health.brain_readiness,
168 feagi_session,
169 fitness,
170 cortical_area_count: Some(health.cortical_area_count as i32),
171 neuron_count: Some(health.neuron_count as i64),
172 memory_neuron_count,
173 regular_neuron_count,
174 synapse_count,
175 estimated_brain_size_in_MB,
176 genome_num,
177 genome_timestamp,
178 simulation_timestep,
179 memory_area_stats,
180 amalgamation_pending,
181 brain_regions_root, }))
183}
184
185#[utoipa::path(
187 get,
188 path = "/v1/system/cortical_area_visualization_skip_rate",
189 responses(
190 (status = 200, description = "Skip rate retrieved successfully", body = i32),
191 (status = 500, description = "Internal server error")
192 ),
193 tag = "system"
194)]
195pub async fn get_cortical_area_visualization_skip_rate(
196 State(_state): State<ApiState>,
197) -> ApiResult<Json<i32>> {
198 Ok(Json(1))
201}
202
203#[utoipa::path(
205 put,
206 path = "/v1/system/cortical_area_visualization_skip_rate",
207 request_body = i32,
208 responses(
209 (status = 200, description = "Skip rate updated successfully"),
210 (status = 500, description = "Internal server error")
211 ),
212 tag = "system"
213)]
214pub async fn set_cortical_area_visualization_skip_rate(
215 State(_state): State<ApiState>,
216 Json(skip_rate): Json<i32>,
217) -> ApiResult<Json<serde_json::Value>> {
218 Ok(Json(serde_json::json!({
220 "message": format!("Skip rate set to {}", skip_rate)
221 })))
222}
223
224#[utoipa::path(
226 get,
227 path = "/v1/system/cortical_area_visualization_suppression_threshold",
228 responses(
229 (status = 200, description = "Threshold retrieved successfully", body = i32),
230 (status = 500, description = "Internal server error")
231 ),
232 tag = "system"
233)]
234pub async fn get_cortical_area_visualization_suppression_threshold(
235 State(_state): State<ApiState>,
236) -> ApiResult<Json<i32>> {
237 Ok(Json(0))
240}
241
242#[utoipa::path(
244 put,
245 path = "/v1/system/cortical_area_visualization_suppression_threshold",
246 request_body = i32,
247 responses(
248 (status = 200, description = "Threshold updated successfully"),
249 (status = 500, description = "Internal server error")
250 ),
251 tag = "system"
252)]
253pub async fn set_cortical_area_visualization_suppression_threshold(
254 State(_state): State<ApiState>,
255 Json(threshold): Json<i32>,
256) -> ApiResult<Json<serde_json::Value>> {
257 Ok(Json(serde_json::json!({
259 "message": format!("Suppression threshold set to {}", threshold)
260 })))
261}
262
263#[utoipa::path(
269 get,
270 path = "/v1/system/version",
271 tag = "system",
272 responses(
273 (status = 200, description = "Version string", body = String)
274 )
275)]
276pub async fn get_version(State(_state): State<ApiState>) -> ApiResult<Json<String>> {
277 Ok(Json(env!("CARGO_PKG_VERSION").to_string()))
278}
279
280#[utoipa::path(
282 get,
283 path = "/v1/system/versions",
284 tag = "system",
285 responses(
286 (status = 200, description = "Version information", body = HashMap<String, String>)
287 )
288)]
289pub async fn get_versions(
290 State(state): State<ApiState>,
291) -> ApiResult<Json<HashMap<String, String>>> {
292 match state.system_service.get_version().await {
295 Ok(version_info) => {
296 let mut versions = version_info.crates.clone();
297
298 versions.insert("rust".to_string(), version_info.rust_version);
300 versions.insert("build_timestamp".to_string(), version_info.build_timestamp);
301
302 Ok(Json(versions))
303 }
304 Err(e) => {
305 tracing::warn!(
307 "Failed to get version from system service: {}, using fallback",
308 e
309 );
310 let mut versions = HashMap::new();
311 versions.insert(
312 "error".to_string(),
313 "system service unavailable".to_string(),
314 );
315 Ok(Json(versions))
316 }
317 }
318}
319
320#[utoipa::path(
322 get,
323 path = "/v1/system/configuration",
324 tag = "system",
325 responses(
326 (status = 200, description = "System configuration", body = HashMap<String, serde_json::Value>)
327 )
328)]
329pub async fn get_configuration(
330 State(state): State<ApiState>,
331) -> ApiResult<Json<HashMap<String, serde_json::Value>>> {
332 let health = state
334 .analytics_service
335 .get_system_health()
336 .await
337 .map_err(|e| ApiError::internal(format!("Failed to get system health: {}", e)))?;
338
339 let mut config = HashMap::new();
340 config.insert("api_host".to_string(), serde_json::json!("0.0.0.0"));
341 config.insert("api_port".to_string(), serde_json::json!(8000));
342 config.insert(
344 "max_neurons".to_string(),
345 serde_json::json!(health.neuron_capacity),
346 );
347 config.insert(
348 "max_synapses".to_string(),
349 serde_json::json!(health.synapse_capacity),
350 );
351
352 Ok(Json(config))
353}
354
355#[utoipa::path(
357 get,
358 path = "/v1/system/user_preferences",
359 tag = "system",
360 responses(
361 (status = 200, description = "User preferences", body = HashMap<String, serde_json::Value>)
362 )
363)]
364pub async fn get_user_preferences(
365 State(_state): State<ApiState>,
366) -> ApiResult<Json<HashMap<String, serde_json::Value>>> {
367 let mut prefs = HashMap::new();
368 prefs.insert("adv_mode".to_string(), serde_json::json!(false));
369 prefs.insert("ui_magnification".to_string(), serde_json::json!(1.0));
370 prefs.insert(
371 "auto_pns_area_creation".to_string(),
372 serde_json::json!(true),
373 );
374
375 Ok(Json(prefs))
376}
377
378#[utoipa::path(
380 put,
381 path = "/v1/system/user_preferences",
382 tag = "system",
383 responses(
384 (status = 200, description = "Preferences updated", body = HashMap<String, String>)
385 )
386)]
387pub async fn put_user_preferences(
388 State(_state): State<ApiState>,
389 Json(_prefs): Json<HashMap<String, serde_json::Value>>,
390) -> ApiResult<Json<HashMap<String, String>>> {
391 Ok(Json(HashMap::from([(
392 "message".to_string(),
393 "User preferences updated successfully".to_string(),
394 )])))
395}
396
397#[utoipa::path(
399 get,
400 path = "/v1/system/cortical_area_types",
401 tag = "system",
402 responses(
403 (status = 200, description = "Cortical area types", body = Vec<String>)
404 )
405)]
406pub async fn get_cortical_area_types_list(
407 State(_state): State<ApiState>,
408) -> ApiResult<Json<Vec<String>>> {
409 Ok(Json(vec![
410 "Sensory".to_string(),
411 "Motor".to_string(),
412 "Custom".to_string(),
413 "Memory".to_string(),
414 "Core".to_string(),
415 ]))
416}
417
418#[utoipa::path(
420 post,
421 path = "/v1/system/enable_visualization_fq_sampler",
422 tag = "system",
423 responses(
424 (status = 200, description = "FQ sampler enabled", body = HashMap<String, String>)
425 )
426)]
427pub async fn post_enable_visualization_fq_sampler(
428 State(state): State<ApiState>,
429) -> ApiResult<Json<HashMap<String, String>>> {
430 let runtime_service = state.runtime_service.as_ref();
431
432 runtime_service
433 .set_fcl_sampler_config(None, Some(1))
434 .await
435 .map_err(|e| ApiError::internal(format!("Failed to enable FQ sampler: {}", e)))?;
436
437 Ok(Json(HashMap::from([(
438 "message".to_string(),
439 "Visualization FQ sampler enabled".to_string(),
440 )])))
441}
442
443#[utoipa::path(
445 post,
446 path = "/v1/system/disable_visualization_fq_sampler",
447 tag = "system",
448 responses(
449 (status = 200, description = "FQ sampler disabled", body = HashMap<String, String>)
450 )
451)]
452pub async fn post_disable_visualization_fq_sampler(
453 State(state): State<ApiState>,
454) -> ApiResult<Json<HashMap<String, String>>> {
455 let runtime_service = state.runtime_service.as_ref();
456
457 runtime_service
458 .set_fcl_sampler_config(None, Some(0))
459 .await
460 .map_err(|e| ApiError::internal(format!("Failed to disable FQ sampler: {}", e)))?;
461
462 Ok(Json(HashMap::from([(
463 "message".to_string(),
464 "Visualization FQ sampler disabled".to_string(),
465 )])))
466}
467
468#[utoipa::path(
470 get,
471 path = "/v1/system/fcl_status",
472 tag = "system",
473 responses(
474 (status = 200, description = "FCL status", body = HashMap<String, serde_json::Value>)
475 )
476)]
477pub async fn get_fcl_status_system(
478 State(state): State<ApiState>,
479) -> ApiResult<Json<HashMap<String, serde_json::Value>>> {
480 let runtime_service = state.runtime_service.as_ref();
481
482 let (frequency, consumer) = runtime_service
483 .get_fcl_sampler_config()
484 .await
485 .map_err(|e| ApiError::internal(format!("Failed to get FCL status: {}", e)))?;
486
487 let mut response = HashMap::new();
488 response.insert("available".to_string(), serde_json::json!(true));
489 response.insert("frequency".to_string(), serde_json::json!(frequency));
490 response.insert("consumer".to_string(), serde_json::json!(consumer));
491 response.insert("enabled".to_string(), serde_json::json!(consumer > 0));
492
493 Ok(Json(response))
494}
495
496#[utoipa::path(
498 post,
499 path = "/v1/system/fcl_reset",
500 tag = "system",
501 responses(
502 (status = 200, description = "FCL reset", body = HashMap<String, String>)
503 )
504)]
505pub async fn post_fcl_reset_system(
506 State(_state): State<ApiState>,
507) -> ApiResult<Json<HashMap<String, String>>> {
508 tracing::info!(target: "feagi-api", "FCL reset requested");
509
510 Ok(Json(HashMap::from([(
511 "message".to_string(),
512 "FCL reset successfully".to_string(),
513 )])))
514}
515
516#[utoipa::path(
518 get,
519 path = "/v1/system/processes",
520 tag = "system",
521 responses(
522 (status = 200, description = "Active processes", body = HashMap<String, serde_json::Value>)
523 )
524)]
525pub async fn get_processes(
526 State(state): State<ApiState>,
527) -> ApiResult<Json<HashMap<String, serde_json::Value>>> {
528 let runtime_service = state.runtime_service.as_ref();
529
530 let status = runtime_service
531 .get_status()
532 .await
533 .map_err(|e| ApiError::internal(format!("Failed to get processes: {}", e)))?;
534
535 let mut processes = HashMap::new();
536 processes.insert(
537 "burst_engine".to_string(),
538 serde_json::json!({
539 "active": status.is_running,
540 "paused": status.is_paused
541 }),
542 );
543 processes.insert(
544 "api_server".to_string(),
545 serde_json::json!({"active": true}),
546 );
547
548 Ok(Json(processes))
549}
550
551#[utoipa::path(
553 get,
554 path = "/v1/system/unique_logs",
555 tag = "system",
556 responses(
557 (status = 200, description = "Unique logs", body = HashMap<String, Vec<String>>)
558 )
559)]
560pub async fn get_unique_logs(
561 State(_state): State<ApiState>,
562) -> ApiResult<Json<HashMap<String, Vec<String>>>> {
563 let mut response = HashMap::new();
564 response.insert("logs".to_string(), Vec::new());
565
566 Ok(Json(response))
567}
568
569#[utoipa::path(
571 post,
572 path = "/v1/system/logs",
573 tag = "system",
574 responses(
575 (status = 200, description = "Log config updated", body = HashMap<String, String>)
576 )
577)]
578pub async fn post_logs(
579 State(_state): State<ApiState>,
580 Json(_config): Json<HashMap<String, serde_json::Value>>,
581) -> ApiResult<Json<HashMap<String, String>>> {
582 Ok(Json(HashMap::from([(
583 "message".to_string(),
584 "Log configuration updated".to_string(),
585 )])))
586}
587
588#[utoipa::path(
590 get,
591 path = "/v1/system/beacon/subscribers",
592 tag = "system",
593 responses(
594 (status = 200, description = "Beacon subscribers", body = Vec<String>)
595 )
596)]
597pub async fn get_beacon_subscribers(
598 State(_state): State<ApiState>,
599) -> ApiResult<Json<Vec<String>>> {
600 Ok(Json(Vec::new()))
601}
602
603#[utoipa::path(
605 post,
606 path = "/v1/system/beacon/subscribe",
607 tag = "system",
608 responses(
609 (status = 200, description = "Subscribed", body = HashMap<String, String>)
610 )
611)]
612pub async fn post_beacon_subscribe(
613 State(_state): State<ApiState>,
614 Json(_request): Json<HashMap<String, String>>,
615) -> ApiResult<Json<HashMap<String, String>>> {
616 Ok(Json(HashMap::from([(
617 "message".to_string(),
618 "Subscribed to beacon".to_string(),
619 )])))
620}
621
622#[utoipa::path(
624 delete,
625 path = "/v1/system/beacon/unsubscribe",
626 tag = "system",
627 responses(
628 (status = 200, description = "Unsubscribed", body = HashMap<String, String>)
629 )
630)]
631pub async fn delete_beacon_unsubscribe(
632 State(_state): State<ApiState>,
633 Json(_request): Json<HashMap<String, String>>,
634) -> ApiResult<Json<HashMap<String, String>>> {
635 Ok(Json(HashMap::from([(
636 "message".to_string(),
637 "Unsubscribed from beacon".to_string(),
638 )])))
639}
640
641#[utoipa::path(
643 get,
644 path = "/v1/system/global_activity_visualization",
645 tag = "system",
646 responses(
647 (status = 200, description = "Global activity viz status", body = HashMap<String, serde_json::Value>)
648 )
649)]
650pub async fn get_global_activity_visualization(
651 State(_state): State<ApiState>,
652) -> ApiResult<Json<HashMap<String, serde_json::Value>>> {
653 let mut response = HashMap::new();
654 response.insert("enabled".to_string(), serde_json::json!(false));
655 response.insert("frequency_hz".to_string(), serde_json::json!(30.0));
656
657 Ok(Json(response))
658}
659
660#[utoipa::path(
662 put,
663 path = "/v1/system/global_activity_visualization",
664 tag = "system",
665 responses(
666 (status = 200, description = "Configured", body = HashMap<String, String>)
667 )
668)]
669pub async fn put_global_activity_visualization(
670 State(_state): State<ApiState>,
671 Json(_config): Json<HashMap<String, serde_json::Value>>,
672) -> ApiResult<Json<HashMap<String, String>>> {
673 Ok(Json(HashMap::from([(
674 "message".to_string(),
675 "Global activity visualization configured".to_string(),
676 )])))
677}
678
679#[utoipa::path(
681 post,
682 path = "/v1/system/circuit_library_path",
683 tag = "system",
684 responses(
685 (status = 200, description = "Path set", body = HashMap<String, String>)
686 )
687)]
688pub async fn post_circuit_library_path(
689 State(_state): State<ApiState>,
690 Json(_request): Json<HashMap<String, String>>,
691) -> ApiResult<Json<HashMap<String, String>>> {
692 Ok(Json(HashMap::from([(
693 "message".to_string(),
694 "Circuit library path updated".to_string(),
695 )])))
696}
697
698#[utoipa::path(
700 get,
701 path = "/v1/system/db/influxdb/test",
702 tag = "system",
703 responses(
704 (status = 200, description = "Test result", body = HashMap<String, bool>)
705 )
706)]
707pub async fn get_influxdb_test(
708 State(_state): State<ApiState>,
709) -> ApiResult<Json<HashMap<String, bool>>> {
710 let mut response = HashMap::new();
711 response.insert("connected".to_string(), false);
712 response.insert("available".to_string(), false);
713
714 Ok(Json(response))
715}
716
717#[utoipa::path(
719 post,
720 path = "/v1/system/register",
721 tag = "system",
722 responses(
723 (status = 200, description = "Registered", body = HashMap<String, String>)
724 )
725)]
726pub async fn post_register_system(
727 State(_state): State<ApiState>,
728 Json(_request): Json<HashMap<String, serde_json::Value>>,
729) -> ApiResult<Json<HashMap<String, String>>> {
730 Ok(Json(HashMap::from([(
731 "message".to_string(),
732 "System component registered".to_string(),
733 )])))
734}