Skip to main content

feagi_api/transports/http/
server.rs

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