trusty_memory/transport/methods/
chat.rs1use serde::Deserialize;
19use serde_json::{json, Value};
20use uuid::Uuid;
21
22use crate::service::load_user_config;
23use crate::transport::api_error::{open_handle, ApiError};
24use crate::{ActivitySource, AppState, DaemonEvent};
25
26use super::{CallerParams, NoParams};
27
28pub async fn chat_providers(state: &AppState, _params: NoParams) -> Result<Value, ApiError> {
34 let cfg = load_user_config().unwrap_or_default();
35 let ollama_available = if cfg.local_model.enabled {
36 trusty_common::auto_detect_local_provider(&cfg.local_model.base_url)
37 .await
38 .is_some()
39 } else {
40 false
41 };
42 let openrouter_available = !cfg.openrouter_api_key.is_empty();
43 let active = state.chat_provider().await.map(|p| p.name().to_string());
44 Ok(json!({
45 "providers": [
46 {
47 "name": "ollama",
48 "model": cfg.local_model.model,
49 "available": ollama_available,
50 },
51 {
52 "name": "openrouter",
53 "model": cfg.openrouter_model,
54 "available": openrouter_available,
55 }
56 ],
57 "active": active,
58 }))
59}
60
61#[derive(Debug, Deserialize)]
63pub struct ListMessagesParams {
64 pub palace: String,
66 #[serde(default)]
68 pub unread_only: Option<bool>,
69}
70
71pub async fn messages_list(
76 state: &AppState,
77 params: ListMessagesParams,
78) -> Result<Value, ApiError> {
79 let handle = open_handle(state, ¶ms.palace)?;
80 let unread_only = params.unread_only.unwrap_or(false);
81 let payload: Vec<Value> = crate::messaging::list_messages(&handle, unread_only)
82 .into_iter()
83 .map(|m| {
84 let formatted = m.to_injection_block();
85 json!({
86 "id": m.id.to_string(),
87 "from_palace": m.from_palace,
88 "to_palace": m.to_palace,
89 "purpose": m.purpose,
90 "sent_at": m.sent_at.to_rfc3339(),
91 "read": m.read,
92 "content": m.content,
93 "formatted": formatted,
94 })
95 })
96 .collect();
97 Ok(Value::Array(payload))
98}
99
100#[derive(Debug, Deserialize)]
102pub struct SendMessageParams {
103 pub to_palace: String,
105 pub purpose: String,
107 pub content: String,
109 #[serde(default)]
113 pub from_palace: Option<String>,
114 #[serde(flatten)]
116 pub caller: CallerParams,
117}
118
119pub async fn message_send(state: &AppState, params: SendMessageParams) -> Result<Value, ApiError> {
121 let from_palace = params
122 .from_palace
123 .or_else(|| state.default_palace.clone())
124 .unwrap_or_else(|| "<unknown>".to_string());
125 let drawer_id = crate::messaging::send_message_to_palace(
126 &state.registry,
127 &state.data_root,
128 &from_palace,
129 ¶ms.to_palace,
130 ¶ms.purpose,
131 params.content,
132 params.caller.creator(),
133 )
134 .await
135 .map_err(|e| ApiError::internal(format!("send_message: {e:#}")))?;
136
137 let drawer_count = open_handle(state, ¶ms.to_palace)
140 .map(|h| h.drawers.read().len())
141 .unwrap_or(0);
142 state.emit(DaemonEvent::DrawerAdded {
143 palace_id: params.to_palace.clone(),
144 palace_name: params.to_palace.clone(),
145 drawer_count,
146 timestamp: chrono::Utc::now(),
147 content_preview: format!("[msg from {from_palace}] {}", params.purpose),
148 source: ActivitySource::Http,
149 });
150
151 Ok(json!({
152 "drawer_id": drawer_id.to_string(),
153 "from_palace": from_palace,
154 "to_palace": params.to_palace,
155 "purpose": params.purpose,
156 "status": "sent",
157 }))
158}
159
160#[derive(Debug, Deserialize)]
162pub struct MarkReadParams {
163 pub palace: String,
165 pub drawer_id: String,
167}
168
169pub async fn message_mark_read(
176 state: &AppState,
177 params: MarkReadParams,
178) -> Result<Value, ApiError> {
179 let uuid = Uuid::parse_str(¶ms.drawer_id)
180 .map_err(|_| ApiError::bad_request("drawer_id must be a UUID"))?;
181 let handle = open_handle(state, ¶ms.palace)?;
182 let flipped = crate::messaging::mark_message_read(&handle, uuid)
183 .await
184 .map_err(|e| ApiError::internal(format!("mark_read: {e:#}")))?;
185 Ok(json!({ "flipped": flipped }))
186}