1use crate::mcp_manager::McpManagerClient;
2use makepad_widgets::{Cx, LiveDependency, LiveId, LivePtr, WidgetRef};
3
4pub use crate::utils::asynchronous::{BoxPlatformSendFuture, BoxPlatformSendStream};
6
7#[derive(Clone, Debug, PartialEq)]
8#[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
9pub struct Tool {
10 pub name: String,
11 pub description: Option<String>,
12 #[cfg_attr(feature = "json", serde(default))]
14 pub input_schema: std::sync::Arc<serde_json::Map<String, serde_json::Value>>,
15}
16
17impl Tool {
18 pub fn new(name: String, description: Option<String>) -> Self {
19 use serde_json::Map;
20 use std::sync::Arc;
21
22 Tool {
23 name,
24 description,
25 input_schema: Arc::new(Map::new()),
26 }
27 }
28}
29
30#[cfg(not(target_arch = "wasm32"))]
32impl From<rmcp::model::Tool> for Tool {
33 fn from(rmcp_tool: rmcp::model::Tool) -> Self {
34 Tool {
35 name: rmcp_tool.name.into_owned(),
36 description: rmcp_tool.description.map(|d| d.into_owned()),
37 input_schema: rmcp_tool.input_schema,
38 }
39 }
40}
41
42#[cfg(not(target_arch = "wasm32"))]
43impl From<Tool> for rmcp::model::Tool {
44 fn from(tool: Tool) -> Self {
45 rmcp::model::Tool {
46 name: tool.name.into(),
47 description: tool.description.map(|d| d.into()),
48 input_schema: tool.input_schema,
49 output_schema: None,
50 annotations: None,
51 }
52 }
53}
54
55use chrono::{DateTime, Utc};
56use serde::{Deserialize, Serialize};
57use std::{
58 collections::{HashMap, HashSet},
59 error::Error,
60 fmt,
61 sync::{Arc, Mutex},
62};
63
64mod attachment;
65pub use attachment::*;
66
67#[derive(Debug, Clone, PartialEq)]
69pub enum Upgrade {
70 Realtime(RealtimeChannel),
72}
73
74#[derive(Debug, Clone)]
76pub struct RealtimeChannel {
77 pub event_sender: futures::channel::mpsc::UnboundedSender<RealtimeEvent>,
79 pub event_receiver:
81 Arc<Mutex<Option<futures::channel::mpsc::UnboundedReceiver<RealtimeEvent>>>>,
82 pub command_sender: futures::channel::mpsc::UnboundedSender<RealtimeCommand>,
84}
85
86impl PartialEq for RealtimeChannel {
87 fn eq(&self, _other: &Self) -> bool {
88 true
90 }
91}
92
93#[derive(Debug, Clone)]
95pub enum RealtimeEvent {
96 SessionReady,
98 AudioData(Vec<u8>),
100 AudioTranscript(String),
102 AudioTranscriptCompleted(String, String), UserTranscriptCompleted(String, String), SpeechStarted,
108 SpeechStopped,
110 ResponseCompleted,
112 FunctionCallRequest {
114 name: String,
115 call_id: String,
116 arguments: String,
117 },
118 Error(String),
120}
121
122#[derive(Debug, Clone)]
124pub enum RealtimeCommand {
125 StopSession,
127 SendAudio(Vec<u8>),
129 SendText(String),
131 Interrupt,
133 UpdateSessionConfig {
135 voice: String,
136 transcription_model: String,
137 },
138 CreateGreetingResponse,
140 SendFunctionCallResult { call_id: String, output: String },
142}
143
144#[derive(Clone, Debug)]
146pub enum Picture {
147 Grapheme(String),
149 Image(String),
150 Dependency(LiveDependency),
152}
153
154#[derive(Clone, PartialEq, Eq, Hash, Debug, Default)]
156#[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
157pub enum EntityId {
158 User,
160
161 System,
164
165 Bot(BotId),
167
168 Tool,
171
172 #[default]
177 App,
178}
179
180#[derive(Clone, Debug, PartialEq, Eq, Hash)]
182#[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
183pub enum BotCapability {
184 Realtime,
186 Attachments,
188 FunctionCalling,
190}
191
192#[derive(Clone, Debug, Default)]
194#[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
195pub struct BotCapabilities {
196 capabilities: HashSet<BotCapability>,
197}
198
199impl BotCapabilities {
200 pub fn new() -> Self {
201 Self {
202 capabilities: HashSet::new(),
203 }
204 }
205
206 pub fn with_capability(mut self, capability: BotCapability) -> Self {
207 self.capabilities.insert(capability);
208 self
209 }
210
211 pub fn add_capability(&mut self, capability: BotCapability) {
212 self.capabilities.insert(capability);
213 }
214
215 pub fn has_capability(&self, capability: &BotCapability) -> bool {
216 self.capabilities.contains(capability)
217 }
218
219 pub fn supports_realtime(&self) -> bool {
220 self.has_capability(&BotCapability::Realtime)
221 }
222
223 pub fn supports_attachments(&self) -> bool {
224 self.has_capability(&BotCapability::Attachments)
225 }
226
227 pub fn supports_function_calling(&self) -> bool {
228 self.has_capability(&BotCapability::FunctionCalling)
229 }
230
231 pub fn iter(&self) -> impl Iterator<Item = &BotCapability> {
232 self.capabilities.iter()
233 }
234}
235
236#[derive(Clone, Debug)]
237pub struct Bot {
238 pub id: BotId,
240 pub name: String,
241 pub avatar: Picture,
242 pub capabilities: BotCapabilities,
243}
244
245#[derive(Clone, PartialEq, Eq, Hash, Debug, Default)]
252#[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
253pub struct BotId(Arc<str>);
254
255impl BotId {
256 pub fn as_str(&self) -> &str {
257 &self.0
258 }
259
260 pub fn new(id: &str, provider: &str) -> Self {
262 let id = format!("{};{}@{}", id.len(), id, provider);
267 BotId(id.into())
268 }
269
270 fn deconstruct(&self) -> (usize, &str) {
271 let (id_length, raw) = self.0.split_once(';').expect("malformed bot id");
272 let id_length = id_length.parse::<usize>().expect("malformed bot id");
273 (id_length, raw)
274 }
275
276 pub fn id(&self) -> &str {
280 let (id_length, raw) = self.deconstruct();
281 &raw[..id_length]
282 }
283
284 pub fn provider(&self) -> &str {
286 let (id_length, raw) = self.deconstruct();
287 &raw[id_length + 1..]
289 }
290}
291
292impl fmt::Display for BotId {
293 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
294 write!(f, "{}", self.0)
295 }
296}
297
298#[derive(Clone, PartialEq, Debug, Default)]
300#[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
301pub enum ToolCallPermissionStatus {
302 #[default]
304 Pending,
305 Approved,
307 Denied,
309}
310
311#[derive(Clone, PartialEq, Debug, Default)]
313#[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
314pub struct ToolCall {
315 pub id: String,
317 pub name: String,
319 pub arguments: serde_json::Map<String, serde_json::Value>,
321 #[cfg_attr(feature = "json", serde(default))]
323 pub permission_status: ToolCallPermissionStatus,
324}
325
326#[derive(Clone, PartialEq, Debug)]
328#[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
329pub struct ToolResult {
330 pub tool_call_id: String,
332 pub content: String,
334 pub is_error: bool,
336}
337
338#[derive(Clone, Debug, PartialEq, Default)]
340#[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
341pub struct MessageContent {
342 pub text: String,
347
348 pub citations: Vec<String>,
350
351 #[cfg_attr(
353 feature = "json",
354 serde(deserialize_with = "crate::utils::serde::deserialize_default_on_error")
355 )]
356 pub reasoning: String,
357
358 #[cfg_attr(feature = "json", serde(default))]
360 pub attachments: Vec<Attachment>,
361
362 #[cfg_attr(feature = "json", serde(default))]
364 pub tool_calls: Vec<ToolCall>,
365
366 #[cfg_attr(feature = "json", serde(default))]
368 pub tool_results: Vec<ToolResult>,
369
370 pub data: Option<String>,
386
387 #[cfg_attr(feature = "json", serde(skip))]
389 pub upgrade: Option<Upgrade>,
390}
391
392impl MessageContent {
393 pub fn is_empty(&self) -> bool {
395 self.text.is_empty()
396 && self.citations.is_empty()
397 && self.data.is_none()
398 && self.reasoning.is_empty()
399 && self.attachments.is_empty()
400 && self.tool_calls.is_empty()
401 && self.tool_results.is_empty()
402 && self.upgrade.is_none()
403 }
404}
405
406#[derive(Clone, Debug, PartialEq)]
411#[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
412pub struct MessageMetadata {
413 #[cfg_attr(feature = "json", serde(skip))]
417 pub is_writing: bool,
418
419 #[cfg_attr(feature = "json", serde(default))]
424 pub created_at: DateTime<Utc>,
425
426 #[cfg_attr(feature = "json", serde(default))]
431 pub reasoning_updated_at: DateTime<Utc>,
432
433 #[cfg_attr(feature = "json", serde(default))]
438 pub text_updated_at: DateTime<Utc>,
439}
440
441impl Default for MessageMetadata {
442 fn default() -> Self {
443 let now = Utc::now();
445 MessageMetadata {
446 is_writing: false,
447 created_at: now,
448 reasoning_updated_at: now,
449 text_updated_at: now,
450 }
451 }
452}
453
454impl MessageMetadata {
455 pub fn new() -> Self {
457 MessageMetadata::default()
458 }
459
460 pub fn epoch() -> Self {
462 MessageMetadata {
463 is_writing: false,
464 created_at: DateTime::UNIX_EPOCH,
465 reasoning_updated_at: DateTime::UNIX_EPOCH,
466 text_updated_at: DateTime::UNIX_EPOCH,
467 }
468 }
469}
470
471impl MessageMetadata {
472 pub fn reasoning_time_taken_seconds(&self) -> f32 {
474 let delta = self.reasoning_updated_at - self.created_at;
475 delta.as_seconds_f32()
476 }
477
478 pub fn is_idle(&self) -> bool {
479 !self.is_writing
480 }
481
482 pub fn is_writing(&self) -> bool {
483 self.is_writing
484 }
485}
486
487#[derive(Clone, PartialEq, Debug, Default)]
489#[cfg_attr(feature = "json", derive(Serialize, Deserialize))]
490pub struct Message {
491 pub from: EntityId,
493
494 #[cfg_attr(feature = "json", serde(default = "MessageMetadata::epoch"))]
499 pub metadata: MessageMetadata,
500
501 pub content: MessageContent,
503}
504
505impl Message {
506 pub fn app_error(error: impl fmt::Display) -> Self {
508 Message {
509 from: EntityId::App,
510 content: MessageContent {
511 text: format!("Error: {}", error),
512 ..MessageContent::default()
513 },
514 ..Default::default()
515 }
516 }
517
518 pub fn set_content(&mut self, content: MessageContent) {
520 self.update_content(|c| {
521 *c = content;
522 });
523 }
524
525 pub fn update_content(&mut self, f: impl FnOnce(&mut MessageContent)) {
527 let bk = self.content.clone();
528 let now = Utc::now();
529
530 f(&mut self.content);
531
532 if self.content.text != bk.text {
533 self.metadata.text_updated_at = now;
534 }
535
536 if self.content.reasoning != bk.reasoning {
537 self.metadata.reasoning_updated_at = now;
538 }
539 }
540}
541
542#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
544pub enum ClientErrorKind {
545 Network,
547
548 Response,
554
555 Format,
561
562 Unknown,
564}
565
566impl ClientErrorKind {
567 pub fn to_human_readable(&self) -> &str {
568 match self {
569 ClientErrorKind::Network => "Network error",
570 ClientErrorKind::Response => "Remote error",
571 ClientErrorKind::Format => "Format error",
572 ClientErrorKind::Unknown => "Unknown error",
573 }
574 }
575}
576
577#[derive(Debug, Clone)]
579pub struct ClientError {
580 kind: ClientErrorKind,
581 message: String,
582 source: Option<Arc<dyn Error + Send + Sync + 'static>>,
583}
584
585impl fmt::Display for ClientError {
586 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
587 write!(f, "{}: {}", self.kind.to_human_readable(), self.message)
588 }
589}
590
591impl Error for ClientError {
592 fn source(&self) -> Option<&(dyn Error + 'static)> {
593 self.source.as_ref().map(|s| &**s as _)
594 }
595}
596
597impl From<ClientError> for Vec<ClientError> {
598 fn from(error: ClientError) -> Self {
599 vec![error]
600 }
601}
602
603impl<T> From<ClientError> for ClientResult<T> {
604 fn from(error: ClientError) -> Self {
605 ClientResult::new_err(vec![error])
606 }
607}
608
609impl ClientError {
610 pub fn new(kind: ClientErrorKind, message: String) -> Self {
615 ClientError {
616 kind,
617 message,
618 source: None,
619 }
620 }
621
622 pub fn new_with_source<S>(kind: ClientErrorKind, message: String, source: Option<S>) -> Self
624 where
625 S: Error + Send + Sync + 'static,
626 {
627 ClientError {
628 kind,
629 message,
630 source: source.map(|s| Arc::new(s) as _),
631 }
632 }
633
634 pub fn kind(&self) -> ClientErrorKind {
636 self.kind
637 }
638
639 pub fn message(&self) -> &str {
641 self.message.as_str()
642 }
643}
644
645#[derive(Debug)]
653#[must_use]
654pub struct ClientResult<T> {
655 errors: Vec<ClientError>,
656 value: Option<T>,
657}
658
659impl<T> ClientResult<T> {
660 pub fn new_ok(value: T) -> Self {
662 ClientResult {
663 errors: Vec::new(),
664 value: Some(value),
665 }
666 }
667
668 pub fn new_err(errors: Vec<ClientError>) -> Self {
674 let errors = if errors.is_empty() {
675 vec![ClientError::new(
676 ClientErrorKind::Unknown,
677 "An error ocurred, but no details were provided.".into(),
678 )]
679 } else {
680 errors
681 };
682
683 ClientResult {
684 errors,
685 value: None,
686 }
687 }
688
689 pub fn new_ok_and_err(value: T, errors: Vec<ClientError>) -> Self {
696 let errors = if errors.is_empty() {
697 vec![ClientError::new(
698 ClientErrorKind::Unknown,
699 "An error ocurred, but no details were provided.".into(),
700 )]
701 } else {
702 errors
703 };
704
705 ClientResult {
706 errors,
707 value: Some(value),
708 }
709 }
710
711 pub fn value(&self) -> Option<&T> {
713 self.value.as_ref()
714 }
715
716 pub fn errors(&self) -> &[ClientError] {
718 &self.errors
719 }
720
721 pub fn has_value(&self) -> bool {
723 self.value.is_some()
724 }
725
726 pub fn has_errors(&self) -> bool {
728 !self.errors.is_empty()
729 }
730
731 pub fn into_value(self) -> Option<T> {
733 self.value
734 }
735
736 pub fn into_errors(self) -> Vec<ClientError> {
738 self.errors
739 }
740
741 pub fn into_value_and_errors(self) -> (Option<T>, Vec<ClientError>) {
743 (self.value, self.errors)
744 }
745
746 pub fn into_result(self) -> Result<T, Vec<ClientError>> {
748 if self.errors.is_empty() {
749 Ok(self.value.expect("ClientResult has no value nor errors"))
750 } else {
751 Err(self.errors)
752 }
753 }
754}
755
756pub trait BotClient: Send {
765 fn send(
772 &mut self,
773 bot_id: &BotId,
774 messages: &[Message],
775 tools: &[Tool],
776 ) -> BoxPlatformSendStream<'static, ClientResult<MessageContent>>;
777
778 fn bots(&self) -> BoxPlatformSendFuture<'static, ClientResult<Vec<Bot>>>;
788
789 fn clone_box(&self) -> Box<dyn BotClient>;
791
792 fn content_widget(
802 &mut self,
803 _cx: &mut Cx,
804 _previous_widget: WidgetRef,
805 _templates: &HashMap<LiveId, LivePtr>,
806 _content: &MessageContent,
807 ) -> Option<WidgetRef> {
808 None
809 }
810}
811
812impl Clone for Box<dyn BotClient> {
813 fn clone(&self) -> Self {
814 self.clone_box()
815 }
816}
817
818struct InnerBotContext {
819 client: Box<dyn BotClient>,
820 bots: Vec<Bot>,
821 tool_manager: Option<McpManagerClient>,
822}
823
824pub struct BotContext(Arc<Mutex<InnerBotContext>>);
832
833impl Clone for BotContext {
834 fn clone(&self) -> Self {
835 BotContext(self.0.clone())
836 }
837}
838
839impl PartialEq for BotContext {
840 fn eq(&self, other: &Self) -> bool {
841 self.id() == other.id()
842 }
843}
844
845impl BotContext {
846 pub fn id(&self) -> usize {
851 Arc::as_ptr(&self.0) as usize
852 }
853
854 pub fn load(&mut self) -> BoxPlatformSendFuture<'_, ClientResult<()>> {
858 let future = async move {
859 let result = self.client().bots().await;
860 let (new_bots, errors) = result.into_value_and_errors();
861
862 if let Some(new_bots) = new_bots {
863 self.0.lock().unwrap().bots = new_bots;
864 }
865
866 if errors.is_empty() {
867 ClientResult::new_ok(())
868 } else {
869 ClientResult::new_err(errors)
870 }
871 };
872
873 Box::pin(future)
874 }
875 pub fn client(&self) -> Box<dyn BotClient> {
876 self.0.lock().unwrap().client.clone_box()
877 }
878
879 pub fn bots(&self) -> Vec<Bot> {
880 self.0.lock().unwrap().bots.clone()
881 }
882
883 pub fn get_bot(&self, id: &BotId) -> Option<Bot> {
884 self.bots().into_iter().find(|bot| bot.id == *id)
885 }
886
887 pub fn tool_manager(&self) -> Option<McpManagerClient> {
888 self.0.lock().unwrap().tool_manager.clone()
889 }
890
891 pub fn set_tool_manager(&mut self, tool_manager: McpManagerClient) {
892 self.0.lock().unwrap().tool_manager = Some(tool_manager);
893 }
894
895 pub fn replace_tool_manager(&mut self, tool_manager: McpManagerClient) {
896 self.0.lock().unwrap().tool_manager = Some(tool_manager);
897 }
898}
899
900impl<T: BotClient + 'static> From<T> for BotContext {
901 fn from(client: T) -> Self {
902 BotContext(Arc::new(Mutex::new(InnerBotContext {
903 client: Box::new(client),
904 bots: Vec::new(),
905 tool_manager: None,
906 })))
907 }
908}
909
910#[cfg(test)]
911mod tests {
912 use super::*;
913
914 #[test]
915 fn test_bot_id() {
916 let id = BotId::new("123", "example.com");
918 assert_eq!(id.as_str(), "3;123@example.com");
919 assert_eq!(id.id(), "123");
920 assert_eq!(id.provider(), "example.com");
921
922 let id = BotId::new("a;b@c", "https://ex@a@m;ple.co@m");
924 assert_eq!(id.as_str(), "5;a;b@c@https://ex@a@m;ple.co@m");
925 assert_eq!(id.id(), "a;b@c");
926 assert_eq!(id.provider(), "https://ex@a@m;ple.co@m");
927
928 let id1 = BotId::new("a@", "b");
930 let id2 = BotId::new("a", "@b");
931 assert_ne!(id1.as_str(), id2.as_str());
932 assert_ne!(id1, id2);
933 }
934}