Files
1jehuang--jcode/crates/jcode-base/src/auth/account_store.rs
T
wehub-resource-sync a789495a98
FreeBSD Smoke / FreeBSD Smoke (x86_64) (push) Has been cancelled
CI / Quality Guardrails (push) Has been cancelled
CI / Build & Test (macos-latest) (push) Has been cancelled
CI / Build & Test (ubuntu-latest) (push) Has been cancelled
CI / Build & Test (windows-latest) (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / PowerShell Syntax (push) Has been cancelled
CI / Windows Cross-Target Check (Linux) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:10:34 +08:00

273 lines
7.2 KiB
Rust

use anyhow::Result;
use std::collections::HashMap;
use std::sync::{LazyLock, RwLock};
/// Runtime (process-local) active-account overrides, keyed by provider
/// prefix ("claude", "openai", ...). Lets `/account switch <label>` take
/// effect immediately without rewriting the provider auth file.
///
/// Centralized here so every provider shares one mechanism instead of
/// duplicating a `static ACTIVE_ACCOUNT_OVERRIDE` per module.
static RUNTIME_ACTIVE_OVERRIDES: LazyLock<RwLock<HashMap<&'static str, String>>> =
LazyLock::new(|| RwLock::new(HashMap::new()));
pub fn set_runtime_active_override(prefix: &'static str, label: Option<String>) {
if let Ok(mut overrides) = RUNTIME_ACTIVE_OVERRIDES.write() {
match label {
Some(label) => {
overrides.insert(prefix, label);
}
None => {
overrides.remove(prefix);
}
}
}
}
pub fn runtime_active_override(prefix: &str) -> Option<String> {
RUNTIME_ACTIVE_OVERRIDES
.read()
.ok()
.and_then(|overrides| overrides.get(prefix).cloned())
}
pub fn canonical_account_label(prefix: &str, index: usize) -> String {
format!("{prefix}-{index}")
}
pub fn next_account_label(prefix: &str, account_count: usize) -> String {
canonical_account_label(prefix, account_count + 1)
}
pub fn login_target_label<T, F>(
prefix: &str,
requested: Option<&str>,
active_label: Option<String>,
accounts: &[T],
label_of: F,
) -> String
where
F: Fn(&T) -> &str + Copy,
{
if let Some(requested) = requested
.map(str::trim)
.filter(|requested| !requested.is_empty())
{
if accounts
.iter()
.any(|account| label_of(account) == requested)
{
return requested.to_string();
}
return next_account_label(prefix, accounts.len());
}
active_label
.or_else(|| {
accounts
.first()
.map(|account| label_of(account).to_string())
})
.unwrap_or_else(|| canonical_account_label(prefix, 1))
}
pub fn active_account_label<T, F>(
override_label: Option<String>,
stored_active_label: Option<String>,
accounts: &[T],
label_of: F,
) -> Option<String>
where
F: Fn(&T) -> &str + Copy,
{
override_label.or(stored_active_label).or_else(|| {
accounts
.first()
.map(|account| label_of(account).to_string())
})
}
pub fn set_active_account<T, F>(
label: &str,
accounts: &[T],
stored_active_label: &mut Option<String>,
missing_message: &str,
label_of: F,
) -> Result<()>
where
F: Fn(&T) -> &str + Copy,
{
if !accounts.iter().any(|account| label_of(account) == label) {
anyhow::bail!(missing_message.replace("{}", label));
}
*stored_active_label = Some(label.to_string());
Ok(())
}
pub fn upsert_account<T, FGet, FSet>(
prefix: &str,
accounts: &mut Vec<T>,
stored_active_label: &mut Option<String>,
account: T,
label_of: FGet,
set_label: FSet,
) -> String
where
FGet: Fn(&T) -> &str + Copy,
FSet: Fn(&mut T, String) + Copy,
{
let requested_label = label_of(&account).to_string();
if let Some(existing) = accounts
.iter_mut()
.find(|existing| label_of(existing) == requested_label)
{
*existing = account;
return requested_label;
}
let label = next_account_label(prefix, accounts.len());
let mut account = account;
set_label(&mut account, label.clone());
accounts.push(account);
if stored_active_label.is_none() || accounts.len() == 1 {
*stored_active_label = Some(label.clone());
}
label
}
pub struct RelabelOutcome {
pub changed: bool,
pub canonical_override_label: Option<String>,
}
pub fn relabel_accounts<T, FGet, FSet>(
prefix: &str,
accounts: &mut [T],
stored_active_label: &mut Option<String>,
override_label: Option<String>,
label_of: FGet,
set_label: FSet,
) -> RelabelOutcome
where
FGet: Fn(&T) -> &str + Copy,
FSet: Fn(&mut T, String) + Copy,
{
let label_map = accounts
.iter()
.enumerate()
.map(|(index, account)| {
(
label_of(account).to_string(),
canonical_account_label(prefix, index + 1),
)
})
.collect::<Vec<_>>();
let mut changed = false;
for (account, (_, canonical_label)) in accounts.iter_mut().zip(label_map.iter()) {
if label_of(account) != canonical_label {
set_label(account, canonical_label.clone());
changed = true;
}
}
let desired_active = if accounts.is_empty() {
None
} else {
stored_active_label
.as_deref()
.and_then(|label| {
label_map
.iter()
.find(|(original, _)| original == label)
.map(|(_, canonical)| canonical.clone())
})
.or_else(|| {
accounts
.first()
.map(|account| label_of(account).to_string())
})
};
if *stored_active_label != desired_active {
*stored_active_label = desired_active;
changed = true;
}
let canonical_override_label = override_label.and_then(|override_label| {
label_map
.iter()
.find(|(original, _)| original == &override_label)
.and_then(|(_, canonical)| (override_label != *canonical).then(|| canonical.clone()))
});
RelabelOutcome {
changed,
canonical_override_label,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Clone)]
struct Account {
label: String,
}
#[test]
fn relabel_accounts_canonicalizes_labels_and_active_label() {
let mut accounts = vec![
Account {
label: "default".to_string(),
},
Account {
label: "other".to_string(),
},
];
let mut active = Some("other".to_string());
let outcome = relabel_accounts(
"openai",
&mut accounts,
&mut active,
Some("default".to_string()),
|account| account.label.as_str(),
|account, label| account.label = label,
);
assert!(outcome.changed);
assert_eq!(accounts[0].label, "openai-1");
assert_eq!(accounts[1].label, "openai-2");
assert_eq!(active.as_deref(), Some("openai-2"));
assert_eq!(
outcome.canonical_override_label.as_deref(),
Some("openai-1")
);
}
#[test]
fn upsert_account_assigns_next_label_and_sets_initial_active() {
let mut accounts = Vec::<Account>::new();
let mut active = None;
let label = upsert_account(
"claude",
&mut accounts,
&mut active,
Account {
label: "ignored".to_string(),
},
|account| account.label.as_str(),
|account, label| account.label = label,
);
assert_eq!(label, "claude-1");
assert_eq!(accounts[0].label, "claude-1");
assert_eq!(active.as_deref(), Some("claude-1"));
}
}