Skip to main content

codex_runtime/runtime/core/
config.rs

1//! Runtime configuration types.
2//! Pure data: no async, no runtime dependencies. Copy/Clone safe where possible.
3
4use std::sync::Arc;
5
6use serde_json::{json, Value};
7use tokio::time::Duration;
8
9use crate::runtime::approvals::ServerRequestConfig;
10use crate::runtime::hooks::RuntimeHookConfig;
11use crate::runtime::sink::EventSink;
12use crate::runtime::state::StateProjectionLimits;
13use crate::runtime::transport::{StdioProcessSpec, StdioTransportConfig};
14
15// ── Supervisor ────────────────────────────────────────────────────────────
16
17/// Restart strategy for the supervised process.
18/// Copy type — zero allocation.
19#[derive(Clone, Copy, Debug, PartialEq, Eq)]
20pub enum RestartPolicy {
21    Never,
22    OnCrash {
23        max_restarts: u32,
24        base_backoff_ms: u64,
25        max_backoff_ms: u64,
26    },
27}
28
29/// Configuration for the process supervisor lifecycle.
30/// Copy type — zero allocation.
31#[derive(Clone, Copy, Debug, PartialEq, Eq)]
32pub struct SupervisorConfig {
33    pub restart: RestartPolicy,
34    pub shutdown_flush_timeout_ms: u64,
35    pub shutdown_terminate_grace_ms: u64,
36    pub restart_budget_reset_ms: u64,
37}
38
39impl Default for SupervisorConfig {
40    fn default() -> Self {
41        Self {
42            restart: RestartPolicy::Never,
43            shutdown_flush_timeout_ms: 500,
44            shutdown_terminate_grace_ms: 750,
45            restart_budget_reset_ms: 30_000,
46        }
47    }
48}
49
50/// Initialize capability switches exposed to the child app-server.
51/// Copy type — zero allocation.
52#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
53pub struct InitializeCapabilities {
54    pub experimental_api: bool,
55}
56
57impl InitializeCapabilities {
58    /// Create capability set with safe defaults.
59    pub fn new() -> Self {
60        Self::default()
61    }
62
63    /// Opt into Codex experimental app-server methods and fields.
64    pub fn enable_experimental_api(mut self) -> Self {
65        self.experimental_api = true;
66        self
67    }
68}
69
70// ── Runtime config ────────────────────────────────────────────────────────
71
72/// Full configuration for spawning a Runtime instance.
73///
74/// All fields are set with safe defaults via `RuntimeConfig::new`.
75/// Override individual fields with builder methods.
76/// Allocation: O(1) except `event_sink` which may hold heap state.
77#[derive(Clone)]
78pub struct RuntimeConfig {
79    pub process: StdioProcessSpec,
80    pub hooks: RuntimeHookConfig,
81    pub transport: StdioTransportConfig,
82    pub supervisor: SupervisorConfig,
83    pub rpc_response_timeout: Duration,
84    pub server_requests: ServerRequestConfig,
85    pub initialize_params: Value,
86    pub live_channel_capacity: usize,
87    pub server_request_channel_capacity: usize,
88    pub event_sink: Option<Arc<dyn EventSink>>,
89    pub event_sink_channel_capacity: usize,
90    pub state_projection_limits: StateProjectionLimits,
91}
92
93impl RuntimeConfig {
94    /// Create config with safe defaults.
95    /// Allocation: one Value (JSON object). Complexity: O(1).
96    pub fn new(process: StdioProcessSpec) -> Self {
97        Self {
98            process,
99            hooks: RuntimeHookConfig::default(),
100            transport: StdioTransportConfig::default(),
101            supervisor: SupervisorConfig::default(),
102            rpc_response_timeout: Duration::from_secs(30),
103            server_requests: ServerRequestConfig::default(),
104            initialize_params: json!({
105                "clientInfo": {
106                    "name": env!("CARGO_PKG_NAME"),
107                    "title": env!("CARGO_PKG_NAME"),
108                    "version": env!("CARGO_PKG_VERSION")
109                },
110                "capabilities": {}
111            }),
112            live_channel_capacity: 1024,
113            server_request_channel_capacity: 128,
114            event_sink: None,
115            event_sink_channel_capacity: 1024,
116            state_projection_limits: StateProjectionLimits::default(),
117        }
118    }
119
120    /// Override lifecycle hook configuration.
121    /// Allocation: O(h), h = hook count. Complexity: O(1).
122    pub fn with_hooks(mut self, hooks: RuntimeHookConfig) -> Self {
123        self.hooks = hooks;
124        self
125    }
126
127    /// Override initialize capability switches while preserving other init params.
128    pub fn with_initialize_capabilities(mut self, capabilities: InitializeCapabilities) -> Self {
129        set_initialize_capabilities(&mut self.initialize_params, capabilities);
130        self
131    }
132}
133
134fn set_initialize_capabilities(
135    initialize_params: &mut Value,
136    capabilities: InitializeCapabilities,
137) {
138    if !initialize_params.is_object() {
139        *initialize_params = json!({});
140    }
141
142    let Some(root) = initialize_params.as_object_mut() else {
143        return;
144    };
145    let capabilities_value = root
146        .entry("capabilities".to_owned())
147        .or_insert_with(|| Value::Object(Default::default()));
148    if !capabilities_value.is_object() {
149        *capabilities_value = Value::Object(Default::default());
150    }
151
152    let Some(capabilities_object) = capabilities_value.as_object_mut() else {
153        return;
154    };
155    if capabilities.experimental_api {
156        capabilities_object.insert("experimentalApi".to_owned(), Value::Bool(true));
157    } else {
158        capabilities_object.remove("experimentalApi");
159    }
160}