mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
## Why `codex-rs/app-server-protocol/src/protocol/v2.rs` had grown into a single ~12k-line definition file for the entire app-server v2 API. This is purely a mechanical refactor to break up the monolithic `v2.rs` file that contains all app-server API v2 types into more modular files, grouped by resource (e.g. account, thread, turn, etc.). `just write-app-server-schema` shows no real changes, so we can be sure that this is purely an internal organizational change. ## What changed - Replaced the monolithic `protocol/v2.rs` with a `protocol/v2/` module tree and a small `mod.rs` that only declares and reexports modules. - Grouped v2 API definitions by conceptual owner, including `account`, `apps`, `collaboration_mode`, `command_exec`, `config`, `device_key`, `experimental_feature`, `feedback`, `fs`, `hook`, `item`, `mcp`, `model`, `notification`, `permissions`, `plugin`, `process`, `realtime`, `review`, `thread`, `thread_data`, `turn`, and `windows_sandbox`. - Moved v2 tests into `protocol/v2/tests.rs` so `mod.rs` stays small. - Kept shared protocol helpers in `protocol/v2/shared.rs`, including the enum mirroring macro and common cross-resource types. - Co-located resource-specific notifications and server-request payloads with the modules that own those resources. - Regenerated app-server protocol schema fixtures. The schema diffs are non-semantic newline-only changes after the refactor. ## Verification - `cargo check -p codex-app-server-protocol` - `cargo test -p codex-app-server-protocol` - `just write-app-server-schema`
317 lines
12 KiB
Rust
317 lines
12 KiB
Rust
use codex_experimental_api_macros::ExperimentalApi;
|
|
use codex_protocol::config_types::ApprovalsReviewer as CoreApprovalsReviewer;
|
|
use codex_protocol::config_types::SandboxMode as CoreSandboxMode;
|
|
use codex_protocol::protocol::AskForApproval as CoreAskForApproval;
|
|
use codex_protocol::protocol::CodexErrorInfo as CoreCodexErrorInfo;
|
|
use codex_protocol::protocol::GranularApprovalConfig as CoreGranularApprovalConfig;
|
|
use codex_protocol::protocol::NonSteerableTurnKind as CoreNonSteerableTurnKind;
|
|
use schemars::JsonSchema;
|
|
use schemars::r#gen::SchemaGenerator;
|
|
use schemars::schema::InstanceType;
|
|
use schemars::schema::Metadata;
|
|
use schemars::schema::Schema;
|
|
use schemars::schema::SchemaObject;
|
|
use serde::Deserialize;
|
|
use serde::Serialize;
|
|
use serde_json::Value as JsonValue;
|
|
use ts_rs::TS;
|
|
|
|
// Macro to declare a camelCased API v2 enum mirroring a core enum which
|
|
// tends to use either snake_case or kebab-case.
|
|
macro_rules! v2_enum_from_core {
|
|
(
|
|
$(#[$enum_meta:meta])*
|
|
pub enum $Name:ident from $Src:path {
|
|
$( $(#[$variant_meta:meta])* $Variant:ident ),+ $(,)?
|
|
}
|
|
) => {
|
|
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
|
|
$(#[$enum_meta])*
|
|
#[serde(rename_all = "camelCase")]
|
|
#[ts(export_to = "v2/")]
|
|
pub enum $Name {
|
|
$( $(#[$variant_meta])* $Variant ),+
|
|
}
|
|
|
|
impl $Name {
|
|
pub fn to_core(self) -> $Src {
|
|
match self { $( $Name::$Variant => <$Src>::$Variant ),+ }
|
|
}
|
|
}
|
|
|
|
impl From<$Src> for $Name {
|
|
fn from(value: $Src) -> Self {
|
|
match value { $( <$Src>::$Variant => $Name::$Variant ),+ }
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
pub(super) use v2_enum_from_core;
|
|
|
|
pub(super) const fn default_enabled() -> bool {
|
|
true
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
|
|
#[serde(rename_all = "camelCase")]
|
|
#[ts(export_to = "v2/")]
|
|
pub enum NonSteerableTurnKind {
|
|
Review,
|
|
Compact,
|
|
}
|
|
|
|
/// This translation layer make sure that we expose codex error code in camel case.
|
|
///
|
|
/// When an upstream HTTP status is available (for example, from the Responses API or a provider),
|
|
/// it is forwarded in `httpStatusCode` on the relevant `codexErrorInfo` variant.
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
|
#[serde(rename_all = "camelCase")]
|
|
#[ts(export_to = "v2/")]
|
|
pub enum CodexErrorInfo {
|
|
ContextWindowExceeded,
|
|
UsageLimitExceeded,
|
|
ServerOverloaded,
|
|
CyberPolicy,
|
|
HttpConnectionFailed {
|
|
#[serde(rename = "httpStatusCode")]
|
|
#[ts(rename = "httpStatusCode")]
|
|
http_status_code: Option<u16>,
|
|
},
|
|
/// Failed to connect to the response SSE stream.
|
|
ResponseStreamConnectionFailed {
|
|
#[serde(rename = "httpStatusCode")]
|
|
#[ts(rename = "httpStatusCode")]
|
|
http_status_code: Option<u16>,
|
|
},
|
|
InternalServerError,
|
|
Unauthorized,
|
|
BadRequest,
|
|
ThreadRollbackFailed,
|
|
SandboxError,
|
|
/// The response SSE stream disconnected in the middle of a turn before completion.
|
|
ResponseStreamDisconnected {
|
|
#[serde(rename = "httpStatusCode")]
|
|
#[ts(rename = "httpStatusCode")]
|
|
http_status_code: Option<u16>,
|
|
},
|
|
/// Reached the retry limit for responses.
|
|
ResponseTooManyFailedAttempts {
|
|
#[serde(rename = "httpStatusCode")]
|
|
#[ts(rename = "httpStatusCode")]
|
|
http_status_code: Option<u16>,
|
|
},
|
|
/// Returned when `turn/start` or `turn/steer` is submitted while the current active turn
|
|
/// cannot accept same-turn steering, for example `/review` or manual `/compact`.
|
|
ActiveTurnNotSteerable {
|
|
#[serde(rename = "turnKind")]
|
|
#[ts(rename = "turnKind")]
|
|
turn_kind: NonSteerableTurnKind,
|
|
},
|
|
Other,
|
|
}
|
|
|
|
impl From<CoreCodexErrorInfo> for CodexErrorInfo {
|
|
fn from(value: CoreCodexErrorInfo) -> Self {
|
|
match value {
|
|
CoreCodexErrorInfo::ContextWindowExceeded => CodexErrorInfo::ContextWindowExceeded,
|
|
CoreCodexErrorInfo::UsageLimitExceeded => CodexErrorInfo::UsageLimitExceeded,
|
|
CoreCodexErrorInfo::ServerOverloaded => CodexErrorInfo::ServerOverloaded,
|
|
CoreCodexErrorInfo::CyberPolicy => CodexErrorInfo::CyberPolicy,
|
|
CoreCodexErrorInfo::HttpConnectionFailed { http_status_code } => {
|
|
CodexErrorInfo::HttpConnectionFailed { http_status_code }
|
|
}
|
|
CoreCodexErrorInfo::ResponseStreamConnectionFailed { http_status_code } => {
|
|
CodexErrorInfo::ResponseStreamConnectionFailed { http_status_code }
|
|
}
|
|
CoreCodexErrorInfo::InternalServerError => CodexErrorInfo::InternalServerError,
|
|
CoreCodexErrorInfo::Unauthorized => CodexErrorInfo::Unauthorized,
|
|
CoreCodexErrorInfo::BadRequest => CodexErrorInfo::BadRequest,
|
|
CoreCodexErrorInfo::ThreadRollbackFailed => CodexErrorInfo::ThreadRollbackFailed,
|
|
CoreCodexErrorInfo::SandboxError => CodexErrorInfo::SandboxError,
|
|
CoreCodexErrorInfo::ResponseStreamDisconnected { http_status_code } => {
|
|
CodexErrorInfo::ResponseStreamDisconnected { http_status_code }
|
|
}
|
|
CoreCodexErrorInfo::ResponseTooManyFailedAttempts { http_status_code } => {
|
|
CodexErrorInfo::ResponseTooManyFailedAttempts { http_status_code }
|
|
}
|
|
CoreCodexErrorInfo::ActiveTurnNotSteerable { turn_kind } => {
|
|
CodexErrorInfo::ActiveTurnNotSteerable {
|
|
turn_kind: turn_kind.into(),
|
|
}
|
|
}
|
|
CoreCodexErrorInfo::Other => CodexErrorInfo::Other,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<CoreNonSteerableTurnKind> for NonSteerableTurnKind {
|
|
fn from(value: CoreNonSteerableTurnKind) -> Self {
|
|
match value {
|
|
CoreNonSteerableTurnKind::Review => Self::Review,
|
|
CoreNonSteerableTurnKind::Compact => Self::Compact,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(
|
|
Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS, ExperimentalApi,
|
|
)]
|
|
#[serde(rename_all = "kebab-case")]
|
|
#[ts(rename_all = "kebab-case", export_to = "v2/")]
|
|
pub enum AskForApproval {
|
|
#[serde(rename = "untrusted")]
|
|
#[ts(rename = "untrusted")]
|
|
UnlessTrusted,
|
|
OnFailure,
|
|
OnRequest,
|
|
#[experimental("askForApproval.granular")]
|
|
Granular {
|
|
sandbox_approval: bool,
|
|
rules: bool,
|
|
#[serde(default)]
|
|
skill_approval: bool,
|
|
#[serde(default)]
|
|
request_permissions: bool,
|
|
mcp_elicitations: bool,
|
|
},
|
|
Never,
|
|
}
|
|
|
|
impl AskForApproval {
|
|
pub fn to_core(self) -> CoreAskForApproval {
|
|
match self {
|
|
AskForApproval::UnlessTrusted => CoreAskForApproval::UnlessTrusted,
|
|
AskForApproval::OnFailure => CoreAskForApproval::OnFailure,
|
|
AskForApproval::OnRequest => CoreAskForApproval::OnRequest,
|
|
AskForApproval::Granular {
|
|
sandbox_approval,
|
|
rules,
|
|
skill_approval,
|
|
request_permissions,
|
|
mcp_elicitations,
|
|
} => CoreAskForApproval::Granular(CoreGranularApprovalConfig {
|
|
sandbox_approval,
|
|
rules,
|
|
skill_approval,
|
|
request_permissions,
|
|
mcp_elicitations,
|
|
}),
|
|
AskForApproval::Never => CoreAskForApproval::Never,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<CoreAskForApproval> for AskForApproval {
|
|
fn from(value: CoreAskForApproval) -> Self {
|
|
match value {
|
|
CoreAskForApproval::UnlessTrusted => AskForApproval::UnlessTrusted,
|
|
CoreAskForApproval::OnFailure => AskForApproval::OnFailure,
|
|
CoreAskForApproval::OnRequest => AskForApproval::OnRequest,
|
|
CoreAskForApproval::Granular(granular_config) => AskForApproval::Granular {
|
|
sandbox_approval: granular_config.sandbox_approval,
|
|
rules: granular_config.rules,
|
|
skill_approval: granular_config.skill_approval,
|
|
request_permissions: granular_config.request_permissions,
|
|
mcp_elicitations: granular_config.mcp_elicitations,
|
|
},
|
|
CoreAskForApproval::Never => AskForApproval::Never,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, TS)]
|
|
#[ts(
|
|
type = r#""user" | "auto_review" | "guardian_subagent""#,
|
|
export_to = "v2/"
|
|
)]
|
|
/// Configures who approval requests are routed to for review. Examples
|
|
/// include sandbox escapes, blocked network access, MCP approval prompts, and
|
|
/// ARC escalations. Defaults to `user`. `auto_review` uses a carefully
|
|
/// prompted subagent to gather relevant context and apply a risk-based
|
|
/// decision framework before approving or denying the request.
|
|
pub enum ApprovalsReviewer {
|
|
#[serde(rename = "user")]
|
|
User,
|
|
#[serde(rename = "guardian_subagent", alias = "auto_review")]
|
|
AutoReview,
|
|
}
|
|
|
|
impl JsonSchema for ApprovalsReviewer {
|
|
fn schema_name() -> String {
|
|
"ApprovalsReviewer".to_string()
|
|
}
|
|
|
|
fn json_schema(_generator: &mut SchemaGenerator) -> Schema {
|
|
string_enum_schema_with_description(
|
|
&["user", "auto_review", "guardian_subagent"],
|
|
"Configures who approval requests are routed to for review. Examples include sandbox escapes, blocked network access, MCP approval prompts, and ARC escalations. Defaults to `user`. `auto_review` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request. The legacy value `guardian_subagent` is accepted for compatibility.",
|
|
)
|
|
}
|
|
}
|
|
|
|
fn string_enum_schema_with_description(values: &[&str], description: &str) -> Schema {
|
|
let mut schema = SchemaObject {
|
|
instance_type: Some(InstanceType::String.into()),
|
|
metadata: Some(Box::new(Metadata {
|
|
description: Some(description.to_string()),
|
|
..Default::default()
|
|
})),
|
|
..Default::default()
|
|
};
|
|
schema.enum_values = Some(
|
|
values
|
|
.iter()
|
|
.map(|value| JsonValue::String((*value).to_string()))
|
|
.collect(),
|
|
);
|
|
Schema::Object(schema)
|
|
}
|
|
|
|
impl ApprovalsReviewer {
|
|
pub fn to_core(self) -> CoreApprovalsReviewer {
|
|
match self {
|
|
ApprovalsReviewer::User => CoreApprovalsReviewer::User,
|
|
ApprovalsReviewer::AutoReview => CoreApprovalsReviewer::AutoReview,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<CoreApprovalsReviewer> for ApprovalsReviewer {
|
|
fn from(value: CoreApprovalsReviewer) -> Self {
|
|
match value {
|
|
CoreApprovalsReviewer::User => ApprovalsReviewer::User,
|
|
CoreApprovalsReviewer::AutoReview => ApprovalsReviewer::AutoReview,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
|
|
#[serde(rename_all = "kebab-case")]
|
|
#[ts(rename_all = "kebab-case", export_to = "v2/")]
|
|
pub enum SandboxMode {
|
|
ReadOnly,
|
|
WorkspaceWrite,
|
|
DangerFullAccess,
|
|
}
|
|
|
|
impl SandboxMode {
|
|
pub fn to_core(self) -> CoreSandboxMode {
|
|
match self {
|
|
SandboxMode::ReadOnly => CoreSandboxMode::ReadOnly,
|
|
SandboxMode::WorkspaceWrite => CoreSandboxMode::WorkspaceWrite,
|
|
SandboxMode::DangerFullAccess => CoreSandboxMode::DangerFullAccess,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<CoreSandboxMode> for SandboxMode {
|
|
fn from(value: CoreSandboxMode) -> Self {
|
|
match value {
|
|
CoreSandboxMode::ReadOnly => SandboxMode::ReadOnly,
|
|
CoreSandboxMode::WorkspaceWrite => SandboxMode::WorkspaceWrite,
|
|
CoreSandboxMode::DangerFullAccess => SandboxMode::DangerFullAccess,
|
|
}
|
|
}
|
|
}
|