Skip to content

Commit 90add3b

Browse files
committed
snake case camel case shenanigans
1 parent 81007a1 commit 90add3b

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
@@ -167,21 +167,23 @@ pub enum ConfigLayerName {
167167
#[serde(rename_all = "camelCase")]
168168
#[ts(export_to = "v2/")]
169169
pub struct SandboxWorkspaceWrite {
170-
#[serde(default)]
170+
#[serde(default, alias = "writable_roots")]
171171
pub writable_roots: Vec<PathBuf>,
172-
#[serde(default)]
172+
#[serde(default, alias = "network_access")]
173173
pub network_access: bool,
174-
#[serde(default)]
174+
#[serde(default, alias = "exclude_tmpdir_env_var")]
175175
pub exclude_tmpdir_env_var: bool,
176-
#[serde(default)]
176+
#[serde(default, alias = "exclude_slash_tmp")]
177177
pub exclude_slash_tmp: bool,
178178
}
179179

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

@@ -190,16 +192,22 @@ pub struct ToolsV2 {
190192
#[ts(export_to = "v2/")]
191193
pub struct ProfileV2 {
192194
pub model: Option<String>,
195+
#[serde(alias = "model_provider")]
193196
pub model_provider: Option<String>,
194197
#[serde(
195198
default,
196199
deserialize_with = "deserialize_approval_policy",
197-
serialize_with = "serialize_approval_policy"
200+
serialize_with = "serialize_approval_policy",
201+
alias = "approval_policy"
198202
)]
199203
pub approval_policy: Option<AskForApproval>,
204+
#[serde(alias = "model_reasoning_effort")]
200205
pub model_reasoning_effort: Option<ReasoningEffort>,
206+
#[serde(alias = "model_reasoning_summary")]
201207
pub model_reasoning_summary: Option<ReasoningSummary>,
208+
#[serde(alias = "model_verbosity")]
202209
pub model_verbosity: Option<Verbosity>,
210+
#[serde(alias = "chatgpt_base_url")]
203211
pub chatgpt_base_url: Option<String>,
204212
}
205213

@@ -208,36 +216,50 @@ pub struct ProfileV2 {
208216
#[ts(export_to = "v2/")]
209217
pub struct Config {
210218
pub model: Option<String>,
219+
#[serde(alias = "review_model")]
211220
pub review_model: Option<String>,
221+
#[serde(alias = "model_context_window")]
212222
pub model_context_window: Option<i64>,
223+
#[serde(alias = "model_auto_compact_token_limit")]
213224
pub model_auto_compact_token_limit: Option<i64>,
225+
#[serde(alias = "model_provider")]
214226
pub model_provider: Option<String>,
215227
#[serde(
216228
default,
217229
deserialize_with = "deserialize_approval_policy",
218-
serialize_with = "serialize_approval_policy"
230+
serialize_with = "serialize_approval_policy",
231+
alias = "approval_policy"
219232
)]
220233
pub approval_policy: Option<AskForApproval>,
221234
#[serde(
222235
default,
223236
deserialize_with = "deserialize_sandbox_mode",
224-
serialize_with = "serialize_sandbox_mode"
237+
serialize_with = "serialize_sandbox_mode",
238+
alias = "sandbox_mode"
225239
)]
226240
pub sandbox_mode: Option<SandboxMode>,
241+
#[serde(alias = "sandbox_workspace_write")]
227242
pub sandbox_workspace_write: Option<SandboxWorkspaceWrite>,
243+
#[serde(alias = "forced_chatgpt_workspace_id")]
228244
pub forced_chatgpt_workspace_id: Option<String>,
245+
#[serde(alias = "forced_login_method")]
229246
pub forced_login_method: Option<ForcedLoginMethod>,
230247
pub tools: Option<ToolsV2>,
231248
pub profile: Option<String>,
232249
#[serde(default)]
233250
pub profiles: HashMap<String, ProfileV2>,
234251
pub instructions: Option<String>,
252+
#[serde(alias = "developer_instructions")]
235253
pub developer_instructions: Option<String>,
254+
#[serde(alias = "compact_prompt")]
236255
pub compact_prompt: Option<String>,
256+
#[serde(alias = "model_reasoning_effort")]
237257
pub model_reasoning_effort: Option<ReasoningEffort>,
258+
#[serde(alias = "model_reasoning_summary")]
238259
pub model_reasoning_summary: Option<ReasoningSummary>,
260+
#[serde(alias = "model_verbosity")]
239261
pub model_verbosity: Option<Verbosity>,
240-
#[serde(default, flatten)]
262+
#[serde(default, flatten, deserialize_with = "deserialize_additional")]
241263
pub additional: HashMap<String, JsonValue>,
242264
}
243265

@@ -280,6 +302,38 @@ where
280302
.serialize(serializer)
281303
}
282304

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

18361890
assert_eq!(
18371891
config.tools,
1838-
Some(Tools {
1892+
Some(ToolsV2 {
18391893
web_search: Some(true),
18401894
view_image: Some(false),
18411895
})

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)