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