mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
[profile-switcher][rust] -- [1/2] Add app-server account session protocol (#25469)
## Summary Adds the app-server v2 `accountSession/*` protocol used by the Desktop profile switcher and the backend account metadata client needed to populate workspace choices. This is the protocol layer only. The app-server lifecycle and consolidated saved-session storage are split into a follow-up PR. ## Rust Stack 1. This PR 2. [openai/codex#25383](https://github.com/openai/codex/pull/25383) adds app-server session lifecycle behavior and consolidated saved-session storage. ## Validation - Generated app-server schema fixtures are included from the existing generation flow in the lifecycle PR where the routes are registered. - Did not run tests per requested scope.
This commit is contained in:
committed by
GitHub
Unverified
parent
57ab4c89e0
commit
a2ebe07b39
@@ -138,6 +138,78 @@ 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/")]
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::types::AccountsCheckResponse;
|
||||
use crate::types::CodeTaskDetailsResponse;
|
||||
use crate::types::ConfigBundleResponse;
|
||||
use crate::types::PaginatedListTaskListItem;
|
||||
@@ -302,6 +303,16 @@ impl Client {
|
||||
Ok(Self::rate_limit_snapshots_from_payload(payload))
|
||||
}
|
||||
|
||||
pub async fn get_accounts_check(&self) -> Result<AccountsCheckResponse> {
|
||||
let url = match self.path_style {
|
||||
PathStyle::CodexApi => format!("{}/api/codex/accounts/check", self.base_url),
|
||||
PathStyle::ChatGptApi => format!("{}/wham/accounts/check", self.base_url),
|
||||
};
|
||||
let req = self.http.get(&url).headers(self.headers());
|
||||
let (body, ct) = self.exec_request(req, "GET", &url).await?;
|
||||
self.decode_json(&url, &ct, &body)
|
||||
}
|
||||
|
||||
pub async fn send_add_credits_nudge_email(
|
||||
&self,
|
||||
credit_type: AddCreditsNudgeCreditType,
|
||||
|
||||
@@ -4,6 +4,8 @@ pub(crate) mod types;
|
||||
pub use client::AddCreditsNudgeCreditType;
|
||||
pub use client::Client;
|
||||
pub use client::RequestError;
|
||||
pub use types::AccountEntry;
|
||||
pub use types::AccountsCheckResponse;
|
||||
pub use types::CodeTaskDetailsResponse;
|
||||
pub use types::CodeTaskDetailsResponseExt;
|
||||
pub use types::ConfigBundleResponse;
|
||||
|
||||
@@ -17,6 +17,93 @@ use serde::de::Deserializer;
|
||||
use serde_json::Value;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct AccountsCheckResponse {
|
||||
pub accounts: Vec<AccountEntry>,
|
||||
pub account_ordering: Vec<String>,
|
||||
pub default_account_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct AccountEntry {
|
||||
pub id: String,
|
||||
#[serde(default)]
|
||||
pub name: Option<String>,
|
||||
#[serde(default)]
|
||||
pub profile_picture_url: Option<String>,
|
||||
#[serde(default)]
|
||||
pub structure: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct RawAccountsCheckResponse {
|
||||
#[serde(default)]
|
||||
accounts: RawAccounts,
|
||||
#[serde(default)]
|
||||
account_ordering: Vec<String>,
|
||||
#[serde(default)]
|
||||
default_account_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(untagged)]
|
||||
enum RawAccounts {
|
||||
List(Vec<AccountEntry>),
|
||||
Map(HashMap<String, ChatGptAccountEntry>),
|
||||
}
|
||||
|
||||
impl Default for RawAccounts {
|
||||
fn default() -> Self {
|
||||
Self::List(Vec::new())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct ChatGptAccountEntry {
|
||||
account: ChatGptAccountInfo,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct ChatGptAccountInfo {
|
||||
account_id: Option<String>,
|
||||
#[serde(default)]
|
||||
name: Option<String>,
|
||||
#[serde(default)]
|
||||
profile_picture_url: Option<String>,
|
||||
#[serde(default)]
|
||||
structure: String,
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for AccountsCheckResponse {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let raw = RawAccountsCheckResponse::deserialize(deserializer)?;
|
||||
let accounts = match raw.accounts {
|
||||
RawAccounts::List(accounts) => accounts,
|
||||
RawAccounts::Map(mut accounts) => raw
|
||||
.account_ordering
|
||||
.iter()
|
||||
.filter_map(|account_id| {
|
||||
let account = accounts.remove(account_id)?.account;
|
||||
Some(AccountEntry {
|
||||
id: account.account_id?,
|
||||
name: account.name,
|
||||
profile_picture_url: account.profile_picture_url,
|
||||
structure: account.structure,
|
||||
})
|
||||
})
|
||||
.collect(),
|
||||
};
|
||||
Ok(Self {
|
||||
accounts,
|
||||
account_ordering: raw.account_ordering,
|
||||
default_account_id: raw.default_account_id,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Hand-rolled models for the Cloud Tasks task-details response.
|
||||
/// The generated OpenAPI models are pretty bad. This is a half-step
|
||||
/// towards hand-rolling them.
|
||||
|
||||
Reference in New Issue
Block a user