Skip to main content

codex_runtime/ergonomic/
config.rs

1use std::path::PathBuf;
2use std::sync::Arc;
3use std::time::Duration;
4
5use serde_json::Value;
6
7use crate::plugin::{PostHook, PreHook};
8use crate::runtime::{
9    ApprovalPolicy, ClientConfig, CompatibilityGuard, InitializeCapabilities, PromptAttachment,
10    ReasoningEffort, RunProfile, RuntimeHookConfig, SandboxPolicy, SessionConfig, ShellCommandHook,
11};
12
13use crate::ergonomic::paths::absolutize_cwd_without_fs_checks;
14
15/// One explicit data model for reusable workflow defaults.
16/// This keeps simple and advanced paths on a single concrete structure.
17#[derive(Clone, Debug, PartialEq)]
18pub struct WorkflowConfig {
19    pub cwd: String,
20    pub client_config: ClientConfig,
21    pub run_profile: RunProfile,
22}
23
24impl WorkflowConfig {
25    /// Create config with safe defaults:
26    /// - runtime discovery via `ClientConfig::new()`
27    /// - model unset, effort medium, approval never, sandbox read-only
28    /// - cwd normalized to absolute path without filesystem existence checks
29    ///   (non-utf8 absolute paths fall back to caller-provided UTF-8 input without lossy conversion)
30    pub fn new(cwd: impl Into<String>) -> Self {
31        let normalized_cwd = absolutize_cwd_without_fs_checks(&cwd.into());
32        Self {
33            cwd: normalized_cwd,
34            client_config: ClientConfig::new(),
35            run_profile: RunProfile::new(),
36        }
37    }
38
39    /// Replace whole client config.
40    pub fn with_client_config(mut self, client_config: ClientConfig) -> Self {
41        self.client_config = client_config;
42        self
43    }
44
45    /// Replace whole run profile.
46    pub fn with_run_profile(mut self, run_profile: RunProfile) -> Self {
47        self.run_profile = run_profile;
48        self
49    }
50
51    /// Override codex binary location.
52    pub fn with_cli_bin(mut self, cli_bin: impl Into<PathBuf>) -> Self {
53        self.client_config = self.client_config.with_cli_bin(cli_bin);
54        self
55    }
56
57    /// Override runtime compatibility policy.
58    pub fn with_compatibility_guard(mut self, guard: CompatibilityGuard) -> Self {
59        self.client_config = self.client_config.with_compatibility_guard(guard);
60        self
61    }
62
63    /// Disable compatibility guard.
64    pub fn without_compatibility_guard(mut self) -> Self {
65        self.client_config = self.client_config.without_compatibility_guard();
66        self
67    }
68
69    /// Override initialize capability switches.
70    pub fn with_initialize_capabilities(
71        mut self,
72        initialize_capabilities: InitializeCapabilities,
73    ) -> Self {
74        self.client_config = self
75            .client_config
76            .with_initialize_capabilities(initialize_capabilities);
77        self
78    }
79
80    /// Opt into Codex experimental app-server methods and fields.
81    pub fn enable_experimental_api(mut self) -> Self {
82        self.client_config = self.client_config.enable_experimental_api();
83        self
84    }
85
86    /// Replace global runtime hooks (connect-time).
87    pub fn with_global_hooks(mut self, hooks: RuntimeHookConfig) -> Self {
88        self.client_config = self.client_config.with_hooks(hooks);
89        self
90    }
91
92    /// Register one global runtime pre hook (connect-time).
93    pub fn with_global_pre_hook(mut self, hook: Arc<dyn PreHook>) -> Self {
94        self.client_config = self.client_config.with_pre_hook(hook);
95        self
96    }
97
98    /// Register one global runtime post hook (connect-time).
99    pub fn with_global_post_hook(mut self, hook: Arc<dyn PostHook>) -> Self {
100        self.client_config = self.client_config.with_post_hook(hook);
101        self
102    }
103
104    /// Register one global pre-tool-use hook (fires via the internal approval loop).
105    /// The runtime manages the approval channel and auto-escalates ApprovalPolicy → Untrusted.
106    /// Allocation: amortized O(1) push. Complexity: O(1).
107    pub fn with_global_pre_tool_use_hook(mut self, hook: Arc<dyn PreHook>) -> Self {
108        self.client_config = self.client_config.with_pre_tool_use_hook(hook);
109        self
110    }
111
112    /// Set explicit model override.
113    pub fn with_model(mut self, model: impl Into<String>) -> Self {
114        self.run_profile = self.run_profile.with_model(model);
115        self
116    }
117
118    /// Set explicit reasoning effort.
119    pub fn with_effort(mut self, effort: ReasoningEffort) -> Self {
120        self.run_profile = self.run_profile.with_effort(effort);
121        self
122    }
123
124    /// Set approval policy override.
125    pub fn with_approval_policy(mut self, approval_policy: ApprovalPolicy) -> Self {
126        self.run_profile = self.run_profile.with_approval_policy(approval_policy);
127        self
128    }
129
130    /// Set sandbox policy override.
131    pub fn with_sandbox_policy(mut self, sandbox_policy: SandboxPolicy) -> Self {
132        self.run_profile = self.run_profile.with_sandbox_policy(sandbox_policy);
133        self
134    }
135
136    /// Set prompt timeout.
137    pub fn with_timeout(mut self, timeout: Duration) -> Self {
138        self.run_profile = self.run_profile.with_timeout(timeout);
139        self
140    }
141
142    /// Set one optional JSON Schema for the final assistant message.
143    pub fn with_output_schema(mut self, output_schema: Value) -> Self {
144        self.run_profile = self.run_profile.with_output_schema(output_schema);
145        self
146    }
147
148    /// Add one attachment.
149    pub fn with_attachment(mut self, attachment: PromptAttachment) -> Self {
150        self.run_profile = self.run_profile.with_attachment(attachment);
151        self
152    }
153
154    /// Add one `@path` attachment.
155    pub fn attach_path(mut self, path: impl Into<String>) -> Self {
156        self.run_profile = self.run_profile.attach_path(path);
157        self
158    }
159
160    /// Add one `@path` attachment with placeholder.
161    pub fn attach_path_with_placeholder(
162        mut self,
163        path: impl Into<String>,
164        placeholder: impl Into<String>,
165    ) -> Self {
166        self.run_profile = self
167            .run_profile
168            .attach_path_with_placeholder(path, placeholder);
169        self
170    }
171
172    /// Add one remote image attachment.
173    pub fn attach_image_url(mut self, url: impl Into<String>) -> Self {
174        self.run_profile = self.run_profile.attach_image_url(url);
175        self
176    }
177
178    /// Add one local image attachment.
179    pub fn attach_local_image(mut self, path: impl Into<String>) -> Self {
180        self.run_profile = self.run_profile.attach_local_image(path);
181        self
182    }
183
184    /// Add one skill attachment.
185    pub fn attach_skill(mut self, name: impl Into<String>, path: impl Into<String>) -> Self {
186        self.run_profile = self.run_profile.attach_skill(name, path);
187        self
188    }
189
190    /// Replace run-level hooks.
191    pub fn with_run_hooks(mut self, hooks: RuntimeHookConfig) -> Self {
192        self.run_profile = self.run_profile.with_hooks(hooks);
193        self
194    }
195
196    /// Register one run-level pre hook.
197    pub fn with_run_pre_hook(mut self, hook: Arc<dyn PreHook>) -> Self {
198        self.run_profile = self.run_profile.with_pre_hook(hook);
199        self
200    }
201
202    /// Register one run-level post hook.
203    pub fn with_run_post_hook(mut self, hook: Arc<dyn PostHook>) -> Self {
204        self.run_profile = self.run_profile.with_post_hook(hook);
205        self
206    }
207
208    /// Register an external shell command as a global pre-hook (connect-time).
209    /// The command is run via `sh -c`. Default timeout: 5 seconds.
210    /// On exit 0 → Noop or Mutate. On exit 2 → Block. On other exit → HookIssue.
211    /// Allocation: two Strings.
212    pub fn with_shell_pre_hook(self, name: &'static str, command: impl Into<String>) -> Self {
213        self.with_global_pre_hook(Arc::new(ShellCommandHook::new(name, command)))
214    }
215
216    /// Register an external shell command as a global post-hook (connect-time).
217    /// The command is run via `sh -c`. Default timeout: 5 seconds.
218    /// On exit 0 → Ok(()). On other exit → HookIssue (non-fatal, logged in report).
219    /// Allocation: two Strings.
220    pub fn with_shell_post_hook(self, name: &'static str, command: impl Into<String>) -> Self {
221        self.with_global_post_hook(Arc::new(ShellCommandHook::new(name, command)))
222    }
223
224    /// Register an external shell command as a global pre-hook with explicit timeout.
225    /// Allocation: two Strings.
226    pub fn with_shell_pre_hook_timeout(
227        self,
228        name: &'static str,
229        command: impl Into<String>,
230        timeout: Duration,
231    ) -> Self {
232        self.with_global_pre_hook(Arc::new(
233            ShellCommandHook::new(name, command).with_timeout(timeout),
234        ))
235    }
236
237    /// Build session config with the same cwd/profile defaults.
238    pub fn to_session_config(&self) -> SessionConfig {
239        SessionConfig::from_profile(self.cwd.clone(), self.run_profile.clone())
240    }
241}