monothink 0.3.1

A totally non-agentic coding assistant
/*
* This file is part of monothink.
*
* monothink is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Foundation,
* either version 3 of the License, or (at your option) any later version.
*
* monothink is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with Wavething.
* If not, see <https://www.gnu.org/licenses/>.

*/

use rig::agent::{AgentHook, CompletionCallAction, HookContext, RequestPatch};
use tokio::sync::mpsc::Sender;

use crate::{
    llm::{
        compactors::{
            Compactor, ObservationMaskingCompactor, ReasoningCapCompactor, SharedObservations,
            SlidingWindowCompactor,
        },
        estimators::Estimator,
    },
    session::SessionMessage,
};

pub struct CompactionHook<E: Estimator + Clone + Send + Sync> {
    session_tx: Sender<SessionMessage>,
    compactors: (
        ObservationMaskingCompactor,
        ReasoningCapCompactor<E>,
        SlidingWindowCompactor<E>,
    ),
    context_window: u64,
    estimator: E,
}

impl<E: Estimator + Clone + Send + Sync> AgentHook for CompactionHook<E> {
    async fn on_completion_call(
        &self,
        _ctx: &HookContext,
        event: rig::agent::CompletionCallEvent<'_>,
    ) -> CompletionCallAction {
        let prompt = event.prompt;
        let mut effective_history = event.history.to_owned();
        let inital_pressure = self.estimator.context_pressure(prompt, &effective_history);

        effective_history = self.compactors.0.compact(prompt, &effective_history);
        effective_history = self.compactors.1.compact(prompt, &effective_history);
        effective_history = self.compactors.2.compact(prompt, &effective_history);

        let updated_pressure = self.estimator.context_pressure(prompt, &effective_history);

        self.session_tx
            .send(SessionMessage::ContextPressure {
                initial: inital_pressure,
                updated: updated_pressure,
                window: self.context_window,
            })
            .await
            .unwrap();

        CompletionCallAction::patch(RequestPatch::new().history(effective_history))
    }

    // @todo check if we need this to detect the end of the stream or if we can do that from stream events
    // fn on_stream_response_finish(
    //     &self,
    //     _ctx: &HookContext,
    //     _event: rig::agent::StreamResponseFinish<'_>,
    // ) -> impl Future<Output = rig::agent::ObservationAction> + rig::wasm_compat::WasmCompatSend {
    //     async { rig::agent::ObservationAction::Continue }
    // }

    // @todo to handle change of model in session
    // fn on_model_select(
    //     &self,
    //     _ctx: &HookContext,
    //     _event: rig::agent::ModelSelection<'_>,
    // ) -> rig::agent::ModelSelectionAction {
    //     rig::agent::ModelSelectionAction::Continue
    // }
}

impl<E: Estimator + Clone + Send + Sync> CompactionHook<E> {
    pub fn new(
        session_tx: Sender<SessionMessage>,
        context_window: u64,
        estimator: E,
        max_tokens: u64,
        max_tool_observations: u64,
        min_reasoning_observations: u64,
        observations: SharedObservations,
    ) -> Self {
        let context_limit = (context_window - max_tokens) * 9 / 10; // 10% safety margin
        Self {
            session_tx,
            // @todo make compactors a parameter
            compactors: (
                ObservationMaskingCompactor::new(max_tool_observations, observations),
                ReasoningCapCompactor::new(
                    estimator.clone(),
                    context_limit,
                    min_reasoning_observations,
                ),
                SlidingWindowCompactor::new(estimator.clone(), context_limit),
            ),
            context_window,
            estimator,
        }
    }
}