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