Skip to main content

codex_runtime/ergonomic/
config.rs

1use std::path::PathBuf;
2use std::sync::Arc;
3use std::time::Duration;
4
5use crate::plugin::{PostHook, PreHook};
6use crate::runtime::{
7    ClientConfig, CompatibilityGuard, InitializeCapabilities, RunProfile, RuntimeHookConfig,
8    SessionConfig, ShellCommandHook,
9};
10
11use crate::ergonomic::paths::absolutize_cwd_without_fs_checks;
12
13/// One explicit data model for reusable workflow defaults.
14/// This keeps simple and advanced paths on a single concrete structure.
15#[derive(Clone, Debug, PartialEq)]
16pub struct WorkflowConfig {
17    pub cwd: String,
18    pub client_config: ClientConfig,
19    pub run_profile: RunProfile,
20}
21
22impl WorkflowConfig {
23    /// Create config with safe defaults:
24    /// - runtime discovery via `ClientConfig::new()`
25    /// - model unset, effort medium, approval never, sandbox read-only
26    /// - cwd normalized to absolute path without filesystem existence checks
27    ///   (non-utf8 absolute paths fall back to caller-provided UTF-8 input without lossy conversion)
28    pub fn new(cwd: impl Into<String>) -> Self {
29        let normalized_cwd = absolutize_cwd_without_fs_checks(&cwd.into());
30        Self {
31            cwd: normalized_cwd,
32            client_config: ClientConfig::new(),
33            run_profile: RunProfile::new(),
34        }
35    }
36
37    /// Replace whole client config.
38    pub fn with_client_config(mut self, client_config: ClientConfig) -> Self {
39        self.client_config = client_config;
40        self
41    }
42
43    /// Replace whole run profile.
44    pub fn with_run_profile(mut self, run_profile: RunProfile) -> Self {
45        self.run_profile = run_profile;
46        self
47    }
48
49    /// Override codex binary location.
50    pub fn with_cli_bin(mut self, cli_bin: impl Into<PathBuf>) -> Self {
51        self.client_config = self.client_config.with_cli_bin(cli_bin);
52        self
53    }
54
55    /// Override runtime compatibility policy.
56    pub fn with_compatibility_guard(mut self, guard: CompatibilityGuard) -> Self {
57        self.client_config = self.client_config.with_compatibility_guard(guard);
58        self
59    }
60
61    /// Disable compatibility guard.
62    pub fn without_compatibility_guard(mut self) -> Self {
63        self.client_config = self.client_config.without_compatibility_guard();
64        self
65    }
66
67    /// Override initialize capability switches.
68    pub fn with_initialize_capabilities(
69        mut self,
70        initialize_capabilities: InitializeCapabilities,
71    ) -> Self {
72        self.client_config = self
73            .client_config
74            .with_initialize_capabilities(initialize_capabilities);
75        self
76    }
77
78    /// Opt into Codex experimental app-server methods and fields.
79    pub fn enable_experimental_api(mut self) -> Self {
80        self.client_config = self.client_config.enable_experimental_api();
81        self
82    }
83
84    /// Replace global runtime hooks (connect-time).
85    pub fn with_global_hooks(mut self, hooks: RuntimeHookConfig) -> Self {
86        self.client_config = self.client_config.with_hooks(hooks);
87        self
88    }
89
90    /// Register one global runtime pre hook (connect-time).
91    pub fn with_global_pre_hook(mut self, hook: Arc<dyn PreHook>) -> Self {
92        self.client_config = self.client_config.with_pre_hook(hook);
93        self
94    }
95
96    /// Register one global runtime post hook (connect-time).
97    pub fn with_global_post_hook(mut self, hook: Arc<dyn PostHook>) -> Self {
98        self.client_config = self.client_config.with_post_hook(hook);
99        self
100    }
101
102    /// Register one global pre-tool-use hook (fires via the internal approval loop).
103    /// The runtime manages the approval channel and auto-escalates ApprovalPolicy → Untrusted.
104    /// Allocation: amortized O(1) push. Complexity: O(1).
105    pub fn with_global_pre_tool_use_hook(mut self, hook: Arc<dyn PreHook>) -> Self {
106        self.client_config = self.client_config.with_pre_tool_use_hook(hook);
107        self
108    }
109
110    /// Register an external shell command as a global pre-hook (connect-time).
111    /// The command is run via `sh -c`. Default timeout: 5 seconds.
112    /// On exit 0 → Noop or Mutate. On exit 2 → Block. On other exit → HookIssue.
113    /// Allocation: two Strings.
114    pub fn with_shell_pre_hook(self, name: &'static str, command: impl Into<String>) -> Self {
115        self.with_global_pre_hook(Arc::new(ShellCommandHook::new(name, command)))
116    }
117
118    /// Register an external shell command as a global post-hook (connect-time).
119    /// The command is run via `sh -c`. Default timeout: 5 seconds.
120    /// On exit 0 → Ok(()). On other exit → HookIssue (non-fatal, logged in report).
121    /// Allocation: two Strings.
122    pub fn with_shell_post_hook(self, name: &'static str, command: impl Into<String>) -> Self {
123        self.with_global_post_hook(Arc::new(ShellCommandHook::new(name, command)))
124    }
125
126    /// Register an external shell command as a global pre-hook with explicit timeout.
127    /// Allocation: two Strings.
128    pub fn with_shell_pre_hook_timeout(
129        self,
130        name: &'static str,
131        command: impl Into<String>,
132        timeout: Duration,
133    ) -> Self {
134        self.with_global_pre_hook(Arc::new(
135            ShellCommandHook::new(name, command).with_timeout(timeout),
136        ))
137    }
138
139    /// Build session config with the same cwd/profile defaults.
140    pub fn to_session_config(&self) -> SessionConfig {
141        SessionConfig::from_profile(self.cwd.clone(), self.run_profile.clone())
142    }
143}