Skip to content

Commit fb4695f

Browse files
committed
[app-server] define new v2 approval request
1 parent f01f2ec commit fb4695f

File tree

2 files changed

+254
-38
lines changed

2 files changed

+254
-38
lines changed

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

Lines changed: 57 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ use codex_protocol::parse_command::ParsedCommand;
1414
use codex_protocol::protocol::FileChange;
1515
use codex_protocol::protocol::ReviewDecision;
1616
use codex_protocol::protocol::SandboxCommandAssessment;
17-
use paste::paste;
1817
use schemars::JsonSchema;
1918
use serde::Deserialize;
2019
use serde::Serialize;
@@ -303,44 +302,46 @@ macro_rules! server_request_definitions {
303302
(
304303
$(
305304
$(#[$variant_meta:meta])*
306-
$variant:ident
305+
$variant:ident $(=> $wire:literal)? {
306+
params: $params:ty,
307+
response: $response:ty,
308+
}
307309
),* $(,)?
308310
) => {
309-
paste! {
310-
/// Request initiated from the server and sent to the client.
311-
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
312-
#[serde(tag = "method", rename_all = "camelCase")]
313-
pub enum ServerRequest {
314-
$(
315-
$(#[$variant_meta])*
316-
$variant {
317-
#[serde(rename = "id")]
318-
request_id: RequestId,
319-
params: [<$variant Params>],
320-
},
321-
)*
322-
}
311+
/// Request initiated from the server and sent to the client.
312+
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
313+
#[serde(tag = "method", rename_all = "camelCase")]
314+
pub enum ServerRequest {
315+
$(
316+
$(#[$variant_meta])*
317+
$(#[serde(rename = $wire)] #[ts(rename = $wire)])?
318+
$variant {
319+
#[serde(rename = "id")]
320+
request_id: RequestId,
321+
params: $params,
322+
},
323+
)*
324+
}
323325

324-
#[derive(Debug, Clone, PartialEq, JsonSchema)]
325-
pub enum ServerRequestPayload {
326-
$( $variant([<$variant Params>]), )*
327-
}
326+
#[derive(Debug, Clone, PartialEq, JsonSchema)]
327+
pub enum ServerRequestPayload {
328+
$( $variant($params), )*
329+
}
328330

329-
impl ServerRequestPayload {
330-
pub fn request_with_id(self, request_id: RequestId) -> ServerRequest {
331-
match self {
332-
$(Self::$variant(params) => ServerRequest::$variant { request_id, params },)*
333-
}
331+
impl ServerRequestPayload {
332+
pub fn request_with_id(self, request_id: RequestId) -> ServerRequest {
333+
match self {
334+
$(Self::$variant(params) => ServerRequest::$variant { request_id, params },)*
334335
}
335336
}
336337
}
337338

338339
pub fn export_server_responses(
339340
out_dir: &::std::path::Path,
340341
) -> ::std::result::Result<(), ::ts_rs::ExportError> {
341-
paste! {
342-
$(<[<$variant Response>] as ::ts_rs::TS>::export_all_to(out_dir)?;)*
343-
}
342+
$(
343+
<$response as ::ts_rs::TS>::export_all_to(out_dir)?;
344+
)*
344345
Ok(())
345346
}
346347

@@ -349,9 +350,12 @@ macro_rules! server_request_definitions {
349350
out_dir: &Path,
350351
) -> ::anyhow::Result<Vec<GeneratedSchema>> {
351352
let mut schemas = Vec::new();
352-
paste! {
353-
$(schemas.push(crate::export::write_json_schema::<[<$variant Response>]>(out_dir, stringify!([<$variant Response>]))?);)*
354-
}
353+
$(
354+
schemas.push(crate::export::write_json_schema::<$response>(
355+
out_dir,
356+
concat!(stringify!($variant), "Response"),
357+
)?);
358+
)*
355359
Ok(schemas)
356360
}
357361

@@ -360,9 +364,12 @@ macro_rules! server_request_definitions {
360364
out_dir: &Path,
361365
) -> ::anyhow::Result<Vec<GeneratedSchema>> {
362366
let mut schemas = Vec::new();
363-
paste! {
364-
$(schemas.push(crate::export::write_json_schema::<[<$variant Params>]>(out_dir, stringify!([<$variant Params>]))?);)*
365-
}
367+
$(
368+
schemas.push(crate::export::write_json_schema::<$params>(
369+
out_dir,
370+
concat!(stringify!($variant), "Params"),
371+
)?);
372+
)*
366373
Ok(schemas)
367374
}
368375
};
@@ -452,10 +459,24 @@ impl TryFrom<JSONRPCRequest> for ServerRequest {
452459
}
453460

454461
server_request_definitions! {
462+
/// NEW APIs
463+
/// Sent when approval is requested for a specific item (e.g. file edit, command execution).
464+
ItemRequestApproval => "item/requestApproval" {
465+
params: v2::ItemRequestApprovalParams,
466+
response: v2::ItemRequestApprovalResponse,
467+
},
468+
469+
/// DEPRECATED APIs below
455470
/// Request to approve a patch.
456-
ApplyPatchApproval,
471+
ApplyPatchApproval {
472+
params: ApplyPatchApprovalParams,
473+
response: ApplyPatchApprovalResponse,
474+
},
457475
/// Request to exec a command.
458-
ExecCommandApproval,
476+
ExecCommandApproval {
477+
params: ExecCommandApprovalParams,
478+
response: ExecCommandApprovalResponse,
479+
},
459480
}
460481

461482
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]

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

Lines changed: 197 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@ use std::path::PathBuf;
44
use crate::protocol::common::AuthMode;
55
use codex_protocol::ConversationId;
66
use codex_protocol::account::PlanType;
7+
use codex_protocol::approvals::SandboxCommandAssessment as CoreSandboxCommandAssessment;
78
use codex_protocol::config_types::ReasoningEffort;
89
use codex_protocol::config_types::ReasoningSummary;
10+
use codex_protocol::parse_command::ParsedCommand as CoreParsedCommand;
11+
use codex_protocol::protocol::FileChange as CoreFileChange;
912
use codex_protocol::protocol::RateLimitSnapshot as CoreRateLimitSnapshot;
1013
use codex_protocol::protocol::RateLimitWindow as CoreRateLimitWindow;
1114
use codex_protocol::user_input::UserInput as CoreUserInput;
@@ -17,7 +20,7 @@ use serde_json::Value as JsonValue;
1720
use ts_rs::TS;
1821

1922
// Macro to declare a camelCased API v2 enum mirroring a core enum which
20-
// tends to use kebab-case.
23+
// tends to use either snake_case or kebab-case.
2124
macro_rules! v2_enum_from_core {
2225
(
2326
pub enum $Name:ident from $Src:path { $( $Variant:ident ),+ $(,)? }
@@ -53,6 +56,23 @@ v2_enum_from_core!(
5356
}
5457
);
5558

59+
v2_enum_from_core!(
60+
pub enum ReviewDecision from codex_protocol::protocol::ReviewDecision {
61+
Approved,
62+
ApprovedForSession,
63+
Denied,
64+
Abort
65+
}
66+
);
67+
68+
v2_enum_from_core!(
69+
pub enum SandboxRiskLevel from codex_protocol::approvals::SandboxRiskLevel {
70+
Low,
71+
Medium,
72+
High
73+
}
74+
);
75+
5676
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
5777
#[serde(tag = "mode", rename_all = "camelCase")]
5878
#[ts(tag = "mode")]
@@ -116,6 +136,131 @@ impl From<codex_protocol::protocol::SandboxPolicy> for SandboxPolicy {
116136
}
117137
}
118138

139+
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
140+
#[serde(rename_all = "camelCase")]
141+
#[ts(export_to = "v2/")]
142+
pub struct SandboxCommandAssessment {
143+
pub description: String,
144+
pub risk_level: SandboxRiskLevel,
145+
}
146+
147+
impl SandboxCommandAssessment {
148+
pub fn into_core(self) -> CoreSandboxCommandAssessment {
149+
CoreSandboxCommandAssessment {
150+
description: self.description,
151+
risk_level: self.risk_level.to_core(),
152+
}
153+
}
154+
}
155+
156+
impl From<CoreSandboxCommandAssessment> for SandboxCommandAssessment {
157+
fn from(value: CoreSandboxCommandAssessment) -> Self {
158+
Self {
159+
description: value.description,
160+
risk_level: SandboxRiskLevel::from(value.risk_level),
161+
}
162+
}
163+
}
164+
165+
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
166+
#[serde(tag = "type", rename_all = "camelCase")]
167+
#[ts(tag = "type")]
168+
#[ts(export_to = "v2/")]
169+
pub enum ParsedCommand {
170+
Read {
171+
cmd: String,
172+
name: String,
173+
path: PathBuf,
174+
},
175+
ListFiles {
176+
cmd: String,
177+
path: Option<String>,
178+
},
179+
Search {
180+
cmd: String,
181+
query: Option<String>,
182+
path: Option<String>,
183+
},
184+
Unknown {
185+
cmd: String,
186+
},
187+
}
188+
189+
impl ParsedCommand {
190+
pub fn into_core(self) -> CoreParsedCommand {
191+
match self {
192+
ParsedCommand::Read { cmd, name, path } => CoreParsedCommand::Read { cmd, name, path },
193+
ParsedCommand::ListFiles { cmd, path } => CoreParsedCommand::ListFiles { cmd, path },
194+
ParsedCommand::Search { cmd, query, path } => {
195+
CoreParsedCommand::Search { cmd, query, path }
196+
}
197+
ParsedCommand::Unknown { cmd } => CoreParsedCommand::Unknown { cmd },
198+
}
199+
}
200+
}
201+
202+
impl From<CoreParsedCommand> for ParsedCommand {
203+
fn from(value: CoreParsedCommand) -> Self {
204+
match value {
205+
CoreParsedCommand::Read { cmd, name, path } => ParsedCommand::Read { cmd, name, path },
206+
CoreParsedCommand::ListFiles { cmd, path } => ParsedCommand::ListFiles { cmd, path },
207+
CoreParsedCommand::Search { cmd, query, path } => {
208+
ParsedCommand::Search { cmd, query, path }
209+
}
210+
CoreParsedCommand::Unknown { cmd } => ParsedCommand::Unknown { cmd },
211+
}
212+
}
213+
}
214+
215+
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
216+
#[serde(tag = "type", rename_all = "camelCase")]
217+
#[ts(tag = "type")]
218+
#[ts(export_to = "v2/")]
219+
pub enum FileChange {
220+
Add {
221+
content: String,
222+
},
223+
Delete {
224+
content: String,
225+
},
226+
Update {
227+
unified_diff: String,
228+
move_path: Option<PathBuf>,
229+
},
230+
}
231+
232+
impl FileChange {
233+
pub fn into_core(self) -> CoreFileChange {
234+
match self {
235+
FileChange::Add { content } => CoreFileChange::Add { content },
236+
FileChange::Delete { content } => CoreFileChange::Delete { content },
237+
FileChange::Update {
238+
unified_diff,
239+
move_path,
240+
} => CoreFileChange::Update {
241+
unified_diff,
242+
move_path,
243+
},
244+
}
245+
}
246+
}
247+
248+
impl From<CoreFileChange> for FileChange {
249+
fn from(value: CoreFileChange) -> Self {
250+
match value {
251+
CoreFileChange::Add { content } => FileChange::Add { content },
252+
CoreFileChange::Delete { content } => FileChange::Delete { content },
253+
CoreFileChange::Update {
254+
unified_diff,
255+
move_path,
256+
} => FileChange::Update {
257+
unified_diff,
258+
move_path,
259+
},
260+
}
261+
}
262+
}
263+
119264
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
120265
#[serde(tag = "type", rename_all = "camelCase")]
121266
#[ts(tag = "type")]
@@ -276,7 +421,7 @@ pub struct ThreadStartParams {
276421
pub cwd: Option<String>,
277422
pub approval_policy: Option<AskForApproval>,
278423
pub sandbox: Option<SandboxMode>,
279-
pub config: Option<HashMap<String, serde_json::Value>>,
424+
pub config: Option<HashMap<String, JsonValue>>,
280425
pub base_instructions: Option<String>,
281426
pub developer_instructions: Option<String>,
282427
}
@@ -655,6 +800,56 @@ pub struct McpToolCallProgressNotification {
655800
pub message: String,
656801
}
657802

803+
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
804+
#[serde(rename_all = "camelCase")]
805+
#[ts(export_to = "v2/")]
806+
pub struct ItemRequestApprovalParams {
807+
pub thread_id: String,
808+
pub turn_id: String,
809+
pub item_id: String,
810+
pub request: ItemApprovalRequest,
811+
}
812+
813+
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
814+
#[serde(tag = "type", rename_all = "camelCase")]
815+
#[ts(tag = "type")]
816+
#[ts(export_to = "v2/")]
817+
pub enum ItemApprovalRequest {
818+
CommandExecution(CommandExecutionRequest),
819+
FileEdit(FileEditRequest),
820+
}
821+
822+
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
823+
#[serde(rename_all = "camelCase")]
824+
#[ts(export_to = "v2/")]
825+
pub struct CommandExecutionRequest {
826+
pub call_id: String,
827+
pub command: Vec<String>,
828+
pub cwd: PathBuf,
829+
/// Optional explanatory reason (e.g. request for network access).
830+
pub reason: Option<String>,
831+
pub risk: Option<SandboxCommandAssessment>,
832+
pub parsed_cmd: Vec<ParsedCommand>,
833+
}
834+
835+
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
836+
#[serde(rename_all = "camelCase")]
837+
#[ts(export_to = "v2/")]
838+
pub struct FileEditRequest {
839+
pub call_id: String,
840+
pub file_changes: HashMap<PathBuf, FileChange>,
841+
/// Optional explanatory reason (e.g. request for extra write access).
842+
pub reason: Option<String>,
843+
pub grant_root: Option<PathBuf>,
844+
}
845+
846+
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
847+
#[serde(rename_all = "camelCase")]
848+
#[ts(export_to = "v2/")]
849+
pub struct ItemRequestApprovalResponse {
850+
pub decision: ReviewDecision,
851+
}
852+
658853
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
659854
#[serde(rename_all = "camelCase")]
660855
#[ts(export_to = "v2/")]

0 commit comments

Comments
 (0)