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