Skip to main content

feagi_api/endpoints/
network.rs

1// Copyright 2025 Neuraville Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4/*!
5 * FEAGI v1 Network API
6 *
7 * Endpoints for network configuration and status
8 * Maps to Python: feagi/api/v1/network.py
9 */
10
11use crate::common::ApiState;
12use crate::common::{ApiError, ApiResult, Json, State};
13use crate::v1::{
14    ConnectionInfoApi, ConnectionInfoBluetooth, ConnectionInfoShm, ConnectionInfoStreamStatus,
15    ConnectionInfoUdp, ConnectionInfoWebSocket, ConnectionInfoWebSocketEndpoints,
16    ConnectionInfoWebSocketPorts, ConnectionInfoZmq, ConnectionInfoZmqEndpoints,
17    ConnectionInfoZmqPorts, NetworkConnectionInfo,
18};
19use serde_json::{json, Value};
20use std::collections::HashMap;
21
22// ============================================================================
23// NETWORK CONFIGURATION
24// ============================================================================
25
26/// Get network configuration status including ZMQ, HTTP, and WebSocket states.
27#[utoipa::path(
28    get,
29    path = "/v1/network/status",
30    tag = "network",
31    responses(
32        (status = 200, description = "Network status", body = HashMap<String, serde_json::Value>),
33        (status = 500, description = "Internal server error")
34    )
35)]
36pub async fn get_status(State(_state): State<ApiState>) -> ApiResult<Json<HashMap<String, Value>>> {
37    // TODO: Retrieve network status
38    let mut response = HashMap::new();
39    response.insert("zmq_enabled".to_string(), json!(false));
40    response.insert("http_enabled".to_string(), json!(true));
41    response.insert("websocket_enabled".to_string(), json!(false));
42
43    Ok(Json(response))
44}
45
46/// Configure network parameters including transport protocols and ports.
47#[utoipa::path(
48    post,
49    path = "/v1/network/config",
50    tag = "network",
51    responses(
52        (status = 200, description = "Network configured", body = HashMap<String, String>),
53        (status = 500, description = "Internal server error")
54    )
55)]
56pub async fn post_config(
57    State(_state): State<ApiState>,
58    Json(request): Json<HashMap<String, Value>>,
59) -> ApiResult<Json<HashMap<String, String>>> {
60    // Validate config is provided
61    let _config = request
62        .get("config")
63        .ok_or_else(|| ApiError::invalid_input("Missing 'config' field"))?;
64
65    // TODO: Apply network configuration
66    tracing::info!(target: "feagi-api", "Network configuration updated");
67
68    Ok(Json(HashMap::from([(
69        "message".to_string(),
70        "Network configured successfully".to_string(),
71    )])))
72}
73
74// ============================================================================
75// CONNECTION INFO
76// ============================================================================
77
78/// Provider trait for network connection info (implemented by embedders like feagi-rs)
79pub trait NetworkConnectionInfoProvider: Send + Sync {
80    fn get(&self) -> NetworkConnectionInfo;
81}
82
83/// Get all active networking details: API, ZMQ, WebSocket, SHM, UDP (placeholder), Bluetooth (placeholder).
84#[utoipa::path(
85    get,
86    path = "/v1/network/connection_info",
87    tag = "network",
88    responses(
89        (status = 200, description = "Network connection info", body = NetworkConnectionInfo),
90        (status = 500, description = "Internal server error")
91    )
92)]
93pub async fn get_connection_info(
94    State(state): State<ApiState>,
95) -> ApiResult<Json<NetworkConnectionInfo>> {
96    let info = state
97        .network_connection_info_provider
98        .as_ref()
99        .map(|p| p.get())
100        .unwrap_or_else(placeholder_connection_info);
101
102    Ok(Json(info))
103}
104
105fn placeholder_connection_info() -> NetworkConnectionInfo {
106    NetworkConnectionInfo {
107        api: ConnectionInfoApi {
108            enabled: true,
109            base_url: "http://127.0.0.1:8000".to_string(),
110            host: "127.0.0.1".to_string(),
111            port: 8000,
112            swagger_url: "http://127.0.0.1:8000/swagger-ui/".to_string(),
113        },
114        zmq: ConnectionInfoZmq {
115            enabled: false,
116            host: "127.0.0.1".to_string(),
117            ports: ConnectionInfoZmqPorts {
118                registration: 30001,
119                sensory: 5558,
120                motor: 5564,
121                visualization: 5562,
122                api_control: 5565,
123            },
124            endpoints: ConnectionInfoZmqEndpoints {
125                registration: "tcp://127.0.0.1:30001".to_string(),
126                sensory: "tcp://127.0.0.1:5558".to_string(),
127                motor: "tcp://127.0.0.1:5564".to_string(),
128                visualization: "tcp://127.0.0.1:5562".to_string(),
129            },
130        },
131        websocket: ConnectionInfoWebSocket {
132            enabled: false,
133            host: "127.0.0.1".to_string(),
134            ports: ConnectionInfoWebSocketPorts {
135                registration: 9053,
136                sensory: 9051,
137                motor: 9052,
138                visualization: 9050,
139                rest_api: 9054,
140            },
141            endpoints: ConnectionInfoWebSocketEndpoints {
142                registration: "ws://127.0.0.1:9053".to_string(),
143                sensory: "ws://127.0.0.1:9051".to_string(),
144                motor: "ws://127.0.0.1:9052".to_string(),
145                visualization: "ws://127.0.0.1:9050".to_string(),
146            },
147        },
148        shm: ConnectionInfoShm {
149            enabled: false,
150            base_path: "/tmp".to_string(),
151            policy: "auto".to_string(),
152            note: "Actual paths are allocated per-agent at registration".to_string(),
153        },
154        udp: ConnectionInfoUdp {
155            enabled: false,
156            visualization: None,
157            sensory: None,
158            note: "Placeholder for future UDP transport support".to_string(),
159        },
160        bluetooth: ConnectionInfoBluetooth {
161            enabled: false,
162            relay_port: None,
163            note: "Placeholder for future use. Bluetooth relay is provided by feagi-desktop for embodied controllers, not by FEAGI server".to_string(),
164        },
165        stream_status: ConnectionInfoStreamStatus {
166            zmq_control_started: false,
167            zmq_data_streams_started: false,
168            websocket_started: false,
169            note: "Data streams start when genome is loaded and agents with matching capabilities are registered"
170                .to_string(),
171        },
172    }
173}