Skip to main content

stakpak_api/
models.rs

1use std::collections::HashMap;
2
3use chrono::{DateTime, Utc};
4use rmcp::model::Content;
5use serde::{Deserialize, Serialize};
6use serde_json::Value;
7use stakpak_shared::models::{
8    integrations::openai::{
9        AgentModel, ChatMessage, FunctionCall, MessageContent, Role, Tool, ToolCall,
10    },
11    llm::{LLMInput, LLMMessage, LLMMessageContent, LLMMessageTypedContent, LLMTokenUsage},
12};
13use uuid::Uuid;
14
15#[derive(Debug, Clone, Deserialize, Serialize)]
16pub enum ApiStreamError {
17    AgentInputInvalid(String),
18    AgentStateInvalid,
19    AgentNotSupported,
20    AgentExecutionLimitExceeded,
21    AgentInvalidResponseStream,
22    InvalidGeneratedCode,
23    CopilotError,
24    SaveError,
25    Unknown(String),
26}
27
28impl From<&str> for ApiStreamError {
29    fn from(error_str: &str) -> Self {
30        match error_str {
31            s if s.contains("Agent not supported") => ApiStreamError::AgentNotSupported,
32            s if s.contains("Agent state is not valid") => ApiStreamError::AgentStateInvalid,
33            s if s.contains("Agent thinking limit exceeded") => {
34                ApiStreamError::AgentExecutionLimitExceeded
35            }
36            s if s.contains("Invalid response stream") => {
37                ApiStreamError::AgentInvalidResponseStream
38            }
39            s if s.contains("Invalid generated code") => ApiStreamError::InvalidGeneratedCode,
40            s if s.contains(
41                "Our copilot is handling too many requests at this time, please try again later.",
42            ) =>
43            {
44                ApiStreamError::CopilotError
45            }
46            s if s
47                .contains("An error occurred while saving your data. Please try again later.") =>
48            {
49                ApiStreamError::SaveError
50            }
51            s if s.contains("Agent input is not valid: ") => {
52                ApiStreamError::AgentInputInvalid(s.replace("Agent input is not valid: ", ""))
53            }
54            _ => ApiStreamError::Unknown(error_str.to_string()),
55        }
56    }
57}
58
59impl From<String> for ApiStreamError {
60    fn from(error_str: String) -> Self {
61        ApiStreamError::from(error_str.as_str())
62    }
63}
64
65#[derive(Debug, Deserialize, Serialize, Clone)]
66pub struct AgentSession {
67    pub id: Uuid,
68    pub title: String,
69    pub agent_id: AgentID,
70    pub visibility: AgentSessionVisibility,
71    pub checkpoints: Vec<AgentCheckpointListItem>,
72    pub created_at: DateTime<Utc>,
73    pub updated_at: DateTime<Utc>,
74}
75
76#[derive(Debug, Deserialize, Serialize, Default, Clone, PartialEq)]
77pub enum AgentID {
78    #[default]
79    #[serde(rename = "pablo:v1")]
80    PabloV1,
81}
82
83impl std::str::FromStr for AgentID {
84    type Err = String;
85
86    fn from_str(s: &str) -> Result<Self, Self::Err> {
87        match s {
88            "pablo:v1" => Ok(AgentID::PabloV1),
89            _ => Err(format!("Invalid agent ID: {}", s)),
90        }
91    }
92}
93
94#[derive(Debug, Deserialize, Serialize, Clone)]
95pub enum AgentSessionVisibility {
96    #[serde(rename = "PRIVATE")]
97    Private,
98    #[serde(rename = "PUBLIC")]
99    Public,
100}
101
102impl std::fmt::Display for AgentSessionVisibility {
103    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
104        match self {
105            AgentSessionVisibility::Private => write!(f, "PRIVATE"),
106            AgentSessionVisibility::Public => write!(f, "PUBLIC"),
107        }
108    }
109}
110
111#[derive(Debug, Deserialize, Serialize, Clone)]
112pub struct AgentCheckpointListItem {
113    pub id: Uuid,
114    pub status: AgentStatus,
115    pub execution_depth: usize,
116    pub parent: Option<AgentParentCheckpoint>,
117    pub created_at: DateTime<Utc>,
118    pub updated_at: DateTime<Utc>,
119}
120
121#[derive(Debug, Deserialize, Serialize, Clone)]
122pub struct AgentSessionListItem {
123    pub id: Uuid,
124    pub agent_id: AgentID,
125    pub visibility: AgentSessionVisibility,
126    pub created_at: DateTime<Utc>,
127    pub updated_at: DateTime<Utc>,
128}
129
130impl From<AgentSession> for AgentSessionListItem {
131    fn from(item: AgentSession) -> Self {
132        Self {
133            id: item.id,
134            agent_id: item.agent_id,
135            visibility: item.visibility,
136            created_at: item.created_at,
137            updated_at: item.updated_at,
138        }
139    }
140}
141
142#[derive(Debug, Deserialize, Serialize, Clone)]
143pub struct AgentParentCheckpoint {
144    pub id: Uuid,
145}
146#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
147pub enum AgentStatus {
148    #[serde(rename = "RUNNING")]
149    Running,
150    #[serde(rename = "COMPLETE")]
151    Complete,
152    #[serde(rename = "BLOCKED")]
153    Blocked,
154    #[serde(rename = "FAILED")]
155    Failed,
156}
157impl std::fmt::Display for AgentStatus {
158    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
159        match self {
160            AgentStatus::Running => write!(f, "RUNNING"),
161            AgentStatus::Complete => write!(f, "COMPLETE"),
162            AgentStatus::Blocked => write!(f, "BLOCKED"),
163            AgentStatus::Failed => write!(f, "FAILED"),
164        }
165    }
166}
167
168#[derive(Debug, Deserialize, Serialize, Clone)]
169pub struct RunAgentInput {
170    pub checkpoint_id: Uuid,
171    pub input: AgentInput,
172}
173
174impl PartialEq for RunAgentInput {
175    fn eq(&self, other: &Self) -> bool {
176        self.input == other.input
177    }
178}
179
180#[derive(Debug, Deserialize, Serialize, Clone)]
181pub struct RunAgentOutput {
182    pub checkpoint: AgentCheckpointListItem,
183    pub session: AgentSessionListItem,
184    pub output: AgentOutput,
185}
186
187#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
188#[serde(tag = "agent_id")]
189pub enum AgentInput {
190    #[serde(rename = "pablo:v1")]
191    PabloV1 {
192        messages: Option<Vec<ChatMessage>>,
193        node_states: Option<serde_json::Value>,
194    },
195}
196
197impl AgentInput {
198    pub fn new(agent_id: &AgentID) -> Self {
199        match agent_id {
200            AgentID::PabloV1 => AgentInput::PabloV1 {
201                messages: None,
202                node_states: None,
203            },
204        }
205    }
206    pub fn set_user_prompt(&mut self, prompt: Option<String>) {
207        match self {
208            AgentInput::PabloV1 { messages, .. } => {
209                if let Some(prompt) = prompt {
210                    *messages = Some(vec![ChatMessage {
211                        role: Role::User,
212                        content: Some(MessageContent::String(prompt)),
213                        name: None,
214                        tool_calls: None,
215                        tool_call_id: None,
216                        usage: None,
217                        ..Default::default()
218                    }]);
219                }
220            }
221        }
222    }
223    pub fn get_agent_id(&self) -> AgentID {
224        match self {
225            AgentInput::PabloV1 { .. } => AgentID::PabloV1,
226        }
227    }
228}
229#[derive(Debug, Deserialize, Serialize, Clone)]
230#[serde(tag = "agent_id")]
231pub enum AgentOutput {
232    #[serde(rename = "pablo:v1")]
233    PabloV1 {
234        messages: Vec<ChatMessage>,
235        node_states: serde_json::Value,
236    },
237}
238
239impl AgentOutput {
240    pub fn get_agent_id(&self) -> AgentID {
241        match self {
242            AgentOutput::PabloV1 { .. } => AgentID::PabloV1,
243        }
244    }
245    pub fn get_messages(&self) -> Vec<ChatMessage> {
246        match self {
247            AgentOutput::PabloV1 { messages, .. } => messages.clone(),
248        }
249    }
250    pub fn set_messages(&mut self, new_messages: Vec<ChatMessage>) {
251        match self {
252            AgentOutput::PabloV1 { messages, .. } => *messages = new_messages,
253        }
254    }
255}
256
257#[derive(Deserialize, Serialize, Debug)]
258pub struct Document {
259    pub content: String,
260    pub uri: String,
261    pub provisioner: ProvisionerType,
262}
263
264#[derive(Deserialize, Serialize, Debug)]
265pub struct SimpleDocument {
266    pub uri: String,
267    pub content: String,
268}
269
270#[derive(Deserialize, Serialize, Debug, Clone)]
271pub struct Block {
272    pub id: Uuid,
273    pub provider: String,
274    pub provisioner: ProvisionerType,
275    pub language: String,
276    pub key: String,
277    pub digest: u64,
278    pub references: Vec<Vec<Segment>>,
279    pub kind: String,
280    pub r#type: Option<String>,
281    pub name: Option<String>,
282    pub config: serde_json::Value,
283    pub document_uri: String,
284    pub code: String,
285    pub start_byte: usize,
286    pub end_byte: usize,
287    pub start_point: Point,
288    pub end_point: Point,
289    pub state: Option<serde_json::Value>,
290    pub updated_at: Option<DateTime<Utc>>,
291    pub created_at: Option<DateTime<Utc>>,
292    pub dependents: Vec<DependentBlock>,
293    pub dependencies: Vec<Dependency>,
294    pub api_group_version: Option<ApiGroupVersion>,
295
296    pub generated_summary: Option<String>,
297}
298
299impl Block {
300    pub fn get_uri(&self) -> String {
301        format!(
302            "{}#L{}-L{}",
303            self.document_uri, self.start_point.row, self.end_point.row
304        )
305    }
306}
307
308#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone)]
309pub enum ProvisionerType {
310    #[serde(rename = "Terraform")]
311    Terraform,
312    #[serde(rename = "Kubernetes")]
313    Kubernetes,
314    #[serde(rename = "Dockerfile")]
315    Dockerfile,
316    #[serde(rename = "GithubActions")]
317    GithubActions,
318    #[serde(rename = "None")]
319    None,
320}
321impl std::str::FromStr for ProvisionerType {
322    type Err = String;
323
324    fn from_str(s: &str) -> Result<Self, Self::Err> {
325        match s.to_lowercase().as_str() {
326            "terraform" => Ok(Self::Terraform),
327            "kubernetes" => Ok(Self::Kubernetes),
328            "dockerfile" => Ok(Self::Dockerfile),
329            "github-actions" => Ok(Self::GithubActions),
330            _ => Ok(Self::None),
331        }
332    }
333}
334impl std::fmt::Display for ProvisionerType {
335    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
336        match self {
337            ProvisionerType::Terraform => write!(f, "terraform"),
338            ProvisionerType::Kubernetes => write!(f, "kubernetes"),
339            ProvisionerType::Dockerfile => write!(f, "dockerfile"),
340            ProvisionerType::GithubActions => write!(f, "github-actions"),
341            ProvisionerType::None => write!(f, "none"),
342        }
343    }
344}
345
346#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
347#[serde(untagged)]
348pub enum Segment {
349    Key(String),
350    Index(usize),
351}
352
353impl std::fmt::Display for Segment {
354    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
355        match self {
356            Segment::Key(key) => write!(f, "{}", key),
357            Segment::Index(index) => write!(f, "{}", index),
358        }
359    }
360}
361impl std::fmt::Debug for Segment {
362    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
363        match self {
364            Segment::Key(key) => write!(f, "{}", key),
365            Segment::Index(index) => write!(f, "{}", index),
366        }
367    }
368}
369
370#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq)]
371pub struct Point {
372    pub row: usize,
373    pub column: usize,
374}
375
376#[derive(Deserialize, Serialize, Debug, Clone)]
377pub struct DependentBlock {
378    pub key: String,
379}
380
381#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
382pub struct Dependency {
383    pub id: Option<Uuid>,
384    pub expression: Option<String>,
385    pub from_path: Option<Vec<Segment>>,
386    pub to_path: Option<Vec<Segment>>,
387    #[serde(default = "Vec::new")]
388    pub selectors: Vec<DependencySelector>,
389    #[serde(skip_serializing)]
390    pub key: Option<String>,
391    pub digest: Option<u64>,
392    #[serde(default = "Vec::new")]
393    pub from: Vec<Segment>,
394    pub from_field: Option<Vec<Segment>>,
395    pub to_field: Option<Vec<Segment>>,
396    pub start_byte: Option<usize>,
397    pub end_byte: Option<usize>,
398    pub start_point: Option<Point>,
399    pub end_point: Option<Point>,
400    pub satisfied: bool,
401}
402
403#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
404pub struct DependencySelector {
405    pub references: Vec<Vec<Segment>>,
406    pub operator: DependencySelectorOperator,
407}
408
409#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
410pub enum DependencySelectorOperator {
411    Equals,
412    NotEquals,
413    In,
414    NotIn,
415    Exists,
416    DoesNotExist,
417}
418
419#[derive(Serialize, Deserialize, Debug, Clone)]
420pub struct ApiGroupVersion {
421    pub alias: String,
422    pub group: String,
423    pub version: String,
424    pub provisioner: ProvisionerType,
425    pub status: APIGroupVersionStatus,
426}
427
428#[derive(Serialize, Deserialize, Debug, Clone)]
429pub enum APIGroupVersionStatus {
430    #[serde(rename = "UNAVAILABLE")]
431    Unavailable,
432    #[serde(rename = "PENDING")]
433    Pending,
434    #[serde(rename = "AVAILABLE")]
435    Available,
436}
437
438#[derive(Serialize, Deserialize, Debug)]
439pub struct BuildCodeIndexInput {
440    pub documents: Vec<SimpleDocument>,
441}
442
443#[derive(Serialize, Deserialize, Debug, Clone)]
444pub struct IndexError {
445    pub uri: String,
446    pub message: String,
447    pub details: Option<serde_json::Value>,
448}
449
450#[derive(Serialize, Deserialize, Debug, Clone)]
451pub struct BuildCodeIndexOutput {
452    pub blocks: Vec<Block>,
453    pub errors: Vec<IndexError>,
454    pub warnings: Vec<IndexError>,
455}
456
457#[derive(Serialize, Deserialize, Debug, Clone)]
458pub struct CodeIndex {
459    pub last_updated: DateTime<Utc>,
460    pub index: BuildCodeIndexOutput,
461}
462
463#[derive(Debug, Deserialize, Serialize, Clone, Default)]
464pub struct AgentSessionStats {
465    pub aborted_tool_calls: u32,
466    pub analysis_period: Option<String>,
467    pub failed_tool_calls: u32,
468    pub from_date: Option<String>,
469    pub sessions_with_activity: u32,
470    pub successful_tool_calls: u32,
471    pub to_date: Option<String>,
472    pub tools_usage: Vec<ToolUsageStats>,
473    pub total_sessions: u32,
474    pub total_time_saved_seconds: Option<u32>,
475    pub total_tool_calls: u32,
476}
477
478#[derive(Debug, Deserialize, Serialize, Clone)]
479pub struct ToolUsageStats {
480    pub display_name: String,
481    pub time_saved_per_call: Option<f64>,
482    pub time_saved_seconds: Option<u32>,
483    pub tool_name: String,
484    pub usage_counts: ToolUsageCounts,
485}
486
487#[derive(Debug, Deserialize, Serialize, Clone)]
488pub struct ToolUsageCounts {
489    pub aborted: u32,
490    pub failed: u32,
491    pub successful: u32,
492    pub total: u32,
493}
494
495#[derive(Serialize, Deserialize, Debug, Clone, Copy, Default)]
496#[serde(rename_all = "UPPERCASE")]
497pub enum RuleBookVisibility {
498    #[default]
499    Public,
500    Private,
501}
502
503#[derive(Serialize, Deserialize, Debug, Clone)]
504pub struct RuleBook {
505    pub id: String,
506    pub uri: String,
507    pub description: String,
508    pub content: String,
509    pub visibility: RuleBookVisibility,
510    pub tags: Vec<String>,
511    pub created_at: Option<DateTime<Utc>>,
512    pub updated_at: Option<DateTime<Utc>>,
513}
514
515#[derive(Serialize, Deserialize, Debug)]
516pub struct ToolsCallParams {
517    pub name: String,
518    pub arguments: Value,
519}
520
521#[derive(Serialize, Deserialize, Debug)]
522pub struct ToolsCallResponse {
523    pub content: Vec<Content>,
524}
525
526#[derive(Serialize, Deserialize, Debug, Clone)]
527pub struct APIKeyScope {
528    pub r#type: String,
529    pub name: String,
530}
531
532impl std::fmt::Display for APIKeyScope {
533    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
534        write!(f, "{} ({})", self.name, self.r#type)
535    }
536}
537
538#[derive(Serialize, Deserialize, Clone, Debug)]
539pub struct GetMyAccountResponse {
540    pub username: String,
541    pub id: String,
542    pub first_name: String,
543    pub last_name: String,
544    pub email: String,
545    pub scope: Option<APIKeyScope>,
546}
547
548impl GetMyAccountResponse {
549    pub fn to_text(&self) -> String {
550        format!(
551            "ID: {}\nUsername: {}\nName: {} {}\nEmail: {}",
552            self.id, self.username, self.first_name, self.last_name, self.email
553        )
554    }
555}
556
557#[derive(Serialize, Deserialize, Debug, Clone)]
558pub struct ListRuleBook {
559    pub id: String,
560    pub uri: String,
561    pub description: String,
562    pub visibility: RuleBookVisibility,
563    pub tags: Vec<String>,
564    pub created_at: Option<DateTime<Utc>>,
565    pub updated_at: Option<DateTime<Utc>>,
566}
567
568#[derive(Serialize, Deserialize, Debug)]
569pub struct ListRulebooksResponse {
570    pub results: Vec<ListRuleBook>,
571}
572
573#[derive(Serialize, Deserialize, Debug)]
574pub struct CreateRuleBookInput {
575    pub uri: String,
576    pub description: String,
577    pub content: String,
578    pub tags: Vec<String>,
579    #[serde(skip_serializing_if = "Option::is_none")]
580    pub visibility: Option<RuleBookVisibility>,
581}
582
583#[derive(Serialize, Deserialize, Debug)]
584pub struct CreateRuleBookResponse {
585    pub id: String,
586}
587
588impl ListRuleBook {
589    pub fn to_text(&self) -> String {
590        format!(
591            "URI: {}\nDescription: {}\nTags: {}\n",
592            self.uri,
593            self.description,
594            self.tags.join(", ")
595        )
596    }
597}
598
599#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
600pub struct SimpleLLMMessage {
601    #[serde(rename = "role")]
602    pub role: SimpleLLMRole,
603    pub content: String,
604}
605
606#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
607#[serde(rename_all = "lowercase")]
608pub enum SimpleLLMRole {
609    User,
610    Assistant,
611}
612
613impl std::fmt::Display for SimpleLLMRole {
614    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
615        match self {
616            SimpleLLMRole::User => write!(f, "user"),
617            SimpleLLMRole::Assistant => write!(f, "assistant"),
618        }
619    }
620}
621
622#[derive(Debug, Deserialize, Serialize)]
623pub struct SearchDocsRequest {
624    pub keywords: String,
625    pub exclude_keywords: Option<String>,
626    pub limit: Option<u32>,
627}
628
629#[derive(Debug, Deserialize, Serialize)]
630pub struct SearchMemoryRequest {
631    pub keywords: Vec<String>,
632    pub start_time: Option<DateTime<Utc>>,
633    pub end_time: Option<DateTime<Utc>>,
634}
635
636#[derive(Debug, Deserialize, Serialize)]
637pub struct SlackReadMessagesRequest {
638    pub channel: String,
639    pub limit: Option<u32>,
640}
641
642#[derive(Debug, Deserialize, Serialize)]
643pub struct SlackReadRepliesRequest {
644    pub channel: String,
645    pub ts: String,
646}
647
648#[derive(Debug, Deserialize, Serialize)]
649pub struct SlackSendMessageRequest {
650    pub channel: String,
651    pub mrkdwn_text: String,
652    pub thread_ts: Option<String>,
653}
654
655#[derive(Debug, Clone, Default, Serialize)]
656pub struct AgentState {
657    pub agent_model: AgentModel,
658    pub messages: Vec<ChatMessage>,
659    pub tools: Option<Vec<Tool>>,
660
661    pub llm_input: Option<LLMInput>,
662    pub llm_output: Option<LLMOutput>,
663
664    pub metadata: Option<HashMap<String, Value>>,
665}
666
667#[derive(Debug, Clone, Default, Serialize)]
668pub struct LLMOutput {
669    pub new_message: LLMMessage,
670    pub usage: LLMTokenUsage,
671}
672
673impl From<&LLMOutput> for ChatMessage {
674    fn from(value: &LLMOutput) -> Self {
675        let message_content = match &value.new_message.content {
676            LLMMessageContent::String(s) => s.clone(),
677            LLMMessageContent::List(l) => l
678                .iter()
679                .map(|c| match c {
680                    LLMMessageTypedContent::Text { text } => text.clone(),
681                    LLMMessageTypedContent::ToolCall { .. } => String::new(),
682                    LLMMessageTypedContent::ToolResult { content, .. } => content.clone(),
683                    LLMMessageTypedContent::Image { .. } => String::new(),
684                })
685                .collect::<Vec<_>>()
686                .join("\n"),
687        };
688        let tool_calls = if let LLMMessageContent::List(items) = &value.new_message.content {
689            let calls: Vec<ToolCall> = items
690                .iter()
691                .filter_map(|item| {
692                    if let LLMMessageTypedContent::ToolCall { id, name, args } = item {
693                        Some(ToolCall {
694                            id: id.clone(),
695                            r#type: "function".to_string(),
696                            function: FunctionCall {
697                                name: name.clone(),
698                                arguments: args.to_string(),
699                            },
700                        })
701                    } else {
702                        None
703                    }
704                })
705                .collect();
706
707            if calls.is_empty() { None } else { Some(calls) }
708        } else {
709            None
710        };
711        ChatMessage {
712            role: Role::Assistant,
713            content: Some(MessageContent::String(message_content)),
714            name: None,
715            tool_calls,
716            tool_call_id: None,
717            usage: Some(value.usage.clone()),
718            ..Default::default()
719        }
720    }
721}
722
723impl AgentState {
724    pub fn new(
725        agent_model: AgentModel,
726        messages: Vec<ChatMessage>,
727        tools: Option<Vec<Tool>>,
728    ) -> Self {
729        Self {
730            agent_model,
731            messages,
732            tools,
733            llm_input: None,
734            llm_output: None,
735            metadata: None,
736        }
737    }
738
739    pub fn set_messages(&mut self, messages: Vec<ChatMessage>) {
740        self.messages = messages;
741    }
742
743    pub fn set_tools(&mut self, tools: Option<Vec<Tool>>) {
744        self.tools = tools;
745    }
746
747    pub fn set_agent_model(&mut self, agent_model: AgentModel) {
748        self.agent_model = agent_model;
749    }
750
751    pub fn set_llm_input(&mut self, llm_input: Option<LLMInput>) {
752        self.llm_input = llm_input;
753    }
754
755    pub fn set_llm_output(&mut self, new_message: LLMMessage, new_usage: Option<LLMTokenUsage>) {
756        self.llm_output = Some(LLMOutput {
757            new_message,
758            usage: new_usage.unwrap_or_default(),
759        });
760    }
761
762    pub fn append_new_message(&mut self, new_message: ChatMessage) {
763        self.messages.push(new_message);
764    }
765}