Skip to main content

codex_runtime/appserver/
mod.rs

1use serde::{de::DeserializeOwned, Serialize};
2use serde_json::Value;
3
4use crate::runtime::{
5    Client, ClientConfig, ClientError, CommandExecParams, CommandExecResizeParams,
6    CommandExecResizeResponse, CommandExecResponse, CommandExecTerminateParams,
7    CommandExecTerminateResponse, CommandExecWriteParams, CommandExecWriteResponse, RpcError,
8    RpcErrorObject, RpcValidationMode, Runtime, RuntimeError, ServerRequestRx, SkillsListParams,
9    SkillsListResponse,
10};
11
12mod service;
13
14/// Canonical app-server JSON-RPC method names.
15pub mod methods {
16    pub use crate::runtime::rpc_contract::methods::{
17        COMMAND_EXEC, COMMAND_EXEC_OUTPUT_DELTA, COMMAND_EXEC_RESIZE, COMMAND_EXEC_TERMINATE,
18        COMMAND_EXEC_WRITE, SKILLS_CHANGED, SKILLS_LIST, THREAD_ARCHIVE, THREAD_FORK, THREAD_LIST,
19        THREAD_LOADED_LIST, THREAD_READ, THREAD_RESUME, THREAD_ROLLBACK, THREAD_START,
20        TURN_CANCELLED, TURN_COMPLETED, TURN_FAILED, TURN_INTERRUPT, TURN_START,
21    };
22}
23
24/// Thin, explicit JSON-RPC facade for codex app-server.
25///
26/// - `request_json` / `notify_json`: validated calls for known methods.
27/// - `request_typed` / `notify_typed`: typed wrappers with contract validation.
28/// - `*_unchecked`: bypass contract checks for experimental/custom methods.
29/// - server request loop is exposed directly for approval/user-input workflows.
30#[derive(Clone)]
31pub struct AppServer {
32    client: Client,
33}
34
35impl AppServer {
36    fn from_client(client: Client) -> Self {
37        Self { client }
38    }
39
40    /// Connect app-server with explicit config.
41    pub async fn connect(config: ClientConfig) -> Result<Self, ClientError> {
42        let client = service::connect(config).await?;
43        Ok(Self::from_client(client))
44    }
45
46    /// Connect app-server with default runtime discovery.
47    pub async fn connect_default() -> Result<Self, ClientError> {
48        let client = service::connect_default().await?;
49        Ok(Self::from_client(client))
50    }
51
52    /// Validated JSON-RPC request for known methods.
53    pub async fn request_json(&self, method: &str, params: Value) -> Result<Value, RpcError> {
54        self.request_json_with_mode(method, params, RpcValidationMode::KnownMethods)
55            .await
56    }
57
58    /// JSON-RPC request with explicit validation mode.
59    pub async fn request_json_with_mode(
60        &self,
61        method: &str,
62        params: Value,
63        mode: RpcValidationMode,
64    ) -> Result<Value, RpcError> {
65        service::request_json(&self.client, method, params, mode).await
66    }
67
68    /// Typed JSON-RPC request for known methods.
69    pub async fn request_typed<P, R>(&self, method: &str, params: P) -> Result<R, RpcError>
70    where
71        P: Serialize,
72        R: DeserializeOwned,
73    {
74        self.request_typed_with_mode(method, params, RpcValidationMode::KnownMethods)
75            .await
76    }
77
78    /// Typed JSON-RPC request with explicit validation mode.
79    pub async fn request_typed_with_mode<P, R>(
80        &self,
81        method: &str,
82        params: P,
83        mode: RpcValidationMode,
84    ) -> Result<R, RpcError>
85    where
86        P: Serialize,
87        R: DeserializeOwned,
88    {
89        service::request_typed(&self.client, method, params, mode).await
90    }
91
92    /// Unchecked JSON-RPC request.
93    /// Use for experimental/custom methods where strict contracts are not fixed yet.
94    pub async fn request_json_unchecked(
95        &self,
96        method: &str,
97        params: Value,
98    ) -> Result<Value, RpcError> {
99        service::request_json_unchecked(&self.client, method, params).await
100    }
101
102    /// Typed helper for `skills/list`.
103    pub async fn skills_list(
104        &self,
105        params: SkillsListParams,
106    ) -> Result<SkillsListResponse, RpcError> {
107        self.client.runtime().skills_list(params).await
108    }
109
110    /// Typed helper for `command/exec`.
111    pub async fn command_exec(
112        &self,
113        params: CommandExecParams,
114    ) -> Result<CommandExecResponse, RpcError> {
115        self.client.runtime().command_exec(params).await
116    }
117
118    /// Typed helper for `command/exec/write`.
119    pub async fn command_exec_write(
120        &self,
121        params: CommandExecWriteParams,
122    ) -> Result<CommandExecWriteResponse, RpcError> {
123        self.client.runtime().command_exec_write(params).await
124    }
125
126    /// Typed helper for `command/exec/resize`.
127    pub async fn command_exec_resize(
128        &self,
129        params: CommandExecResizeParams,
130    ) -> Result<CommandExecResizeResponse, RpcError> {
131        self.client.runtime().command_exec_resize(params).await
132    }
133
134    /// Typed helper for `command/exec/terminate`.
135    pub async fn command_exec_terminate(
136        &self,
137        params: CommandExecTerminateParams,
138    ) -> Result<CommandExecTerminateResponse, RpcError> {
139        self.client.runtime().command_exec_terminate(params).await
140    }
141
142    /// Validated JSON-RPC notification for known methods.
143    pub async fn notify_json(&self, method: &str, params: Value) -> Result<(), RuntimeError> {
144        self.notify_json_with_mode(method, params, RpcValidationMode::KnownMethods)
145            .await
146    }
147
148    /// JSON-RPC notification with explicit validation mode.
149    pub async fn notify_json_with_mode(
150        &self,
151        method: &str,
152        params: Value,
153        mode: RpcValidationMode,
154    ) -> Result<(), RuntimeError> {
155        service::notify_json(&self.client, method, params, mode).await
156    }
157
158    /// Typed JSON-RPC notification for known methods.
159    pub async fn notify_typed<P>(&self, method: &str, params: P) -> Result<(), RuntimeError>
160    where
161        P: Serialize,
162    {
163        self.notify_typed_with_mode(method, params, RpcValidationMode::KnownMethods)
164            .await
165    }
166
167    /// Typed JSON-RPC notification with explicit validation mode.
168    pub async fn notify_typed_with_mode<P>(
169        &self,
170        method: &str,
171        params: P,
172        mode: RpcValidationMode,
173    ) -> Result<(), RuntimeError>
174    where
175        P: Serialize,
176    {
177        service::notify_typed(&self.client, method, params, mode).await
178    }
179
180    /// Unchecked JSON-RPC notification.
181    pub async fn notify_json_unchecked(
182        &self,
183        method: &str,
184        params: Value,
185    ) -> Result<(), RuntimeError> {
186        service::notify_json_unchecked(&self.client, method, params).await
187    }
188
189    /// Take exclusive server-request stream receiver.
190    ///
191    /// This enables explicit handling of approval / requestUserInput / tool-call cycles.
192    pub async fn take_server_requests(&self) -> Result<ServerRequestRx, RuntimeError> {
193        service::take_server_requests(&self.client).await
194    }
195
196    /// Reply success payload for one server request.
197    pub async fn respond_server_request_ok(
198        &self,
199        approval_id: &str,
200        result: Value,
201    ) -> Result<(), RuntimeError> {
202        service::respond_server_request_ok(&self.client, approval_id, result).await
203    }
204
205    /// Reply error payload for one server request.
206    pub async fn respond_server_request_err(
207        &self,
208        approval_id: &str,
209        err: RpcErrorObject,
210    ) -> Result<(), RuntimeError> {
211        service::respond_server_request_err(&self.client, approval_id, err).await
212    }
213
214    /// Borrow server runtime for full low-level control.
215    pub fn runtime(&self) -> &Runtime {
216        self.client.runtime()
217    }
218
219    /// Borrow underlying connected client.
220    pub fn client(&self) -> &Client {
221        &self.client
222    }
223
224    /// Explicit shutdown.
225    pub async fn shutdown(&self) -> Result<(), RuntimeError> {
226        service::shutdown(&self.client).await
227    }
228}
229
230#[cfg(test)]
231mod tests;