Skip to content

Commit 22d4219

Browse files
committed
1.5.2 添加了账号切换前额度回写逻辑。
1 parent 01eb78d commit 22d4219

7 files changed

Lines changed: 455 additions & 11 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "codex_switch",
33
"private": true,
4-
"version": "1.5.1",
4+
"version": "1.5.2",
55
"type": "module",
66
"scripts": {
77
"dev": "vite",

src-tauri/Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src-tauri/Cargo.toml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "codex_switch"
3-
version = "1.5.1"
3+
version = "1.5.2"
44
description = "Native Tauri control panel for Codex account switching"
55
authors = ["Cmochance"]
66
edition = "2021"
@@ -24,4 +24,7 @@ tauri-plugin-opener = "2.5.3"
2424
custom-protocol = ["tauri/custom-protocol"]
2525

2626
[target.'cfg(any(target_os = "macos", windows, target_os = "linux"))'.dependencies]
27-
tauri-plugin-single-instance = "2.4.1"
27+
tauri-plugin-single-instance = "2.4.1"
28+
29+
30+

src-tauri/shared/runtime/profiles_index.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,13 +255,15 @@ fn build_current_card(entry: &ProfileIndexEntry, codex_home: &Path) -> CurrentCa
255255
}
256256
}
257257

258+
#[cfg_attr(not(target_os = "macos"), allow(dead_code))]
258259
fn quota_summary_has_data(quota: &crate::models::QuotaSummary) -> bool {
259260
quota.five_hour.remaining_percent.is_some()
260261
|| quota.five_hour.refresh_at.is_some()
261262
|| quota.weekly.remaining_percent.is_some()
262263
|| quota.weekly.refresh_at.is_some()
263264
}
264265

266+
#[cfg_attr(not(target_os = "macos"), allow(dead_code))]
265267
fn select_current_quota(
266268
entry: &ProfileIndexEntry,
267269
live_snapshot: Option<&super::session_usage::LocalQuotaSnapshot>,
@@ -309,6 +311,7 @@ pub fn load_profiles_snapshot(codex_home: Option<&Path>) -> AppResult<ProfilesSn
309311
})
310312
}
311313

