use std::path::Path;
use log::debug;
use crate::cm_internal::context_bootstrap::first_turn_inject::build_first_turn_user_context_markdown;
use crate::cm_config::{AgentConfig, SharedAgentConfig};
use crate::cm_types::Message;
pub fn project_scan_needs_spawn_blocking(cfg: &AgentConfig) -> bool {
(cfg.context_bootstrap_inject.living_docs_inject_enabled
&& cfg.context_bootstrap_inject.living_docs_inject_max_chars > 0)
|| (cfg.context_bootstrap_inject.project_profile_inject_enabled
&& cfg
.context_bootstrap_inject
.project_profile_inject_max_chars
> 0)
|| (cfg
.context_bootstrap_inject
.project_dependency_brief_inject_enabled
&& cfg
.context_bootstrap_inject
.project_dependency_brief_inject_max_chars
> 0)
}
pub async fn first_turn_project_context_user_message(
workspace_root: &Path,
cfg: &AgentConfig,
memory_snippet: Option<String>,
) -> Option<String> {
if project_scan_needs_spawn_blocking(cfg) {
let cfg_owned = cfg.clone();
let root = workspace_root.to_path_buf();
let mem = memory_snippet.clone();
match tokio::task::spawn_blocking(move || {
build_first_turn_user_context_markdown(&root, &cfg_owned, mem)
})
.await
{
Ok(v) => v,
Err(e) => {
debug!(
target: "crabmate",
"first_turn_project_context spawn_blocking failed: {}",
e
);
None
}
}
} else {
build_first_turn_user_context_markdown(workspace_root, cfg, memory_snippet)
}
}
pub async fn first_turn_project_context_user_message_for_web(
workspace_root: &Path,
cfg: &AgentConfig,
memory_snippet: Option<String>,
) -> Option<String> {
first_turn_project_context_user_message(workspace_root, cfg, memory_snippet).await
}
pub fn first_turn_project_context_user_message_sync(
workspace_root: &Path,
cfg: &AgentConfig,
memory_snippet: Option<String>,
) -> Option<String> {
build_first_turn_user_context_markdown(workspace_root, cfg, memory_snippet)
}
pub fn augmented_system_for_new_conversation(
cfg: &AgentConfig,
agent_role: Option<&str>,
tool_recorder: &std::sync::Arc<crate::cm_internal::tool_stats::ToolOutcomeRecorder>,
) -> Result<String, String> {
let role = super::prompt_compose::resolve_agent_role_for_prompt_compose(cfg, agent_role, None)?;
super::prompt_compose::compose_system_for_turn_arc(
cfg,
role.as_deref(),
tool_recorder,
None,
super::prompt_compose::RoleSystemResolution::Strict,
)
}
pub fn new_session_prompt_baseline_messages(
cfg: &AgentConfig,
system_for_turn: &str,
workspace_root: &Path,
memory_snippet: Option<String>,
) -> Vec<Message> {
let project_context =
first_turn_project_context_user_message_sync(workspace_root, cfg, memory_snippet);
compose_new_conversation_messages(system_for_turn, project_context, None)
}
pub fn compose_new_conversation_messages(
system_for_turn: &str,
project_context: Option<String>,
last_user: Option<Message>,
) -> Vec<Message> {
match (project_context, last_user) {
(Some(ctx), Some(u)) => vec![
Message::system_only(system_for_turn.to_string()),
Message::user_first_turn_workspace_context(ctx),
u,
],
(None, Some(u)) => vec![Message::system_only(system_for_turn.to_string()), u],
(Some(ctx), None) => vec![
Message::system_only(system_for_turn.to_string()),
Message::user_first_turn_workspace_context(ctx),
],
(None, None) => vec![Message::system_only(system_for_turn.to_string())],
}
}
pub async fn prepend_first_turn_project_context_between_system_and_user(
cfg_holder: &SharedAgentConfig,
work_dir: &Path,
messages: &mut Vec<Message>,
) {
if messages.len() < 2 {
return;
}
if !messages[0].role.trim().eq_ignore_ascii_case("system")
|| !messages[1].role.trim().eq_ignore_ascii_case("user")
{
return;
}
let cfg = cfg_holder.read().await.clone();
if let Some(body) = first_turn_project_context_user_message(work_dir, &cfg, None).await {
messages.insert(1, Message::user_first_turn_workspace_context(body));
}
}
#[cfg(test)]
mod tests {
use super::first_turn_project_context_user_message_for_web;
#[tokio::test]
async fn web_first_turn_injects_context_like_cli() {
let cfg = crate::cm_config::load_config(None).expect("embed default");
let out =
first_turn_project_context_user_message_for_web(std::path::Path::new("."), &cfg, None)
.await;
let _ = out;
}
}