use std::sync::Arc;
use serde_json::json;
use crate::content::Content;
use crate::hooks::{HookRunner, TurnContext};
use crate::tools::ToolRunner;
use crate::types::{HookResult, ToolCall, ToolResult};
pub(crate) async fn gate_pre_turn(
hook_runner: Option<&Arc<HookRunner>>,
turn_ctx: &TurnContext,
prompt: &Content,
) -> Option<String> {
let hooks = hook_runner?;
let decision = hooks.dispatch_pre_turn(turn_ctx, prompt).await;
if decision.allow {
None
} else {
Some(format!("turn denied by hook: {}", decision.message))
}
}
pub(crate) async fn dispatch_post_turn(
hook_runner: Option<&Arc<HookRunner>>,
turn_ctx: &TurnContext,
response: &str,
) {
if let Some(hooks) = hook_runner {
hooks.dispatch_post_turn(turn_ctx, response).await;
}
}
pub(crate) async fn dispatch_tool_call(
tool_runner: Option<&Arc<ToolRunner>>,
hook_runner: Option<&Arc<HookRunner>>,
turn_ctx: &TurnContext,
call: &ToolCall,
) -> ToolResult {
let (decision, op_ctx) = if let Some(hooks) = hook_runner {
hooks.dispatch_pre_tool_call(turn_ctx, call).await
} else {
(HookResult::allow(), turn_ctx.clone())
};
let (result_value, error): (serde_json::Value, Option<String>) = if !decision.allow {
let msg = decision.message.clone();
(json!({ "error": msg.clone() }), Some(msg))
} else if let Some(runner) = tool_runner {
match runner.execute(&call.name, call.args.clone()).await {
Ok(v) => {
let err = v.get("error").and_then(|e| e.as_str()).map(String::from);
(v, err)
}
Err(e) => {
let s = e.to_string();
(json!({ "error": s.clone() }), Some(s))
}
}
} else {
let s = format!("no tool runner registered for '{}'", call.name);
(json!({ "error": s.clone() }), Some(s))
};
let result = ToolResult {
name: call.name.clone(),
id: call.id.clone(),
result: Some(result_value),
error,
};
if let Some(hooks) = hook_runner {
hooks.dispatch_post_tool_call(&op_ctx, &result).await;
}
result
}