use std::path::Path;
use std::process::Command;
use std::time::Duration as StdDuration;
use anyhow::Result;
use holon::{
config::{AppConfig, ControlAuthMode},
provider::{ConversationMessage, ProviderTurnRequest},
runtime::RuntimeHandle,
tool::ToolResult,
types::OperatorTransportBinding,
};
use tokio::time::sleep;
use super::{eventually, eventually_async, TestConfigBuilder};
pub fn parse_tool_result_value(result: &ToolResult) -> Result<serde_json::Value> {
Ok(serde_json::from_str(&result.content_text()?)?)
}
pub fn parse_tool_result_payload(result: &ToolResult) -> Result<serde_json::Value> {
Ok(parse_tool_result_value(result)?["result"].clone())
}
pub fn operator_transport_binding(binding_id: &str, route_id: &str) -> OperatorTransportBinding {
use chrono::Utc;
use holon::types::OperatorTransportBindingStatus;
use holon::types::OperatorTransportCapabilities;
use holon::types::OperatorTransportDeliveryAuth;
use holon::types::OperatorTransportDeliveryAuthKind;
OperatorTransportBinding {
binding_id: binding_id.to_string(),
transport: "agentinbox".into(),
operator_actor_id: "operator:jolestar".into(),
target_agent_id: "default".into(),
default_route_id: route_id.to_string(),
delivery_callback_url: "http://127.0.0.1:1/delivery".into(),
delivery_auth: OperatorTransportDeliveryAuth {
kind: OperatorTransportDeliveryAuthKind::Bearer,
key_id: None,
bearer_token: Some("delivery-secret".into()),
},
capabilities: OperatorTransportCapabilities {
text: true,
markdown: None,
attachments: None,
},
provider: Some("agentinbox".into()),
provider_identity_ref: Some("agentinbox:operator:jolestar".into()),
status: OperatorTransportBindingStatus::Active,
created_at: Utc::now(),
last_seen_at: None,
metadata: None,
}
}
pub fn preserves_prior_tool_context(request: &ProviderTurnRequest) -> bool {
let has_exact_tool_results = request
.conversation
.iter()
.any(|message| matches!(message, ConversationMessage::UserToolResults(_)));
let has_turn_local_recap = request.conversation.iter().any(|message| {
matches!(
message,
ConversationMessage::UserText(text)
if text.contains("Turn-local recap for older completed rounds")
)
});
has_exact_tool_results || has_turn_local_recap
}
pub fn test_config() -> AppConfig {
TestConfigBuilder::new()
.with_control_auth_mode(ControlAuthMode::Auto)
.build()
}
pub fn aggressive_compaction_config() -> AppConfig {
let mut config = TestConfigBuilder::new()
.with_control_auth_mode(ControlAuthMode::Auto)
.with_compaction(2, 1, 1, 1, 4096)
.build();
let override_config = holon::model_catalog::ModelRuntimeOverride {
prompt_budget_estimated_tokens: Some(4096),
compaction_trigger_estimated_tokens: Some(1),
compaction_keep_recent_estimated_tokens: Some(1),
..holon::model_catalog::ModelRuntimeOverride::default()
};
config.stored_config.models.catalog.insert(
"anthropic/claude-sonnet-4-6".into(),
override_config.clone(),
);
config.validated_model_overrides.insert(
holon::config::ModelRef::parse("anthropic/claude-sonnet-4-6").unwrap(),
override_config,
);
config
}
pub async fn wait_until(predicate: impl Fn() -> Result<bool>) -> Result<()> {
eventually(predicate).await
}
pub async fn wait_until_async<F, Fut>(predicate: F) -> Result<()>
where
F: Fn() -> Fut,
Fut: std::future::Future<Output = Result<bool>>,
{
eventually_async(predicate).await
}
pub async fn wait_until_async_for<F, Fut>(timeout: StdDuration, predicate: F) -> Result<()>
where
F: Fn() -> Fut,
Fut: std::future::Future<Output = Result<bool>>,
{
let deadline = tokio::time::Instant::now() + timeout;
loop {
if predicate().await? {
return Ok(());
}
if tokio::time::Instant::now() >= deadline {
break;
}
sleep(StdDuration::from_millis(100)).await;
}
Err(anyhow::anyhow!("timed out waiting for condition"))
}
pub fn delegated_prompt_text(request: &ProviderTurnRequest) -> String {
request
.conversation
.iter()
.find_map(|message| match message {
ConversationMessage::UserText(text) => Some(text.clone()),
ConversationMessage::UserBlocks(blocks) => Some(
blocks
.iter()
.map(|block| block.text.clone())
.collect::<Vec<_>>()
.join("\n\n"),
),
_ => None,
})
.unwrap_or_default()
}
pub fn git(path: &Path, args: &[&str]) -> Result<String> {
let output = Command::new("git").args(args).current_dir(path).output()?;
if !output.status.success() {
return Err(anyhow::anyhow!(
"git {:?} failed: {}",
args,
String::from_utf8_lossy(&output.stderr)
));
}
Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
}
pub fn init_git_repo(path: &Path) -> Result<String> {
git(path, &["init"])?;
git(path, &["config", "user.email", "holon@example.com"])?;
git(path, &["config", "user.name", "Holon Test"])?;
std::fs::write(path.join("README.md"), "holon\n")?;
git(path, &["add", "README.md"])?;
git(path, &["commit", "-m", "init"])?;
git(path, &["rev-parse", "--abbrev-ref", "HEAD"])
}
pub async fn wait_for_worktree_presence(
runtime: &RuntimeHandle,
expected_present: bool,
) -> Result<()> {
for _ in 0..30 {
let present = runtime.agent_state().await?.worktree_session.is_some();
if present == expected_present {
return Ok(());
}
sleep(StdDuration::from_millis(100)).await;
}
Err(anyhow::anyhow!("timed out waiting for worktree presence"))
}
#[derive(Debug, Clone)]
pub struct CompactionRequestSnapshot {
pub call_index: usize,
pub user_text_snapshot: String,
pub assistant_text_snapshot: String,
pub has_turn_local_recap: bool,
pub has_full_checkpoint_request: bool,
pub has_delta_checkpoint_request: bool,
pub has_progress_checkpoint_request: bool,
}
pub fn compact_request_snapshot(
call_index: usize,
request: &ProviderTurnRequest,
) -> CompactionRequestSnapshot {
use holon::provider::{ConversationMessage, ModelBlock};
let user_texts: Vec<String> = request
.conversation
.iter()
.filter_map(|message| match message {
ConversationMessage::UserText(text) => Some(text.clone()),
ConversationMessage::UserBlocks(blocks) => Some(
blocks
.iter()
.map(|block| block.text.clone())
.collect::<Vec<_>>()
.join("\n"),
),
_ => None,
})
.filter(|text| !text.trim().is_empty())
.collect();
let assistant_texts: Vec<String> = request
.conversation
.iter()
.filter_map(|message| match message {
ConversationMessage::AssistantBlocks(blocks) => Some(
blocks
.iter()
.filter_map(|block| match block {
ModelBlock::Text { text } => Some(text.clone()),
ModelBlock::ToolUse { .. } => None,
ModelBlock::Thinking { .. } | ModelBlock::RedactedThinking { .. } => None,
})
.collect::<Vec<_>>()
.join("\n"),
),
_ => None,
})
.filter(|text| !text.trim().is_empty())
.collect();
let user_text_snapshot = user_texts.join("\n");
let assistant_text_snapshot = assistant_texts.join("\n");
let user_text_snapshot_lower = user_text_snapshot.to_ascii_lowercase();
let has_turn_local_recap = user_text_snapshot_lower.contains("turn-local recap");
let has_full_checkpoint_request = user_text_snapshot_lower.contains("full checkpoint request")
|| user_text_snapshot_lower.contains("full progress checkpoint request");
let has_delta_checkpoint_request = user_text_snapshot_lower
.contains("delta checkpoint request")
|| user_text_snapshot_lower.contains("delta progress checkpoint request");
let has_progress_checkpoint_request = user_text_snapshot_lower
.contains("progress-only checkpoint request")
|| (user_text_snapshot_lower.contains("progress checkpoint request")
&& !has_full_checkpoint_request
&& !has_delta_checkpoint_request);
CompactionRequestSnapshot {
call_index,
user_text_snapshot,
assistant_text_snapshot,
has_turn_local_recap,
has_full_checkpoint_request,
has_delta_checkpoint_request,
has_progress_checkpoint_request,
}
}