use std::path::Path;
use std::sync::Arc;
use tokio_util::sync::CancellationToken;
use crate::child_agent::ChildAgentRegistry;
use crate::config::KodaConfig;
use crate::db::Database;
use crate::engine::EngineSink;
use crate::sub_agent_cache::SubAgentCache;
use crate::tools::ToolRegistry;
use crate::trust::TrustMode;
pub struct TurnContext<'a> {
pub project_root: &'a Path,
pub config: &'a KodaConfig,
pub db: &'a Database,
pub session_id: &'a str,
pub sink: &'a dyn EngineSink,
pub cancel: CancellationToken,
pub sub_agent_cache: &'a SubAgentCache,
pub bg_agents: &'a Arc<ChildAgentRegistry>,
pub mode: TrustMode,
pub tools: &'a ToolRegistry,
}
impl<'a> TurnContext<'a> {
#[allow(clippy::too_many_arguments)] pub fn new(
project_root: &'a Path,
config: &'a KodaConfig,
db: &'a Database,
session_id: &'a str,
sink: &'a dyn EngineSink,
cancel: CancellationToken,
sub_agent_cache: &'a SubAgentCache,
bg_agents: &'a Arc<ChildAgentRegistry>,
mode: TrustMode,
tools: &'a ToolRegistry,
) -> Self {
Self {
project_root,
config,
db,
session_id,
sink,
cancel,
sub_agent_cache,
bg_agents,
mode,
tools,
}
}
}
#[derive(Clone, Copy)]
pub struct ToolExecutionContext<'a> {
pub turn: &'a TurnContext<'a>,
pub caller_spawner: Option<u32>,
}
impl<'a> ToolExecutionContext<'a> {
pub fn new(turn: &'a TurnContext<'a>, caller_spawner: Option<u32>) -> Self {
Self {
turn,
caller_spawner,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;
use std::sync::Arc;
use tempfile::TempDir;
use crate::config::ProviderType;
use crate::engine::EngineEvent;
use crate::trust::TrustMode;
struct NullSink;
impl EngineSink for NullSink {
fn emit(&self, _event: EngineEvent) {}
}
async fn fixtures() -> (TempDir, crate::config::KodaConfig, crate::db::Database) {
let dir = TempDir::new().expect("tempdir");
let config = crate::config::KodaConfig::default_for_testing(ProviderType::Mock);
let db = crate::db::Database::open(&dir.path().join("turn_ctx.db"))
.await
.expect("open db");
(dir, config, db)
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn turn_context_holds_all_fields() {
let (dir, config, db) = fixtures().await;
let root: PathBuf = dir.path().to_path_buf();
let cancel = CancellationToken::new();
let cache = SubAgentCache::new();
let bg_agents = Arc::new(ChildAgentRegistry::new());
let tools = ToolRegistry::new(root.clone(), 8_000);
let sink = NullSink;
let ctx = TurnContext::new(
&root,
&config,
&db,
"session-smoke",
&sink,
cancel.clone(),
&cache,
&bg_agents,
TrustMode::Safe,
&tools,
);
assert_eq!(ctx.project_root, root.as_path());
assert_eq!(ctx.session_id, "session-smoke");
assert!(matches!(ctx.mode, TrustMode::Safe));
assert!(!ctx.cancel.is_cancelled());
assert!(Arc::ptr_eq(ctx.bg_agents, &bg_agents));
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn tool_execution_context_borrows_turn() {
let (dir, config, db) = fixtures().await;
let root: PathBuf = dir.path().to_path_buf();
let cache = SubAgentCache::new();
let bg_agents = Arc::new(ChildAgentRegistry::new());
let tools = ToolRegistry::new(root.clone(), 8_000);
let sink = NullSink;
let turn = TurnContext::new(
&root,
&config,
&db,
"session-tx",
&sink,
CancellationToken::new(),
&cache,
&bg_agents,
TrustMode::Auto,
&tools,
);
let tx = ToolExecutionContext::new(&turn, None);
assert!(tx.caller_spawner.is_none());
assert_eq!(tx.turn.session_id, "session-tx");
assert!(matches!(tx.turn.mode, TrustMode::Auto));
let nested = ToolExecutionContext::new(&turn, Some(42));
assert_eq!(nested.caller_spawner, Some(42));
assert_eq!(nested.turn.session_id, "session-tx");
}
#[test]
fn tool_execution_context_is_copy() {
fn assert_copy<T: Copy>() {}
assert_copy::<ToolExecutionContext<'_>>();
}
}