longbridge/agent/
stream.rs1use std::sync::{
2 Arc,
3 atomic::{AtomicU64, Ordering},
4};
5
6use futures_util::{Stream, StreamExt};
7use tokio::{sync::Notify, task::AbortHandle};
8
9use crate::{
10 Error, Result,
11 agent::types::{ConversationResponse, ConversationStreamEvent},
12};
13
14pub async fn drive_conversation_stream<S, F>(
27 mut stream: S,
28 mut on_event: F,
29) -> Result<ConversationResponse>
30where
31 S: Stream<Item = Result<ConversationStreamEvent>> + Send + Unpin,
32 F: FnMut(ConversationStreamEvent) + Send,
33{
34 let mut final_response = None;
35 while let Some(event) = stream.next().await {
36 let event = event?;
37 match &event {
38 ConversationStreamEvent::WorkflowFinished(resp)
39 | ConversationStreamEvent::HumanInteractionRequired(resp) => {
40 final_response = Some(resp.clone());
41 }
42 _ => {}
43 }
44 on_event(event);
45 }
46 final_response.ok_or(Error::ConversationStreamEnded)
47}
48
49pub struct ConversationStreamIter(std::sync::mpsc::Receiver<Result<ConversationStreamEvent>>);
54
55impl Iterator for ConversationStreamIter {
56 type Item = Result<ConversationStreamEvent>;
57
58 fn next(&mut self) -> Option<Self::Item> {
59 self.0.recv().ok()
60 }
61}
62
63pub fn conversation_stream_iter(
66 stream: impl Stream<Item = Result<ConversationStreamEvent>> + Send + 'static,
67) -> ConversationStreamIter {
68 let (tx, rx) = std::sync::mpsc::channel();
69 let mut stream = Box::pin(stream);
70 crate::runtime_handle().spawn(async move {
71 while let Some(item) = stream.next().await {
72 if tx.send(item).is_err() {
73 break; }
75 }
76 });
77 ConversationStreamIter(rx)
78}
79
80pub struct ConversationStreamSubscription {
87 demand: Arc<(AtomicU64, Notify)>,
88 abort: AbortHandle,
89}
90
91impl ConversationStreamSubscription {
92 pub fn spawn<S, F1, F2, F3>(stream: S, on_next: F1, on_error: F2, on_complete: F3) -> Self
106 where
107 S: Stream<Item = Result<ConversationStreamEvent>> + Send + 'static,
108 F1: Fn(ConversationStreamEvent) + Send + Sync + 'static,
109 F2: FnOnce(Error) + Send + 'static,
110 F3: FnOnce() + Send + 'static,
111 {
112 let demand = Arc::new((AtomicU64::new(0), Notify::new()));
113 let demand2 = demand.clone();
114 let handle = crate::runtime_handle().spawn(async move {
115 let mut stream = Box::pin(stream);
116 loop {
117 while demand2.0.load(Ordering::Acquire) == 0 {
119 demand2.1.notified().await;
120 }
121 match stream.next().await {
122 Some(Ok(event)) => {
123 demand2.0.fetch_sub(1, Ordering::AcqRel);
124 on_next(event);
125 }
126 Some(Err(err)) => {
127 on_error(err);
128 break;
129 }
130 None => {
131 on_complete();
132 break;
133 }
134 }
135 }
136 });
137 Self {
138 demand,
139 abort: handle.abort_handle(),
140 }
141 }
142
143 pub fn request(&self, n: u64) {
145 self.demand.0.fetch_add(n, Ordering::AcqRel);
146 self.demand.1.notify_one();
147 }
148
149 pub fn cancel(&self) {
151 self.abort.abort();
152 }
153}
154
155#[cfg(test)]
156mod tests {
157 use futures_util::stream;
158
159 use super::*;
160 use crate::agent::types::{ChatFinishedPayload, ChatStartedPayload, Interrupt};
161
162 #[tokio::test]
169 async fn drive_conversation_stream_terminates_on_human_interaction_required() {
170 let interrupt_resp = ConversationResponse::from_stream_interrupt(
171 Some(("ct_1".to_string(), "1".to_string())),
172 Interrupt {
173 node_id: "n_ask_human".to_string(),
174 tool_call_id: "call_1".to_string(),
175 questions: vec![],
176 interactions: vec![],
177 message_id: 1,
178 chat_id: 1,
179 },
180 );
181 let events: Vec<Result<ConversationStreamEvent>> = vec![
182 Ok(ConversationStreamEvent::ChatStarted(ChatStartedPayload {
183 chat_uid: "ct_1".to_string(),
184 message_id: "1".to_string(),
185 chat_id: 0,
186 error: String::new(),
187 error_message: String::new(),
188 })),
189 Ok(ConversationStreamEvent::HumanInteractionRequired(
190 interrupt_resp,
191 )),
192 Ok(ConversationStreamEvent::ChatFinished(
193 ChatFinishedPayload::default(),
194 )),
195 ];
196
197 let mut seen = 0;
198 let resp = drive_conversation_stream(stream::iter(events), |_| seen += 1)
199 .await
200 .unwrap();
201 assert_eq!(seen, 3);
202 assert_eq!(
203 resp.status,
204 crate::agent::types::ConversationStatus::Interrupted
205 );
206 assert!(resp.interrupt.is_some());
207 }
208}