Codex Handbook
app-server-protocol/src/protocol/v2/account.rs 560 lines
use crate::protocol::common::AuthMode;use codex_experimental_api_macros::ExperimentalApi;use codex_protocol::account::AmazonBedrockCredentialSource;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 codex_protocol::protocol::SpendControlLimitSnapshot as CoreSpendControlLimitSnapshot;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 {        #[serde(default = "default_bedrock_credential_source")]        credential_source: AmazonBedrockCredentialSource,    },}fn default_bedrock_credential_source() -> AmazonBedrockCredentialSource {    AmazonBedrockCredentialSource::AwsManaged}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 { credential_source } => {                Self::AmazonBedrock { credential_source }            }        }    }}#[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, Eq, JsonSchema, TS)]#[serde(rename_all = "camelCase")]#[ts(export_to = "v2/")]pub struct AccountSessionsAddParams {    #[serde(default, skip_serializing_if = "std::ops::Not::not")]    pub switch_to_added_account: bool,}#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]#[serde(rename_all = "camelCase")]#[ts(export_to = "v2/")]pub struct AccountSessionsListParams {    #[serde(default, skip_serializing_if = "std::ops::Not::not")]    pub refresh_workspace_metadata: bool,}#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]#[serde(rename_all = "camelCase")]#[ts(export_to = "v2/")]pub struct AccountSessionsLogoutParams {    pub session_id: String,}#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]#[serde(rename_all = "camelCase")]#[ts(export_to = "v2/")]pub struct AccountSessionsSwitchParams {    pub session_id: String,    pub account_id: String,}#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]#[serde(rename_all = "camelCase")]#[ts(export_to = "v2/")]pub struct AccountSessionsResponse {    pub active_session_id: Option<String>,    pub sessions: Vec<AccountSession>,}#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]#[serde(rename_all = "camelCase")]#[ts(export_to = "v2/")]pub struct AccountSession {    pub session_id: String,    pub email: Option<String>,    pub user_id: Option<String>,    pub display_name: Option<String>,    pub image_url: Option<String>,    pub last_used_at: i64,    pub is_active: bool,    pub selected_workspace_account_id: Option<String>,    pub workspaces: Vec<AccountSessionWorkspace>,}#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]#[serde(rename_all = "camelCase")]#[ts(export_to = "v2/")]pub struct AccountSessionWorkspace {    pub account_id: String,    pub name: Option<String>,    pub image_url: Option<String>,    pub kind: Option<AccountSessionWorkspaceKind>,}#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]#[serde(rename_all = "camelCase")]#[ts(export_to = "v2/")]pub enum AccountSessionWorkspaceKind {    Personal,    Workspace,}#[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>>,    pub rate_limit_reset_credits: Option<RateLimitResetCreditsSummary>,}#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]#[serde(rename_all = "camelCase")]#[ts(export_to = "v2/")]pub struct RateLimitResetCreditsSummary {    pub available_count: i64,}#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]#[serde(rename_all = "camelCase")]#[ts(export_to = "v2/")]pub struct ConsumeAccountRateLimitResetCreditParams {    /// Identifies one logical reset attempt. A UUID is recommended; reuse the same value when    /// retrying that attempt.    pub idempotency_key: String,}#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]#[serde(rename_all = "camelCase")]#[ts(export_to = "v2/")]pub struct ConsumeAccountRateLimitResetCreditResponse {    pub outcome: ConsumeAccountRateLimitResetCreditOutcome,}#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]#[serde(rename_all = "camelCase")]#[ts(export_to = "v2/", rename_all = "camelCase")]pub enum ConsumeAccountRateLimitResetCreditOutcome {    /// A reset credit was consumed and the eligible rate-limit windows were reset.    Reset,    /// No current rate-limit window is eligible for a reset.    NothingToReset,    /// The account has no earned reset credits available.    NoCredit,    /// The same idempotency key already completed a reset successfully.    AlreadyRedeemed,}#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]#[serde(rename_all = "camelCase")]#[ts(export_to = "v2/")]pub struct GetAccountTokenUsageResponse {    pub summary: AccountTokenUsageSummary,    pub daily_usage_buckets: Option<Vec<AccountTokenUsageDailyBucket>>,}#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]#[serde(rename_all = "camelCase")]#[ts(export_to = "v2/")]pub struct AccountTokenUsageSummary {    pub lifetime_tokens: Option<i64>,    pub peak_daily_tokens: Option<i64>,    pub longest_running_turn_sec: Option<i64>,    pub current_streak_days: Option<i64>,    pub longest_streak_days: Option<i64>,}#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]#[serde(rename_all = "camelCase")]#[ts(export_to = "v2/")]pub struct AccountTokenUsageDailyBucket {    pub start_date: String,    pub tokens: i64,}#[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, skip_serializing_if = "std::ops::Not::not")]    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/")]/// Sparse rolling rate-limit update.////// Clients should merge available values into the most recent `account/rateLimits/read` response/// or refetch that snapshot. Nullable account metadata may be unavailable in a rolling update and/// does not clear a previously observed value.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 individual_limit: Option<SpendControlLimitSnapshot>,    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),            individual_limit: value.individual_limit.map(SpendControlLimitSnapshot::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 SpendControlLimitSnapshot {    pub limit: String,    pub used: String,    pub remaining_percent: i32,    #[ts(type = "number")]    pub resets_at: i64,}impl From<CoreSpendControlLimitSnapshot> for SpendControlLimitSnapshot {    fn from(value: CoreSpendControlLimitSnapshot) -> Self {        Self {            limit: value.limit,            used: value.used,            remaining_percent: value.remaining_percent,            resets_at: value.resets_at,        }    }}#[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>,}