314+
#[cfg_attr(not(target_os = "macos"), allow(dead_code))]
312315
pub fn load_current_live_quota(codex_home: Option<&Path>) -> AppResult<CurrentQuotaResponse> {
313316
let codex_home = codex_home.map(PathBuf::from).unwrap_or_else(get_codex_home);
314317
let index = load_profiles_index(Some(&codex_home))?;

src-tauri/win/runtime/mod.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,11 @@ pub mod bootstrap;
44
pub mod install;
55
pub mod process;
66
pub mod profile_actions;
7+
pub mod profiles_index;
78
pub mod refresh_runtime;
89
pub mod switch;
910

10-
pub use crate::shared::{
11-
config, fs_ops, metadata, paths, profiles, profiles_index, session_files, session_usage,
12-
};
11+
pub use crate::shared::{config, fs_ops, metadata, paths, profiles, session_files, session_usage};
1312

1413
pub mod actions {
1514
pub use super::profile_actions::{
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
use std::path::{Path, PathBuf};
2+
3+
use crate::errors::AppResult;
4+
use crate::models::{CurrentQuotaResponse, ProfileIndexEntry, ProfilesIndex};
5+
6+
use super::paths::get_codex_home;
7+
use super::session_usage::{
8+
load_latest_local_quota_snapshot, normalize_quota_summary, LocalQuotaSnapshot,
9+
};
10+
11+
pub use crate::shared::profiles_index::{load_profiles_index, load_profiles_snapshot};
12+
13+
fn select_current_quota(
14+
entry: &ProfileIndexEntry,
15+
live_snapshot: Option<&LocalQuotaSnapshot>,
16+
) -> crate::models::QuotaSummary {
17+
let stored_updated_at_ms = entry.stored_quota_updated_at_ms.unwrap_or(0);
18+
19+
match live_snapshot {
20+
Some(snapshot) if snapshot.source_mtime_ms.unwrap_or(0) > stored_updated_at_ms => {
21+
snapshot.quota.clone()
22+
}
23+
_ => entry.stored_quota.clone(),
24+
}
25+
}
26+
27+
pub fn load_current_live_quota(codex_home: Option<&Path>) -> AppResult<CurrentQuotaResponse> {
28+
let codex_home = codex_home.map(PathBuf::from).unwrap_or_else(get_codex_home);
29+
let index: ProfilesIndex = load_profiles_index(Some(&codex_home))?;
30+
let Some(current_profile) = index.current_profile.clone() else {
31+
return Ok(CurrentQuotaResponse {
32+
profile: None,
33+
quota: None,
34+
});
35+
};
36+
let Some(entry) = index
37+
.profiles
38+
.iter()
39+
.find(|profile| profile.folder_name == current_profile)
40+
else {
41+
return Ok(CurrentQuotaResponse {
42+
profile: Some(current_profile),
43+
quota: None,
44+
});
45+
};
46+
47+
let live_snapshot = load_latest_local_quota_snapshot(Some(&codex_home));
48+
let quota = normalize_quota_summary(
49+
Some(select_current_quota(entry, live_snapshot.as_ref())),
50+
entry.plan_name.as_deref(),
51+
entry.has_account_identity,
52+
);
53+
54+
Ok(CurrentQuotaResponse {
55+
profile: Some(entry.folder_name.clone()),
56+
quota: Some(quota),
57+
})
58+
}
59+
60+
#[cfg(test)]
61+
mod tests {
62+
use std::fs;
63+
use std::path::PathBuf;
64+
use std::time::{SystemTime, UNIX_EPOCH};
65+
66+
use super::load_current_live_quota;
67+
use crate::windows::env_guard;
68+
69+
fn temp_codex_home(name: &str) -> PathBuf {
70+
let unique = SystemTime::now()
71+
.duration_since(UNIX_EPOCH)
72+
.unwrap()
73+
.as_nanos();
74+
std::env::temp_dir().join(format!("codex-switch-win-profiles-index-{name}-{unique}"))
75+
}
76+
77+
fn write_quota_session(path: PathBuf, primary_used_percent: f64, secondary_used_percent: f64) {
78+
let line = format!(
79+
r#"{{"type":"event_msg","payload":{{"type":"token_count","rate_limits":{{"limit_id":"codex","primary":{{"used_percent":{primary_used_percent},"resets_at":1730000000,"window_minutes":300}},"secondary":{{"used_percent":{secondary_used_percent},"resets_at":1730600000,"window_minutes":10080}}}}}}}}"#
80+
);
81+
if let Some(parent) = path.parent() {
82+
fs::create_dir_all(parent).unwrap();
83+
}
84+
fs::write(path, format!("{line}\n")).unwrap();
85+
}
86+
87+
#[test]
88+
fn current_live_quota_prefers_stored_target_quota_when_it_was_primed_after_switch() {
89+
let _guard = env_guard();
90+
let codex_home = temp_codex_home("prefer-primed-stored");
91+
let profile_dir = codex_home.join("account_backup").join("b");
92+
fs::create_dir_all(&profile_dir).unwrap();
93+
fs::write(
94+
codex_home.join("account_backup").join(".current_profile"),
95+
"b\n",
96+
)
97+
.unwrap();
98+
fs::write(profile_dir.join("auth.json"), "profile-b-auth\n").unwrap();
99+
fs::write(
100+
profile_dir.join("profile.json"),
101+
r#"{"folder_name":"b","account_label":"b@example.com","quota":{"five_hour":{},"weekly":{}},"quota_updated_at_ms":9999999999999}"#,
102+
)
103+
.unwrap();
104+
write_quota_session(
105+
codex_home.join("sessions").join("session-001.jsonl"),
106+
11.0,
107+
12.0,
108+
);
109+
110+
let response = load_current_live_quota(Some(&codex_home)).unwrap();
111+
112+
assert_eq!(response.profile.as_deref(), Some("b"));
113+
let quota = response.quota.expect("expected quota");
114+
assert_eq!(quota.five_hour.remaining_percent, None);
115+
assert_eq!(quota.weekly.remaining_percent, None);
116+
117+
let _ = fs::remove_dir_all(&codex_home);
118+
}
119+
120+
#[test]
121+
fn current_live_quota_still_uses_live_session_when_it_is_newer_than_stored_quota() {
122+
let _guard = env_guard();
123+
let codex_home = temp_codex_home("prefer-live-when-newer");
124+
let profile_dir = codex_home.join("account_backup").join("b");
125+
fs::create_dir_all(&profile_dir).unwrap();
126+
fs::write(
127+
codex_home.join("account_backup").join(".current_profile"),
128+
"b\n",
129+
)
130+
.unwrap();
131+
fs::write(profile_dir.join("auth.json"), "profile-b-auth\n").unwrap();
132+
fs::write(
133+
profile_dir.join("profile.json"),
134+
r#"{"folder_name":"b","account_label":"b@example.com","quota":{"five_hour":{"remaining_percent":41},"weekly":{"remaining_percent":63}},"quota_updated_at_ms":1}"#,
135+
)
136+
.unwrap();
137+
write_quota_session(
138+
codex_home.join("sessions").join("session-001.jsonl"),
139+
11.0,
140+
12.0,
141+
);
142+
143+
let response = load_current_live_quota(Some(&codex_home)).unwrap();
144+
145+
assert_eq!(response.profile.as_deref(), Some("b"));
146+
let quota = response.quota.expect("expected quota");
147+
assert_eq!(quota.five_hour.remaining_percent, Some(89));
148+
assert_eq!(quota.weekly.remaining_percent, Some(88));
149+
150+
let _ = fs::remove_dir_all(&codex_home);
151+
}
152+
}

0 commit comments

Comments
 (0)