1pub mod actions;
33pub mod wire;
34
35use crate::llm::action_helper::call_llm;
36use crate::llm::actions::executor::ExecutionResult;
37use crate::llm::actions::protocol_trait::ActionResult;
38use crate::llm::ollama_client::OllamaClient;
39use crate::logging::emit::Log;
40use crate::protocol::{Event, EventType};
41use crate::server::connection::ConnectionId;
42use crate::state::app_state::AppState;
43use crate::state::client_handles::{ClientCommand, ClientSendOutcome};
44use actions::{Answer, Delivery};
45use anyhow::Result;
46use serde_json::json;
47use std::collections::{BTreeMap, VecDeque};
48use std::net::SocketAddr;
49use std::sync::atomic::{AtomicU64, Ordering};
50use std::sync::Arc;
51use std::time::Duration;
52use tokio::io::{AsyncRead, AsyncReadExt, AsyncWriteExt, WriteHalf};
53use tokio::net::{TcpListener, TcpStream};
54use tokio::sync::mpsc;
55
56pub const FIRST_BYTE_TIMEOUT: Duration = Duration::from_secs(300);
60
61pub const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(30);
63
64pub const IDLE_TIMEOUT: Duration = Duration::from_secs(300);
66
67const MAX_CONNECTIONS: usize = crate::server::accept_bounded::DEFAULT_MAX_CONNECTIONS;
69
70pub const MAX_PENDING_MESSAGES: usize = 1000;
73pub const MAX_PENDING_BYTES: usize = 16 * 1024 * 1024;
74
75const EVENT_MESSAGES: usize = 20;
77const EVENT_BODY_BYTES: usize = 1000;
78
79fn failed_text(command: &str, overloaded: bool) -> &'static str {
82 match (command, overloaded) {
83 ("MPUB", false) => "MPUB failed: broker backend unavailable",
84 ("MPUB", true) => "MPUB failed: broker backend at capacity",
85 ("DPUB", false) => "DPUB failed: broker backend unavailable",
86 ("DPUB", true) => "DPUB failed: broker backend at capacity",
87 ("SUB", false) => "SUB failed: broker backend unavailable",
88 ("SUB", true) => "SUB failed: broker backend at capacity",
89 (_, false) => "PUB failed: broker backend unavailable",
90 (_, true) => "PUB failed: broker backend at capacity",
91 }
92}
93
94#[derive(Debug, Clone, Copy)]
96pub struct NsqConfig {
97 pub first_byte_timeout: Duration,
98 pub heartbeat_interval: Duration,
99 pub idle_timeout: Duration,
100}
101
102pub struct NsqServer;
103
104impl NsqServer {
105 pub async fn spawn_with_llm_actions(
106 listen_addr: SocketAddr,
107 llm_client: OllamaClient,
108 app_state: Arc<AppState>,
109 status_tx: mpsc::UnboundedSender<String>,
110 server_id: crate::state::ServerId,
111 config: NsqConfig,
112 ) -> Result<SocketAddr> {
113 let listener = TcpListener::bind(listen_addr).await?;
114 let local_addr = listener.local_addr()?;
115
116 Log::new(Some(&status_tx)).info(format!("NSQ broker listening on {}", local_addr));
117
118 let protocol = Arc::new(actions::NsqProtocol::new());
119 let ids = Arc::new(AtomicU64::new(0));
120 let task_registrar = app_state.clone();
121 let limiter = crate::server::accept_bounded::ConnectionLimiter::new(MAX_CONNECTIONS);
122 let accept_handle = tokio::spawn(async move {
123 loop {
124 match crate::server::accept_bounded::accept_bounded(
127 &listener,
128 &limiter,
129 b"",
130 "NSQ",
131 Some(&status_tx),
132 )
133 .await
134 {
135 Ok((socket, peer_addr, permit)) => {
136 let connection_id =
137 ConnectionId::new(app_state.get_next_unified_id().await);
138 use crate::state::server::{
139 ConnectionState as ServerConnectionState, ConnectionStatus,
140 ProtocolConnectionInfo,
141 };
142 let now = crate::utils::clock::Instant::now();
143 app_state
144 .add_connection_to_server(
145 server_id,
146 ServerConnectionState {
147 id: connection_id,
148 remote_addr: peer_addr,
149 local_addr,
150 bytes_sent: 0,
151 bytes_received: 0,
152 packets_sent: 0,
153 packets_received: 0,
154 last_activity: now,
155 status: ConnectionStatus::Active,
156 status_changed_at: now,
157 protocol_info: ProtocolConnectionInfo::empty(),
158 },
159 )
160 .await;
161 let _ = status_tx.send("__UPDATE_UI__".to_string());
162 Log::new(Some(&status_tx))
163 .info(format!("NSQ client connected from {}", peer_addr));
164
165 let session = Session {
166 peer_addr,
167 llm_client: llm_client.clone(),
168 app_state: app_state.clone(),
169 status_tx: status_tx.clone(),
170 server_id,
171 protocol: protocol.clone(),
172 connection_id,
173 config,
174 ids: ids.clone(),
175 };
176 let task_owner = app_state.clone();
178 task_owner
179 .spawn_server_task(server_id, async move {
180 let _permit = permit;
181 session.run(socket).await
182 })
183 .await;
184 }
185 Err(e) => {
186 Log::new(Some(&status_tx)).error(format!("NSQ accept error: {}", e));
187 break;
188 }
189 }
190 }
191 });
192
193 task_registrar
194 .register_server_task(server_id, accept_handle)
195 .await;
196 Ok(local_addr)
197 }
198}
199
200type Writer = WriteHalf<TcpStream>;
201
202struct Session {
203 peer_addr: SocketAddr,
204 llm_client: OllamaClient,
205 app_state: Arc<AppState>,
206 status_tx: mpsc::UnboundedSender<String>,
207 server_id: crate::state::ServerId,
208 protocol: Arc<actions::NsqProtocol>,
209 connection_id: ConnectionId,
210 config: NsqConfig,
211 ids: Arc<AtomicU64>,
212}
213
214struct Flow {
216 heartbeat: Option<Duration>,
217 ticker: Option<tokio::time::Interval>,
218 identified: bool,
219 client_id: String,
220 user_agent: String,
221 sub: Option<(String, String)>,
222 closing: bool,
223 rdy: u64,
224 in_flight: BTreeMap<String, (String, u16)>,
226 pending: VecDeque<Delivery>,
227 pending_bytes: usize,
228}
229
230fn ticker(every: Duration) -> tokio::time::Interval {
231 let mut t = tokio::time::interval_at(tokio::time::Instant::now() + every, every);
232 t.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
233 t
234}
235
236impl Flow {
237 fn new(heartbeat: Duration) -> Self {
238 Self {
239 heartbeat: Some(heartbeat),
240 ticker: Some(ticker(heartbeat)),
241 identified: false,
242 client_id: String::new(),
243 user_agent: String::new(),
244 sub: None,
245 closing: false,
246 rdy: 0,
247 in_flight: BTreeMap::new(),
248 pending: VecDeque::new(),
249 pending_bytes: 0,
250 }
251 }
252
253 fn silence_window(&self, idle: Duration) -> Duration {
255 match self.heartbeat {
256 Some(hb) => hb * 2,
257 None => idle,
258 }
259 }
260
261 fn can_deliver(&self) -> u64 {
262 if self.sub.is_none() || self.closing {
263 return 0;
264 }
265 self.rdy.saturating_sub(self.in_flight.len() as u64)
266 }
267
268 fn topic(&self) -> (String, String) {
269 self.sub.clone().unwrap_or_default()
270 }
271}
272
273async fn tick(ticker: &mut Option<tokio::time::Interval>) {
275 match ticker {
276 Some(t) => {
277 t.tick().await;
278 }
279 None => std::future::pending::<()>().await,
280 }
281}
282
283async fn next_injected(rx: &mut Option<mpsc::Receiver<ClientCommand>>) -> Option<ClientCommand> {
285 match rx {
286 Some(r) => r.recv().await,
287 None => std::future::pending().await,
288 }
289}
290
291enum ReadError {
292 Eof,
293 TimedOut,
294 Wire(wire::WireError),
295 Io(std::io::Error),
296}
297
298enum Step {
299 Continue,
300 Close,
301}
302
303enum Wake {
304 Command(Result<(wire::Command, usize), ReadError>),
305 Heartbeat,
306 Injected(Option<ClientCommand>),
307}
308
309impl Session {
310 async fn run(self, socket: TcpStream) {
311 let (mut reader, mut writer) = tokio::io::split(socket);
312 let peer_rx = crate::server::peer_support::register_peer_channel(
313 &self.app_state,
314 self.server_id,
315 self.connection_id.as_u32(),
316 )
317 .await;
318
319 {
320 let mut framer = Framer::new(&mut reader);
321 self.session(&mut framer, &mut writer, peer_rx).await;
322 }
323
324 self.app_state
325 .remove_peer_handle(self.server_id, self.connection_id.as_u32())
326 .await;
327 let _ = writer.shutdown().await;
328 linger(&mut reader).await;
329 self.app_state
330 .update_connection_status(
331 self.server_id,
332 self.connection_id,
333 crate::state::server::ConnectionStatus::Closed,
334 )
335 .await;
336 let _ = self.status_tx.send("__UPDATE_UI__".to_string());
337 }
338
339 async fn write(&self, writer: &mut Writer, data: &[u8]) -> bool {
340 let ok = writer.write_all(data).await.is_ok() && writer.flush().await.is_ok();
341 if ok {
342 self.app_state
343 .update_connection_stats(
344 self.server_id,
345 self.connection_id,
346 None,
347 Some(data.len() as u64),
348 None,
349 Some(1),
350 )
351 .await;
352 }
353 ok
354 }
355
356 async fn refuse(&self, writer: &mut Writer, code: &'static str, message: &str) -> Step {
358 let written = self.write(writer, &wire::error_frame(code, message)).await;
359 if written && !wire::is_fatal(code) {
360 Step::Continue
361 } else {
362 Step::Close
363 }
364 }
365
366 async fn session<R: AsyncRead + Unpin>(
367 &self,
368 framer: &mut Framer<'_, R>,
369 writer: &mut Writer,
370 peer_rx: mpsc::Receiver<ClientCommand>,
371 ) {
372 let log = Log::new(Some(&self.status_tx));
373 let magic_deadline = tokio::time::Instant::now() + self.config.first_byte_timeout;
374 match framer.magic(magic_deadline).await {
375 Ok(true) => {}
376 Ok(false) => {
377 log.warn(format!(
378 "NSQ peer {} did not send the V2 magic decision=fail_closed_bad_magic",
379 self.peer_addr
380 ));
381 let _ = self
382 .write(writer, &wire::error_frame("E_BAD_PROTOCOL", ""))
383 .await;
384 return;
385 }
386 Err(ReadError::TimedOut) => {
387 log.info(format!(
388 "NSQ client {} sent nothing within {}s; closing",
389 self.peer_addr,
390 self.config.first_byte_timeout.as_secs()
391 ));
392 return;
393 }
394 Err(_) => return,
395 }
396
397 let mut flow = Flow::new(self.config.heartbeat_interval);
398 let mut peer_rx = Some(peer_rx);
399 let mut answered_at = tokio::time::Instant::now();
400 let heartbeat = wire::response_frame(wire::HEARTBEAT);
401
402 loop {
403 let window = flow.silence_window(self.config.idle_timeout);
404 let deadline = framer.last_read.max(answered_at) + window;
405 let wake = tokio::select! {
406 r = framer.next_command(deadline) => Wake::Command(r),
407 _ = tick(&mut flow.ticker) => Wake::Heartbeat,
408 c = next_injected(&mut peer_rx) => Wake::Injected(c),
409 };
410 let step = match wake {
411 Wake::Heartbeat => {
412 if self.write(writer, &heartbeat).await {
413 Step::Continue
414 } else {
415 Step::Close
416 }
417 }
418 Wake::Injected(None) => {
419 peer_rx = None;
420 Step::Continue
421 }
422 Wake::Injected(Some(command)) => self.inject(command, &mut flow, writer).await,
423 Wake::Command(Err(ReadError::Eof)) => {
424 log.info(format!("NSQ client {} disconnected", self.peer_addr));
425 Step::Close
426 }
427 Wake::Command(Err(ReadError::TimedOut)) => {
428 log.info(format!(
429 "NSQ client {} silent for {}s decision=fail_closed_idle; closing",
430 self.peer_addr,
431 window.as_secs()
432 ));
433 Step::Close
434 }
435 Wake::Command(Err(ReadError::Io(e))) => {
436 log.error(format!("NSQ read error from {}: {}", self.peer_addr, e));
437 Step::Close
438 }
439 Wake::Command(Err(ReadError::Wire(e))) => {
440 log.warn(format!(
441 "NSQ command from {} refused: {} {} decision={}",
442 self.peer_addr,
443 e.code,
444 e.message,
445 e.decision()
446 ));
447 let _ = self.write(writer, &e.to_frame()).await;
448 Step::Close
449 }
450 Wake::Command(Ok((command, used))) => {
451 self.app_state
452 .update_connection_stats(
453 self.server_id,
454 self.connection_id,
455 Some(used as u64),
456 None,
457 Some(1),
458 None,
459 )
460 .await;
461 let step = self.command(command, &mut flow, writer).await;
462 answered_at = tokio::time::Instant::now();
463 step
464 }
465 };
466 if let Step::Close = step {
467 return;
468 }
469 }
470 }
471
472 async fn command(&self, command: wire::Command, flow: &mut Flow, writer: &mut Writer) -> Step {
474 use wire::Command as C;
475 let log = Log::new(Some(&self.status_tx));
476 let name = command.name();
477 let subscribed_or_closing = flow.sub.is_some() || flow.closing;
478 match command {
479 C::Nop => Step::Continue,
480 C::Identify(body) => {
481 if flow.identified {
482 return self
483 .refuse(writer, "E_INVALID", "cannot IDENTIFY in current state")
484 .await;
485 }
486 let identify = match wire::parse_identify(&body) {
487 Ok(i) => i,
488 Err(e) => {
489 log.warn(format!(
490 "NSQ IDENTIFY from {} refused: {} decision=fail_closed_bad_identify",
491 self.peer_addr, e.message
492 ));
493 let _ = self.write(writer, &e.to_frame()).await;
494 return Step::Close;
495 }
496 };
497 flow.identified = true;
498 match identify.heartbeat_ms {
499 None => {
500 flow.heartbeat = None;
501 flow.ticker = None;
502 }
503 Some(0) => {}
504 Some(ms) => {
505 let every = Duration::from_millis(ms as u64);
506 flow.heartbeat = Some(every);
507 flow.ticker = Some(ticker(every));
508 }
509 }
510 flow.client_id = identify.client_id;
511 flow.user_agent = identify.user_agent;
512 let reply = if identify.feature_negotiation {
513 wire::identify_response(identify.msg_timeout_ms)
514 } else {
515 b"OK".to_vec()
516 };
517 log.debug(format!(
518 "NSQ IDENTIFY from {} (user_agent {:?}, heartbeat {:?})",
519 self.peer_addr, flow.user_agent, flow.heartbeat
520 ));
521 self.step(self.write(writer, &wire::response_frame(&reply)).await)
522 }
523 C::Auth(_) => {
524 log.warn(format!(
525 "NSQ AUTH from {} refused decision=fail_closed_auth_disabled",
526 self.peer_addr
527 ));
528 self.refuse(writer, "E_AUTH_DISABLED", "AUTH disabled")
529 .await
530 }
531 C::Cls => {
532 if flow.sub.is_none() {
533 return self
534 .refuse(writer, "E_INVALID", "cannot CLS in current state")
535 .await;
536 }
537 flow.closing = true;
538 flow.pending.clear();
539 flow.pending_bytes = 0;
540 self.step(
541 self.write(writer, &wire::response_frame(b"CLOSE_WAIT"))
542 .await,
543 )
544 }
545 C::Touch(id) => {
546 if !subscribed_or_closing {
547 return self
548 .refuse(writer, "E_INVALID", "cannot TOUCH in current state")
549 .await;
550 }
551 if flow.in_flight.contains_key(&id) {
552 Step::Continue
553 } else {
554 self.refuse(
555 writer,
556 "E_TOUCH_FAILED",
557 &format!("TOUCH {id} failed ID not in flight"),
558 )
559 .await
560 }
561 }
562 C::Sub { topic, channel } => {
563 if flow.sub.is_some() || flow.closing {
564 return self
565 .refuse(writer, "E_INVALID", "cannot SUB in current state")
566 .await;
567 }
568 self.subscribe(topic, channel, flow, writer).await
569 }
570 C::Rdy(count) => {
571 if flow.closing {
572 return Step::Continue;
573 }
574 if flow.sub.is_none() {
575 return self
576 .refuse(writer, "E_INVALID", "cannot RDY in current state")
577 .await;
578 }
579 flow.rdy = count;
580 if !self.flush(flow, writer).await {
581 return Step::Close;
582 }
583 if flow.can_deliver() == 0 {
584 return Step::Continue;
585 }
586 let (topic, _) = flow.topic();
587 let mut data = self.flow_data(flow);
588 data["count"] = json!(count);
589 data["answer_with"] = json!(actions::deliver_answer_with(
590 &topic,
591 flow.can_deliver(),
592 flow.pending.len()
593 ));
594 self.flow_event(&actions::NSQ_READY_EVENT, data, name, flow, writer)
595 .await
596 }
597 C::Fin(id) => {
598 if !subscribed_or_closing {
599 return self
600 .refuse(writer, "E_INVALID", "cannot FIN in current state")
601 .await;
602 }
603 if flow.in_flight.remove(&id).is_none() {
604 return self
605 .refuse(
606 writer,
607 "E_FIN_FAILED",
608 &format!("FIN {id} failed ID not in flight"),
609 )
610 .await;
611 }
612 if !self.flush(flow, writer).await {
613 return Step::Close;
614 }
615 if flow.closing {
616 return Step::Continue;
617 }
618 let (topic, _) = flow.topic();
619 let mut data = self.flow_data(flow);
620 data["message_id"] = json!(id);
621 data["answer_with"] = json!(actions::deliver_answer_with(
622 &topic,
623 flow.can_deliver(),
624 flow.pending.len()
625 ));
626 self.flow_event(&actions::NSQ_FINISH_EVENT, data, name, flow, writer)
627 .await
628 }
629 C::Req { id, timeout_ms } => {
630 if !subscribed_or_closing {
631 return self
632 .refuse(writer, "E_INVALID", "cannot REQ in current state")
633 .await;
634 }
635 let Some((body, attempts)) = flow.in_flight.remove(&id) else {
636 return self
637 .refuse(
638 writer,
639 "E_REQ_FAILED",
640 &format!("REQ {id} failed ID not in flight"),
641 )
642 .await;
643 };
644 if !self.flush(flow, writer).await {
645 return Step::Close;
646 }
647 if flow.closing {
648 return Step::Continue;
649 }
650 let mut data = self.flow_data(flow);
651 data["message_id"] = json!(id);
652 data["timeout_ms"] = json!(timeout_ms);
653 data["attempts"] = json!(attempts);
654 data["body"] = json!(crate::utils::truncate_for_llm(&body, EVENT_BODY_BYTES));
655 data["answer_with"] = json!(actions::requeue_answer_with(attempts));
656 self.flow_event(&actions::NSQ_REQUEUE_EVENT, data, name, flow, writer)
657 .await
658 }
659 C::Pub { topic, body } => {
660 self.publish(name, topic, vec![body], None, flow, writer)
661 .await
662 }
663 C::Dpub {
664 topic,
665 defer_ms,
666 body,
667 } => {
668 self.publish(name, topic, vec![body], Some(defer_ms), flow, writer)
669 .await
670 }
671 C::Mpub { topic, messages } => {
672 self.publish(name, topic, messages, None, flow, writer)
673 .await
674 }
675 }
676 }
677
678 fn step(&self, written: bool) -> Step {
679 if written {
680 Step::Continue
681 } else {
682 Step::Close
683 }
684 }
685
686 fn flow_data(&self, flow: &Flow) -> serde_json::Value {
687 let (topic, channel) = flow.topic();
688 json!({
689 "topic": topic,
690 "channel": channel,
691 "ready": flow.rdy,
692 "in_flight": flow.in_flight.len(),
693 "pending": flow.pending.len(),
694 "can_deliver": flow.can_deliver(),
695 })
696 }
697
698 async fn ask(
700 &self,
701 event: &Event,
702 flow: &mut Flow,
703 writer: &mut Writer,
704 ) -> Result<ExecutionResult> {
705 let heartbeat = wire::response_frame(wire::HEARTBEAT);
706 let call = call_llm(
707 &self.llm_client,
708 &self.app_state,
709 self.server_id,
710 Some(self.connection_id),
711 event,
712 self.protocol.as_ref(),
713 );
714 tokio::pin!(call);
715 loop {
716 tokio::select! {
717 result = &mut call => return result,
718 _ = tick(&mut flow.ticker) => {
719 let _ = self.write(writer, &heartbeat).await;
721 }
722 }
723 }
724 }
725
726 fn answers(&self, result: ExecutionResult) -> Vec<Answer> {
728 let log = Log::new(Some(&self.status_tx));
729 for message in &result.messages {
730 log.info(message);
731 }
732 let mut out = Vec::new();
733 let mut stack = result.protocol_results;
734 stack.reverse();
735 while let Some(item) = stack.pop() {
736 match item {
737 ActionResult::Multiple(inner) => stack.extend(inner.into_iter().rev()),
738 other => match Answer::from_result(&other) {
739 Some(answer) => out.push(answer),
740 None => log.debug(format!("NSQ: ignoring non-NSQ result {other:?}")),
741 },
742 }
743 }
744 out
745 }
746
747 async fn subscribe(
748 &self,
749 topic: String,
750 channel: String,
751 flow: &mut Flow,
752 writer: &mut Writer,
753 ) -> Step {
754 let log = Log::new(Some(&self.status_tx));
755 let event = Event::new(
756 &actions::NSQ_SUBSCRIBE_EVENT,
757 json!({
758 "topic": topic,
759 "channel": channel,
760 "ephemeral": wire::is_ephemeral(&channel),
761 "client_id": flow.client_id,
762 "user_agent": flow.user_agent,
763 "answer_with": actions::subscribe_answer_with(&topic, &channel),
764 }),
765 );
766 let answers = match self.ask(&event, flow, writer).await {
767 Ok(result) => self.answers(result),
768 Err(e) => {
769 let failure = crate::utils::WireFailure::classify(&e);
770 log.warn(format!(
771 "NSQ SUB {topic} {channel} from {} decision=fail_closed_llm_error category={:?}",
772 self.peer_addr, failure
773 ));
774 log.debug(format!("NSQ LLM call failed: {}", e));
775 let text = failed_text("SUB", failure.is_overloaded());
776 return self.refuse(writer, "E_INVALID", text).await;
777 }
778 };
779 let step = match reply_of(&answers) {
780 Some(Answer::Ok) => {
781 log.info(format!(
782 "NSQ SUB {topic} {channel} from {} decision=model_answer",
783 self.peer_addr
784 ));
785 flow.sub = Some((topic, channel));
786 self.step(self.write(writer, &wire::response_frame(b"OK")).await)
787 }
788 Some(Answer::Error { code, message }) => {
789 log.info(format!(
790 "NSQ SUB {topic} {channel} from {} decision=model_reject code={code}",
791 self.peer_addr
792 ));
793 self.refuse(writer, static_code(code), message).await
794 }
795 _ => {
796 log.warn(format!(
797 "NSQ SUB {topic} {channel} from {} decision=model_silent; refusing",
798 self.peer_addr
799 ));
800 return self.refuse(writer, "E_INVALID", "SUB failed").await;
801 }
802 };
803 self.finish_answers(step, &answers, flow, writer).await
804 }
805
806 async fn publish(
807 &self,
808 command: &'static str,
809 topic: String,
810 bodies: Vec<Vec<u8>>,
811 defer_ms: Option<i64>,
812 flow: &mut Flow,
813 writer: &mut Writer,
814 ) -> Step {
815 let log = Log::new(Some(&self.status_tx));
816 let failed_code: &'static str = match command {
817 "MPUB" => "E_MPUB_FAILED",
818 "DPUB" => "E_DPUB_FAILED",
819 _ => "E_PUB_FAILED",
820 };
821 let total_bytes: usize = bodies.iter().map(Vec::len).sum();
822 let shown: Vec<String> = bodies
823 .iter()
824 .take(EVENT_MESSAGES)
825 .map(|b| crate::utils::truncate_for_llm(&String::from_utf8_lossy(b), EVENT_BODY_BYTES))
826 .collect();
827 let mut data = json!({
828 "command": command,
829 "topic": topic,
830 "messages": shown,
831 "message_count": bodies.len(),
832 "total_bytes": total_bytes,
833 "answer_with": actions::publish_answer_with(command, &topic, bodies.len()),
834 });
835 if let Some(defer) = defer_ms {
836 data["defer_ms"] = json!(defer);
837 }
838 let event = Event::new(&actions::NSQ_PUBLISH_EVENT, data);
839 let answers = match self.ask(&event, flow, writer).await {
840 Ok(result) => self.answers(result),
841 Err(e) => {
842 let failure = crate::utils::WireFailure::classify(&e);
843 log.warn(format!(
844 "NSQ {command} {topic} from {} decision=fail_closed_llm_error category={:?}",
845 self.peer_addr, failure
846 ));
847 log.debug(format!("NSQ LLM call failed: {}", e));
848 let text = failed_text(command, failure.is_overloaded());
849 return self.refuse(writer, failed_code, text).await;
850 }
851 };
852 let step = match reply_of(&answers) {
853 Some(Answer::Ok) => {
854 log.info(format!(
855 "NSQ {command} {topic} ({} message(s)) from {} decision=model_answer",
856 bodies.len(),
857 self.peer_addr
858 ));
859 self.step(self.write(writer, &wire::response_frame(b"OK")).await)
860 }
861 Some(Answer::Error { code, message }) => {
862 log.info(format!(
863 "NSQ {command} {topic} from {} decision=model_reject code={code}",
864 self.peer_addr
865 ));
866 self.refuse(writer, static_code(code), message).await
867 }
868 _ => {
869 log.warn(format!(
870 "NSQ {command} {topic} from {} decision=model_silent; refusing",
871 self.peer_addr
872 ));
873 return self
874 .refuse(writer, failed_code, &format!("{command} failed"))
875 .await;
876 }
877 };
878 self.finish_answers(step, &answers, flow, writer).await
879 }
880
881 async fn finish_answers(
883 &self,
884 step: Step,
885 answers: &[Answer],
886 flow: &mut Flow,
887 writer: &mut Writer,
888 ) -> Step {
889 if let Step::Close = step {
890 return Step::Close;
891 }
892 let deliveries: Vec<Delivery> = answers
893 .iter()
894 .filter_map(|a| match a {
895 Answer::Deliver(d) => Some(d.clone()),
896 _ => None,
897 })
898 .flatten()
899 .collect();
900 if !deliveries.is_empty() {
901 self.enqueue(flow, deliveries);
902 if !self.flush(flow, writer).await {
903 return Step::Close;
904 }
905 }
906 if answers.iter().any(|a| matches!(a, Answer::Close)) {
907 return Step::Close;
908 }
909 Step::Continue
910 }
911
912 async fn flow_event(
915 &self,
916 event_type: &'static EventType,
917 data: serde_json::Value,
918 command: &'static str,
919 flow: &mut Flow,
920 writer: &mut Writer,
921 ) -> Step {
922 let log = Log::new(Some(&self.status_tx));
923 let event = Event::new(event_type, data);
924 let answers = match self.ask(&event, flow, writer).await {
925 Ok(result) => self.answers(result),
926 Err(e) => {
927 log.warn(format!(
928 "NSQ {command} from {} decision=fail_closed_llm_error category={:?}; \
929 delivering nothing",
930 self.peer_addr,
931 crate::utils::WireFailure::classify(&e)
932 ));
933 log.debug(format!("NSQ LLM call failed: {}", e));
934 return Step::Continue;
935 }
936 };
937 if let Some(Answer::Error { code, message }) =
938 answers.iter().find(|a| matches!(a, Answer::Error { .. }))
939 {
940 log.info(format!(
941 "NSQ {command} from {} decision=model_reject code={code}",
942 self.peer_addr
943 ));
944 if let Step::Close = self.refuse(writer, static_code(code), message).await {
945 return Step::Close;
946 }
947 }
948 let offered: usize = answers
949 .iter()
950 .map(|a| match a {
951 Answer::Deliver(d) => d.len(),
952 _ => 0,
953 })
954 .sum();
955 let step = self
956 .finish_answers(Step::Continue, &answers, flow, writer)
957 .await;
958 if offered > 0 {
959 log.info(format!(
960 "NSQ {command} from {} decision=model_answer offered={offered} in_flight={} \
961 pending={}",
962 self.peer_addr,
963 flow.in_flight.len(),
964 flow.pending.len()
965 ));
966 } else if !answers.iter().any(|a| matches!(a, Answer::Error { .. })) {
967 log.info(format!(
968 "NSQ {command} from {} decision=model_silent (nothing delivered)",
969 self.peer_addr
970 ));
971 }
972 step
973 }
974
975 fn enqueue(&self, flow: &mut Flow, deliveries: Vec<Delivery>) {
977 let log = Log::new(Some(&self.status_tx));
978 if flow.sub.is_none() || flow.closing {
979 log.warn(format!(
980 "NSQ: {} message(s) for {} dropped - the connection is not subscribed{} \
981 decision=fail_closed_not_subscribed",
982 deliveries.len(),
983 self.peer_addr,
984 if flow.closing { " (closing)" } else { "" }
985 ));
986 return;
987 }
988 let mut dropped = 0usize;
989 for delivery in deliveries {
990 if flow.pending.len() >= MAX_PENDING_MESSAGES
991 || flow.pending_bytes + delivery.body.len() > MAX_PENDING_BYTES
992 {
993 dropped += 1;
994 continue;
995 }
996 flow.pending_bytes += delivery.body.len();
997 flow.pending.push_back(delivery);
998 }
999 if dropped > 0 {
1000 log.warn(format!(
1001 "NSQ: {dropped} message(s) for {} dropped over the pending bound ({} messages, \
1002 {} bytes) decision=fail_closed_pending_full",
1003 self.peer_addr, MAX_PENDING_MESSAGES, MAX_PENDING_BYTES
1004 ));
1005 }
1006 }
1007
1008 async fn flush(&self, flow: &mut Flow, writer: &mut Writer) -> bool {
1010 self.flush_counting(flow, writer).await.is_some()
1011 }
1012
1013 async fn flush_counting(&self, flow: &mut Flow, writer: &mut Writer) -> Option<usize> {
1015 let mut written = 0usize;
1016 while flow.can_deliver() > 0 {
1017 let Some(delivery) = flow.pending.pop_front() else {
1018 break;
1019 };
1020 flow.pending_bytes -= delivery.body.len();
1021 let n = self.ids.fetch_add(1, Ordering::SeqCst) + 1;
1022 let id = wire::message_id_for(n);
1023 let timestamp = crate::utils::clock::SystemTime::now()
1024 .duration_since(crate::utils::clock::UNIX_EPOCH)
1025 .map(|d| d.as_nanos() as i64)
1026 .unwrap_or(0);
1027 let frame =
1028 wire::message_frame(timestamp, delivery.attempts, &id, delivery.body.as_bytes());
1029 if !self.write(writer, &frame).await {
1030 return None;
1031 }
1032 written += frame.len();
1033 flow.in_flight.insert(
1034 String::from_utf8_lossy(&id).into_owned(),
1035 (delivery.body, delivery.attempts),
1036 );
1037 }
1038 Some(written)
1039 }
1040
1041 async fn inject(&self, command: ClientCommand, flow: &mut Flow, writer: &mut Writer) -> Step {
1043 let action = command.action.clone();
1044 let result = crate::llm::actions::executor::execute_actions(
1045 vec![action.clone()],
1046 &self.app_state,
1047 Some(self.protocol.as_ref()),
1048 Some(self.server_id),
1049 None,
1050 )
1051 .await;
1052 let (outcome, step) = match result {
1053 Err(e) => (Err(e), Step::Continue),
1054 Ok(result) => {
1055 let failures = result.failure_summary();
1056 let answers = self.answers(result);
1057 if answers.is_empty() {
1058 let error = failures.unwrap_or_else(|| "no NSQ action".to_string());
1059 (Ok(ClientSendOutcome::Rejected { error }), Step::Continue)
1060 } else {
1061 let mut bytes = 0usize;
1062 let mut step = Step::Continue;
1063 for answer in &answers {
1064 let frame = match answer {
1065 Answer::Ok => wire::response_frame(b"OK"),
1066 Answer::Error { code, message } => {
1067 let code = static_code(code);
1068 if wire::is_fatal(code) {
1069 step = Step::Close;
1070 }
1071 wire::error_frame(code, message)
1072 }
1073 Answer::Close => {
1074 step = Step::Close;
1075 continue;
1076 }
1077 Answer::Deliver(d) => {
1078 self.enqueue(flow, d.clone());
1079 match self.flush_counting(flow, writer).await {
1080 Some(n) => bytes += n,
1081 None => step = Step::Close,
1082 }
1083 continue;
1084 }
1085 };
1086 if self.write(writer, &frame).await {
1087 bytes += frame.len();
1088 } else {
1089 step = Step::Close;
1090 }
1091 }
1092 let outcome = match step {
1093 Step::Close => ClientSendOutcome::Disconnected,
1094 Step::Continue if bytes > 0 => {
1095 ClientSendOutcome::Sent { bytes_sent: bytes }
1096 }
1097 Step::Continue => ClientSendOutcome::Executed {
1098 detail: format!("queued; {} pending for RDY", flow.pending.len()),
1099 },
1100 };
1101 (Ok(outcome), step)
1102 }
1103 }
1104 };
1105 let outcome_json = match &outcome {
1106 Ok(o) => serde_json::to_value(o).unwrap_or(serde_json::Value::Null),
1107 Err(e) => json!({"error": e.to_string()}),
1108 };
1109 self.app_state
1110 .record_access_log(
1111 crate::state::AccessLogOwner::Server(self.server_id.as_u32()),
1112 "NSQ",
1113 Some(self.connection_id.as_u32()),
1114 "injected_action",
1115 action,
1116 vec![outcome_json],
1117 )
1118 .await;
1119 let _ = self.status_tx.send("__UPDATE_UI__".to_string());
1120 let _ = command.reply_tx.send(outcome);
1121 step
1122 }
1123}
1124
1125fn reply_of(answers: &[Answer]) -> Option<&Answer> {
1127 answers
1128 .iter()
1129 .find(|a| matches!(a, Answer::Ok | Answer::Error { .. }))
1130}
1131
1132fn static_code(code: &str) -> &'static str {
1134 wire::ERROR_CODES
1135 .iter()
1136 .copied()
1137 .find(|c| *c == code)
1138 .unwrap_or("E_INVALID")
1139}
1140
1141const LINGER_TIME: Duration = Duration::from_secs(2);
1144const LINGER_BYTES: usize = 64 * 1024;
1145
1146async fn linger<R: AsyncRead + Unpin>(reader: &mut R) {
1147 let deadline = tokio::time::Instant::now() + LINGER_TIME;
1148 let mut sink = [0u8; 4096];
1149 let mut drained = 0usize;
1150 while drained < LINGER_BYTES {
1151 match tokio::time::timeout_at(deadline, reader.read(&mut sink)).await {
1152 Ok(Ok(n)) if n > 0 => drained += n,
1153 _ => break,
1154 }
1155 }
1156}
1157
1158struct Framer<'a, R> {
1162 reader: &'a mut R,
1163 pending: Vec<u8>,
1164 chunk: Vec<u8>,
1165 last_read: tokio::time::Instant,
1166}
1167
1168impl<'a, R: AsyncRead + Unpin> Framer<'a, R> {
1169 fn new(reader: &'a mut R) -> Self {
1170 Self {
1171 reader,
1172 pending: Vec::new(),
1173 chunk: vec![0u8; 16 * 1024],
1174 last_read: tokio::time::Instant::now(),
1175 }
1176 }
1177
1178 async fn fill(&mut self, deadline: tokio::time::Instant) -> Result<(), ReadError> {
1179 match tokio::time::timeout_at(deadline, self.reader.read(&mut self.chunk)).await {
1180 Err(_) => Err(ReadError::TimedOut),
1181 Ok(Ok(0)) => Err(ReadError::Eof),
1182 Ok(Ok(n)) => {
1183 self.pending.extend_from_slice(&self.chunk[..n]);
1184 self.last_read = tokio::time::Instant::now();
1185 Ok(())
1186 }
1187 Ok(Err(e)) => Err(ReadError::Io(e)),
1188 }
1189 }
1190
1191 async fn magic(&mut self, deadline: tokio::time::Instant) -> Result<bool, ReadError> {
1193 while self.pending.len() < wire::MAGIC_V2.len() {
1194 self.fill(deadline).await?;
1195 }
1196 let ok = &self.pending[..4] == wire::MAGIC_V2;
1197 self.pending.drain(..4);
1198 Ok(ok)
1199 }
1200
1201 async fn next_command(
1202 &mut self,
1203 deadline: tokio::time::Instant,
1204 ) -> Result<(wire::Command, usize), ReadError> {
1205 loop {
1206 match wire::parse_command(&self.pending) {
1207 Ok(Some((command, used))) => {
1208 self.pending.drain(..used);
1209 return Ok((command, used));
1210 }
1211 Ok(None) => self.fill(deadline).await?,
1212 Err(e) => return Err(ReadError::Wire(e)),
1213 }
1214 }
1215 }
1216}