chasm/providers/cloud/
common.rs1use crate::models::{ChatMessage, ChatRequest, ChatSession};
6use anyhow::Result;
7use chrono::{DateTime, Utc};
8use serde::{Deserialize, Serialize};
9
10#[derive(Debug, Clone, Default)]
12pub struct FetchOptions {
13 pub limit: Option<usize>,
15 pub after: Option<DateTime<Utc>>,
17 pub before: Option<DateTime<Utc>>,
19 pub include_archived: bool,
21 pub session_token: Option<String>,
23}
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct CloudConversation {
28 pub id: String,
30 pub title: Option<String>,
32 pub created_at: DateTime<Utc>,
34 pub updated_at: Option<DateTime<Utc>>,
36 pub model: Option<String>,
38 pub messages: Vec<CloudMessage>,
40 pub metadata: Option<serde_json::Value>,
42}
43
44#[derive(Debug, Clone, Serialize, Deserialize)]
46pub struct CloudMessage {
47 pub id: Option<String>,
49 pub role: String,
51 pub content: String,
53 pub timestamp: Option<DateTime<Utc>>,
55 pub model: Option<String>,
57}
58
59impl CloudConversation {
60 pub fn to_chat_session(&self, provider_name: &str) -> ChatSession {
62 use uuid::Uuid;
63
64 let mut requests = Vec::new();
66 let mut i = 0;
67 while i < self.messages.len() {
68 let msg = &self.messages[i];
69
70 if msg.role == "user" {
71 let mut request = ChatRequest {
73 timestamp: msg.timestamp.map(|t| t.timestamp_millis()),
74 message: Some(ChatMessage {
75 text: Some(msg.content.clone()),
76 parts: None,
77 }),
78 response: None,
79 variable_data: None,
80 request_id: Some(msg.id.clone().unwrap_or_else(|| Uuid::new_v4().to_string())),
81 response_id: None,
82 model_id: self.model.clone(),
83 agent: None,
84 result: None,
85 followups: None,
86 is_canceled: Some(false),
87 content_references: None,
88 code_citations: None,
89 response_markdown_info: None,
90 source_session: Some(format!("{}:{}", provider_name, self.id)),
91 model_state: None,
92 time_spent_waiting: None,
93 };
94
95 if i + 1 < self.messages.len() && self.messages[i + 1].role == "assistant" {
97 let assistant_msg = &self.messages[i + 1];
98 request.response = Some(serde_json::json!({
99 "text": assistant_msg.content,
100 "result": {
101 "metadata": {
102 "provider": provider_name,
103 "model": assistant_msg.model.clone().or_else(|| self.model.clone())
104 }
105 }
106 }));
107 request.response_id = Some(
108 assistant_msg
109 .id
110 .clone()
111 .unwrap_or_else(|| Uuid::new_v4().to_string()),
112 );
113 i += 1;
114 }
115
116 requests.push(request);
117 } else if msg.role == "system" {
118 }
121 i += 1;
122 }
123
124 ChatSession {
125 version: 3,
126 session_id: Some(format!("{}:{}", provider_name, self.id)),
127 creation_date: self.created_at.timestamp_millis(),
128 last_message_date: self
129 .updated_at
130 .unwrap_or(self.created_at)
131 .timestamp_millis(),
132 is_imported: true,
133 initial_location: "panel".to_string(),
134 custom_title: self.title.clone(),
135 requester_username: None,
136 requester_avatar_icon_uri: None,
137 responder_username: Some(provider_name.to_string()),
138 responder_avatar_icon_uri: None,
139 requests,
140 }
141 }
142}
143
144pub trait CloudProvider: Send + Sync {
146 fn name(&self) -> &'static str;
148
149 fn api_base_url(&self) -> &str;
151
152 fn is_authenticated(&self) -> bool;
154
155 fn set_credentials(&mut self, api_key: Option<String>, session_token: Option<String>);
157
158 fn list_conversations(&self, options: &FetchOptions) -> Result<Vec<CloudConversation>>;
160
161 fn fetch_conversation(&self, id: &str) -> Result<CloudConversation>;
163
164 fn fetch_all_conversations(&self, options: &FetchOptions) -> Result<Vec<ChatSession>> {
166 let conversations = self.list_conversations(options)?;
167 let mut sessions = Vec::new();
168
169 for conv in conversations {
170 if !conv.messages.is_empty() {
172 sessions.push(conv.to_chat_session(self.name()));
173 } else {
174 match self.fetch_conversation(&conv.id) {
176 Ok(full_conv) => sessions.push(full_conv.to_chat_session(self.name())),
177 Err(e) => {
178 eprintln!("Warning: Failed to fetch conversation {}: {}", conv.id, e);
179 }
180 }
181 }
182 }
183
184 Ok(sessions)
185 }
186
187 fn api_key_env_var(&self) -> &'static str;
189
190 fn load_api_key_from_env(&self) -> Option<String> {
192 std::env::var(self.api_key_env_var()).ok()
193 }
194}
195
196#[derive(Debug, Clone)]
198pub struct HttpClientConfig {
199 pub timeout_secs: u64,
200 pub user_agent: String,
201 pub accept_invalid_certs: bool,
202}
203
204impl Default for HttpClientConfig {
205 fn default() -> Self {
206 Self {
207 timeout_secs: 30,
208 user_agent: format!("csm/{}", env!("CARGO_PKG_VERSION")),
209 accept_invalid_certs: false,
210 }
211 }
212}
213
214pub fn build_http_client(config: &HttpClientConfig) -> Result<reqwest::blocking::Client> {
216 use std::time::Duration;
217
218 reqwest::blocking::Client::builder()
219 .timeout(Duration::from_secs(config.timeout_secs))
220 .user_agent(&config.user_agent)
221 .danger_accept_invalid_certs(config.accept_invalid_certs)
222 .build()
223 .map_err(|e| anyhow::anyhow!("Failed to build HTTP client: {}", e))
224}