stakpak-api 0.3.88

Stakpak: Your DevOps AI Agent. Generate infrastructure code, debug Kubernetes, configure CI/CD, automate deployments, without giving an LLM the keys to production.
Documentation
use crate::local::context_managers::file_scratchpad_context_manager::{
    FileScratchpadContextManager, FileScratchpadContextManagerOptions,
};
use crate::models::AgentState;
use stakpak_shared::define_hook;
use stakpak_shared::hooks::{Hook, HookAction, HookContext, HookError, LifecycleEvent};
use stakpak_shared::models::integrations::openai::Role;
use stakpak_shared::models::llm::{LLMInput, LLMMessage, LLMMessageContent};

const SYSTEM_PROMPT: &str = include_str!("./system_prompt.txt");
const SCRATCHPAD_FILE: &str = ".stakpak/session/scratchpad.md";
const TODO_FILE: &str = ".stakpak/session/todo.md";

pub struct FileScratchpadContextHook {
    pub context_manager: FileScratchpadContextManager,
}

pub struct FileScratchpadContextHookOptions {
    pub scratchpad_path: Option<String>,
    pub todo_path: Option<String>,
    pub history_action_message_size_limit: Option<usize>,
    pub history_action_message_keep_last_n: Option<usize>,
    pub history_action_result_keep_last_n: Option<usize>,
    pub overwrite_if_different: Option<bool>,
}

impl FileScratchpadContextHook {
    pub fn new(options: FileScratchpadContextHookOptions) -> Self {
        let context_manager =
            FileScratchpadContextManager::new(FileScratchpadContextManagerOptions {
                scratchpad_file_path: options
                    .scratchpad_path
                    .unwrap_or(SCRATCHPAD_FILE.to_string())
                    .into(),
                todo_file_path: options.todo_path.unwrap_or(TODO_FILE.to_string()).into(),
                history_action_message_size_limit: options
                    .history_action_message_size_limit
                    .unwrap_or(100),
                history_action_message_keep_last_n: options
                    .history_action_message_keep_last_n
                    .unwrap_or(1),
                history_action_result_keep_last_n: options
                    .history_action_result_keep_last_n
                    .unwrap_or(50),
                overwrite_if_different: options.overwrite_if_different.unwrap_or(true),
            });

        Self { context_manager }
    }
}

define_hook!(
    FileScratchpadContextHook,
    "file_scratchpad_context",
    async |&self, ctx: &mut HookContext<AgentState>, event: &LifecycleEvent| {
        if *event != LifecycleEvent::BeforeInference {
            return Ok(HookAction::Continue);
        }

        let model = ctx.state.active_model.clone();

        let tools = ctx
            .state
            .tools
            .clone()
            .map(|t| t.into_iter().map(Into::into).collect());

        let cwd = std::env::current_dir().unwrap_or_default();
        let scratchpad_file_path =
            cwd.join(self.context_manager.get_scratchpad_path(ctx.session_id));
        let todo_file_path = cwd.join(self.context_manager.get_todo_path(ctx.session_id));
        let system_prompt = SYSTEM_PROMPT
            .replace(
                "{{SCRATCHPAD_PATH}}",
                &scratchpad_file_path.display().to_string(),
            )
            .replace("{{TODO_PATH}}", &todo_file_path.display().to_string());

        let mut messages = Vec::new();
        messages.push(LLMMessage {
            role: Role::System.to_string(),
            content: LLMMessageContent::String(system_prompt),
        });
        messages.extend(
            self.context_manager
                .reduce_context_with_session(ctx.state.messages.clone(), ctx.session_id),
        );

        ctx.state.llm_input = Some(LLMInput {
            model,
            messages,
            max_tokens: 16000,
            tools,
            provider_options: None,
            headers: None,
        });

        Ok(HookAction::Continue)
    }
);