use stakpak_shared::models::integrations::openai::{
ChatMessage, FunctionDefinition, MessageContent, Role, Tool, ToolCallResult,
};
use uuid::Uuid;
pub async fn resolve_model_from_provider(
model: stakai::Model,
client: &dyn stakpak_api::AgentProvider,
) -> stakai::Model {
if model.provider != "stakpak" || model.id.contains('/') {
return model;
}
let available_models = client.list_models().await;
if available_models.is_empty() {
return model;
}
let short_name = model.id.to_lowercase();
if let Some(matched) = available_models.iter().find(|m| m.id == model.id) {
return matched.clone();
}
if let Some(matched) = available_models.iter().find(|m| {
m.id.rsplit('/')
.next()
.is_some_and(|last| last.to_lowercase() == short_name)
}) {
return matched.clone();
}
if let Some(matched) = available_models
.iter()
.find(|m| m.name.to_lowercase() == short_name)
{
return matched.clone();
}
model
}
pub fn build_resume_command(
session_id: Option<Uuid>,
checkpoint_id: Option<Uuid>,
) -> Option<String> {
if let Some(session_id) = session_id {
return Some(format!("stakpak -s {}", session_id));
}
checkpoint_id.map(|checkpoint_id| format!("stakpak -c {}", checkpoint_id))
}
pub fn extract_last_checkpoint_id(messages: &[ChatMessage]) -> Option<Uuid> {
messages
.iter()
.rev()
.filter(|m| m.role == Role::Assistant)
.find_map(|m| {
m.content
.as_ref()
.and_then(MessageContent::extract_checkpoint_id)
})
}
pub fn is_first_non_system_message(messages: &[ChatMessage]) -> bool {
messages.iter().all(|message| message.role == Role::System)
}
pub fn convert_tools_with_filter(
tools: &[rmcp::model::Tool],
allowed_tools: Option<&Vec<String>>,
) -> Vec<Tool> {
tools
.iter()
.filter_map(|tool| {
let tool_name = tool.name.as_ref();
if let Some(allowed) = allowed_tools
&& !allowed.is_empty()
&& !allowed.contains(&tool_name.to_string())
{
return None;
}
Some(Tool {
r#type: "function".to_string(),
function: FunctionDefinition {
name: tool_name.to_owned(),
description: tool.description.clone().map(|d| d.to_string()),
parameters: serde_json::Value::Object((*tool.input_schema).clone()),
},
})
})
.collect()
}
pub fn user_message(user_input: String) -> ChatMessage {
ChatMessage {
role: Role::User,
content: Some(MessageContent::String(user_input)),
name: None,
tool_calls: None,
tool_call_id: None,
usage: None,
..Default::default()
}
}
pub fn system_message(system_prompt: String) -> ChatMessage {
ChatMessage {
role: Role::System,
content: Some(MessageContent::String(system_prompt)),
name: None,
tool_calls: None,
tool_call_id: None,
usage: None,
..Default::default()
}
}
pub fn tool_result(tool_call_id: String, result: String) -> ChatMessage {
ChatMessage {
role: Role::Tool,
content: Some(MessageContent::String(result)),
name: None,
tool_calls: None,
tool_call_id: Some(tool_call_id),
usage: None,
..Default::default()
}
}
pub fn tool_call_history_string(tool_calls: &[ToolCallResult]) -> Option<String> {
if tool_calls.is_empty() {
return None;
}
let history = tool_calls
.iter()
.map(|tc| {
let command = if let Ok(json) =
serde_json::from_str::<serde_json::Value>(&tc.call.function.arguments)
{
json.get("command")
.and_then(|v| v.as_str())
.unwrap_or(&tc.call.function.arguments)
.to_string()
} else {
tc.call.function.arguments.clone()
};
let output = if tc.result.trim().is_empty() {
"No output".to_string()
} else {
tc.result.clone()
};
format!("```shell\n$ {}\n{}\n```", command, output)
})
.collect::<Vec<_>>()
.join("\n");
Some(format!("Here's my shell history:\n{}", history))
}
pub fn build_plan_mode_instructions() -> &'static str {
include_str!("prompts/plan_mode_activated.txt")
}
pub async fn refresh_billing_info(
client: &dyn stakpak_api::AgentProvider,
input_tx: &tokio::sync::mpsc::Sender<stakpak_tui::InputEvent>,
) {
if let Ok(account_data) = client.get_my_account().await {
let billing_username = account_data
.scope
.as_ref()
.map(|s| s.name.as_str())
.unwrap_or(&account_data.username);
if let Ok(billing_info) = client.get_billing_info(billing_username).await {
let _ = crate::commands::agent::run::tui::send_input_event(
input_tx,
stakpak_tui::InputEvent::BillingInfoLoaded(billing_info),
)
.await;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_build_plan_mode_instructions_contains_key_sections() {
let instructions = build_plan_mode_instructions();
assert!(instructions.contains("PLAN MODE ACTIVATED"));
assert!(instructions.contains("status: drafting"));
assert!(instructions.contains("status: pending_review"));
assert!(instructions.contains(".stakpak/session/plan.md"));
assert!(instructions.contains("Do NOT execute any changes"));
assert!(instructions.contains("title:"));
assert!(instructions.contains("version:"));
assert!(instructions.contains("created:"));
assert!(instructions.contains("updated:"));
}
#[test]
fn test_build_plan_mode_instructions_ends_with_user_request_marker() {
let instructions = build_plan_mode_instructions();
assert!(instructions.trim().ends_with("User request:"));
}
}