use crate::agent::subagent::SubagentSpawnContext; use crate::session::SessionCommand; use agent_client_protocol as acp; use kigi_acp_lib::AcpAgentGatewaySender as GatewaySender; use kigi_tools::implementations::kigi::task::types::{SubagentRequest, SubagentResult}; use std::collections::HashMap; use std::path::PathBuf; use std::sync::Arc; use tokio::sync::{mpsc, oneshot}; pub(crate) type GatewayOut = ::OutMessage; pub(crate) fn test_gateway() -> GatewaySender { let (tx, _rx) = mpsc::unbounded_channel(); GatewaySender::new(tx) } /// Like `test_gateway` but returns the receiver; keep it alive for the test. pub(crate) fn test_gateway_with_receiver() -> (GatewaySender, mpsc::UnboundedReceiver) { let (tx, rx) = mpsc::unbounded_channel(); (GatewaySender::new(tx), rx) } /// `ctx_with_toggle` with a wired `parent_cmd_tx`. pub(crate) fn ctx_with_toggle_and_cmd_tx( toggle: HashMap, ) -> ( SubagentSpawnContext, mpsc::UnboundedReceiver, ) { let mut ctx = ctx_with_toggle(toggle); let (tx, rx) = mpsc::unbounded_channel(); ctx.parent_cmd_tx = Some(tx); (ctx, rx) } pub(crate) fn ctx_with_toggle(toggle: HashMap) -> SubagentSpawnContext { let (tx, _rx) = mpsc::unbounded_channel(); SubagentSpawnContext { lsp: None, parent_max_turns: None, gateway: test_gateway(), client_hooks: Default::default(), sampling_config: kigi_sampler::SamplerConfig { api_key: None, base_url: String::new(), model: String::new(), max_completion_tokens: None, temperature: None, top_p: None, api_backend: Default::default(), chat_compat: Default::default(), auth_scheme: Default::default(), anthropic_oauth: false, github_copilot: false, openai_codex: false, extra_headers: Default::default(), context_window: 256_000, force_http1: false, max_retries: None, stream_tool_calls: false, idle_timeout_secs: None, reasoning_effort: None, origin_client: None, attribution_callback: None, bearer_resolver: None, supports_backend_search: false, compactions_remaining: None, compaction_at_tokens: None, doom_loop_recovery: None, header_injector: None, }, alpha_test_key: None, auth_method_id: acp::AuthMethodId::new("test"), model_id: acp::ModelId::new("test"), storage_mode: crate::config::StorageMode::Local, auth: None, parent_cwd: PathBuf::from("/tmp"), parent_session_id: "test-parent".into(), yolo_mode: false, subagent_event_tx: tx, hunk_tracker_handle: kigi_hunk_tracker::HunkTrackerHandle::noop(), hunk_tracking_enabled: false, fs: Arc::new(kigi_workspace::file_system::LocalFs::new(PathBuf::from( "/tmp", ))), terminal: Arc::new(crate::terminal::TerminalRunner::new( Arc::new(test_gateway()), acp::SessionId::new("test"), )), session_env: Arc::new(HashMap::new()), memory_config: None, web_search_config: Default::default(), web_fetch_config: Default::default(), app_builder_deployer_config: Default::default(), write_file_enabled: true, goal_enabled: false, ask_user_question_enabled: true, parent_cmd_tx: None, parent_session_info: None, subagent_roles: HashMap::new(), subagent_personas: HashMap::new(), persona_io_summaries: Vec::new(), parent_chat_state: None, available_models: indexmap::IndexMap::new(), subagent_model_overrides: HashMap::new(), subagent_toggle: toggle, disable_web_search: false, todo_gate: false, remote_settings: None, laziness_debug_log: None, backend_tools_enabled: true, respect_gitignore: false, path_not_found_hints: false, plugin_registry: None, models_manager: Default::default(), file_tool_overrides: None, agent_config: None, hook_registry: None, hook_workspace_root: String::new(), parent_depth: 0, inference_idle_timeout_secs: 600, auto_compact_threshold_tiers: crate::agent::subagent::AutoCompactThresholdTiers::default(), permission_handle: None, worktree_type: crate::util::config::WorktreeType::Linked, api_key_provider: None, image_description_model: crate::test_support::TEST_MODEL.to_owned(), workspace_ops: kigi_workspace::WorkspaceOps::for_test(), auth_manager: Arc::new(crate::auth::AuthManager::new( std::path::Path::new("/tmp/nonexistent-kigi-test"), crate::auth::KimiCodeConfig::default(), )), attribution_callback: None, parent_agent_name: None, parent_model_agent_type: None, allowed_subagent_types: None, parent_mcp_configs: vec![], parent_mcp_pool: None, parent_tool_snapshot: None, parent_skills: None, parent_skills_config: kigi_agent::prompt::skills::SkillsConfig::default(), parent_compat: kigi_tools::types::compat::CompatConfig::default(), auto_wake_delivered: None, task_output_tool_name: kigi_tools::reminders::task_completion::DEFAULT_TASK_OUTPUT_TOOL .to_string(), auto_wake_enabled: true, goal_loop_active: std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)), parent_blocking_wait_depth: std::sync::Arc::new(std::sync::atomic::AtomicUsize::new(0)), parent_terminal_backend: None, parent_notification_handle: None, parent_scheduler_handle: None, } } pub(crate) fn make_request( subagent_type: &str, ) -> (SubagentRequest, oneshot::Receiver) { let (tx, rx) = oneshot::channel(); let req = SubagentRequest { id: uuid::Uuid::now_v7().to_string(), prompt: "do something".into(), description: "test task".into(), subagent_type: subagent_type.into(), parent_session_id: "test-parent".into(), parent_prompt_id: Some("parent-prompt".into()), resume_from: None, cwd: None, runtime_overrides: Default::default(), run_in_background: false, surface_completion: true, fork_context: false, result_tx: tx, }; (req, rx) } #[derive(Default)] pub(crate) struct DummyLspDispatch; #[async_trait::async_trait] impl kigi_tools::implementations::lsp::LspBackend for DummyLspDispatch { fn ensure_started_background(&self) {} async fn ensure_ready(&self) -> Result<(), String> { Ok(()) } fn is_ready(&self) -> bool { true } async fn dispatch( &self, _input: &kigi_tools::implementations::lsp::LspToolInput, ) -> kigi_tools::implementations::lsp::LspToolResult { kigi_tools::implementations::lsp::LspToolResult { text: String::new(), is_error: false, } } async fn drain_diagnostics( &self, _timeout: std::time::Duration, ) -> Option { None } async fn notify_file_changed(&self, _path: &std::path::Path, _content: &str) {} async fn read_diagnostics( &self, _paths: &[std::path::PathBuf], ) -> Vec { vec![] } }