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/log_tail", get(system::get_log_tail))
459 .route("/system/logs", axum::routing::post(system::post_logs))
460 .route(
461 "/system/beacon/subscribers",
462 get(system::get_beacon_subscribers),
463 )
464 .route(
465 "/system/beacon/subscribe",
466 axum::routing::post(system::post_beacon_subscribe),
467 )
468 .route(
469 "/system/beacon/unsubscribe",
470 axum::routing::delete(system::delete_beacon_unsubscribe),
471 )
472 .route(
473 "/system/global_activity_visualization",
474 get(system::get_global_activity_visualization)
475 .put(system::put_global_activity_visualization),
476 )
477 .route(
478 "/system/circuit_library_path",
479 axum::routing::post(system::post_circuit_library_path),
480 )
481 .route("/system/db/influxdb/test", get(system::get_influxdb_test))
482 .route(
483 "/system/register",
484 axum::routing::post(system::post_register_system),
485 )
486 .route("/cortical_area/ipu", get(cortical_area::get_ipu))
488 .route(
489 "/cortical_area/ipu/types",
490 get(cortical_area::get_ipu_types),
491 )
492 .route("/cortical_area/opu", get(cortical_area::get_opu))
493 .route(
494 "/cortical_area/opu/types",
495 get(cortical_area::get_opu_types),
496 )
497 .route(
498 "/cortical_area/cortical_area_id_list",
499 get(cortical_area::get_cortical_area_id_list),
500 )
501 .route(
502 "/cortical_area/cortical_area_name_list",
503 get(cortical_area::get_cortical_area_name_list),
504 )
505 .route(
506 "/cortical_area/cortical_id_name_mapping",
507 get(cortical_area::get_cortical_id_name_mapping),
508 )
509 .route(
510 "/cortical_area/cortical_types",
511 get(cortical_area::get_cortical_types),
512 )
513 .route(
514 "/cortical_area/cortical_map_detailed",
515 get(cortical_area::get_cortical_map_detailed),
516 )
517 .route(
518 "/cortical_area/cortical_locations_2d",
519 get(cortical_area::get_cortical_locations_2d),
520 )
521 .route(
522 "/cortical_area/cortical_area/geometry",
523 get(cortical_area::get_cortical_area_geometry),
524 )
525 .route(
526 "/cortical_area/cortical_visibility",
527 get(cortical_area::get_cortical_visibility),
528 )
529 .route(
530 "/cortical_area/cortical_name_location",
531 axum::routing::post(cortical_area::post_cortical_name_location),
532 )
533 .route(
534 "/cortical_area/cortical_area_properties",
535 axum::routing::post(cortical_area::post_cortical_area_properties),
536 )
537 .route(
538 "/cortical_area/multi/cortical_area_properties",
539 axum::routing::post(cortical_area::post_multi_cortical_area_properties),
540 )
541 .route(
542 "/cortical_area/cortical_area",
543 axum::routing::post(cortical_area::post_cortical_area)
544 .put(cortical_area::put_cortical_area)
545 .delete(cortical_area::delete_cortical_area),
546 )
547 .route(
548 "/cortical_area/custom_cortical_area",
549 axum::routing::post(cortical_area::post_custom_cortical_area),
550 )
551 .route(
552 "/cortical_area/clone",
553 axum::routing::post(cortical_area::post_clone),
554 )
555 .route(
556 "/cortical_area/multi/cortical_area",
557 put(cortical_area::put_multi_cortical_area)
558 .delete(cortical_area::delete_multi_cortical_area),
559 )
560 .route("/cortical_area/coord_2d", put(cortical_area::put_coord_2d))
561 .route(
562 "/cortical_area/suppress_cortical_visibility",
563 put(cortical_area::put_suppress_cortical_visibility),
564 )
565 .route("/cortical_area/reset", put(cortical_area::put_reset))
566 .route(
567 "/cortical_area/visualization",
568 get(cortical_area::get_visualization),
569 )
570 .route(
571 "/cortical_area/batch_operations",
572 axum::routing::post(cortical_area::post_batch_operations),
573 )
574 .route("/cortical_area/ipu/list", get(cortical_area::get_ipu_list))
575 .route("/cortical_area/opu/list", get(cortical_area::get_opu_list))
576 .route(
577 "/cortical_area/coordinates_3d",
578 put(cortical_area::put_coordinates_3d),
579 )
580 .route(
581 "/cortical_area/bulk_delete",
582 axum::routing::delete(cortical_area::delete_bulk),
583 )
584 .route(
585 "/cortical_area/resize",
586 axum::routing::post(cortical_area::post_resize),
587 )
588 .route(
589 "/cortical_area/reposition",
590 axum::routing::post(cortical_area::post_reposition),
591 )
592 .route(
593 "/cortical_area/voxel_neurons",
594 get(cortical_area::get_voxel_neurons).post(cortical_area::post_voxel_neurons),
595 )
596 .route(
597 "/cortical_area/memory",
598 get(cortical_area::get_memory_cortical_area),
599 )
600 .route(
601 "/cortical_area/cortical_area_index_list",
602 get(cortical_area::get_cortical_area_index_list),
603 )
604 .route(
605 "/cortical_area/cortical_idx_mapping",
606 get(cortical_area::get_cortical_idx_mapping),
607 )
608 .route(
609 "/cortical_area/mapping_restrictions",
610 get(cortical_area::get_mapping_restrictions_query)
611 .post(cortical_area::post_mapping_restrictions),
612 )
613 .route(
614 "/cortical_area/:cortical_id/memory_usage",
615 get(cortical_area::get_memory_usage),
616 )
617 .route(
618 "/cortical_area/:cortical_id/neuron_count",
619 get(cortical_area::get_area_neuron_count),
620 )
621 .route(
622 "/cortical_area/cortical_type_options",
623 axum::routing::post(cortical_area::post_cortical_type_options),
624 )
625 .route(
626 "/cortical_area/mapping_restrictions_between_areas",
627 axum::routing::post(cortical_area::post_mapping_restrictions_between_areas),
628 )
629 .route("/cortical_area/coord_3d", put(cortical_area::put_coord_3d))
630 .route(
632 "/morphology/morphology_list",
633 get(morphology::get_morphology_list),
634 )
635 .route(
636 "/morphology/morphology_types",
637 get(morphology::get_morphology_types),
638 )
639 .route("/morphology/list/types", get(morphology::get_list_types))
640 .route(
641 "/morphology/morphologies",
642 get(morphology::get_morphologies),
643 )
644 .route(
645 "/morphology/morphology",
646 axum::routing::post(morphology::post_morphology)
647 .put(morphology::put_morphology)
648 .delete(morphology::delete_morphology_by_name),
649 )
650 .route(
651 "/morphology/rename",
652 axum::routing::put(morphology::put_rename_morphology),
653 )
654 .route(
655 "/morphology/morphology_properties",
656 axum::routing::post(morphology::post_morphology_properties),
657 )
658 .route(
659 "/morphology/morphology_usage",
660 axum::routing::post(morphology::post_morphology_usage),
661 )
662 .route("/morphology/list", get(morphology::get_list))
663 .route("/morphology/info/:morphology_id", get(morphology::get_info))
664 .route(
665 "/morphology/create",
666 axum::routing::post(morphology::post_create),
667 )
668 .route(
669 "/morphology/update",
670 axum::routing::put(morphology::put_update),
671 )
672 .route(
673 "/morphology/delete/:morphology_id",
674 axum::routing::delete(morphology::delete_morphology),
675 )
676 .route("/region/regions_members", get(region::get_regions_members))
678 .route(
679 "/region/region",
680 axum::routing::post(region::post_region)
681 .put(region::put_region)
682 .delete(region::delete_region),
683 )
684 .route("/region/clone", axum::routing::post(region::post_clone))
685 .route(
686 "/region/relocate_members",
687 put(region::put_relocate_members),
688 )
689 .route(
690 "/region/region_and_members",
691 axum::routing::delete(region::delete_region_and_members),
692 )
693 .route("/region/regions", get(region::get_regions))
694 .route("/region/region_titles", get(region::get_region_titles))
695 .route("/region/region/:region_id", get(region::get_region_detail))
696 .route(
697 "/region/change_region_parent",
698 put(region::put_change_region_parent),
699 )
700 .route(
701 "/region/change_cortical_area_region",
702 put(region::put_change_cortical_area_region),
703 )
704 .route(
706 "/cortical_mapping/afferents",
707 axum::routing::post(cortical_mapping::post_afferents),
708 )
709 .route(
710 "/cortical_mapping/efferents",
711 axum::routing::post(cortical_mapping::post_efferents),
712 )
713 .route(
714 "/cortical_mapping/mapping_properties",
715 axum::routing::post(cortical_mapping::post_mapping_properties)
716 .put(cortical_mapping::put_mapping_properties),
717 )
718 .route(
719 "/cortical_mapping/mapping",
720 get(cortical_mapping::get_mapping).delete(cortical_mapping::delete_mapping),
721 )
722 .route(
723 "/cortical_mapping/mapping_list",
724 get(cortical_mapping::get_mapping_list),
725 )
726 .route(
727 "/cortical_mapping/batch_update",
728 axum::routing::post(cortical_mapping::post_batch_update),
729 )
730 .route(
731 "/cortical_mapping/mapping",
732 axum::routing::post(cortical_mapping::post_mapping).put(cortical_mapping::put_mapping),
733 )
734 .route(
736 "/connectome/cortical_areas/list/detailed",
737 get(connectome::get_cortical_areas_list_detailed),
738 )
739 .route(
740 "/connectome/properties/dimensions",
741 get(connectome::get_properties_dimensions),
742 )
743 .route(
744 "/connectome/properties/mappings",
745 get(connectome::get_properties_mappings),
746 )
747 .route("/connectome/snapshot", get(connectome::get_snapshot))
748 .route("/connectome/stats", get(connectome::get_stats))
749 .route(
750 "/connectome/batch_neuron_operations",
751 axum::routing::post(connectome::post_batch_neuron_operations),
752 )
753 .route(
754 "/connectome/batch_synapse_operations",
755 axum::routing::post(connectome::post_batch_synapse_operations),
756 )
757 .route(
758 "/connectome/neuron_count",
759 get(connectome::get_neuron_count),
760 )
761 .route(
762 "/connectome/synapse_count",
763 get(connectome::get_synapse_count),
764 )
765 .route("/connectome/paths", get(connectome::get_paths))
766 .route(
767 "/connectome/cumulative_stats",
768 get(connectome::get_cumulative_stats),
769 )
770 .route(
771 "/connectome/area_details",
772 get(connectome::get_area_details),
773 )
774 .route(
775 "/connectome/rebuild",
776 axum::routing::post(connectome::post_rebuild),
777 )
778 .route("/connectome/structure", get(connectome::get_structure))
779 .route(
780 "/connectome/clear",
781 axum::routing::post(connectome::post_clear),
782 )
783 .route("/connectome/validation", get(connectome::get_validation))
784 .route("/connectome/topology", get(connectome::get_topology))
785 .route(
786 "/connectome/optimize",
787 axum::routing::post(connectome::post_optimize),
788 )
789 .route(
790 "/connectome/connectivity_matrix",
791 get(connectome::get_connectivity_matrix),
792 )
793 .route(
794 "/connectome/neurons/batch",
795 axum::routing::post(connectome::post_neurons_batch),
796 )
797 .route(
798 "/connectome/synapses/batch",
799 axum::routing::post(connectome::post_synapses_batch),
800 )
801 .route(
802 "/connectome/cortical_areas/list/summary",
803 get(connectome::get_cortical_areas_list_summary),
804 )
805 .route(
806 "/connectome/cortical_areas/list/transforming",
807 get(connectome::get_cortical_areas_list_transforming),
808 )
809 .route(
810 "/connectome/cortical_area/list/types",
811 get(connectome::get_cortical_area_list_types),
812 )
813 .route(
814 "/connectome/cortical_area/:cortical_id/neurons",
815 get(connectome::get_cortical_area_neurons),
816 )
817 .route(
818 "/connectome/:cortical_area_id/synapses/incoming",
819 get(connectome::get_area_synapses_incoming),
820 )
821 .route(
822 "/connectome/:cortical_area_id/synapses",
823 get(connectome::get_area_synapses),
824 )
825 .route(
826 "/connectome/cortical_info/:cortical_area",
827 get(connectome::get_cortical_info),
828 )
829 .route(
830 "/connectome/stats/cortical/cumulative/:cortical_area",
831 get(connectome::get_stats_cortical_cumulative),
832 )
833 .route(
834 "/connectome/neuron/:neuron_id/properties",
835 get(connectome::get_neuron_properties_by_id),
836 )
837 .route(
838 "/connectome/neuron_properties",
839 get(connectome::get_neuron_properties_query),
840 )
841 .route(
842 "/connectome/neuron_properties_at",
843 get(connectome::get_neuron_properties_at_query),
844 )
845 .route(
846 "/connectome/memory_neuron",
847 get(connectome::get_memory_neuron),
848 )
849 .route(
850 "/connectome/area_neurons",
851 get(connectome::get_area_neurons_query),
852 )
853 .route(
854 "/connectome/fire_queue/:cortical_area",
855 get(connectome::get_fire_queue_area),
856 )
857 .route(
858 "/connectome/plasticity",
859 get(connectome::get_plasticity_info),
860 )
861 .route("/connectome/path", get(connectome::get_path_query))
862 .route(
863 "/connectome/download",
864 get(connectome::get_download_connectome),
865 )
866 .route(
867 "/connectome/download-cortical-area/:cortical_area",
868 get(connectome::get_download_cortical_area),
869 )
870 .route(
871 "/connectome/upload",
872 axum::routing::post(connectome::post_upload_connectome),
873 )
874 .route(
875 "/connectome/upload-cortical-area",
876 axum::routing::post(connectome::post_upload_cortical_area),
877 )
878 .route(
880 "/burst_engine/simulation_timestep",
881 get(burst_engine::get_simulation_timestep).post(burst_engine::post_simulation_timestep),
882 )
883 .route("/burst_engine/fcl", get(burst_engine::get_fcl))
884 .route(
885 "/burst_engine/fcl/neuron",
886 get(burst_engine::get_fcl_neuron),
887 )
888 .route(
889 "/burst_engine/fire_queue",
890 get(burst_engine::get_fire_queue),
891 )
892 .route(
893 "/burst_engine/fire_queue/neuron",
894 get(burst_engine::get_fire_queue_neuron),
895 )
896 .route(
897 "/burst_engine/fcl_reset",
898 axum::routing::post(burst_engine::post_fcl_reset),
899 )
900 .route(
901 "/burst_engine/fcl_status",
902 get(burst_engine::get_fcl_status),
903 )
904 .route(
905 "/burst_engine/fcl_sampler/config",
906 get(burst_engine::get_fcl_sampler_config).post(burst_engine::post_fcl_sampler_config),
907 )
908 .route(
909 "/burst_engine/fcl_sampler/area/:area_id/sample_rate",
910 get(burst_engine::get_area_fcl_sample_rate)
911 .post(burst_engine::post_area_fcl_sample_rate),
912 )
913 .route(
914 "/burst_engine/fire_ledger/default_window_size",
915 get(burst_engine::get_fire_ledger_default_window_size)
916 .put(burst_engine::put_fire_ledger_default_window_size),
917 )
918 .route(
919 "/burst_engine/fire_ledger/areas_window_config",
920 get(burst_engine::get_fire_ledger_areas_window_config),
921 )
922 .route("/burst_engine/stats", get(burst_engine::get_stats))
923 .route("/burst_engine/status", get(burst_engine::get_status))
924 .route(
925 "/burst_engine/control",
926 axum::routing::post(burst_engine::post_control),
927 )
928 .route(
929 "/burst_engine/burst_counter",
930 get(burst_engine::get_burst_counter),
931 )
932 .route(
933 "/burst_engine/start",
934 axum::routing::post(burst_engine::post_start),
935 )
936 .route(
937 "/burst_engine/stop",
938 axum::routing::post(burst_engine::post_stop),
939 )
940 .route(
941 "/burst_engine/hold",
942 axum::routing::post(burst_engine::post_hold),
943 )
944 .route(
945 "/burst_engine/resume",
946 axum::routing::post(burst_engine::post_resume),
947 )
948 .route(
949 "/burst_engine/config",
950 get(burst_engine::get_config).put(burst_engine::put_config),
951 )
952 .route(
953 "/burst_engine/fire_ledger/area/:area_id/window_size",
954 get(burst_engine::get_fire_ledger_area_window_size)
955 .put(burst_engine::put_fire_ledger_area_window_size),
956 )
957 .route(
958 "/burst_engine/fire_ledger/area/:area_id/history",
959 get(burst_engine::get_fire_ledger_history),
960 )
961 .route(
962 "/burst_engine/membrane_potentials",
963 get(burst_engine::get_membrane_potentials).put(burst_engine::put_membrane_potentials),
964 )
965 .route(
966 "/burst_engine/frequency_status",
967 get(burst_engine::get_frequency_status),
968 )
969 .route(
970 "/burst_engine/measure_frequency",
971 axum::routing::post(burst_engine::post_measure_frequency),
972 )
973 .route(
974 "/burst_engine/frequency_history",
975 get(burst_engine::get_frequency_history),
976 )
977 .route(
978 "/burst_engine/force_connectome_integration",
979 axum::routing::post(burst_engine::post_force_connectome_integration),
980 )
981 .route("/genome/file_name", get(genome::get_file_name))
983 .route("/genome/circuits", get(genome::get_circuits))
984 .route(
985 "/genome/amalgamation_destination",
986 axum::routing::post(genome::post_amalgamation_destination),
987 )
988 .route(
989 "/genome/amalgamation_cancellation",
990 axum::routing::delete(genome::delete_amalgamation_cancellation),
991 )
992 .route(
993 "/feagi/genome/append",
994 axum::routing::post(genome::post_genome_append),
995 )
996 .route(
997 "/genome/upload/barebones",
998 axum::routing::post(genome::post_upload_barebones_genome),
999 )
1000 .route(
1001 "/genome/upload/essential",
1002 axum::routing::post(genome::post_upload_essential_genome),
1003 )
1004 .route("/genome/name", get(genome::get_name))
1005 .route("/genome/timestamp", get(genome::get_timestamp))
1006 .route("/genome/save", axum::routing::post(genome::post_save))
1007 .route("/genome/load", axum::routing::post(genome::post_load))
1008 .route("/genome/upload", axum::routing::post(genome::post_upload))
1009 .route("/genome/download", get(genome::get_download))
1010 .route("/genome/properties", get(genome::get_properties))
1011 .route(
1012 "/genome/validate",
1013 axum::routing::post(genome::post_validate),
1014 )
1015 .route(
1016 "/genome/transform",
1017 axum::routing::post(genome::post_transform),
1018 )
1019 .route("/genome/clone", axum::routing::post(genome::post_clone))
1020 .route("/genome/reset", axum::routing::post(genome::post_reset))
1021 .route("/genome/metadata", get(genome::get_metadata))
1022 .route("/genome/merge", axum::routing::post(genome::post_merge))
1023 .route("/genome/diff", get(genome::get_diff))
1024 .route(
1025 "/genome/export_format",
1026 axum::routing::post(genome::post_export_format),
1027 )
1028 .route("/genome/amalgamation", get(genome::get_amalgamation))
1029 .route(
1030 "/genome/amalgamation_history",
1031 get(genome::get_amalgamation_history_exact),
1032 )
1033 .route(
1034 "/genome/cortical_template",
1035 get(genome::get_cortical_template),
1036 )
1037 .route("/genome/defaults/files", get(genome::get_defaults_files))
1038 .route("/genome/download_region", get(genome::get_download_region))
1039 .route("/genome/genome_number", get(genome::get_genome_number))
1040 .route(
1041 "/genome/amalgamation_by_filename",
1042 axum::routing::post(genome::post_amalgamation_by_filename),
1043 )
1044 .route(
1045 "/genome/amalgamation_by_payload",
1046 axum::routing::post(genome::post_amalgamation_by_payload),
1047 )
1048 .route(
1049 "/genome/amalgamation_by_upload",
1050 axum::routing::post(genome::post_amalgamation_by_upload),
1051 )
1052 .route(
1053 "/genome/append-file",
1054 axum::routing::post(genome::post_append_file),
1055 )
1056 .route(
1057 "/genome/upload/file",
1058 axum::routing::post(genome::post_upload_file),
1059 )
1060 .route(
1061 "/genome/upload/file/edit",
1062 axum::routing::post(genome::post_upload_file_edit),
1063 )
1064 .route(
1065 "/genome/upload/string",
1066 axum::routing::post(genome::post_upload_string),
1067 )
1068 .route(
1070 "/neuroplasticity/plasticity_queue_depth",
1071 get(neuroplasticity::get_plasticity_queue_depth)
1072 .put(neuroplasticity::put_plasticity_queue_depth),
1073 )
1074 .route("/neuroplasticity/status", get(neuroplasticity::get_status))
1075 .route(
1076 "/neuroplasticity/transforming",
1077 get(neuroplasticity::get_transforming),
1078 )
1079 .route(
1080 "/neuroplasticity/configure",
1081 axum::routing::post(neuroplasticity::post_configure),
1082 )
1083 .route(
1084 "/neuroplasticity/enable/:area_id",
1085 axum::routing::post(neuroplasticity::post_enable_area),
1086 )
1087 .route(
1088 "/neuroplasticity/disable/:area_id",
1089 axum::routing::post(neuroplasticity::post_disable_area),
1090 )
1091 .route(
1093 "/insight/neurons/membrane_potential_status",
1094 axum::routing::post(insight::post_neurons_membrane_potential_status),
1095 )
1096 .route(
1097 "/insight/neuron/synaptic_potential_status",
1098 axum::routing::post(insight::post_neuron_synaptic_potential_status),
1099 )
1100 .route(
1101 "/insight/neurons/membrane_potential_set",
1102 axum::routing::post(insight::post_neurons_membrane_potential_set),
1103 )
1104 .route(
1105 "/insight/neuron/synaptic_potential_set",
1106 axum::routing::post(insight::post_neuron_synaptic_potential_set),
1107 )
1108 .route("/insight/analytics", get(insight::get_analytics))
1109 .route("/insight/data", get(insight::get_data))
1110 .route(
1112 "/input/vision",
1113 get(input::get_vision).post(input::post_vision),
1114 )
1115 .route("/input/sources", get(input::get_sources))
1116 .route(
1117 "/input/configure",
1118 axum::routing::post(input::post_configure),
1119 )
1120 .route(
1121 "/input/sensor_snapshot/last",
1122 get(input::get_sensor_snapshot_last),
1123 )
1124 .route("/output/targets", get(outputs::get_targets))
1126 .route(
1127 "/output/motor_snapshot/last",
1128 get(outputs::get_motor_snapshot_last),
1129 )
1130 .route(
1131 "/output/configure",
1132 axum::routing::post(outputs::post_configure),
1133 )
1134 .route(
1136 "/physiology/",
1137 get(physiology::get_physiology).put(physiology::put_physiology),
1138 )
1139 .route(
1141 "/simulation/upload/string",
1142 axum::routing::post(simulation::post_stimulation_upload),
1143 )
1144 .route(
1145 "/simulation/reset",
1146 axum::routing::post(simulation::post_reset),
1147 )
1148 .route("/simulation/status", get(simulation::get_status))
1149 .route("/simulation/stats", get(simulation::get_stats))
1150 .route(
1151 "/simulation/config",
1152 axum::routing::post(simulation::post_config),
1153 )
1154 .route("/training/shock", axum::routing::post(training::post_shock))
1156 .route("/training/shock/options", get(training::get_shock_options))
1157 .route("/training/shock/status", get(training::get_shock_status))
1158 .route(
1159 "/training/shock/activate",
1160 axum::routing::post(training::post_shock_activate),
1161 )
1162 .route(
1163 "/training/reward/intensity",
1164 axum::routing::post(training::post_reward_intensity),
1165 )
1166 .route(
1167 "/training/reward",
1168 axum::routing::post(training::post_reward),
1169 )
1170 .route(
1171 "/training/punishment/intensity",
1172 axum::routing::post(training::post_punishment_intensity),
1173 )
1174 .route(
1175 "/training/punishment",
1176 axum::routing::post(training::post_punishment),
1177 )
1178 .route(
1179 "/training/gameover",
1180 axum::routing::post(training::post_gameover),
1181 )
1182 .route("/training/brain_fitness", get(training::get_brain_fitness))
1183 .route(
1184 "/training/fitness_criteria",
1185 get(training::get_fitness_criteria)
1186 .put(training::put_fitness_criteria)
1187 .post(training::post_fitness_criteria),
1188 )
1189 .route(
1190 "/training/fitness_stats",
1191 get(training::get_fitness_stats)
1192 .put(training::put_fitness_stats)
1193 .delete(training::delete_fitness_stats),
1194 )
1195 .route(
1196 "/training/reset_fitness_stats",
1197 axum::routing::delete(training::delete_reset_fitness_stats),
1198 )
1199 .route(
1200 "/training/training_report",
1201 get(training::get_training_report),
1202 )
1203 .route("/training/status", get(training::get_status))
1204 .route("/training/stats", get(training::get_stats))
1205 .route(
1206 "/training/config",
1207 axum::routing::post(training::post_config),
1208 )
1209 .route(
1211 "/visualization/register_client",
1212 axum::routing::post(visualization::post_register_client),
1213 )
1214 .route(
1215 "/visualization/unregister_client",
1216 axum::routing::post(visualization::post_unregister_client),
1217 )
1218 .route(
1219 "/visualization/heartbeat",
1220 axum::routing::post(visualization::post_heartbeat),
1221 )
1222 .route("/visualization/status", get(visualization::get_status))
1223 .route("/monitoring/status", get(monitoring::get_status))
1225 .route("/monitoring/metrics", get(monitoring::get_metrics))
1226 .route("/monitoring/data", get(monitoring::get_data))
1227 .route("/monitoring/performance", get(monitoring::get_performance))
1228 .route(
1229 "/monitoring/cortical_activity",
1230 get(monitoring::get_cortical_activity),
1231 )
1232 .route("/evolution/status", get(evolution::get_status))
1234 .route(
1235 "/evolution/config",
1236 axum::routing::post(evolution::post_config),
1237 )
1238 .route("/network/status", get(network::get_status))
1255 .route("/network/config", axum::routing::post(network::post_config))
1256 .route(
1257 "/network/connection_info",
1258 get(network::get_connection_info),
1259 )
1260}
1261
1262#[allow(dead_code)] async fn openapi_spec() -> Json<utoipa::openapi::OpenApi> {
1265 Json(ApiDoc::openapi())
1266}
1267
1268fn create_cors_layer() -> CorsLayer {
1279 CorsLayer::new()
1280 .allow_origin(Any)
1281 .allow_methods(Any)
1282 .allow_headers(Any)
1283}
1284
1285async fn log_request_response_bodies(
1287 request: Request<Body>,
1288 next: Next,
1289) -> Result<Response, StatusCode> {
1290 let (parts, body) = request.into_parts();
1291
1292 let should_log_request = matches!(parts.method.as_str(), "POST" | "PUT" | "PATCH" | "DELETE");
1294
1295 let body_bytes = if should_log_request {
1296 match body.collect().await {
1298 Ok(collected) => {
1299 let bytes = collected.to_bytes();
1300 if let Ok(body_str) = String::from_utf8(bytes.to_vec()) {
1302 if !body_str.is_empty() {
1303 tracing::trace!(target: "feagi-api", "Request body: {}", body_str);
1304 }
1305 }
1306 bytes
1307 }
1308 Err(_) => {
1309 return Err(StatusCode::INTERNAL_SERVER_ERROR);
1310 }
1311 }
1312 } else {
1313 axum::body::Bytes::new()
1314 };
1315
1316 let request = Request::from_parts(parts, Body::from(body_bytes));
1318
1319 let response = next.run(request).await;
1321
1322 let (parts, body) = response.into_parts();
1324
1325 match body.collect().await {
1326 Ok(collected) => {
1327 let bytes = collected.to_bytes();
1328 if bytes.len() < 10000 {
1330 if let Ok(body_str) = String::from_utf8(bytes.to_vec()) {
1332 if !body_str.is_empty() && body_str.starts_with('{') {
1333 tracing::trace!(target: "feagi-api", "Response body: {}", body_str);
1334 }
1335 }
1336 }
1337 Ok(Response::from_parts(parts, Body::from(bytes)))
1339 }
1340 Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR),
1341 }
1342}
1343
1344async fn root_redirect() -> Redirect {
1350 Redirect::permanent("/swagger-ui/")
1351}
1352
1353async fn custom_swagger_ui() -> Html<&'static str> {
1356 const CUSTOM_SWAGGER_HTML: &str = include_str!("../../../templates/custom-swagger-ui.html");
1357 Html(CUSTOM_SWAGGER_HTML)
1358}
1359
1360#[allow(dead_code)] async fn placeholder_handler(State(_state): State<ApiState>) -> Response {
1368 (
1369 StatusCode::NOT_IMPLEMENTED,
1370 Json(serde_json::json!({
1371 "error": "Not yet implemented",
1372 "message": "This endpoint is registered but not yet implemented in Rust. See Python implementation."
1373 }))
1374 ).into_response()
1375}
1376
1377#[allow(dead_code)] async fn placeholder_health_check(State(_state): State<ApiState>) -> Response {
1380 (
1381 StatusCode::OK,
1382 Json(serde_json::json!({
1383 "status": "ok",
1384 "message": "Health check placeholder - Python-compatible path structure confirmed",
1385 "burst_engine": false,
1386 "brain_readiness": false
1387 })),
1388 )
1389 .into_response()
1390}