Skip to content

Commit 3cc9657

Browse files
committed
snake case camel case shenanigans
1 parent 2e7dc61 commit 3cc9657

File tree

2 files changed

+122
-9
lines changed

2 files changed

+122
-9
lines changed

codex-rs/app-server-protocol/src/protocol/v2.rs

Lines changed: 63 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -166,21 +166,23 @@ pub enum ConfigLayerName {
166166
#[serde(rename_all = "camelCase")]
167167
#[ts(export_to = "v2/")]
168168
pub struct SandboxWorkspaceWrite {
169-
#[serde(default)]
169+
#[serde(default, alias = "writable_roots")]
170170
pub writable_roots: Vec<PathBuf>,
171-
#[serde(default)]
171+
#[serde(default, alias = "network_access")]
172172
pub network_access: bool,
173-
#[serde(default)]
173+
#[serde(default, alias = "exclude_tmpdir_env_var")]
174174
pub exclude_tmpdir_env_var: bool,
175-
#[serde(default)]
175+
#[serde(default, alias = "exclude_slash_tmp")]
176176
pub exclude_slash_tmp: bool,
177177
}
178178

179179
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
180180
#[serde(rename_all = "camelCase")]
181181
#[ts(export_to = "v2/")]
182182
pub struct ToolsV2 {
183+
#[serde(alias = "web_search")]
183184
pub web_search: Option<bool>,
185+
#[serde(alias = "view_image")]
184186
pub view_image: Option<bool>,
185187
}
186188

@@ -189,16 +191,22 @@ pub struct ToolsV2 {
189191
#[ts(export_to = "v2/")]
190192
pub struct ProfileV2 {
191193
pub model: Option<String>,
194+
#[serde(alias = "model_provider")]
192195
pub model_provider: Option<String>,
193196
#[serde(
194197
default,
195198
deserialize_with = "deserialize_approval_policy",
196-
serialize_with = "serialize_approval_policy"
199+
serialize_with = "serialize_approval_policy",
200+
alias = "approval_policy"
197201
)]
198202
pub approval_policy: Option<AskForApproval>,
203+
#[serde(alias = "model_reasoning_effort")]
199204
pub model_reasoning_effort: Option<ReasoningEffort>,
205+
#[serde(alias = "model_reasoning_summary")]
200206
pub model_reasoning_summary: Option<ReasoningSummary>,
207+
#[serde(alias = "model_verbosity")]
201208
pub model_verbosity: Option<Verbosity>,
209+
#[serde(alias = "chatgpt_base_url")]
202210
pub chatgpt_base_url: Option<String>,
203211
}
204212

@@ -207,36 +215,50 @@ pub struct ProfileV2 {
207215
#[ts(export_to = "v2/")]
208216
pub struct Config {
209217
pub model: Option<String>,
218+
#[serde(alias = "review_model")]
210219
pub review_model: Option<String>,
220+
#[serde(alias = "model_context_window")]
211221
pub model_context_window: Option<i64>,
222+
#[serde(alias = "model_auto_compact_token_limit")]
212223
pub model_auto_compact_token_limit: Option<i64>,
224+
#[serde(alias = "model_provider")]
213225
pub model_provider: Option<String>,
214226
#[serde(
215227
default,
216228
deserialize_with = "deserialize_approval_policy",
217-
serialize_with = "serialize_approval_policy"
229+
serialize_with = "serialize_approval_policy",
230+
alias = "approval_policy"
218231
)]
219232
pub approval_policy: Option<AskForApproval>,
220233
#[serde(
221234
default,
222235
deserialize_with = "deserialize_sandbox_mode",
223-
serialize_with = "serialize_sandbox_mode"
236+
serialize_with = "serialize_sandbox_mode",
237+
alias = "sandbox_mode"
224238
)]
225239
pub sandbox_mode: Option<SandboxMode>,
240+
#[serde(alias = "sandbox_workspace_write")]
226241
pub sandbox_workspace_write: Option<SandboxWorkspaceWrite>,
242+
#[serde(alias = "forced_chatgpt_workspace_id")]
227243
pub forced_chatgpt_workspace_id: Option<String>,
244+
#[serde(alias = "forced_login_method")]
228245
pub forced_login_method: Option<ForcedLoginMethod>,
229246
pub tools: Option<ToolsV2>,
230247
pub profile: Option<String>,
231248
#[serde(default)]
232249
pub profiles: HashMap<String, ProfileV2>,
233250
pub instructions: Option<String>,
251+
#[serde(alias = "developer_instructions")]
234252
pub developer_instructions: Option<String>,
253+
#[serde(alias = "compact_prompt")]
235254
pub compact_prompt: Option<String>,
255+
#[serde(alias = "model_reasoning_effort")]
236256
pub model_reasoning_effort: Option<ReasoningEffort>,
257+
#[serde(alias = "model_reasoning_summary")]
237258
pub model_reasoning_summary: Option<ReasoningSummary>,
259+
#[serde(alias = "model_verbosity")]
238260
pub model_verbosity: Option<Verbosity>,
239-
#[serde(default, flatten)]
261+
#[serde(default, flatten, deserialize_with = "deserialize_additional")]
240262
pub additional: HashMap<String, JsonValue>,
241263
}
242264

