1use async_trait::async_trait;
2use futures_util::Stream;
3use models::*;
4use reqwest::header::HeaderMap;
5use rmcp::model::Content;
6use stakpak_shared::models::integrations::openai::{
7 ChatCompletionResponse, ChatCompletionStreamResponse, ChatMessage, Tool,
8};
9use uuid::Uuid;
10
11pub mod client;
12pub mod commands;
13pub mod local;
14pub mod models;
15pub mod stakpak;
16pub mod storage;
17
18pub use client::{AgentClient, AgentClientConfig, DEFAULT_STAKPAK_ENDPOINT, StakpakConfig};
20
21pub use stakai::{Model, ModelCost, ModelLimit};
23
24pub use storage::{
26 BoxedSessionStorage, Checkpoint, CheckpointState, CheckpointSummary, CreateCheckpointRequest,
27 CreateSessionRequest as StorageCreateSessionRequest, CreateSessionResult, ListCheckpointsQuery,
28 ListCheckpointsResult, ListSessionsQuery, ListSessionsResult, LocalStorage, Session,
29 SessionStats, SessionStatus, SessionStorage, SessionSummary, SessionVisibility, StakpakStorage,
30 StorageError, UpdateSessionRequest as StorageUpdateSessionRequest,
31};
32
33pub fn find_model(model_str: &str, use_stakpak: bool) -> Option<Model> {
41 const PROVIDERS: &[&str] = &["anthropic", "openai", "google"];
42
43 let (provider_hint, model_id) = parse_model_string(model_str);
44
45 let model = provider_hint
47 .and_then(|p| find_in_provider(p, model_id))
48 .or_else(|| {
49 PROVIDERS
50 .iter()
51 .find_map(|&p| find_in_provider(p, model_id))
52 })?;
53
54 Some(if use_stakpak {
55 transform_for_stakpak(model)
56 } else {
57 model
58 })
59}
60
61#[allow(clippy::string_slice)] fn parse_model_string(s: &str) -> (Option<&str>, &str) {
64 match s.find('/') {
65 Some(idx) => {
66 let provider = &s[..idx];
67 let model_id = &s[idx + 1..];
68 let normalized = match provider {
69 "gemini" => "google",
70 p => p,
71 };
72 (Some(normalized), model_id)
73 }
74 None => (None, s),
75 }
76}
77
78fn find_in_provider(provider_id: &str, model_id: &str) -> Option<Model> {
80 let models = stakai::load_models_for_provider(provider_id).ok()?;
81
82 if let Some(model) = models.iter().find(|m| m.id == model_id) {
84 return Some(model.clone());
85 }
86
87 let mut best_match: Option<&Model> = None;
90 let mut best_len = 0;
91
92 for model in &models {
93 if model_id.starts_with(&model.id) && model.id.len() > best_len {
94 best_match = Some(model);
95 best_len = model.id.len();
96 }
97 }
98
99 best_match.cloned()
100}
101
102pub fn transform_for_stakpak(model: Model) -> Model {
107 Model {
108 id: format!("{}/{}", model.provider, model.id),
109 provider: "stakpak".into(),
110 name: model.name,
111 reasoning: model.reasoning,
112 cost: model.cost,
113 limit: model.limit,
114 release_date: model.release_date,
115 }
116}
117
118#[async_trait]
124pub trait AgentProvider: SessionStorage + Send + Sync {
125 async fn get_my_account(&self) -> Result<GetMyAccountResponse, String>;
127 async fn get_billing_info(
128 &self,
129 account_username: &str,
130 ) -> Result<stakpak_shared::models::billing::BillingResponse, String>;
131
132 async fn list_rulebooks(&self) -> Result<Vec<ListRuleBook>, String>;
134 async fn get_rulebook_by_uri(&self, uri: &str) -> Result<RuleBook, String>;
135 async fn create_rulebook(
136 &self,
137 uri: &str,
138 description: &str,
139 content: &str,
140 tags: Vec<String>,
141 visibility: Option<RuleBookVisibility>,
142 ) -> Result<CreateRuleBookResponse, String>;
143 async fn delete_rulebook(&self, uri: &str) -> Result<(), String>;
144
145 async fn chat_completion(
147 &self,
148 model: Model,
149 messages: Vec<ChatMessage>,
150 tools: Option<Vec<Tool>>,
151 session_id: Option<Uuid>,
152 metadata: Option<serde_json::Value>,
153 ) -> Result<ChatCompletionResponse, String>;
154 async fn chat_completion_stream(
155 &self,
156 model: Model,
157 messages: Vec<ChatMessage>,
158 tools: Option<Vec<Tool>>,
159 headers: Option<HeaderMap>,
160 session_id: Option<Uuid>,
161 metadata: Option<serde_json::Value>,
162 ) -> Result<
163 (
164 std::pin::Pin<
165 Box<dyn Stream<Item = Result<ChatCompletionStreamResponse, ApiStreamError>> + Send>,
166 >,
167 Option<String>,
168 ),
169 String,
170 >;
171 async fn cancel_stream(&self, request_id: String) -> Result<(), String>;
172
173 async fn search_docs(&self, input: &SearchDocsRequest) -> Result<Vec<Content>, String>;
175
176 async fn memorize_session(&self, checkpoint_id: Uuid) -> Result<(), String>;
178 async fn search_memory(&self, input: &SearchMemoryRequest) -> Result<Vec<Content>, String>;
179
180 async fn slack_read_messages(
182 &self,
183 input: &SlackReadMessagesRequest,
184 ) -> Result<Vec<Content>, String>;
185 async fn slack_read_replies(
186 &self,
187 input: &SlackReadRepliesRequest,
188 ) -> Result<Vec<Content>, String>;
189 async fn slack_send_message(
190 &self,
191 input: &SlackSendMessageRequest,
192 ) -> Result<Vec<Content>, String>;
193
194 async fn list_models(&self) -> Vec<Model>;
196}