tokf_hook_types/engine.rs
1use serde::{Deserialize, Serialize};
2
3/// Configuration for an external permission engine.
4#[derive(Debug, Clone, Deserialize, Serialize)]
5pub struct ExternalEngineConfig {
6 /// Path to the external engine binary (resolved via PATH if not absolute).
7 pub command: String,
8
9 /// Arguments passed to the engine. Use `{format}` as a placeholder for the
10 /// tool format (e.g. `["hook", "handle", "--mode", "{format}"]`).
11 /// The placeholder is replaced with the resolved format string before spawning.
12 #[serde(default, skip_serializing_if = "Vec::is_empty")]
13 pub args: Vec<String>,
14
15 /// Timeout in milliseconds. Default: 5000 (5 seconds).
16 #[serde(default = "default_timeout")]
17 pub timeout_ms: u64,
18
19 /// What to do when the engine fails (crash, timeout, bad output).
20 #[serde(default)]
21 pub on_error: ErrorFallback,
22
23 /// Override the default format strings used for `{format}` substitution.
24 /// Keys are the default names (`claude-code`, `gemini`, `cursor`);
25 /// values are the replacements the engine expects.
26 ///
27 /// Example: `{ "claude-code" = "claude", "gemini" = "google" }`
28 #[serde(default, skip_serializing_if = "std::collections::HashMap::is_empty")]
29 pub format_map: std::collections::HashMap<String, String>,
30}
31
32/// Behaviour when the external engine fails.
33#[derive(Debug, Clone, Deserialize, Serialize, Default, PartialEq, Eq)]
34#[serde(rename_all = "lowercase")]
35pub enum ErrorFallback {
36 /// Fail closed — prompt user for permission (default).
37 #[default]
38 Ask,
39 /// Fail open — auto-allow.
40 Allow,
41 /// Fall back to built-in rule matching.
42 Builtin,
43}
44
45impl Default for ExternalEngineConfig {
46 fn default() -> Self {
47 Self {
48 command: String::new(),
49 args: Vec::new(),
50 timeout_ms: default_timeout(),
51 on_error: ErrorFallback::default(),
52 format_map: std::collections::HashMap::new(),
53 }
54 }
55}
56
57impl ExternalEngineConfig {
58 /// Resolve the format string for a given hook format,
59 /// applying `format_map` overrides if present.
60 pub fn resolve_format(&self, format: crate::HookFormat) -> String {
61 let default = format.as_str();
62 self.format_map
63 .get(default)
64 .cloned()
65 .unwrap_or_else(|| default.to_string())
66 }
67}
68
69pub const fn default_timeout() -> u64 {
70 5000
71}