mirror of
https://github.com/openai/codex.git
synced 2026-05-25 13:34:51 +00: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`
384 lines
14 KiB
Rust
384 lines
14 KiB
Rust
use crate::protocol::common::AuthMode;
|
|
use codex_experimental_api_macros::ExperimentalApi;
|
|
use codex_protocol::account::PlanType;
|
|
use codex_protocol::account::ProviderAccount;
|
|
use codex_protocol::protocol::CreditsSnapshot as CoreCreditsSnapshot;
|
|
use codex_protocol::protocol::RateLimitReachedType as CoreRateLimitReachedType;
|
|
use codex_protocol::protocol::RateLimitSnapshot as CoreRateLimitSnapshot;
|
|
use codex_protocol::protocol::RateLimitWindow as CoreRateLimitWindow;
|
|
use schemars::JsonSchema;
|
|
use serde::Deserialize;
|
|
use serde::Serialize;
|
|
use std::collections::HashMap;
|
|
use ts_rs::TS;
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
|
#[serde(tag = "type", rename_all = "camelCase")]
|
|
#[ts(tag = "type")]
|
|
#[ts(export_to = "v2/")]
|
|
pub enum Account {
|
|
#[serde(rename = "apiKey", rename_all = "camelCase")]
|
|
#[ts(rename = "apiKey", rename_all = "camelCase")]
|
|
ApiKey {},
|
|
|
|
#[serde(rename = "chatgpt", rename_all = "camelCase")]
|
|
#[ts(rename = "chatgpt", rename_all = "camelCase")]
|
|
Chatgpt { email: String, plan_type: PlanType },
|
|
|
|
#[serde(rename = "amazonBedrock", rename_all = "camelCase")]
|
|
#[ts(rename = "amazonBedrock", rename_all = "camelCase")]
|
|
AmazonBedrock {},
|
|
}
|
|
|
|
impl From<ProviderAccount> for Account {
|
|
fn from(account: ProviderAccount) -> Self {
|
|
match account {
|
|
ProviderAccount::ApiKey => Self::ApiKey {},
|
|
ProviderAccount::Chatgpt { email, plan_type } => Self::Chatgpt { email, plan_type },
|
|
ProviderAccount::AmazonBedrock => Self::AmazonBedrock {},
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS, ExperimentalApi)]
|
|
#[serde(tag = "type")]
|
|
#[ts(tag = "type")]
|
|
#[ts(export_to = "v2/")]
|
|
pub enum LoginAccountParams {
|
|
#[serde(rename = "apiKey", rename_all = "camelCase")]
|
|
#[ts(rename = "apiKey", rename_all = "camelCase")]
|
|
ApiKey {
|
|
#[serde(rename = "apiKey")]
|
|
#[ts(rename = "apiKey")]
|
|
api_key: String,
|
|
},
|
|
#[serde(rename = "chatgpt", rename_all = "camelCase")]
|
|
#[ts(rename = "chatgpt", rename_all = "camelCase")]
|
|
Chatgpt {
|
|
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
|
|
codex_streamlined_login: bool,
|
|
},
|
|
#[serde(rename = "chatgptDeviceCode")]
|
|
#[ts(rename = "chatgptDeviceCode")]
|
|
ChatgptDeviceCode,
|
|
/// [UNSTABLE] FOR OPENAI INTERNAL USE ONLY - DO NOT USE.
|
|
/// The access token must contain the same scopes that Codex-managed ChatGPT auth tokens have.
|
|
#[experimental("account/login/start.chatgptAuthTokens")]
|
|
#[serde(rename = "chatgptAuthTokens", rename_all = "camelCase")]
|
|
#[ts(rename = "chatgptAuthTokens", rename_all = "camelCase")]
|
|
ChatgptAuthTokens {
|
|
/// Access token (JWT) supplied by the client.
|
|
/// This token is used for backend API requests and email extraction.
|
|
access_token: String,
|
|
/// Workspace/account identifier supplied by the client.
|
|
chatgpt_account_id: String,
|
|
/// Optional plan type supplied by the client.
|
|
///
|
|
/// When `null`, Codex attempts to derive the plan type from access-token
|
|
/// claims. If unavailable, the plan defaults to `unknown`.
|
|
#[ts(optional = nullable)]
|
|
chatgpt_plan_type: Option<String>,
|
|
},
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
|
#[serde(tag = "type", rename_all = "camelCase")]
|
|
#[ts(tag = "type")]
|
|
#[ts(export_to = "v2/")]
|
|
pub enum LoginAccountResponse {
|
|
#[serde(rename = "apiKey", rename_all = "camelCase")]
|
|
#[ts(rename = "apiKey", rename_all = "camelCase")]
|
|
ApiKey {},
|
|
#[serde(rename = "chatgpt", rename_all = "camelCase")]
|
|
#[ts(rename = "chatgpt", rename_all = "camelCase")]
|
|
Chatgpt {
|
|
// Use plain String for identifiers to avoid TS/JSON Schema quirks around uuid-specific types.
|
|
// Convert to/from UUIDs at the application layer as needed.
|
|
login_id: String,
|
|
/// URL the client should open in a browser to initiate the OAuth flow.
|
|
auth_url: String,
|
|
},
|
|
#[serde(rename = "chatgptDeviceCode", rename_all = "camelCase")]
|
|
#[ts(rename = "chatgptDeviceCode", rename_all = "camelCase")]
|
|
ChatgptDeviceCode {
|
|
// Use plain String for identifiers to avoid TS/JSON Schema quirks around uuid-specific types.
|
|
// Convert to/from UUIDs at the application layer as needed.
|
|
login_id: String,
|
|
/// URL the client should open in a browser to complete device code authorization.
|
|
verification_url: String,
|
|
/// One-time code the user must enter after signing in.
|
|
user_code: String,
|
|
},
|
|
#[serde(rename = "chatgptAuthTokens", rename_all = "camelCase")]
|
|
#[ts(rename = "chatgptAuthTokens", rename_all = "camelCase")]
|
|
ChatgptAuthTokens {},
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
|
#[serde(rename_all = "camelCase")]
|
|
#[ts(export_to = "v2/")]
|
|
pub struct CancelLoginAccountParams {
|
|
pub login_id: String,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
|
#[serde(rename_all = "camelCase")]
|
|
#[ts(rename_all = "camelCase")]
|
|
#[ts(export_to = "v2/")]
|
|
pub enum CancelLoginAccountStatus {
|
|
Canceled,
|
|
NotFound,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
|
#[serde(rename_all = "camelCase")]
|
|
#[ts(export_to = "v2/")]
|
|
pub struct CancelLoginAccountResponse {
|
|
pub status: CancelLoginAccountStatus,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
|
#[serde(rename_all = "camelCase")]
|
|
#[ts(export_to = "v2/")]
|
|
pub struct LogoutAccountResponse {}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
|
|
#[serde(rename_all = "camelCase")]
|
|
#[ts(export_to = "v2/")]
|
|
pub enum ChatgptAuthTokensRefreshReason {
|
|
/// Codex attempted a backend request and received `401 Unauthorized`.
|
|
Unauthorized,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
|
#[serde(rename_all = "camelCase")]
|
|
#[ts(export_to = "v2/")]
|
|
pub struct ChatgptAuthTokensRefreshParams {
|
|
pub reason: ChatgptAuthTokensRefreshReason,
|
|
/// Workspace/account identifier that Codex was previously using.
|
|
///
|
|
/// Clients that manage multiple accounts/workspaces can use this as a hint
|
|
/// to refresh the token for the correct workspace.
|
|
///
|
|
/// This may be `null` when the prior auth state did not include a workspace
|
|
/// identifier (`chatgpt_account_id`).
|
|
#[ts(optional = nullable)]
|
|
pub previous_account_id: Option<String>,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
|
#[serde(rename_all = "camelCase")]
|
|
#[ts(export_to = "v2/")]
|
|
pub struct ChatgptAuthTokensRefreshResponse {
|
|
pub access_token: String,
|
|
pub chatgpt_account_id: String,
|
|
pub chatgpt_plan_type: Option<String>,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
|
#[serde(rename_all = "camelCase")]
|
|
#[ts(export_to = "v2/")]
|
|
pub struct GetAccountRateLimitsResponse {
|
|
/// Backward-compatible single-bucket view; mirrors the historical payload.
|
|
pub rate_limits: RateLimitSnapshot,
|
|
/// Multi-bucket view keyed by metered `limit_id` (for example, `codex`).
|
|
pub rate_limits_by_limit_id: Option<HashMap<String, RateLimitSnapshot>>,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
|
#[serde(rename_all = "camelCase")]
|
|
#[ts(export_to = "v2/")]
|
|
pub struct SendAddCreditsNudgeEmailParams {
|
|
pub credit_type: AddCreditsNudgeCreditType,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
|
|
#[serde(rename_all = "snake_case")]
|
|
#[ts(export_to = "v2/", rename_all = "snake_case")]
|
|
pub enum AddCreditsNudgeCreditType {
|
|
Credits,
|
|
UsageLimit,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
|
#[serde(rename_all = "camelCase")]
|
|
#[ts(export_to = "v2/")]
|
|
pub struct SendAddCreditsNudgeEmailResponse {
|
|
pub status: AddCreditsNudgeEmailStatus,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
|
|
#[serde(rename_all = "snake_case")]
|
|
#[ts(export_to = "v2/", rename_all = "snake_case")]
|
|
pub enum AddCreditsNudgeEmailStatus {
|
|
Sent,
|
|
CooldownActive,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
|
#[serde(rename_all = "camelCase")]
|
|
#[ts(export_to = "v2/")]
|
|
pub struct GetAccountParams {
|
|
/// When `true`, requests a proactive token refresh before returning.
|
|
///
|
|
/// In managed auth mode this triggers the normal refresh-token flow. In
|
|
/// external auth mode this flag is ignored. Clients should refresh tokens
|
|
/// themselves and call `account/login/start` with `chatgptAuthTokens`.
|
|
#[serde(default)]
|
|
pub refresh_token: bool,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
|
#[serde(rename_all = "camelCase")]
|
|
#[ts(export_to = "v2/")]
|
|
pub struct GetAccountResponse {
|
|
pub account: Option<Account>,
|
|
pub requires_openai_auth: bool,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
|
#[serde(rename_all = "camelCase")]
|
|
#[ts(export_to = "v2/")]
|
|
pub struct AccountUpdatedNotification {
|
|
pub auth_mode: Option<AuthMode>,
|
|
pub plan_type: Option<PlanType>,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
|
#[serde(rename_all = "camelCase")]
|
|
#[ts(export_to = "v2/")]
|
|
pub struct AccountRateLimitsUpdatedNotification {
|
|
pub rate_limits: RateLimitSnapshot,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
|
#[serde(rename_all = "camelCase")]
|
|
#[ts(export_to = "v2/")]
|
|
pub struct RateLimitSnapshot {
|
|
pub limit_id: Option<String>,
|
|
pub limit_name: Option<String>,
|
|
pub primary: Option<RateLimitWindow>,
|
|
pub secondary: Option<RateLimitWindow>,
|
|
pub credits: Option<CreditsSnapshot>,
|
|
pub plan_type: Option<PlanType>,
|
|
pub rate_limit_reached_type: Option<RateLimitReachedType>,
|
|
}
|
|
|
|
impl From<CoreRateLimitSnapshot> for RateLimitSnapshot {
|
|
fn from(value: CoreRateLimitSnapshot) -> Self {
|
|
Self {
|
|
limit_id: value.limit_id,
|
|
limit_name: value.limit_name,
|
|
primary: value.primary.map(RateLimitWindow::from),
|
|
secondary: value.secondary.map(RateLimitWindow::from),
|
|
credits: value.credits.map(CreditsSnapshot::from),
|
|
plan_type: value.plan_type,
|
|
rate_limit_reached_type: value
|
|
.rate_limit_reached_type
|
|
.map(RateLimitReachedType::from),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
|
|
#[serde(rename_all = "snake_case")]
|
|
#[ts(export_to = "v2/", rename_all = "snake_case")]
|
|
pub enum RateLimitReachedType {
|
|
RateLimitReached,
|
|
WorkspaceOwnerCreditsDepleted,
|
|
WorkspaceMemberCreditsDepleted,
|
|
WorkspaceOwnerUsageLimitReached,
|
|
WorkspaceMemberUsageLimitReached,
|
|
}
|
|
|
|
impl From<CoreRateLimitReachedType> for RateLimitReachedType {
|
|
fn from(value: CoreRateLimitReachedType) -> Self {
|
|
match value {
|
|
CoreRateLimitReachedType::RateLimitReached => Self::RateLimitReached,
|
|
CoreRateLimitReachedType::WorkspaceOwnerCreditsDepleted => {
|
|
Self::WorkspaceOwnerCreditsDepleted
|
|
}
|
|
CoreRateLimitReachedType::WorkspaceMemberCreditsDepleted => {
|
|
Self::WorkspaceMemberCreditsDepleted
|
|
}
|
|
CoreRateLimitReachedType::WorkspaceOwnerUsageLimitReached => {
|
|
Self::WorkspaceOwnerUsageLimitReached
|
|
}
|
|
CoreRateLimitReachedType::WorkspaceMemberUsageLimitReached => {
|
|
Self::WorkspaceMemberUsageLimitReached
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<RateLimitReachedType> for CoreRateLimitReachedType {
|
|
fn from(value: RateLimitReachedType) -> Self {
|
|
match value {
|
|
RateLimitReachedType::RateLimitReached => Self::RateLimitReached,
|
|
RateLimitReachedType::WorkspaceOwnerCreditsDepleted => {
|
|
Self::WorkspaceOwnerCreditsDepleted
|
|
}
|
|
RateLimitReachedType::WorkspaceMemberCreditsDepleted => {
|
|
Self::WorkspaceMemberCreditsDepleted
|
|
}
|
|
RateLimitReachedType::WorkspaceOwnerUsageLimitReached => {
|
|
Self::WorkspaceOwnerUsageLimitReached
|
|
}
|
|
RateLimitReachedType::WorkspaceMemberUsageLimitReached => {
|
|
Self::WorkspaceMemberUsageLimitReached
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
|
#[serde(rename_all = "camelCase")]
|
|
#[ts(export_to = "v2/")]
|
|
pub struct RateLimitWindow {
|
|
pub used_percent: i32,
|
|
#[ts(type = "number | null")]
|
|
pub window_duration_mins: Option<i64>,
|
|
#[ts(type = "number | null")]
|
|
pub resets_at: Option<i64>,
|
|
}
|
|
|
|
impl From<CoreRateLimitWindow> for RateLimitWindow {
|
|
fn from(value: CoreRateLimitWindow) -> Self {
|
|
Self {
|
|
used_percent: value.used_percent.round() as i32,
|
|
window_duration_mins: value.window_minutes,
|
|
resets_at: value.resets_at,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
|
#[serde(rename_all = "camelCase")]
|
|
#[ts(export_to = "v2/")]
|
|
pub struct CreditsSnapshot {
|
|
pub has_credits: bool,
|
|
pub unlimited: bool,
|
|
pub balance: Option<String>,
|
|
}
|
|
|
|
impl From<CoreCreditsSnapshot> for CreditsSnapshot {
|
|
fn from(value: CoreCreditsSnapshot) -> Self {
|
|
Self {
|
|
has_credits: value.has_credits,
|
|
unlimited: value.unlimited,
|
|
balance: value.balance,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
|
#[serde(rename_all = "camelCase")]
|
|
#[ts(export_to = "v2/")]
|
|
pub struct AccountLoginCompletedNotification {
|
|
// Use plain String for identifiers to avoid TS/JSON Schema quirks around uuid-specific types.
|
|
// Convert to/from UUIDs at the application layer as needed.
|
|
pub login_id: Option<String>,
|
|
pub success: bool,
|
|
pub error: Option<String>,
|
|
}
|