use anyhow::Result;
use rusqlite::Connection;
use crate::context::{load_session_start_candidates, ContextLimits, SessionStartRelevancePlan};
use crate::retrieval_router::{plan, plan_session_start_with_limits, RetrievalPlan};
use super::domain::{ContextBundle, ContextIntent, ContextItem, ContextRequest};
use super::executor::{
blocked_before_load, execute, execute_with_trace, BudgetEnforcement, ExecutorInputs,
};
pub(crate) struct SessionStartCompile {
pub bundle: ContextBundle,
pub relevance_plan: SessionStartRelevancePlan,
}
pub fn compile_session_start_bundle(
conn: &Connection,
request: &ContextRequest,
cwd: &str,
current_branch: Option<&str>,
enrichment_available: bool,
) -> Result<ContextBundle> {
let compiled = plan(request, Some(ContextIntent::SessionStart))?;
Ok(bundle_for_plan(
conn,
&compiled,
&request.project.key,
cwd,
current_branch,
enrichment_available,
))
}
#[allow(clippy::too_many_arguments)]
fn bundle_for_plan(
conn: &Connection,
compiled: &RetrievalPlan,
project: &str,
cwd: &str,
current_branch: Option<&str>,
enrichment_available: bool,
) -> ContextBundle {
match load_session_start_candidates(conn, project, cwd, current_branch) {
Ok(candidates) => execute(
compiled,
&ExecutorInputs {
candidates,
enrichment_available,
},
),
Err(error) => blocked_before_load(compiled, &error.to_string()),
}
}
pub(crate) fn compile_session_start_for_renderer(
request: &ContextRequest,
limits: &ContextLimits,
candidates: Vec<ContextItem>,
enrichment_available: bool,
) -> Result<SessionStartCompile> {
let compiled = plan_session_start_with_limits(request, limits)?;
let trace = execute_with_trace(
&compiled,
&ExecutorInputs {
candidates,
enrichment_available,
},
BudgetEnforcement::DeferToRenderer,
);
Ok(SessionStartCompile {
bundle: trace.bundle,
relevance_plan: trace.relevance_plan,
})
}
pub(crate) fn seal_session_start_bundle(
bundle: &mut ContextBundle,
selected_keys: &std::collections::HashSet<String>,
total_truncated_keys: &std::collections::HashSet<String>,
output_chars: usize,
) {
for section in [
&mut bundle.preferences,
&mut bundle.failure_lessons,
&mut bundle.current_truth,
&mut bundle.workstreams,
&mut bundle.memory_index,
&mut bundle.recent_sessions,
] {
section.retain(|item| selected_keys.contains(&item.stable_key));
}
for entry in &mut bundle.audit.entries {
if entry.selected && !selected_keys.contains(&entry.stable_key) {
entry.selected = false;
entry.reason = if total_truncated_keys.contains(&entry.stable_key) {
"total_char_limit"
} else {
"section_budget"
}
.to_string();
}
}
bundle.audit.selected_count = bundle
.audit
.entries
.iter()
.filter(|entry| entry.selected)
.count() as u32;
bundle.audit.dropped_count = bundle.audit.candidates_considered - bundle.audit.selected_count;
bundle.audit.token_estimate = (output_chars as u32).div_ceil(4);
if !total_truncated_keys.is_empty() {
bundle.audit.truncation_reason = Some("total_char_limit".to_string());
}
}