lha 1.0.2

Long-Horizon Agent command-line package that installs the lha binary.
Documentation
use std::sync::Arc;

use crate::product::agent::codex::Session;
use crate::product::agent::codex::TurnContext;
use crate::product::agent::compact::active_goal_plan_reminder_items;
use crate::product::agent::compact::backfilled_skill_items;
use crate::product::agent::compact::backfilled_update_plan_items;
use crate::product::agent::compact::last_backfillable_update_plan_from_history;
use crate::product::agent::compact::last_completed_plan_from_history;
use crate::product::agent::compact::proposed_plan_backfill_items;
use crate::product::agent::compact::recent_backfillable_skills_from_history;
use crate::product::agent::error::Result as CodexResult;
use crate::product::agent::protocol::CompactedItem;
use crate::product::agent::protocol::EventMsg;
use crate::product::agent::protocol::RolloutItem;
use crate::product::agent::protocol::TurnStartedEvent;
use crate::product::protocol::items::ContextCompactionItem;
use crate::product::protocol::items::TurnItem;
use lha_llm::TurnRequest;

pub(crate) async fn run_inline_remote_auto_compact_task(
    sess: Arc<Session>,
    turn_context: Arc<TurnContext>,
) {
    run_remote_compact_task_inner(&sess, &turn_context).await;
}

pub(crate) async fn run_remote_compact_task(sess: Arc<Session>, turn_context: Arc<TurnContext>) {
    let start_event = EventMsg::TurnStarted(TurnStartedEvent {
        model_context_window: turn_context.runtime.get_model_context_window(),
        identity_kind: turn_context.identity.kind,
    });
    sess.send_event(&turn_context, start_event).await;

    run_remote_compact_task_inner(&sess, &turn_context).await;
}

async fn run_remote_compact_task_inner(sess: &Arc<Session>, turn_context: &Arc<TurnContext>) {
    if let Err(err) = run_remote_compact_task_inner_impl(sess, turn_context).await {
        let event = EventMsg::Error(
            err.to_error_event(Some("Error running remote compact task".to_string())),
        );
        sess.send_event(turn_context, event).await;
    }
}

async fn run_remote_compact_task_inner_impl(
    sess: &Arc<Session>,
    turn_context: &Arc<TurnContext>,
) -> CodexResult<()> {
    let compaction_item = TurnItem::ContextCompaction(ContextCompactionItem::new());
    sess.emit_turn_item_started(turn_context, &compaction_item)
        .await;
    let history = sess.clone_history().await;
    let active_goal_plan_path = sess.active_proposed_plan_goal_path().await;
    let backfilled_plan_text = active_goal_plan_path
        .is_none()
        .then(|| last_completed_plan_from_history(history.raw_items()))
        .flatten();
    let backfilled_update_plan = last_backfillable_update_plan_from_history(history.raw_items());
    let backfilled_skills = recent_backfillable_skills_from_history(history.raw_items());

    let prompt = TurnRequest {
        conversation: history.for_compaction_prompt().into_iter().collect(),
        base_instructions: sess.get_base_instructions().await,
        personality: turn_context.personality,
        ..Default::default()
    };

    let mut new_history = turn_context.runtime.compact_turn_request(&prompt).await?;

    match active_goal_plan_path.as_deref() {
        Some(path) => {
            new_history.extend(active_goal_plan_reminder_items(path));
        }
        None => {
            if let Some(plan_text) = backfilled_plan_text.as_deref() {
                new_history.extend(proposed_plan_backfill_items(plan_text));
            }
        }
    }
    if let Some(update_plan) = backfilled_update_plan.as_ref() {
        new_history.extend(backfilled_update_plan_items(update_plan));
    }
    if !backfilled_skills.is_empty() {
        new_history.extend(backfilled_skill_items(&backfilled_skills));
    }
    sess.replace_history(new_history.clone()).await;
    sess.recompute_token_usage(turn_context).await;

    let compacted_item = CompactedItem {
        message: String::new(),
        replacement_history: Some(new_history.into_iter().collect()),
        replacement_history_omits_initial_context: false,
    };
    sess.persist_rollout_items(&[RolloutItem::Compacted(compacted_item)])
        .await;

    sess.emit_turn_item_completed(turn_context, compaction_item)
        .await;
    Ok(())
}