1use axum::{
10 body::Body,
11 extract::State,
12 http::{Method, Request, StatusCode},
13 middleware::{self, Next},
14 response::{Html, IntoResponse, Json, Redirect, Response},
15 routing::{get, put},
16 Router,
17};
18use http_body_util::BodyExt;
19use std::path::PathBuf;
20use std::sync::atomic::{AtomicBool, Ordering};
21use std::sync::Arc;
22use std::time::Duration;
23use tower_http::{
24 cors::{Any, CorsLayer},
25 trace::TraceLayer,
26};
27use utoipa::OpenApi;
28
29use crate::amalgamation;
30#[cfg(feature = "http")]
31use crate::openapi::ApiDoc;
32#[cfg(feature = "feagi-agent")]
33use feagi_config::load_config;
34#[cfg(feature = "feagi-agent")]
35use feagi_io::protocol_implementations::websocket::websocket_std::{
36 FeagiWebSocketServerPublisherProperties, FeagiWebSocketServerPullerProperties,
37};
38#[cfg(feature = "feagi-agent")]
39use feagi_io::protocol_implementations::zmq::{
40 FeagiZmqServerPublisherProperties, FeagiZmqServerPullerProperties,
41};
42#[cfg(feature = "services")]
43use feagi_services::traits::{AgentService, SystemService};
44#[cfg(feature = "services")]
45use feagi_services::{
46 AnalyticsService, ConnectomeService, GenomeService, NeuronService, RuntimeService,
47};
48
49#[derive(Clone)]
51pub struct ApiState {
52 pub network_connection_info_provider:
54 Option<Arc<dyn crate::endpoints::network::NetworkConnectionInfoProvider>>,
55 pub agent_service: Option<Arc<dyn AgentService + Send + Sync>>,
56 pub analytics_service: Arc<dyn AnalyticsService + Send + Sync>,
57 pub connectome_service: Arc<dyn ConnectomeService + Send + Sync>,
58 pub genome_service: Arc<dyn GenomeService + Send + Sync>,
59 pub neuron_service: Arc<dyn NeuronService + Send + Sync>,
60 pub runtime_service: Arc<dyn RuntimeService + Send + Sync>,
61 pub system_service: Arc<dyn SystemService + Send + Sync>,
62 pub snapshot_service: Option<Arc<dyn feagi_services::SnapshotService + Send + Sync>>,
63 pub feagi_session_timestamp: i64,
66 pub filesystem_data_root: PathBuf,
73 pub memory_stats_cache: Option<feagi_npu_plasticity::MemoryStatsCache>,
75 pub amalgamation_state: amalgamation::SharedAmalgamationState,
77 pub genome_transition_lock: Arc<tokio::sync::Mutex<()>>,
79 pub genome_transition_in_progress: Arc<AtomicBool>,
81 #[cfg(feature = "feagi-agent")]
83 pub agent_handler: Option<Arc<std::sync::Mutex<feagi_agent::server::FeagiAgentHandler>>>,
84}
85
86impl ApiState {
87 #[cfg(feature = "feagi-agent")]
89 pub fn init_agent_registration_handler(
90 ) -> Arc<std::sync::Mutex<feagi_agent::server::FeagiAgentHandler>> {
91 let config = load_config(None, None).expect("Failed to load FEAGI configuration");
92 let liveness_config = feagi_agent::server::AgentLivenessConfig {
93 heartbeat_timeout: Duration::from_millis(config.zmq.client_heartbeat_timeout),
94 stale_check_interval: Duration::from_millis(config.zmq.polling_timeout),
95 };
96 let mut handler = feagi_agent::server::FeagiAgentHandler::new_with_liveness_config(
97 Box::new(feagi_agent::server::auth::DummyAuth {}),
98 liveness_config,
99 );
100 let available_transports: Vec<String> = config
101 .transports
102 .available
103 .iter()
104 .map(|transport| transport.to_lowercase())
105 .collect();
106
107 if available_transports
108 .iter()
109 .any(|transport| transport == "zmq")
110 {
111 let sensory_address =
112 format_tcp_endpoint(&config.zmq.bind_host, config.ports.zmq_sensory_port);
113 let sensory_adv_address =
114 format_tcp_endpoint(&config.zmq.advertised_host, config.ports.zmq_sensory_port);
115 let motor_address =
116 format_tcp_endpoint(&config.zmq.bind_host, config.ports.zmq_motor_port);
117 let motor_adv_address =
118 format_tcp_endpoint(&config.zmq.advertised_host, config.ports.zmq_motor_port);
119 let visualization_address =
120 format_tcp_endpoint(&config.zmq.bind_host, config.ports.zmq_visualization_port);
121 let visualization_adv_address = format_tcp_endpoint(
122 &config.zmq.advertised_host,
123 config.ports.zmq_visualization_port,
124 );
125
126 let sensory =
127 FeagiZmqServerPullerProperties::new(&sensory_address, &sensory_adv_address)
128 .expect("Failed to create ZMQ sensory puller properties");
129 handler.add_puller_server(Box::new(sensory));
130
131 let motor = FeagiZmqServerPublisherProperties::new(&motor_address, &motor_adv_address)
132 .expect("Failed to create ZMQ motor publisher properties");
133 let visualization = FeagiZmqServerPublisherProperties::new(
134 &visualization_address,
135 &visualization_adv_address,
136 )
137 .expect("Failed to create ZMQ visualization publisher properties");
138 handler.add_publisher_server(Box::new(motor));
139 handler.add_publisher_server(Box::new(visualization));
140 }
141
142 if available_transports
143 .iter()
144 .any(|transport| transport == "websocket" || transport == "ws")
145 {
146 let sensory_address =
147 format_ws_address(&config.websocket.bind_host, config.websocket.sensory_port);
148 let sensory_adv_address = format_ws_address(
149 &config.websocket.advertised_host,
150 config.websocket.sensory_port,
151 );
152 let motor_address =
153 format_ws_address(&config.websocket.bind_host, config.websocket.motor_port);
154 let motor_adv_address = format_ws_address(
155 &config.websocket.advertised_host,
156 config.websocket.motor_port,
157 );
158 let visualization_address = format_ws_address(
159 &config.websocket.bind_host,
160 config.websocket.visualization_port,
161 );
162 let visualization_adv_address = format_ws_address(
163 &config.websocket.advertised_host,
164 config.websocket.visualization_port,
165 );
166
167 let sensory = FeagiWebSocketServerPullerProperties::new_with_remote(
168 &sensory_address,
169 &sensory_adv_address,
170 )
171 .expect("Failed to create WebSocket sensory puller properties");
172 handler.add_puller_server(Box::new(sensory));
173
174 let motor =
175 FeagiWebSocketServerPublisherProperties::new(&motor_address, &motor_adv_address)
176 .expect("Failed to create WebSocket motor publisher properties");
177 let visualization = FeagiWebSocketServerPublisherProperties::new(
178 &visualization_address,
179 &visualization_adv_address,
180 )
181 .expect("Failed to create WebSocket visualization publisher properties");
182 handler.add_publisher_server(Box::new(motor));
183 handler.add_publisher_server(Box::new(visualization));
184 }
185
186 Arc::new(std::sync::Mutex::new(handler))
187 }
188
189 pub fn init_amalgamation_state() -> amalgamation::SharedAmalgamationState {
191 amalgamation::new_shared_state()
192 }
193
194 pub fn init_genome_transition_controls() -> (Arc<tokio::sync::Mutex<()>>, Arc<AtomicBool>) {
196 (
197 Arc::new(tokio::sync::Mutex::new(())),
198 Arc::new(AtomicBool::new(false)),
199 )
200 }
201
202 pub fn filesystem_data_root_from_config(data_dir: &std::path::Path) -> PathBuf {
212 if !data_dir.as_os_str().is_empty() {
213 data_dir.to_path_buf()
214 } else {
215 #[cfg(target_arch = "wasm32")]
216 {
217 PathBuf::from("/tmp/feagi")
218 }
219 #[cfg(not(target_arch = "wasm32"))]
220 {
221 dirs::home_dir()
222 .map(|h| h.join(".feagi"))
223 .unwrap_or_else(|| std::env::temp_dir().join("feagi"))
224 }
225 }
226 }
227}
228
229#[cfg(feature = "feagi-agent")]
230fn format_tcp_endpoint(host: &str, port: u16) -> String {
231 if host.contains(':') {
232 format!("tcp://[{host}]:{port}")
233 } else {
234 format!("tcp://{host}:{port}")
235 }
236}
237
238#[cfg(feature = "feagi-agent")]
239fn format_ws_address(host: &str, port: u16) -> String {
240 if host.contains(':') {
241 format!("[{host}]:{port}")
242 } else {
243 format!("{host}:{port}")
244 }
245}
246
247pub fn create_http_server(state: ApiState) -> Router {
249 let middleware_state = state.clone();
250 Router::new()
251 .route("/", get(root_redirect))
253
254 .route("/swagger-ui/", get(custom_swagger_ui))
256
257 .route("/api-docs/openapi.json", get(|| async {
259 Json(ApiDoc::openapi())
260 }))
261
262 .nest("/v1", create_v1_router())
264
265 .fallback(|| async {
267 tracing::warn!(target: "feagi-api", "Unmatched request - 404 Not Found");
268 (StatusCode::NOT_FOUND, "404 Not Found")
269 })
270
271 .with_state(state)
273
274 .layer(middleware::from_fn_with_state(
276 middleware_state,
277 reject_during_genome_transition,
278 ))
279 .layer(middleware::from_fn(log_request_response_bodies))
280 .layer(create_cors_layer())
281 .layer(
282 TraceLayer::new_for_http()
283 .make_span_with(|request: &axum::http::Request<_>| {
284 tracing::span!(
285 target: "feagi-api",
286 tracing::Level::TRACE,
287 "request",
288 method = %request.method(),
289 uri = %request.uri(),
290 version = ?request.version(),
291 )
292 })
293 .on_request(|request: &axum::http::Request<_>, _span: &tracing::Span| {
294 tracing::trace!(target: "feagi-api", "Incoming request: {} {}", request.method(), request.uri());
295 })
296 .on_response(|response: &axum::http::Response<_>, latency: std::time::Duration, span: &tracing::Span| {
297 tracing::trace!(
298 target: "feagi-api",
299 "Response: status={}, latency={:?}",
300 response.status(),
301 latency
302 );
303 span.record("status", response.status().as_u16());
304 span.record("latency_ms", latency.as_millis());
305 })
306 .on_body_chunk(|chunk: &axum::body::Bytes, latency: std::time::Duration, _span: &tracing::Span| {
307 tracing::trace!(target: "feagi-api", "Response chunk: {} bytes, latency={:?}", chunk.len(), latency);
308 })
309 .on_eos(|_trailers: Option<&axum::http::HeaderMap>, stream_duration: std::time::Duration, _span: &tracing::Span| {
310 tracing::trace!(target: "feagi-api", "Stream ended, duration={:?}", stream_duration);
311 })
312 .on_failure(|error: tower_http::classify::ServerErrorsFailureClass, latency: std::time::Duration, _span: &tracing::Span| {
313 tracing::error!(
314 target: "feagi-api",
315 "Request failed: error_class={:?}, latency={:?}",
316 error, latency
317 );
318 })
319 )
320}
321
322async fn reject_during_genome_transition(
324 State(state): State<ApiState>,
325 request: Request<Body>,
326 next: Next,
327) -> Response {
328 let path = request.uri().path();
329 let method = request.method();
330 let transition_in_progress = state.genome_transition_in_progress.load(Ordering::SeqCst);
331
332 if transition_in_progress && !is_transition_allowed_route(path, method) {
333 let payload = crate::common::ApiError::conflict(
334 "Genome transition in progress: request rejected to preserve deterministic load semantics",
335 );
336 return (StatusCode::CONFLICT, Json(payload)).into_response();
337 }
338
339 next.run(request).await
340}
341
342fn is_transition_allowed_route(path: &str, method: &Method) -> bool {
343 if path.starts_with("/v1/genome/upload")
344 || path.starts_with("/v1/genome/load")
345 || path.starts_with("/v1/genome/amalgamation")
346 {
347 return true;
348 }
349
350 matches!(
351 (method, path),
352 (&Method::GET, "/v1/system/health_check") | (&Method::GET, "/v1/system/readiness_check")
353 )
354}
355
356fn create_v1_router() -> Router<ApiState> {
359 use crate::endpoints::agent;
360 use crate::endpoints::burst_engine;
361 use crate::endpoints::connectome;
362 use crate::endpoints::cortical_area;
363 use crate::endpoints::cortical_mapping;
364 use crate::endpoints::evolution;
365 use crate::endpoints::genome;
366 use crate::endpoints::input;
367 use crate::endpoints::insight;
368 use crate::endpoints::monitoring;
369 use crate::endpoints::morphology;
370 use crate::endpoints::network;
371 use crate::endpoints::neuroplasticity;
372 use crate::endpoints::outputs;
373 use crate::endpoints::physiology;
374 use crate::endpoints::region;
375 use crate::endpoints::simulation;
376 use crate::endpoints::system;
377 use crate::endpoints::training;
378 use crate::endpoints::visualization; Router::new()
381 .route(
383 "/agent/register",
384 axum::routing::post(agent::register_agent),
385 )
386 .route("/agent/heartbeat", axum::routing::post(agent::heartbeat))
387 .route("/agent/list", get(agent::list_agents))
388 .route("/agent/properties", get(agent::get_agent_properties))
389 .route("/agent/shared_mem", get(agent::get_shared_memory))
390 .route(
391 "/agent/deregister",
392 axum::routing::delete(agent::deregister_agent),
393 )
394 .route(
395 "/agent/manual_stimulation",
396 axum::routing::post(agent::manual_stimulation),
397 )
398 .route(
399 "/agent/fq_sampler_status",
400 get(agent::get_fq_sampler_status),
401 )
402 .route("/agent/capabilities", get(agent::get_capabilities))
403 .route(
404 "/agent/capabilities/all",
405 get(agent::get_all_agent_capabilities),
406 )
407 .route("/agent/info/{agent_id}", get(agent::get_agent_info))
408 .route(
409 "/agent/properties/{agent_id}",
410 get(agent::get_agent_properties_path),
411 )
412 .route(
413 "/agent/configure",
414 axum::routing::post(agent::post_configure),
415 )
416 .route(
417 "/agent/{agent_id}/device_registrations",
418 get(agent::export_device_registrations).post(agent::import_device_registrations),
419 )
420 .route("/system/health_check", get(system::get_health_check))
422 .route(
423 "/system/cortical_area_visualization_skip_rate",
424 get(system::get_cortical_area_visualization_skip_rate)
425 .put(system::set_cortical_area_visualization_skip_rate),
426 )
427 .route(
428 "/system/cortical_area_visualization_suppression_threshold",
429 get(system::get_cortical_area_visualization_suppression_threshold)
430 .put(system::set_cortical_area_visualization_suppression_threshold),
431 )
432 .route("/system/version", get(system::get_version))
433 .route("/system/versions", get(system::get_versions))
434 .route("/system/configuration", get(system::get_configuration))
435 .route(
436 "/system/user_preferences",
437 get(system::get_user_preferences).put(system::put_user_preferences),
438 )
439 .route(
440 "/system/cortical_area_types",
441 get(system::get_cortical_area_types_list),
442 )
443 .route(
444 "/system/enable_visualization_fq_sampler",
445 axum::routing::post(system::post_enable_visualization_fq_sampler),
446 )
447 .route(
448 "/system/disable_visualization_fq_sampler",
449 axum::routing::post(system::post_disable_visualization_fq_sampler),
450 )
451 .route("/system/fcl_status", get(system::get_fcl_status_system))
452 .route(
453 "/system/fcl_reset",
454 axum::routing::post(system::post_fcl_reset_system),
455 )
456 .route("/system/processes", get(system::get_processes))
457 .route("/system/unique_logs", get(system::get_unique_logs))
458 .route("/system/logs", axum::routing::post(system::post_logs))
459 .route(
460 "/system/beacon/subscribers",
461 get(system::get_beacon_subscribers),
462 )
463 .route(
464 "/system/beacon/subscribe",
465 axum::routing::post(system::post_beacon_subscribe),
466 )
467 .route(
468 "/system/beacon/unsubscribe",
469 axum::routing::delete(system::delete_beacon_unsubscribe),
470 )
471 .route(
472 "/system/global_activity_visualization",
473 get(system::get_global_activity_visualization)
474 .put(system::put_global_activity_visualization),
475 )
476 .route(
477 "/system/circuit_library_path",
478 axum::routing::post(system::post_circuit_library_path),
479 )
480 .route("/system/db/influxdb/test", get(system::get_influxdb_test))
481 .route(
482 "/system/register",
483 axum::routing::post(system::post_register_system),
484 )
485 .route("/cortical_area/ipu", get(cortical_area::get_ipu))
487 .route(
488 "/cortical_area/ipu/types",
489 get(cortical_area::get_ipu_types),
490 )
491 .route("/cortical_area/opu", get(cortical_area::get_opu))
492 .route(
493 "/cortical_area/opu/types",
494 get(cortical_area::get_opu_types),
495 )
496 .route(
497 "/cortical_area/cortical_area_id_list",
498 get(cortical_area::get_cortical_area_id_list),
499 )
500 .route(
501 "/cortical_area/cortical_area_name_list",
502 get(cortical_area::get_cortical_area_name_list),
503 )
504 .route(
505 "/cortical_area/cortical_id_name_mapping",
506 get(cortical_area::get_cortical_id_name_mapping),
507 )
508 .route(
509 "/cortical_area/cortical_types",
510 get(cortical_area::get_cortical_types),
511 )
512 .route(
513 "/cortical_area/cortical_map_detailed",
514 get(cortical_area::get_cortical_map_detailed),
515 )
516 .route(
517 "/cortical_area/cortical_locations_2d",
518 get(cortical_area::get_cortical_locations_2d),
519 )
520 .route(
521 "/cortical_area/cortical_area/geometry",
522 get(cortical_area::get_cortical_area_geometry),
523 )
524 .route(
525 "/cortical_area/cortical_visibility",
526 get(cortical_area::get_cortical_visibility),
527 )
528 .route(
529 "/cortical_area/cortical_name_location",
530 axum::routing::post(cortical_area::post_cortical_name_location),
531 )
532 .route(
533 "/cortical_area/cortical_area_properties",
534 axum::routing::post(cortical_area::post_cortical_area_properties),
535 )
536 .route(
537 "/cortical_area/multi/cortical_area_properties",
538 axum::routing::post(cortical_area::post_multi_cortical_area_properties),
539 )
540 .route(
541 "/cortical_area/cortical_area",
542 axum::routing::post(cortical_area::post_cortical_area)
543 .put(cortical_area::put_cortical_area)
544 .delete(cortical_area::delete_cortical_area),
545 )
546 .route(
547 "/cortical_area/custom_cortical_area",
548 axum::routing::post(cortical_area::post_custom_cortical_area),
549 )
550 .route(
551 "/cortical_area/clone",
552 axum::routing::post(cortical_area::post_clone),
553 )
554 .route(
555 "/cortical_area/multi/cortical_area",
556 put(cortical_area::put_multi_cortical_area)
557 .delete(cortical_area::delete_multi_cortical_area),
558 )
559 .route("/cortical_area/coord_2d", put(cortical_area::put_coord_2d))
560 .route(
561 "/cortical_area/suppress_cortical_visibility",
562 put(cortical_area::put_suppress_cortical_visibility),
563 )
564 .route("/cortical_area/reset", put(cortical_area::put_reset))
565 .route(
566 "/cortical_area/visualization",
567 get(cortical_area::get_visualization),
568 )
569 .route(
570 "/cortical_area/batch_operations",
571 axum::routing::post(cortical_area::post_batch_operations),
572 )
573 .route("/cortical_area/ipu/list", get(cortical_area::get_ipu_list))
574 .route("/cortical_area/opu/list", get(cortical_area::get_opu_list))
575 .route(
576 "/cortical_area/coordinates_3d",
577 put(cortical_area::put_coordinates_3d),
578 )
579 .route(
580 "/cortical_area/bulk_delete",
581 axum::routing::delete(cortical_area::delete_bulk),
582 )
583 .route(
584 "/cortical_area/resize",
585 axum::routing::post(cortical_area::post_resize),
586 )
587 .route(
588 "/cortical_area/reposition",
589 axum::routing::post(cortical_area::post_reposition),
590 )
591 .route(
592 "/cortical_area/voxel_neurons",
593 get(cortical_area::get_voxel_neurons).post(cortical_area::post_voxel_neurons),
594 )
595 .route(
596 "/cortical_area/memory",
597 get(cortical_area::get_memory_cortical_area),
598 )
599 .route(
600 "/cortical_area/cortical_area_index_list",
601 get(cortical_area::get_cortical_area_index_list),
602 )
603 .route(
604 "/cortical_area/cortical_idx_mapping",
605 get(cortical_area::get_cortical_idx_mapping),
606 )
607 .route(
608 "/cortical_area/mapping_restrictions",
609 get(cortical_area::get_mapping_restrictions_query)
610 .post(cortical_area::post_mapping_restrictions),
611 )
612 .route(
613 "/cortical_area/:cortical_id/memory_usage",
614 get(cortical_area::get_memory_usage),
615 )
616 .route(
617 "/cortical_area/:cortical_id/neuron_count",
618 get(cortical_area::get_area_neuron_count),
619 )
620 .route(
621 "/cortical_area/cortical_type_options",
622 axum::routing::post(cortical_area::post_cortical_type_options),
623 )
624 .route(
625 "/cortical_area/mapping_restrictions_between_areas",
626 axum::routing::post(cortical_area::post_mapping_restrictions_between_areas),
627 )
628 .route("/cortical_area/coord_3d", put(cortical_area::put_coord_3d))
629 .route(
631 "/morphology/morphology_list",
632 get(morphology::get_morphology_list),
633 )
634 .route(
635 "/morphology/morphology_types",
636 get(morphology::get_morphology_types),
637 )
638 .route("/morphology/list/types", get(morphology::get_list_types))
639 .route(
640 "/morphology/morphologies",
641 get(morphology::get_morphologies),
642 )
643 .route(
644 "/morphology/morphology",
645 axum::routing::post(morphology::post_morphology)
646 .put(morphology::put_morphology)
647 .delete(morphology::delete_morphology_by_name),
648 )
649 .route(
650 "/morphology/rename",
651 axum::routing::put(morphology::put_rename_morphology),
652 )
653 .route(
654 "/morphology/morphology_properties",
655 axum::routing::post(morphology::post_morphology_properties),
656 )
657 .route(
658 "/morphology/morphology_usage",
659 axum::routing::post(morphology::post_morphology_usage),
660 )
661 .route("/morphology/list", get(morphology::get_list))
662 .route("/morphology/info/:morphology_id", get(morphology::get_info))
663 .route(
664 "/morphology/create",
665 axum::routing::post(morphology::post_create),
666 )
667 .route(
668 "/morphology/update",
669 axum::routing::put(morphology::put_update),
670 )
671 .route(
672 "/morphology/delete/:morphology_id",
673 axum::routing::delete(morphology::delete_morphology),
674 )
675 .route("/region/regions_members", get(region::get_regions_members))
677 .route(
678 "/region/region",
679 axum::routing::post(region::post_region)
680 .put(region::put_region)
681 .delete(region::delete_region),
682 )
683 .route("/region/clone", axum::routing::post(region::post_clone))
684 .route(
685 "/region/relocate_members",
686 put(region::put_relocate_members),
687 )
688 .route(
689 "/region/region_and_members",
690 axum::routing::delete(region::delete_region_and_members),
691 )
692 .route("/region/regions", get(region::get_regions))
693 .route("/region/region_titles", get(region::get_region_titles))
694 .route("/region/region/:region_id", get(region::get_region_detail))
695 .route(
696 "/region/change_region_parent",
697 put(region::put_change_region_parent),
698 )
699 .route(
700 "/region/change_cortical_area_region",
701 put(region::put_change_cortical_area_region),
702 )
703 .route(
705 "/cortical_mapping/afferents",
706 axum::routing::post(cortical_mapping::post_afferents),
707 )
708 .route(
709 "/cortical_mapping/efferents",
710 axum::routing::post(cortical_mapping::post_efferents),
711 )
712 .route(
713 "/cortical_mapping/mapping_properties",
714 axum::routing::post(cortical_mapping::post_mapping_properties)
715 .put(cortical_mapping::put_mapping_properties),
716 )
717 .route(
718 "/cortical_mapping/mapping",
719 get(cortical_mapping::get_mapping).delete(cortical_mapping::delete_mapping),
720 )
721 .route(
722 "/cortical_mapping/mapping_list",
723 get(cortical_mapping::get_mapping_list),
724 )
725 .route(
726 "/cortical_mapping/batch_update",
727 axum::routing::post(cortical_mapping::post_batch_update),
728 )
729 .route(
730 "/cortical_mapping/mapping",
731 axum::routing::post(cortical_mapping::post_mapping).put(cortical_mapping::put_mapping),
732 )
733 .route(
735 "/connectome/cortical_areas/list/detailed",
736 get(connectome::get_cortical_areas_list_detailed),
737 )
738 .route(
739 "/connectome/properties/dimensions",
740 get(connectome::get_properties_dimensions),
741 )
742 .route(
743 "/connectome/properties/mappings",
744 get(connectome::get_properties_mappings),
745 )
746 .route("/connectome/snapshot", get(connectome::get_snapshot))
747 .route("/connectome/stats", get(connectome::get_stats))
748 .route(
749 "/connectome/batch_neuron_operations",
750 axum::routing::post(connectome::post_batch_neuron_operations),
751 )
752 .route(
753 "/connectome/batch_synapse_operations",
754 axum::routing::post(connectome::post_batch_synapse_operations),
755 )
756 .route(
757 "/connectome/neuron_count",
758 get(connectome::get_neuron_count),
759 )
760 .route(
761 "/connectome/synapse_count",
762 get(connectome::get_synapse_count),
763 )
764 .route("/connectome/paths", get(connectome::get_paths))
765 .route(
766 "/connectome/cumulative_stats",
767 get(connectome::get_cumulative_stats),
768 )
769 .route(
770 "/connectome/area_details",
771 get(connectome::get_area_details),
772 )
773 .route(
774 "/connectome/rebuild",
775 axum::routing::post(connectome::post_rebuild),
776 )
777 .route("/connectome/structure", get(connectome::get_structure))
778 .route(
779 "/connectome/clear",
780 axum::routing::post(connectome::post_clear),
781 )
782 .route("/connectome/validation", get(connectome::get_validation))
783 .route("/connectome/topology", get(connectome::get_topology))
784 .route(
785 "/connectome/optimize",
786 axum::routing::post(connectome::post_optimize),
787 )
788 .route(
789 "/connectome/connectivity_matrix",
790 get(connectome::get_connectivity_matrix),
791 )
792 .route(
793 "/connectome/neurons/batch",
794 axum::routing::post(connectome::post_neurons_batch),
795 )
796 .route(
797 "/connectome/synapses/batch",
798 axum::routing::post(connectome::post_synapses_batch),
799 )
800 .route(
801 "/connectome/cortical_areas/list/summary",
802 get(connectome::get_cortical_areas_list_summary),
803 )
804 .route(
805 "/connectome/cortical_areas/list/transforming",
806 get(connectome::get_cortical_areas_list_transforming),
807 )
808 .route(
809 "/connectome/cortical_area/list/types",
810 get(connectome::get_cortical_area_list_types),
811 )
812 .route(
813 "/connectome/cortical_area/:cortical_id/neurons",
814 get(connectome::get_cortical_area_neurons),
815 )
816 .route(
817 "/connectome/:cortical_area_id/synapses",
818 get(connectome::get_area_synapses),
819 )
820 .route(
821 "/connectome/cortical_info/:cortical_area",
822 get(connectome::get_cortical_info),
823 )
824 .route(
825 "/connectome/stats/cortical/cumulative/:cortical_area",
826 get(connectome::get_stats_cortical_cumulative),
827 )
828 .route(
829 "/connectome/neuron/:neuron_id/properties",
830 get(connectome::get_neuron_properties_by_id),
831 )
832 .route(
833 "/connectome/neuron_properties",
834 get(connectome::get_neuron_properties_query),
835 )
836 .route(
837 "/connectome/neuron_properties_at",
838 get(connectome::get_neuron_properties_at_query),
839 )
840 .route(
841 "/connectome/memory_neuron",
842 get(connectome::get_memory_neuron),
843 )
844 .route(
845 "/connectome/area_neurons",
846 get(connectome::get_area_neurons_query),
847 )
848 .route(
849 "/connectome/fire_queue/:cortical_area",
850 get(connectome::get_fire_queue_area),
851 )
852 .route(
853 "/connectome/plasticity",
854 get(connectome::get_plasticity_info),
855 )
856 .route("/connectome/path", get(connectome::get_path_query))
857 .route(
858 "/connectome/download",
859 get(connectome::get_download_connectome),
860 )
861 .route(
862 "/connectome/download-cortical-area/:cortical_area",
863 get(connectome::get_download_cortical_area),
864 )
865 .route(
866 "/connectome/upload",
867 axum::routing::post(connectome::post_upload_connectome),
868 )
869 .route(
870 "/connectome/upload-cortical-area",
871 axum::routing::post(connectome::post_upload_cortical_area),
872 )
873 .route(
875 "/burst_engine/simulation_timestep",
876 get(burst_engine::get_simulation_timestep).post(burst_engine::post_simulation_timestep),
877 )
878 .route("/burst_engine/fcl", get(burst_engine::get_fcl))
879 .route(
880 "/burst_engine/fcl/neuron",
881 get(burst_engine::get_fcl_neuron),
882 )
883 .route(
884 "/burst_engine/fire_queue",
885 get(burst_engine::get_fire_queue),
886 )
887 .route(
888 "/burst_engine/fire_queue/neuron",
889 get(burst_engine::get_fire_queue_neuron),
890 )
891 .route(
892 "/burst_engine/fcl_reset",
893 axum::routing::post(burst_engine::post_fcl_reset),
894 )
895 .route(
896 "/burst_engine/fcl_status",
897 get(burst_engine::get_fcl_status),
898 )
899 .route(
900 "/burst_engine/fcl_sampler/config",
901 get(burst_engine::get_fcl_sampler_config).post(burst_engine::post_fcl_sampler_config),
902 )
903 .route(
904 "/burst_engine/fcl_sampler/area/:area_id/sample_rate",
905 get(burst_engine::get_area_fcl_sample_rate)
906 .post(burst_engine::post_area_fcl_sample_rate),
907 )
908 .route(
909 "/burst_engine/fire_ledger/default_window_size",
910 get(burst_engine::get_fire_ledger_default_window_size)
911 .put(burst_engine::put_fire_ledger_default_window_size),
912 )
913 .route(
914 "/burst_engine/fire_ledger/areas_window_config",
915 get(burst_engine::get_fire_ledger_areas_window_config),
916 )
917 .route("/burst_engine/stats", get(burst_engine::get_stats))
918 .route("/burst_engine/status", get(burst_engine::get_status))
919 .route(
920 "/burst_engine/control",
921 axum::routing::post(burst_engine::post_control),
922 )
923 .route(
924 "/burst_engine/burst_counter",
925 get(burst_engine::get_burst_counter),
926 )
927 .route(
928 "/burst_engine/start",
929 axum::routing::post(burst_engine::post_start),
930 )
931 .route(
932 "/burst_engine/stop",
933 axum::routing::post(burst_engine::post_stop),
934 )
935 .route(
936 "/burst_engine/hold",
937 axum::routing::post(burst_engine::post_hold),
938 )
939 .route(
940 "/burst_engine/resume",
941 axum::routing::post(burst_engine::post_resume),
942 )
943 .route(
944 "/burst_engine/config",
945 get(burst_engine::get_config).put(burst_engine::put_config),
946 )
947 .route(
948 "/burst_engine/fire_ledger/area/:area_id/window_size",
949 get(burst_engine::get_fire_ledger_area_window_size)
950 .put(burst_engine::put_fire_ledger_area_window_size),
951 )
952 .route(
953 "/burst_engine/fire_ledger/area/:area_id/history",
954 get(burst_engine::get_fire_ledger_history),
955 )
956 .route(
957 "/burst_engine/membrane_potentials",
958 get(burst_engine::get_membrane_potentials).put(burst_engine::put_membrane_potentials),
959 )
960 .route(
961 "/burst_engine/frequency_status",
962 get(burst_engine::get_frequency_status),
963 )
964 .route(
965 "/burst_engine/measure_frequency",
966 axum::routing::post(burst_engine::post_measure_frequency),
967 )
968 .route(
969 "/burst_engine/frequency_history",
970 get(burst_engine::get_frequency_history),
971 )
972 .route(
973 "/burst_engine/force_connectome_integration",
974 axum::routing::post(burst_engine::post_force_connectome_integration),
975 )
976 .route("/genome/file_name", get(genome::get_file_name))
978 .route("/genome/circuits", get(genome::get_circuits))
979 .route(
980 "/genome/amalgamation_destination",
981 axum::routing::post(genome::post_amalgamation_destination),
982 )
983 .route(
984 "/genome/amalgamation_cancellation",
985 axum::routing::delete(genome::delete_amalgamation_cancellation),
986 )
987 .route(
988 "/feagi/genome/append",
989 axum::routing::post(genome::post_genome_append),
990 )
991 .route(
992 "/genome/upload/barebones",
993 axum::routing::post(genome::post_upload_barebones_genome),
994 )
995 .route(
996 "/genome/upload/essential",
997 axum::routing::post(genome::post_upload_essential_genome),
998 )
999 .route("/genome/name", get(genome::get_name))
1000 .route("/genome/timestamp", get(genome::get_timestamp))
1001 .route("/genome/save", axum::routing::post(genome::post_save))
1002 .route("/genome/load", axum::routing::post(genome::post_load))
1003 .route("/genome/upload", axum::routing::post(genome::post_upload))
1004 .route("/genome/download", get(genome::get_download))
1005 .route("/genome/properties", get(genome::get_properties))
1006 .route(
1007 "/genome/validate",
1008 axum::routing::post(genome::post_validate),
1009 )
1010 .route(
1011 "/genome/transform",
1012 axum::routing::post(genome::post_transform),
1013 )
1014 .route("/genome/clone", axum::routing::post(genome::post_clone))
1015 .route("/genome/reset", axum::routing::post(genome::post_reset))
1016 .route("/genome/metadata", get(genome::get_metadata))
1017 .route("/genome/merge", axum::routing::post(genome::post_merge))
1018 .route("/genome/diff", get(genome::get_diff))
1019 .route(
1020 "/genome/export_format",
1021 axum::routing::post(genome::post_export_format),
1022 )
1023 .route("/genome/amalgamation", get(genome::get_amalgamation))
1024 .route(
1025 "/genome/amalgamation_history",
1026 get(genome::get_amalgamation_history_exact),
1027 )
1028 .route(
1029 "/genome/cortical_template",
1030 get(genome::get_cortical_template),
1031 )
1032 .route("/genome/defaults/files", get(genome::get_defaults_files))
1033 .route("/genome/download_region", get(genome::get_download_region))
1034 .route("/genome/genome_number", get(genome::get_genome_number))
1035 .route(
1036 "/genome/amalgamation_by_filename",
1037 axum::routing::post(genome::post_amalgamation_by_filename),
1038 )
1039 .route(
1040 "/genome/amalgamation_by_payload",
1041 axum::routing::post(genome::post_amalgamation_by_payload),
1042 )
1043 .route(
1044 "/genome/amalgamation_by_upload",
1045 axum::routing::post(genome::post_amalgamation_by_upload),
1046 )
1047 .route(
1048 "/genome/append-file",
1049 axum::routing::post(genome::post_append_file),
1050 )
1051 .route(
1052 "/genome/upload/file",
1053 axum::routing::post(genome::post_upload_file),
1054 )
1055 .route(
1056 "/genome/upload/file/edit",
1057 axum::routing::post(genome::post_upload_file_edit),
1058 )
1059 .route(
1060 "/genome/upload/string",
1061 axum::routing::post(genome::post_upload_string),
1062 )
1063 .route(
1065 "/neuroplasticity/plasticity_queue_depth",
1066 get(neuroplasticity::get_plasticity_queue_depth)
1067 .put(neuroplasticity::put_plasticity_queue_depth),
1068 )
1069 .route("/neuroplasticity/status", get(neuroplasticity::get_status))
1070 .route(
1071 "/neuroplasticity/transforming",
1072 get(neuroplasticity::get_transforming),
1073 )
1074 .route(
1075 "/neuroplasticity/configure",
1076 axum::routing::post(neuroplasticity::post_configure),
1077 )
1078 .route(
1079 "/neuroplasticity/enable/:area_id",
1080 axum::routing::post(neuroplasticity::post_enable_area),
1081 )
1082 .route(
1083 "/neuroplasticity/disable/:area_id",
1084 axum::routing::post(neuroplasticity::post_disable_area),
1085 )
1086 .route(
1088 "/insight/neurons/membrane_potential_status",
1089 axum::routing::post(insight::post_neurons_membrane_potential_status),
1090 )
1091 .route(
1092 "/insight/neuron/synaptic_potential_status",
1093 axum::routing::post(insight::post_neuron_synaptic_potential_status),
1094 )
1095 .route(
1096 "/insight/neurons/membrane_potential_set",
1097 axum::routing::post(insight::post_neurons_membrane_potential_set),
1098 )
1099 .route(
1100 "/insight/neuron/synaptic_potential_set",
1101 axum::routing::post(insight::post_neuron_synaptic_potential_set),
1102 )
1103 .route("/insight/analytics", get(insight::get_analytics))
1104 .route("/insight/data", get(insight::get_data))
1105 .route(
1107 "/input/vision",
1108 get(input::get_vision).post(input::post_vision),
1109 )
1110 .route("/input/sources", get(input::get_sources))
1111 .route(
1112 "/input/configure",
1113 axum::routing::post(input::post_configure),
1114 )
1115 .route("/output/targets", get(outputs::get_targets))
1117 .route(
1118 "/output/configure",
1119 axum::routing::post(outputs::post_configure),
1120 )
1121 .route(
1123 "/physiology/",
1124 get(physiology::get_physiology).put(physiology::put_physiology),
1125 )
1126 .route(
1128 "/simulation/upload/string",
1129 axum::routing::post(simulation::post_stimulation_upload),
1130 )
1131 .route(
1132 "/simulation/reset",
1133 axum::routing::post(simulation::post_reset),
1134 )
1135 .route("/simulation/status", get(simulation::get_status))
1136 .route("/simulation/stats", get(simulation::get_stats))
1137 .route(
1138 "/simulation/config",
1139 axum::routing::post(simulation::post_config),
1140 )
1141 .route("/training/shock", axum::routing::post(training::post_shock))
1143 .route("/training/shock/options", get(training::get_shock_options))
1144 .route("/training/shock/status", get(training::get_shock_status))
1145 .route(
1146 "/training/shock/activate",
1147 axum::routing::post(training::post_shock_activate),
1148 )
1149 .route(
1150 "/training/reward/intensity",
1151 axum::routing::post(training::post_reward_intensity),
1152 )
1153 .route(
1154 "/training/reward",
1155 axum::routing::post(training::post_reward),
1156 )
1157 .route(
1158 "/training/punishment/intensity",
1159 axum::routing::post(training::post_punishment_intensity),
1160 )
1161 .route(
1162 "/training/punishment",
1163 axum::routing::post(training::post_punishment),
1164 )
1165 .route(
1166 "/training/gameover",
1167 axum::routing::post(training::post_gameover),
1168 )
1169 .route("/training/brain_fitness", get(training::get_brain_fitness))
1170 .route(
1171 "/training/fitness_criteria",
1172 get(training::get_fitness_criteria)
1173 .put(training::put_fitness_criteria)
1174 .post(training::post_fitness_criteria),
1175 )
1176 .route(
1177 "/training/fitness_stats",
1178 get(training::get_fitness_stats)
1179 .put(training::put_fitness_stats)
1180 .delete(training::delete_fitness_stats),
1181 )
1182 .route(
1183 "/training/reset_fitness_stats",
1184 axum::routing::delete(training::delete_reset_fitness_stats),
1185 )
1186 .route(
1187 "/training/training_report",
1188 get(training::get_training_report),
1189 )
1190 .route("/training/status", get(training::get_status))
1191 .route("/training/stats", get(training::get_stats))
1192 .route(
1193 "/training/config",
1194 axum::routing::post(training::post_config),
1195 )
1196 .route(
1198 "/visualization/register_client",
1199 axum::routing::post(visualization::post_register_client),
1200 )
1201 .route(
1202 "/visualization/unregister_client",
1203 axum::routing::post(visualization::post_unregister_client),
1204 )
1205 .route(
1206 "/visualization/heartbeat",
1207 axum::routing::post(visualization::post_heartbeat),
1208 )
1209 .route("/visualization/status", get(visualization::get_status))
1210 .route("/monitoring/status", get(monitoring::get_status))
1212 .route("/monitoring/metrics", get(monitoring::get_metrics))
1213 .route("/monitoring/data", get(monitoring::get_data))
1214 .route("/monitoring/performance", get(monitoring::get_performance))
1215 .route(
1216 "/monitoring/cortical_activity",
1217 get(monitoring::get_cortical_activity),
1218 )
1219 .route("/evolution/status", get(evolution::get_status))
1221 .route(
1222 "/evolution/config",
1223 axum::routing::post(evolution::post_config),
1224 )
1225 .route("/network/status", get(network::get_status))
1242 .route("/network/config", axum::routing::post(network::post_config))
1243 .route(
1244 "/network/connection_info",
1245 get(network::get_connection_info),
1246 )
1247}
1248
1249#[allow(dead_code)] async fn openapi_spec() -> Json<utoipa::openapi::OpenApi> {
1252 Json(ApiDoc::openapi())
1253}
1254
1255fn create_cors_layer() -> CorsLayer {
1266 CorsLayer::new()
1267 .allow_origin(Any)
1268 .allow_methods(Any)
1269 .allow_headers(Any)
1270}
1271
1272async fn log_request_response_bodies(
1274 request: Request<Body>,
1275 next: Next,
1276) -> Result<Response, StatusCode> {
1277 let (parts, body) = request.into_parts();
1278
1279 let should_log_request = matches!(parts.method.as_str(), "POST" | "PUT" | "PATCH" | "DELETE");
1281
1282 let body_bytes = if should_log_request {
1283 match body.collect().await {
1285 Ok(collected) => {
1286 let bytes = collected.to_bytes();
1287 if let Ok(body_str) = String::from_utf8(bytes.to_vec()) {
1289 if !body_str.is_empty() {
1290 tracing::trace!(target: "feagi-api", "Request body: {}", body_str);
1291 }
1292 }
1293 bytes
1294 }
1295 Err(_) => {
1296 return Err(StatusCode::INTERNAL_SERVER_ERROR);
1297 }
1298 }
1299 } else {
1300 axum::body::Bytes::new()
1301 };
1302
1303 let request = Request::from_parts(parts, Body::from(body_bytes));
1305
1306 let response = next.run(request).await;
1308
1309 let (parts, body) = response.into_parts();
1311
1312 match body.collect().await {
1313 Ok(collected) => {
1314 let bytes = collected.to_bytes();
1315 if bytes.len() < 10000 {
1317 if let Ok(body_str) = String::from_utf8(bytes.to_vec()) {
1319 if !body_str.is_empty() && body_str.starts_with('{') {
1320 tracing::trace!(target: "feagi-api", "Response body: {}", body_str);
1321 }
1322 }
1323 }
1324 Ok(Response::from_parts(parts, Body::from(bytes)))
1326 }
1327 Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR),
1328 }
1329}
1330
1331async fn root_redirect() -> Redirect {
1337 Redirect::permanent("/swagger-ui/")
1338}
1339
1340async fn custom_swagger_ui() -> Html<&'static str> {
1343 const CUSTOM_SWAGGER_HTML: &str = include_str!("../../../templates/custom-swagger-ui.html");
1344 Html(CUSTOM_SWAGGER_HTML)
1345}
1346
1347#[allow(dead_code)] async fn placeholder_handler(State(_state): State<ApiState>) -> Response {
1355 (
1356 StatusCode::NOT_IMPLEMENTED,
1357 Json(serde_json::json!({
1358 "error": "Not yet implemented",
1359 "message": "This endpoint is registered but not yet implemented in Rust. See Python implementation."
1360 }))
1361 ).into_response()
1362}
1363
1364#[allow(dead_code)] async fn placeholder_health_check(State(_state): State<ApiState>) -> Response {
1367 (
1368 StatusCode::OK,
1369 Json(serde_json::json!({
1370 "status": "ok",
1371 "message": "Health check placeholder - Python-compatible path structure confirmed",
1372 "burst_engine": false,
1373 "brain_readiness": false
1374 })),
1375 )
1376 .into_response()
1377}