heartbit_core/channel/
mod.rs1#![allow(missing_docs)]
7pub mod bridge;
8pub mod session;
9pub mod types;
10
11use std::future::Future;
12use std::pin::Pin;
13use std::sync::Arc;
14
15use crate::agent::events::OnEvent;
16use crate::error::Error;
17use crate::llm::{OnApproval, OnText};
18use crate::memory::Memory;
19use crate::tool::builtins::OnQuestion;
20
21pub struct MediaAttachment {
23 pub media_type: String,
24 pub data: Vec<u8>,
25 pub caption: Option<String>,
26}
27
28pub trait ChannelBridge: Send + Sync {
33 fn make_on_text(self: Arc<Self>) -> Arc<OnText>;
34 fn make_on_event(self: Arc<Self>) -> Arc<OnEvent>;
35 fn make_on_approval(self: Arc<Self>) -> Arc<OnApproval>;
36 fn make_on_question(self: Arc<Self>) -> Arc<OnQuestion>;
37}
38
39pub struct RunTaskInput {
41 pub task_text: String,
42 pub bridge: Arc<dyn ChannelBridge>,
43 pub memory: Option<Arc<dyn Memory>>,
46 pub user_namespace: Option<String>,
49 pub attachments: Vec<MediaAttachment>,
51}
52
53pub type RunTask = dyn Fn(RunTaskInput) -> Pin<Box<dyn Future<Output = Result<String, Error>> + Send>>
58 + Send
59 + Sync;
60
61pub type ConsolidateSession =
63 dyn Fn(i64) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send>> + Send + Sync;
64
65pub fn chunk_message(text: &str, max_len: usize) -> Vec<&str> {
70 if text.len() <= max_len {
71 return vec![text];
72 }
73 let mut chunks = Vec::new();
74 let mut remaining = text;
75 while !remaining.is_empty() {
76 if remaining.len() <= max_len {
77 chunks.push(remaining);
78 break;
79 }
80 let split_at = remaining[..max_len].rfind('\n').unwrap_or_else(|| {
82 let mut pos = max_len;
84 while pos > 0 && !remaining.is_char_boundary(pos) {
85 pos -= 1;
86 }
87 pos
88 });
89 let split_at = if split_at == 0 {
90 max_len.min(remaining.len())
91 } else {
92 split_at
93 };
94 chunks.push(&remaining[..split_at]);
95 remaining = &remaining[split_at..];
96 if remaining.starts_with('\n') {
98 remaining = &remaining[1..];
99 }
100 }
101 chunks
102}
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107
108 #[test]
109 fn channel_bridge_is_object_safe() {
110 fn _assert(_: &Arc<dyn ChannelBridge>) {}
112 }
113
114 #[test]
115 fn run_task_input_accepts_dyn_bridge() {
116 fn _assert(bridge: Arc<dyn ChannelBridge>) {
118 let _input = RunTaskInput {
119 task_text: String::new(),
120 bridge,
121 memory: None,
122 user_namespace: None,
123 attachments: Vec::new(),
124 };
125 }
126 }
127}