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 }]);
218 }
219 }
220 }
221 }
222 pub fn get_agent_id(&self) -> AgentID {
223 match self {
224 AgentInput::PabloV1 { .. } => AgentID::PabloV1,
225 }
226 }
227}
228#[derive(Debug, Deserialize, Serialize, Clone)]
229#[serde(tag = "agent_id")]
230pub enum AgentOutput {
231 #[serde(rename = "pablo:v1")]
232 PabloV1 {
233 messages: Vec<ChatMessage>,
234 node_states: serde_json::Value,
235 },
236}
237
238impl AgentOutput {
239 pub fn get_agent_id(&self) -> AgentID {
240 match self {
241 AgentOutput::PabloV1 { .. } => AgentID::PabloV1,
242 }
243 }
244 pub fn get_messages(&self) -> Vec<ChatMessage> {
245 match self {
246 AgentOutput::PabloV1 { messages, .. } => messages.clone(),
247 }
248 }
249 pub fn set_messages(&mut self, new_messages: Vec<ChatMessage>) {
250 match self {
251 AgentOutput::PabloV1 { messages, .. } => *messages = new_messages,
252 }
253 }
254}
255
256#[derive(Deserialize, Serialize, Debug)]
257pub struct Document {
258 pub content: String,
259 pub uri: String,
260 pub provisioner: ProvisionerType,
261}
262
263#[derive(Deserialize, Serialize, Debug)]
264pub struct SimpleDocument {
265 pub uri: String,
266 pub content: String,
267}
268
269#[derive(Deserialize, Serialize, Debug, Clone)]
270pub struct Block {
271 pub id: Uuid,
272 pub provider: String,
273 pub provisioner: ProvisionerType,
274 pub language: String,
275 pub key: String,
276 pub digest: u64,
277 pub references: Vec<Vec<Segment>>,
278 pub kind: String,
279 pub r#type: Option<String>,
280 pub name: Option<String>,
281 pub config: serde_json::Value,
282 pub document_uri: String,
283 pub code: String,
284 pub start_byte: usize,
285 pub end_byte: usize,
286 pub start_point: Point,
287 pub end_point: Point,
288 pub state: Option<serde_json::Value>,
289 pub updated_at: Option<DateTime<Utc>>,
290 pub created_at: Option<DateTime<Utc>>,
291 pub dependents: Vec<DependentBlock>,
292 pub dependencies: Vec<Dependency>,
293 pub api_group_version: Option<ApiGroupVersion>,
294
295 pub generated_summary: Option<String>,
296}
297
298impl Block {
299 pub fn get_uri(&self) -> String {
300 format!(
301 "{}#L{}-L{}",
302 self.document_uri, self.start_point.row, self.end_point.row
303 )
304 }
305}
306
307#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone)]
308pub enum ProvisionerType {
309 #[serde(rename = "Terraform")]
310 Terraform,
311 #[serde(rename = "Kubernetes")]
312 Kubernetes,
313 #[serde(rename = "Dockerfile")]
314 Dockerfile,
315 #[serde(rename = "GithubActions")]
316 GithubActions,
317 #[serde(rename = "None")]
318 None,
319}
320impl std::str::FromStr for ProvisionerType {
321 type Err = String;
322
323 fn from_str(s: &str) -> Result<Self, Self::Err> {
324 match s.to_lowercase().as_str() {
325 "terraform" => Ok(Self::Terraform),
326 "kubernetes" => Ok(Self::Kubernetes),
327 "dockerfile" => Ok(Self::Dockerfile),
328 "github-actions" => Ok(Self::GithubActions),
329 _ => Ok(Self::None),
330 }
331 }
332}
333impl std::fmt::Display for ProvisionerType {
334 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
335 match self {
336 ProvisionerType::Terraform => write!(f, "terraform"),
337 ProvisionerType::Kubernetes => write!(f, "kubernetes"),
338 ProvisionerType::Dockerfile => write!(f, "dockerfile"),
339 ProvisionerType::GithubActions => write!(f, "github-actions"),
340 ProvisionerType::None => write!(f, "none"),
341 }
342 }
343}
344
345#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
346#[serde(untagged)]
347pub enum Segment {
348 Key(String),
349 Index(usize),
350}
351
352impl std::fmt::Display for Segment {
353 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
354 match self {
355 Segment::Key(key) => write!(f, "{}", key),
356 Segment::Index(index) => write!(f, "{}", index),
357 }
358 }
359}
360impl std::fmt::Debug for Segment {
361 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
362 match self {
363 Segment::Key(key) => write!(f, "{}", key),
364 Segment::Index(index) => write!(f, "{}", index),
365 }
366 }
367}
368
369#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq)]
370pub struct Point {
371 pub row: usize,
372 pub column: usize,
373}
374
375#[derive(Deserialize, Serialize, Debug, Clone)]
376pub struct DependentBlock {
377 pub key: String,
378}
379
380#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
381pub struct Dependency {
382 pub id: Option<Uuid>,
383 pub expression: Option<String>,
384 pub from_path: Option<Vec<Segment>>,
385 pub to_path: Option<Vec<Segment>>,
386 #[serde(default = "Vec::new")]
387 pub selectors: Vec<DependencySelector>,
388 #[serde(skip_serializing)]
389 pub key: Option<String>,
390 pub digest: Option<u64>,
391 #[serde(default = "Vec::new")]
392 pub from: Vec<Segment>,
393 pub from_field: Option<Vec<Segment>>,
394 pub to_field: Option<Vec<Segment>>,
395 pub start_byte: Option<usize>,
396 pub end_byte: Option<usize>,
397 pub start_point: Option<Point>,
398 pub end_point: Option<Point>,
399 pub satisfied: bool,
400}
401
402#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
403pub struct DependencySelector {
404 pub references: Vec<Vec<Segment>>,
405 pub operator: DependencySelectorOperator,
406}
407
408#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
409pub enum DependencySelectorOperator {
410 Equals,
411 NotEquals,
412 In,
413 NotIn,
414 Exists,
415 DoesNotExist,
416}
417
418#[derive(Serialize, Deserialize, Debug, Clone)]
419pub struct ApiGroupVersion {
420 pub alias: String,
421 pub group: String,
422 pub version: String,
423 pub provisioner: ProvisionerType,
424 pub status: APIGroupVersionStatus,
425}
426
427#[derive(Serialize, Deserialize, Debug, Clone)]
428pub enum APIGroupVersionStatus {
429 #[serde(rename = "UNAVAILABLE")]
430 Unavailable,
431 #[serde(rename = "PENDING")]
432 Pending,
433 #[serde(rename = "AVAILABLE")]
434 Available,
435}
436
437#[derive(Serialize, Deserialize, Debug)]
438pub struct BuildCodeIndexInput {
439 pub documents: Vec<SimpleDocument>,
440}
441
442#[derive(Serialize, Deserialize, Debug, Clone)]
443pub struct IndexError {
444 pub uri: String,
445 pub message: String,
446 pub details: Option<serde_json::Value>,
447}
448
449#[derive(Serialize, Deserialize, Debug, Clone)]
450pub struct BuildCodeIndexOutput {
451 pub blocks: Vec<Block>,
452 pub errors: Vec<IndexError>,
453 pub warnings: Vec<IndexError>,
454}
455
456#[derive(Serialize, Deserialize, Debug, Clone)]
457pub struct CodeIndex {
458 pub last_updated: DateTime<Utc>,
459 pub index: BuildCodeIndexOutput,
460}
461
462#[derive(Debug, Deserialize, Serialize, Clone, Default)]
463pub struct AgentSessionStats {
464 pub aborted_tool_calls: u32,
465 pub analysis_period: Option<String>,
466 pub failed_tool_calls: u32,
467 pub from_date: Option<String>,
468 pub sessions_with_activity: u32,
469 pub successful_tool_calls: u32,
470 pub to_date: Option<String>,
471 pub tools_usage: Vec<ToolUsageStats>,
472 pub total_sessions: u32,
473 pub total_time_saved_seconds: Option<u32>,
474 pub total_tool_calls: u32,
475}
476
477#[derive(Debug, Deserialize, Serialize, Clone)]
478pub struct ToolUsageStats {
479 pub display_name: String,
480 pub time_saved_per_call: Option<f64>,
481 pub time_saved_seconds: Option<u32>,
482 pub tool_name: String,
483 pub usage_counts: ToolUsageCounts,
484}
485
486#[derive(Debug, Deserialize, Serialize, Clone)]
487pub struct ToolUsageCounts {
488 pub aborted: u32,
489 pub failed: u32,
490 pub successful: u32,
491 pub total: u32,
492}
493
494#[derive(Serialize, Deserialize, Debug, Clone, Copy, Default)]
495#[serde(rename_all = "UPPERCASE")]
496pub enum RuleBookVisibility {
497 #[default]
498 Public,
499 Private,
500}
501
502#[derive(Serialize, Deserialize, Debug, Clone)]
503pub struct RuleBook {
504 pub id: String,
505 pub uri: String,
506 pub description: String,
507 pub content: String,
508 pub visibility: RuleBookVisibility,
509 pub tags: Vec<String>,
510 pub created_at: Option<DateTime<Utc>>,
511 pub updated_at: Option<DateTime<Utc>>,
512}
513
514#[derive(Serialize, Deserialize, Debug)]
515pub struct ToolsCallParams {
516 pub name: String,
517 pub arguments: Value,
518}
519
520#[derive(Serialize, Deserialize, Debug)]
521pub struct ToolsCallResponse {
522 pub content: Vec<Content>,
523}
524
525#[derive(Serialize, Deserialize, Clone, Debug)]
526pub struct GetMyAccountResponse {
527 pub username: String,
528 pub id: String,
529 pub first_name: String,
530 pub last_name: String,
531}
532
533impl GetMyAccountResponse {
534 pub fn to_text(&self) -> String {
535 format!(
536 "ID: {}\nUsername: {}\nName: {} {}",
537 self.id, self.username, self.first_name, self.last_name
538 )
539 }
540}
541
542#[derive(Serialize, Deserialize, Debug, Clone)]
543pub struct ListRuleBook {
544 pub id: String,
545 pub uri: String,
546 pub description: String,
547 pub visibility: RuleBookVisibility,
548 pub tags: Vec<String>,
549 pub created_at: Option<DateTime<Utc>>,
550 pub updated_at: Option<DateTime<Utc>>,
551}
552
553#[derive(Serialize, Deserialize, Debug)]
554pub struct ListRulebooksResponse {
555 pub results: Vec<ListRuleBook>,
556}
557
558#[derive(Serialize, Deserialize, Debug)]
559pub struct CreateRuleBookInput {
560 pub uri: String,
561 pub description: String,
562 pub content: String,
563 pub tags: Vec<String>,
564 #[serde(skip_serializing_if = "Option::is_none")]
565 pub visibility: Option<RuleBookVisibility>,
566}
567
568#[derive(Serialize, Deserialize, Debug)]
569pub struct CreateRuleBookResponse {
570 pub id: String,
571}
572
573impl ListRuleBook {
574 pub fn to_text(&self) -> String {
575 format!(
576 "URI: {}\nDescription: {}\nTags: {}\n",
577 self.uri,
578 self.description,
579 self.tags.join(", ")
580 )
581 }
582}
583
584#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
585pub struct SimpleLLMMessage {
586 #[serde(rename = "role")]
587 pub role: SimpleLLMRole,
588 pub content: String,
589}
590
591#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
592#[serde(rename_all = "lowercase")]
593pub enum SimpleLLMRole {
594 User,
595 Assistant,
596}
597
598impl std::fmt::Display for SimpleLLMRole {
599 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
600 match self {
601 SimpleLLMRole::User => write!(f, "user"),
602 SimpleLLMRole::Assistant => write!(f, "assistant"),
603 }
604 }
605}
606
607#[derive(Debug, Deserialize, Serialize)]
608pub struct SearchDocsRequest {
609 pub keywords: String,
610 pub exclude_keywords: Option<String>,
611 pub limit: Option<u32>,
612}
613
614#[derive(Debug, Deserialize, Serialize)]
615pub struct SearchMemoryRequest {
616 pub keywords: Vec<String>,
617 pub start_time: Option<DateTime<Utc>>,
618 pub end_time: Option<DateTime<Utc>>,
619}
620
621#[derive(Debug, Deserialize, Serialize)]
622pub struct SlackReadMessagesRequest {
623 pub channel: String,
624 pub limit: Option<u32>,
625}
626
627#[derive(Debug, Deserialize, Serialize)]
628pub struct SlackReadRepliesRequest {
629 pub channel: String,
630 pub ts: String,
631}
632
633#[derive(Debug, Deserialize, Serialize)]
634pub struct SlackSendMessageRequest {
635 pub channel: String,
636 pub mrkdwn_text: String,
637 pub thread_ts: Option<String>,
638}
639
640#[derive(Debug, Clone, Default, Serialize)]
641pub struct AgentState {
642 pub agent_model: AgentModel,
643 pub messages: Vec<ChatMessage>,
644 pub tools: Option<Vec<Tool>>,
645
646 pub llm_input: Option<LLMInput>,
647 pub llm_output: Option<LLMOutput>,
648
649 pub metadata: Option<HashMap<String, Value>>,
650}
651
652#[derive(Debug, Clone, Default, Serialize)]
653pub struct LLMOutput {
654 pub new_message: LLMMessage,
655 pub usage: LLMTokenUsage,
656}
657
658impl From<&LLMOutput> for ChatMessage {
659 fn from(value: &LLMOutput) -> Self {
660 let message_content = match &value.new_message.content {
661 LLMMessageContent::String(s) => s.clone(),
662 LLMMessageContent::List(l) => l
663 .iter()
664 .map(|c| match c {
665 LLMMessageTypedContent::Text { text } => text.clone(),
666 LLMMessageTypedContent::ToolCall { .. } => String::new(),
667 LLMMessageTypedContent::ToolResult { content, .. } => content.clone(),
668 LLMMessageTypedContent::Image { .. } => String::new(),
669 })
670 .collect::<Vec<_>>()
671 .join("\n"),
672 };
673 let tool_calls = if let LLMMessageContent::List(items) = &value.new_message.content {
674 let calls: Vec<ToolCall> = items
675 .iter()
676 .filter_map(|item| {
677 if let LLMMessageTypedContent::ToolCall { id, name, args } = item {
678 Some(ToolCall {
679 id: id.clone(),
680 r#type: "function".to_string(),
681 function: FunctionCall {
682 name: name.clone(),
683 arguments: args.to_string(),
684 },
685 })
686 } else {
687 None
688 }
689 })
690 .collect();
691
692 if calls.is_empty() { None } else { Some(calls) }
693 } else {
694 None
695 };
696 ChatMessage {
697 role: Role::Assistant,
698 content: Some(MessageContent::String(message_content)),
699 name: None,
700 tool_calls,
701 tool_call_id: None,
702 usage: None,
703 }
704 }
705}
706
707impl AgentState {
708 pub fn new(
709 agent_model: AgentModel,
710 messages: Vec<ChatMessage>,
711 tools: Option<Vec<Tool>>,
712 ) -> Self {
713 Self {
714 agent_model,
715 messages,
716 tools,
717 llm_input: None,
718 llm_output: None,
719 metadata: None,
720 }
721 }
722
723 pub fn set_messages(&mut self, messages: Vec<ChatMessage>) {
724 self.messages = messages;
725 }
726
727 pub fn set_tools(&mut self, tools: Option<Vec<Tool>>) {
728 self.tools = tools;
729 }
730
731 pub fn set_agent_model(&mut self, agent_model: AgentModel) {
732 self.agent_model = agent_model;
733 }
734
735 pub fn set_llm_input(&mut self, llm_input: Option<LLMInput>) {
736 self.llm_input = llm_input;
737 }
738
739 pub fn set_llm_output(&mut self, new_message: LLMMessage, new_usage: Option<LLMTokenUsage>) {
740 self.llm_output = Some(LLMOutput {
741 new_message,
742 usage: new_usage.unwrap_or_default(),
743 });
744 }
745
746 pub fn append_new_message(&mut self, new_message: ChatMessage) {
747 self.messages.push(new_message);
748 }
749}