pub mod actions;
use anyhow::{Context, Result};
use bytes::Bytes;
use std::collections::HashMap;
use std::net::SocketAddr;
use std::sync::Arc;
use std::time::Duration;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpStream;
use tokio::sync::{mpsc, Mutex};
use tracing::{debug, info};
use super::connection::ConnectionId;
use crate::llm::action_helper::call_llm;
use crate::llm::ollama_client::OllamaClient;
use crate::llm::ActionResult;
use crate::logging::emit::Log;
use crate::protocol::Event;
use crate::server::TcpProtocol;
use crate::state::app_state::AppState;
use actions::{TCP_CONNECTION_OPENED_EVENT, TCP_DATA_RECEIVED_EVENT};
pub const MAX_QUEUED_BYTES: usize = 8 * 1024 * 1024;
const FIRST_BYTE_READ_TIMEOUT: Duration = Duration::from_secs(300);
const IDLE_BETWEEN_MESSAGES_TIMEOUT: Duration = Duration::from_secs(900);
const MAX_CONNECTIONS: usize = crate::server::accept_bounded::DEFAULT_MAX_CONNECTIONS;
const CONNECTION_CAP_REFUSAL: &[u8] = b"";
async fn read_bounded<R>(
reader: &mut R,
buffer: &mut [u8],
activity: &crate::server::accept_bounded::ConnectionActivity,
bound: Duration,
) -> Option<std::io::Result<usize>>
where
R: tokio::io::AsyncRead + Unpin,
{
loop {
match tokio::time::timeout(bound, reader.read(buffer)).await {
Ok(result) => return Some(result),
Err(_) => match activity.idle_for() {
None => continue,
Some(idle) if idle < bound => continue,
Some(_) => return None,
},
}
}
}
#[derive(Debug, Clone, PartialEq)]
enum ConnectionState {
Idle,
Processing,
Accumulating,
}
struct ConnectionData {
state: ConnectionState,
queued_data: Vec<u8>,
memory: String,
write_half: Arc<Mutex<tokio::io::WriteHalf<TcpStream>>>,
}
pub struct TcpServer;
impl TcpServer {
pub async fn spawn_with_llm_actions(
listen_addr: SocketAddr,
llm_client: OllamaClient,
app_state: Arc<AppState>,
status_tx: mpsc::UnboundedSender<String>,
send_first: bool,
server_id: crate::state::ServerId,
first_byte_timeout_secs: Option<u64>,
idle_timeout_secs: Option<u64>,
) -> Result<SocketAddr> {
let first_byte_timeout = first_byte_timeout_secs
.map(Duration::from_secs)
.unwrap_or(FIRST_BYTE_READ_TIMEOUT);
let idle_timeout = idle_timeout_secs
.map(Duration::from_secs)
.unwrap_or(IDLE_BETWEEN_MESSAGES_TIMEOUT);
let listener =
crate::server::socket_helpers::create_reusable_tcp_listener(listen_addr).await?;
let local_addr = listener.local_addr()?;
Log::new(Some(&status_tx)).info(format!("TCP server listening on {}", local_addr));
let connections = Arc::new(Mutex::new(HashMap::new()));
let protocol = Arc::new(TcpProtocol::new());
let task_registrar = app_state.clone();
let limiter = crate::server::accept_bounded::ConnectionLimiter::new(MAX_CONNECTIONS);
let accept_handle = tokio::spawn(async move {
loop {
match crate::server::accept_bounded::accept_bounded(
&listener,
&limiter,
CONNECTION_CAP_REFUSAL,
"TCP",
Some(&status_tx),
)
.await
{
Ok((stream, remote_addr, permit)) => {
let connection_id =
ConnectionId::new(app_state.get_next_unified_id().await);
let local_addr_conn = stream.local_addr().unwrap_or(local_addr);
info!("Accepted connection {} from {}", connection_id, remote_addr);
let (read_half, write_half) = tokio::io::split(stream);
let write_half_arc = Arc::new(Mutex::new(write_half));
let activity =
Arc::new(crate::server::accept_bounded::ConnectionActivity::new());
use crate::state::server::{
ConnectionState as ServerConnectionState, ConnectionStatus,
ProtocolConnectionInfo,
};
let now = crate::utils::clock::Instant::now();
let conn_state = ServerConnectionState {
id: connection_id,
remote_addr,
local_addr: local_addr_conn,
bytes_sent: 0,
bytes_received: 0,
packets_sent: 0,
packets_received: 0,
last_activity: now,
status: ConnectionStatus::Active,
status_changed_at: now,
protocol_info: ProtocolConnectionInfo::new(serde_json::json!({
"state": "Idle"
})),
};
app_state
.add_connection_to_server(server_id, conn_state)
.await;
let _ = status_tx.send("__UPDATE_UI__".to_string());
connections.lock().await.insert(
connection_id,
ConnectionData {
state: if send_first {
ConnectionState::Processing
} else {
ConnectionState::Idle
},
queued_data: Vec::new(),
memory: String::new(),
write_half: write_half_arc.clone(),
},
);
let peer_rx = crate::server::peer_support::register_peer_channel(
&app_state,
server_id,
connection_id.as_u32(),
)
.await;
crate::server::peer_support::spawn_peer_command_task(
peer_rx,
protocol.clone(),
app_state.clone(),
server_id,
connection_id.as_u32(),
write_half_arc.clone(),
status_tx.clone(),
);
let (opened_tx, opened_rx) = tokio::sync::oneshot::channel::<()>();
let (reader_alive_tx, reader_alive_rx) =
tokio::sync::oneshot::channel::<()>();
if send_first {
let llm_client_clone = llm_client.clone();
let app_state_clone = app_state.clone();
let status_tx_clone = status_tx.clone();
let connections_clone = connections.clone();
let write_half_for_conn = write_half_arc.clone();
let protocol_clone = protocol.clone();
let opened_busy = activity.busy();
let state_for_spawn = app_state.clone();
state_for_spawn
.spawn_server_task(server_id, async move {
let _opened_busy = opened_busy;
Self::handle_connection_opened(
connection_id,
server_id,
send_first,
opened_tx,
reader_alive_rx,
llm_client_clone,
app_state_clone,
status_tx_clone,
connections_clone,
write_half_for_conn,
protocol_clone,
)
.await;
})
.await;
} else {
drop(opened_tx);
drop(reader_alive_rx);
}
let llm_client_clone = llm_client.clone();
let app_state_clone = app_state.clone();
let status_tx_clone = status_tx.clone();
let connections_clone = connections.clone();
let protocol_clone = protocol.clone();
let activity_clone = activity.clone();
let state_for_reader = app_state.clone();
state_for_reader
.spawn_server_task(server_id, async move {
let _permit = permit;
let _reader_alive = reader_alive_tx;
let mut buffer = vec![0u8; 8192];
let mut read_half = read_half;
let mut seen_bytes = false;
if send_first {
let _ = opened_rx.await;
} else {
drop(opened_rx);
}
loop {
let bound = if seen_bytes {
idle_timeout
} else {
first_byte_timeout
};
let read = match read_bounded(
&mut read_half,
&mut buffer,
&activity_clone,
bound,
)
.await
{
Some(result) => result,
None => {
connections_clone.lock().await.remove(&connection_id);
app_state_clone
.remove_peer_handle(
server_id,
connection_id.as_u32(),
)
.await;
app_state_clone
.close_connection_on_server(
server_id,
connection_id,
)
.await;
Log::new(Some(&status_tx_clone)).info(format!(
"Connection {connection_id} sent nothing for \
{}s; closing idle connection",
bound.as_secs()
));
let _ =
status_tx_clone.send("__UPDATE_UI__".to_string());
break;
}
};
match read {
Ok(0) => {
connections_clone.lock().await.remove(&connection_id);
app_state_clone
.remove_peer_handle(
server_id,
connection_id.as_u32(),
)
.await;
app_state_clone
.close_connection_on_server(
server_id,
connection_id,
)
.await;
Log::new(Some(&status_tx_clone))
.info(format!("Connection {connection_id} closed"));
let _ =
status_tx_clone.send("__UPDATE_UI__".to_string());
break;
}
Ok(n) => {
seen_bytes = true;
activity_clone.touch();
let data = Bytes::copy_from_slice(&buffer[..n]);
let log = Log::new(Some(&status_tx_clone));
if data.iter().all(|&b| {
b.is_ascii_graphic() || b.is_ascii_whitespace()
}) {
let data_str = String::from_utf8_lossy(&data);
let preview = if data_str.len() > 100 {
format!("{}...", &data_str[..100])
} else {
data_str.to_string()
};
log.debug(format!(
"TCP received {} bytes on {}: {}",
n, connection_id, preview
));
log.trace(format!(
"TCP data (text): {:?}",
data_str
));
} else {
log.debug(format!(
"TCP received {} bytes on {} (binary data)",
n, connection_id
));
log.trace(format!(
"TCP data (hex): {}",
hex::encode(&data)
));
}
app_state_clone
.update_connection_stats(
server_id,
connection_id,
Some(n as u64),
None,
Some(1),
None,
)
.await;
let llm_clone = llm_client_clone.clone();
let state_clone = app_state_clone.clone();
let status_clone = status_tx_clone.clone();
let conns_clone = connections_clone.clone();
let protocol_clone = protocol_clone.clone();
let state_for_data = app_state_clone.clone();
let busy = activity_clone.busy();
state_for_data
.spawn_server_task(server_id, async move {
let _busy = busy;
Self::handle_data_with_actions(
connection_id,
server_id,
data,
llm_clone,
state_clone,
status_clone,
conns_clone,
protocol_clone,
)
.await;
})
.await;
}
Err(e) => {
Log::new(Some(&status_tx_clone)).error(format!(
"Read error on {}: {}",
connection_id, e
));
connections_clone.lock().await.remove(&connection_id);
app_state_clone
.remove_peer_handle(
server_id,
connection_id.as_u32(),
)
.await;
break;
}
}
}
})
.await;
}
Err(e) => {
Log::new(Some(&status_tx)).error(format!("Accept error: {}", e));
break;
}
}
}
});
task_registrar
.register_server_task(server_id, accept_handle)
.await;
Ok(local_addr)
}
#[allow(clippy::too_many_arguments)]
async fn handle_connection_opened(
connection_id: ConnectionId,
server_id: crate::state::ServerId,
send_first: bool,
opened: tokio::sync::oneshot::Sender<()>,
reader_alive: tokio::sync::oneshot::Receiver<()>,
llm_client: OllamaClient,
app_state: Arc<AppState>,
status_tx: mpsc::UnboundedSender<String>,
connections: Arc<Mutex<HashMap<ConnectionId, ConnectionData>>>,
write_half: Arc<Mutex<tokio::io::WriteHalf<TcpStream>>>,
protocol: Arc<TcpProtocol>,
) {
let log = Log::new(Some(&status_tx));
let event = Event::new(
&TCP_CONNECTION_OPENED_EVENT,
crate::protocol::event_type::connect_event_data(),
);
let answer = tokio::select! {
answer = call_llm(
&llm_client,
&app_state,
server_id,
Some(connection_id),
&event,
protocol.as_ref(),
) => answer,
_ = reader_alive => {
log.debug(format!(
"Connection {connection_id} closed before its connect event was answered: \
decision=peer_left_before_answer"
));
return;
}
};
match answer {
Ok(execution_result) => {
debug!("LLM TCP connect-event response received");
for msg in execution_result.messages {
let _ = status_tx.send(msg);
}
let mut wrote = false;
let mut close = false;
for protocol_result in execution_result.protocol_results {
match protocol_result {
ActionResult::Output(output_data) => {
let mut write = write_half.lock().await;
if let Err(e) = write.write_all(&output_data).await {
log.error(format!("Failed to send greeting: {}", e));
} else if let Err(e) = write.flush().await {
log.error(format!("Failed to flush greeting: {}", e));
} else {
drop(write);
if output_data
.iter()
.all(|&b| b.is_ascii_graphic() || b.is_ascii_whitespace())
{
let data_str = String::from_utf8_lossy(&output_data);
log.debug(format!(
"TCP sent {} bytes to {}: {}",
output_data.len(),
connection_id,
crate::utils::truncate_for_log(&data_str, 100)
));
log.trace(format!("TCP sent (text): {:?}", data_str));
} else {
log.debug(format!(
"TCP sent {} bytes to {} (binary data)",
output_data.len(),
connection_id
));
log.trace(format!(
"TCP sent (hex): {}",
hex::encode(&output_data)
));
}
app_state
.update_connection_stats(
server_id,
connection_id,
None,
Some(output_data.len() as u64),
None,
Some(1),
)
.await;
wrote = true;
}
}
ActionResult::CloseConnection => close = true,
_ => {}
}
}
if close {
{
let mut write = write_half.lock().await;
let _ = write.shutdown().await;
}
connections.lock().await.remove(&connection_id);
app_state
.close_connection_on_server(server_id, connection_id)
.await;
log.info(format!(
"Closed connection {connection_id} on connect: decision=model_close"
));
let _ = opened.send(());
return;
}
if wrote {
log.debug(format!(
"Greeted {connection_id} on connect: decision=model_answer"
));
} else if send_first {
log.warn(format!(
"No greeting for {connection_id}: decision=model_silent (send_first \
was requested but the model produced no bytes)"
));
} else {
log.debug(format!(
"No greeting for {connection_id}: decision=model_no_actions"
));
}
}
Err(e) => {
let failure = crate::utils::WireFailure::classify(&e);
let class = if failure.is_overloaded() {
"overloaded"
} else {
"unavailable"
};
if send_first {
log.warn(format!(
"TCP greeting failed for {connection_id}: decision=fail_closed_llm_error \
class={class} error={e}"
));
{
let mut write = write_half.lock().await;
let _ = write.shutdown().await;
}
connections.lock().await.remove(&connection_id);
app_state
.close_connection_on_server(server_id, connection_id)
.await;
log.info(format!(
"Closed connection {connection_id} after greeting LLM error"
));
let _ = opened.send(());
return;
}
log.warn(format!(
"TCP connect event for {connection_id} failed: \
decision=connect_event_failed class={class} error={e}"
));
}
}
let _ = opened.send(());
let queued = {
let mut conns = connections.lock().await;
let Some(conn) = conns.get_mut(&connection_id) else {
return;
};
conn.state = ConnectionState::Idle;
!conn.queued_data.is_empty()
};
if queued {
Self::handle_data_with_actions(
connection_id,
server_id,
Bytes::new(),
llm_client,
app_state,
status_tx,
connections,
protocol,
)
.await;
}
}
#[allow(clippy::too_many_arguments)]
async fn handle_data_with_actions(
connection_id: ConnectionId,
server_id: crate::state::ServerId,
data: Bytes,
llm_client: OllamaClient,
app_state: Arc<AppState>,
status_tx: mpsc::UnboundedSender<String>,
connections: Arc<Mutex<HashMap<ConnectionId, ConnectionData>>>,
protocol: Arc<TcpProtocol>,
) {
let current_state = {
let conns = connections.lock().await;
if let Some(conn_data) = conns.get(&connection_id) {
conn_data.state.clone()
} else {
debug!(
"TCP connection {} is no longer registered; dropping {} received bytes",
connection_id,
data.len()
);
return;
}
};
if current_state == ConnectionState::Processing {
let (queued_len, write_half) = {
let mut conns = connections.lock().await;
let Some(conn) = conns.get_mut(&connection_id) else {
return; };
conn.queued_data.extend_from_slice(&data);
(conn.queued_data.len(), conn.write_half.clone())
};
let log = Log::new(Some(&status_tx));
if queued_len > MAX_QUEUED_BYTES {
log.warn(format!(
"Connection {connection_id} queued {queued_len} bytes while awaiting a \
response (limit {MAX_QUEUED_BYTES}); closing: decision=queue_overflow"
));
{
let mut write = write_half.lock().await;
let _ = write.shutdown().await;
}
connections.lock().await.remove(&connection_id);
app_state
.close_connection_on_server(server_id, connection_id)
.await;
let _ = status_tx.send("__UPDATE_UI__".to_string());
return;
}
log.debug(format!(
"Queued {} bytes for {} ({} queued)",
data.len(),
connection_id,
queued_len
));
return;
}
let mut all_data = {
let mut conns = connections.lock().await;
let Some(conn_data) = conns.get_mut(&connection_id) else {
return; };
conn_data.state = ConnectionState::Processing;
let mut merged = conn_data.queued_data.clone();
merged.extend_from_slice(&data);
conn_data.queued_data.clear();
Bytes::from(merged)
};
loop {
let memory = {
let conns = connections.lock().await;
conns
.get(&connection_id)
.map(|c| c.memory.clone())
.unwrap_or_default()
};
let write_half = {
let conns = connections.lock().await;
conns.get(&connection_id).map(|c| c.write_half.clone())
};
let Some(write_half) = write_half else {
debug!(
"TCP connection {} went away before its response could be written",
connection_id
);
return;
};
let (data_str, data_encoding) = if all_data
.iter()
.all(|&b| b.is_ascii_graphic() || b.is_ascii_whitespace())
{
(String::from_utf8_lossy(&all_data).to_string(), "utf8")
} else {
(hex::encode(&all_data), "hex")
};
let event = Event::new(
&TCP_DATA_RECEIVED_EVENT,
serde_json::json!({
"data": data_str,
"encoding": data_encoding
}),
);
match call_llm(
&llm_client,
&app_state,
server_id,
Some(connection_id),
&event,
protocol.as_ref(),
)
.await
{
Ok(execution_result) => {
let log = Log::new(Some(&status_tx));
debug!("LLM TCP response received");
connections
.lock()
.await
.entry(connection_id)
.and_modify(|conn| conn.memory = memory.clone());
for msg in execution_result.messages {
let _ = status_tx.send(msg);
}
let mut should_close = false;
let mut should_wait = false;
let mut wrote_output = false;
for protocol_result in execution_result.protocol_results {
match protocol_result {
ActionResult::Output(output_data) => {
let mut write = write_half.lock().await;
if let Err(e) = write.write_all(&output_data).await {
log.error(format!("Failed to send response: {}", e));
} else if let Err(e) = write.flush().await {
log.error(format!("Failed to flush response: {}", e));
} else {
if output_data
.iter()
.all(|&b| b.is_ascii_graphic() || b.is_ascii_whitespace())
{
let data_str = String::from_utf8_lossy(&output_data);
let preview = if data_str.len() > 100 {
format!("{}...", &data_str[..100])
} else {
data_str.to_string()
};
log.debug(format!(
"TCP sent {} bytes to {}: {}",
output_data.len(),
connection_id,
preview
));
log.trace(format!("TCP sent (text): {:?}", data_str));
} else {
log.debug(format!(
"TCP sent {} bytes to {} (binary data)",
output_data.len(),
connection_id
));
log.trace(format!(
"TCP sent (hex): {}",
hex::encode(&output_data)
));
}
log.debug(format!(
"Sent {} bytes to {}",
output_data.len(),
connection_id
));
app_state
.update_connection_stats(
server_id,
connection_id,
None,
Some(output_data.len() as u64),
None,
Some(1),
)
.await;
wrote_output = true;
}
}
ActionResult::CloseConnection => {
should_close = true;
}
ActionResult::WaitForMore => {
should_wait = true;
}
_ => {}
}
}
if should_wait {
let (queued_len, arrived_during_call, write_half_for_close) = {
let mut conns = connections.lock().await;
let Some(conn) = conns.get_mut(&connection_id) else {
return;
};
let arrived_during_call = !conn.queued_data.is_empty();
let mut merged = all_data.to_vec();
merged.append(&mut conn.queued_data);
conn.queued_data = merged;
conn.state = ConnectionState::Accumulating;
(
conn.queued_data.len(),
arrived_during_call,
conn.write_half.clone(),
)
};
if queued_len > MAX_QUEUED_BYTES {
log.warn(format!(
"Connection {connection_id} accumulated {queued_len} bytes across \
wait_for_more (limit {MAX_QUEUED_BYTES}); closing: \
decision=queue_overflow"
));
{
let mut write = write_half_for_close.lock().await;
let _ = write.shutdown().await;
}
connections.lock().await.remove(&connection_id);
app_state
.close_connection_on_server(server_id, connection_id)
.await;
let _ = status_tx.send("__UPDATE_UI__".to_string());
return;
}
if arrived_during_call {
let joined = {
let mut conns = connections.lock().await;
let Some(conn) = conns.get_mut(&connection_id) else {
return;
};
conn.state = ConnectionState::Processing;
std::mem::take(&mut conn.queued_data)
};
log.debug(format!(
"wait_for_more on {connection_id}: {} bytes already waiting, \
continuing",
joined.len()
));
all_data = Bytes::from(joined);
continue;
}
log.debug(format!(
"Waiting for more data from {connection_id} ({queued_len} bytes held)"
));
return;
}
if should_close {
connections.lock().await.remove(&connection_id);
log.info(format!(
"Closed connection {connection_id}: decision=model_close"
));
return;
}
if !wrote_output {
log.debug(format!(
"No response bytes for {connection_id}: decision=model_no_actions"
));
}
let has_queued = {
let conns = connections.lock().await;
conns
.get(&connection_id)
.map(|c| !c.queued_data.is_empty())
.unwrap_or(false)
};
if has_queued {
let queued = {
let mut conns = connections.lock().await;
match conns.get_mut(&connection_id) {
Some(conn) => std::mem::take(&mut conn.queued_data),
None => return,
}
};
if queued.is_empty() {
connections
.lock()
.await
.entry(connection_id)
.and_modify(|conn| conn.state = ConnectionState::Idle);
return;
}
all_data = Bytes::from(queued);
} else {
connections
.lock()
.await
.entry(connection_id)
.and_modify(|conn| conn.state = ConnectionState::Idle);
return;
}
}
Err(e) => {
let log = Log::new(Some(&status_tx));
let failure = crate::utils::WireFailure::classify(&e);
let class = if failure.is_overloaded() {
"overloaded"
} else {
"unavailable"
};
log.warn(format!(
"LLM error for TCP data on {connection_id}: decision=fail_closed_llm_error class={class} error={e}"
));
if failure.is_overloaded() {
log.warn(format!(
"TCP connection {} closed: LLM capacity exhausted",
connection_id
));
}
{
let mut write = write_half.lock().await;
let _ = write.shutdown().await;
}
connections.lock().await.remove(&connection_id);
app_state
.close_connection_on_server(server_id, connection_id)
.await;
log.info(format!("Closed connection {connection_id} after LLM error"));
return;
}
}
}
}
}
pub async fn send_data(stream: &mut TcpStream, data: &[u8]) -> Result<()> {
stream
.write_all(data)
.await
.context("Failed to write data")?;
stream.flush().await.context("Failed to flush stream")?;
Ok(())
}