mirror of
https://github.com/openai/codex.git
synced 2026-11-07 20:30:33 +00:00
## Why Supported clients currently receive only a reset-credit count from `account/rateLimits/read`. The redemption UI and other app-server clients need each available credit's expiry and ID so they can explain what will expire and consume the credit a user selected. This information belongs on the existing rate-limit read surface rather than a second app-server list RPC that clients would need to coordinate. ## What changed - extend `rateLimitResetCredits` on `account/rateLimits/read` with nullable `credits` detail rows - fetch usage and reset-credit details concurrently; if the detail request fails, times out, or cannot be parsed, preserve the usage response and return `credits: null` - expose each credit's ID, reset type, status, grant time, expiry time, title, and description - add an optional nullable `creditId` to `account/rateLimitResetCredit/consume`; omitting it preserves the existing automatic-selection behavior - forward a selected credit ID to the Codex backend and update the app-server documentation and generated schemas The TUI consumer is stacked in #30488. ## Validation - `just test -p codex-app-server-protocol` (251 passed) - `just test -p codex-backend-client` (16 passed) - `just test -p codex-app-server rate_limit` (18 passed) Part of #29618.
656 lines
23 KiB
Rust
656 lines
23 KiB
Rust
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 {
|
|
#[schemars(required, schema_with = "nullable_string_schema")]
|
|
email: Option<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 nullable_string_schema(
|
|
generator: &mut schemars::r#gen::SchemaGenerator,
|
|
) -> schemars::schema::Schema {
|
|
generator.subschema_for::<Option<String>>()
|
|
}
|
|
|
|
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,
|
|
/// Detail rows for available reset credits, when the backend provides them.
|
|
///
|
|
/// `null` means only `availableCount` is known, while an empty array means details were fetched
|
|
/// and no available credits were returned. The backend may cap this list, so its length can be
|
|
/// less than `availableCount`.
|
|
pub credits: Option<Vec<RateLimitResetCredit>>,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
|
#[serde(rename_all = "camelCase")]
|
|
#[ts(export_to = "v2/")]
|
|
pub struct RateLimitResetCredit {
|
|
/// Opaque backend identifier for this reset credit.
|
|
pub id: String,
|
|
pub reset_type: RateLimitResetType,
|
|
pub status: RateLimitResetCreditStatus,
|
|
/// Unix timestamp in seconds when the credit was granted.
|
|
#[ts(type = "number")]
|
|
pub granted_at: i64,
|
|
/// Unix timestamp in seconds when the credit expires, or `null` if it does not expire.
|
|
#[ts(type = "number | null")]
|
|
pub expires_at: Option<i64>,
|
|
/// Backend-provided display title for this credit, or `null` when unavailable.
|
|
pub title: Option<String>,
|
|
/// Backend-provided display description for this credit, or `null` when unavailable.
|
|
pub description: Option<String>,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
|
|
#[serde(rename_all = "camelCase")]
|
|
#[ts(export_to = "v2/", rename_all = "camelCase")]
|
|
pub enum RateLimitResetType {
|
|
CodexRateLimits,
|
|
#[serde(other)]
|
|
Unknown,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
|
|
#[serde(rename_all = "camelCase")]
|
|
#[ts(export_to = "v2/", rename_all = "camelCase")]
|
|
pub enum RateLimitResetCreditStatus {
|
|
Available,
|
|
Redeeming,
|
|
Redeemed,
|
|
#[serde(other)]
|
|
Unknown,
|
|
}
|
|
|
|
#[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,
|
|
/// Opaque reset-credit identifier to redeem. When omitted, the backend selects the next
|
|
/// available credit.
|
|
#[ts(optional = nullable)]
|
|
pub credit_id: Option<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 GetWorkspaceMessagesResponse {
|
|
/// Whether the workspace-message backend route is available for this client.
|
|
pub feature_enabled: bool,
|
|
/// Active workspace messages returned by the backend.
|
|
pub messages: Vec<WorkspaceMessage>,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
|
#[serde(rename_all = "camelCase")]
|
|
#[ts(export_to = "v2/")]
|
|
pub struct WorkspaceMessage {
|
|
pub message_id: String,
|
|
pub message_type: WorkspaceMessageType,
|
|
pub message_body: String,
|
|
/// Unix timestamp (in seconds) when the message was created.
|
|
#[ts(type = "number | null")]
|
|
pub created_at: Option<i64>,
|
|
/// Unix timestamp (in seconds) when the message was archived.
|
|
#[ts(type = "number | null")]
|
|
pub archived_at: Option<i64>,
|
|
}
|
|
|
|
#[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 WorkspaceMessageType {
|
|
Headline,
|
|
Announcement,
|
|
#[serde(other)]
|
|
Unknown,
|
|
}
|
|
|
|
#[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>,
|
|
}
|