@@ -279,6 +301,38 @@ where
279301
.serialize(serializer)
280302
}
281303

304+
fn deserialize_additional<'de, D>(deserializer: D) -> Result<HashMap<String, JsonValue>, D::Error>
305+
where
306+
D: serde::Deserializer<'de>,
307+
{
308+
let raw = HashMap::<String, JsonValue>::deserialize(deserializer)?;
309+
Ok(raw
310+
.into_iter()
311+
.map(|(key, value)| (snake_to_camel(&key), value))
312+
.collect())
313+
}
314+
315+
fn snake_to_camel(input: &str) -> String {
316+
let mut output = String::with_capacity(input.len());
317+
let mut upper_next = false;
318+
319+
for ch in input.chars() {
320+
if ch == '_' {
321+
upper_next = true;
322+
continue;
323+
}
324+
325+
if upper_next {
326+
output.push(ch.to_ascii_uppercase());
327+
upper_next = false;
328+
} else {
329+
output.push(ch);
330+
}
331+
}
332+
333+
output
334+
}
335+
282336
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
283337
#[serde(rename_all = "camelCase")]
284338
#[ts(export_to = "v2/")]
@@ -1840,7 +1894,7 @@ mod tests {
18401894

18411895
assert_eq!(
18421896
config.tools,
1843-
Some(Tools {
1897+
Some(ToolsV2 {
18441898
web_search: Some(true),
18451899
view_image: Some(false),
18461900
})

codex-rs/app-server/tests/suite/v2/config_rpc.rs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use codex_app_server_protocol::JSONRPCResponse;
1414
use codex_app_server_protocol::MergeStrategy;
1515
use codex_app_server_protocol::RequestId;
1616
use codex_app_server_protocol::SandboxMode;
17+
use codex_app_server_protocol::ToolsV2;
1718
use codex_app_server_protocol::WriteStatus;
1819
use pretty_assertions::assert_eq;
1920
use serde_json::json;
@@ -73,6 +74,64 @@ sandbox_mode = "workspace-write"
7374
Ok(())
7475
}
7576

77+
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
78+
async fn config_read_includes_tools() -> Result<()> {
79+
let codex_home = TempDir::new()?;
80+
write_config(
81+
&codex_home,
82+
r#"
83+
model = "gpt-user"
84+
85+
[tools]
86+
web_search = true
87+
view_image = false
88+
"#,
89+
)?;
90+
91+
let mut mcp = McpProcess::new(codex_home.path()).await?;
92+
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;
93+
94+
let request_id = mcp
95+
.send_config_read_request(ConfigReadParams {
96+
include_layers: true,
97+
})
98+
.await?;
99+
let resp: JSONRPCResponse = timeout(
100+
DEFAULT_READ_TIMEOUT,
101+
mcp.read_stream_until_response_message(RequestId::Integer(request_id)),
102+
)
103+
.await??;
104+
let ConfigReadResponse {
105+
config,
106+
origins,
107+
layers,
108+
} = to_response(resp)?;
109+
110+
let tools = config.tools.expect("tools present");
111+
assert_eq!(
112+
tools,
113+
ToolsV2 {
114+
web_search: Some(true),
115+
view_image: Some(false),
116+
}
117+
);
118+
assert_eq!(
119+
origins.get("tools.web_search").expect("origin").name,
120+
ConfigLayerName::User
121+
);
122+
assert_eq!(
123+
origins.get("tools.view_image").expect("origin").name,
124+
ConfigLayerName::User
125+
);
126+
127+
let layers = layers.expect("layers present");
128+
assert_eq!(layers.len(), 2);
129+
assert_eq!(layers[0].name, ConfigLayerName::SessionFlags);
130+
assert_eq!(layers[1].name, ConfigLayerName::User);
131+
132+
Ok(())
133+
}
134+
76135
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
77136
async fn config_read_includes_system_layer_and_overrides() -> Result<()> {
78137
let codex_home = TempDir::new()?;

0 commit comments

Comments
 (0)