Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 52 additions & 2 deletions codex-rs/tui/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use crate::app_event_sender::AppEventSender;
use crate::app_server_session::AppServerSession;
use crate::app_server_session::AppServerStartedThread;
use crate::app_server_session::ThreadSessionState;
use crate::app_server_session::app_server_rate_limit_snapshots_to_core;
use crate::bottom_pane::ApprovalRequest;
use crate::bottom_pane::FeedbackAudience;
use crate::bottom_pane::McpServerElicitationFormRequest;
Expand Down Expand Up @@ -55,6 +56,7 @@ use codex_app_server_client::TypedRequestError;
use codex_app_server_protocol::ClientRequest;
use codex_app_server_protocol::CodexErrorInfo as AppServerCodexErrorInfo;
use codex_app_server_protocol::ConfigLayerSource;
use codex_app_server_protocol::GetAccountRateLimitsResponse;
use codex_app_server_protocol::ListMcpServerStatusParams;
use codex_app_server_protocol::ListMcpServerStatusResponse;
use codex_app_server_protocol::McpServerStatus;
Expand Down Expand Up @@ -108,6 +110,7 @@ use codex_protocol::protocol::ListSkillsResponseEvent;
#[cfg(test)]
use codex_protocol::protocol::McpAuthStatus;
use codex_protocol::protocol::Op;
use codex_protocol::protocol::RateLimitSnapshot;
use codex_protocol::protocol::SandboxPolicy;
use codex_protocol::protocol::SessionSource;
use codex_protocol::protocol::SkillErrorInfo;
Expand Down Expand Up @@ -1864,6 +1867,20 @@ impl App {
});
}

fn refresh_rate_limits(&mut self, app_server: &AppServerSession, show_status: bool) {
let request_handle = app_server.request_handle();
let app_event_tx = self.app_event_tx.clone();
tokio::spawn(async move {
let result = fetch_account_rate_limits(request_handle)
.await
.map_err(|err| err.to_string());
app_event_tx.send(AppEvent::RateLimitsLoaded {
result,
show_status,
});
});
}

fn fetch_plugins_list(&mut self, app_server: &AppServerSession, cwd: PathBuf) {
let request_handle = app_server.request_handle();
let app_event_tx = self.app_event_tx.clone();
Expand Down Expand Up @@ -4058,8 +4075,26 @@ impl App {
AppEvent::FileSearchResult { query, matches } => {
self.chat_widget.apply_file_search_result(query, matches);
}
AppEvent::RateLimitSnapshotFetched(snapshot) => {
self.chat_widget.on_rate_limit_snapshot(Some(snapshot));
AppEvent::RefreshRateLimits { show_status } => {
self.refresh_rate_limits(app_server, show_status);
}
AppEvent::RateLimitsLoaded {
result,
show_status,
} => {
match result {
Ok(snapshots) => {
for snapshot in snapshots {
self.chat_widget.on_rate_limit_snapshot(Some(snapshot));
}
}
Err(err) => {
tracing::warn!("account/rateLimits/read failed during TUI refresh: {err}");
}
}
if show_status {
self.chat_widget.add_status_output();
}
}
AppEvent::ConnectorsLoaded { result, is_final } => {
self.chat_widget.on_connectors_loaded(result, is_final);
Expand Down Expand Up @@ -5629,6 +5664,21 @@ async fn fetch_all_mcp_server_statuses(
Ok(statuses)
}

async fn fetch_account_rate_limits(
request_handle: AppServerRequestHandle,
) -> Result<Vec<RateLimitSnapshot>> {
let request_id = RequestId::String(format!("account-rate-limits-{}", Uuid::new_v4()));
let response: GetAccountRateLimitsResponse = request_handle
.request_typed(ClientRequest::GetAccountRateLimits {
request_id,
params: None,
})
.await
.wrap_err("account/rateLimits/read failed in TUI")?;

Ok(app_server_rate_limit_snapshots_to_core(response))
}

async fn fetch_plugins_list(
request_handle: AppServerRequestHandle,
cwd: PathBuf,
Expand Down
13 changes: 10 additions & 3 deletions codex-rs/tui/src/app_event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,9 +137,16 @@ pub(crate) enum AppEvent {
matches: Vec<FileMatch>,
},

/// Result of refreshing rate limits
#[allow(dead_code)]
RateLimitSnapshotFetched(RateLimitSnapshot),
/// Refresh account rate limits, optionally rendering `/status` once the read finishes.
RefreshRateLimits {
show_status: bool,
},

/// Result of refreshing rate limits.
RateLimitsLoaded {
result: Result<Vec<RateLimitSnapshot>, String>,
show_status: bool,
},

/// Result of prefetching connectors.
ConnectorsLoaded {
Expand Down
2 changes: 1 addition & 1 deletion codex-rs/tui/src/app_server_session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1063,7 +1063,7 @@ async fn thread_session_state_from_thread_response(
})
}

fn app_server_rate_limit_snapshots_to_core(
pub(crate) fn app_server_rate_limit_snapshots_to_core(
response: GetAccountRateLimitsResponse,
) -> Vec<RateLimitSnapshot> {
let mut snapshots = Vec::new();
Expand Down
7 changes: 6 additions & 1 deletion codex-rs/tui/src/chatwidget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5152,7 +5152,12 @@ impl ChatWidget {
self.open_skills_menu();
}
SlashCommand::Status => {
self.add_status_output();
if self.should_prefetch_rate_limits() {
self.app_event_tx
.send(AppEvent::RefreshRateLimits { show_status: true });
} else {
self.add_status_output();
}
}
SlashCommand::DebugConfig => {
self.add_debug_config_output();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
source: tui/src/chatwidget/tests.rs
assertion_line: 9607
assertion_line: 9776
expression: popup
---
Update Model Permissions
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
source: tui/src/chatwidget/tests.rs
assertion_line: 10445
expression: "lines_to_single_string(&cells[0])"
---
• Permissions updated to Guardian Approvals

This file was deleted.

31 changes: 31 additions & 0 deletions codex-rs/tui/src/chatwidget/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2268,6 +2268,37 @@ async fn prefetch_rate_limits_is_gated_on_chatgpt_auth_provider() {
assert!(!chat.should_prefetch_rate_limits());
}

#[tokio::test]
async fn status_command_refreshes_rate_limits_before_rendering_for_chatgpt_auth() {
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
set_chatgpt_auth(&mut chat);

chat.dispatch_command(SlashCommand::Status);

assert_matches!(
rx.try_recv(),
Ok(AppEvent::RefreshRateLimits { show_status: true })
);
assert!(
rx.try_recv().is_err(),
"status output should wait for fresh limits"
);
}

#[tokio::test]
async fn status_command_renders_immediately_without_rate_limit_refresh() {
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;

chat.dispatch_command(SlashCommand::Status);

assert_matches!(rx.try_recv(), Ok(AppEvent::InsertHistoryCell(_)));
assert!(
!std::iter::from_fn(|| rx.try_recv().ok())
.any(|event| matches!(event, AppEvent::RefreshRateLimits { .. })),
"non-ChatGPT sessions should not request a rate-limit refresh for /status"
);
}

#[tokio::test]
async fn worked_elapsed_from_resets_when_timer_restarts() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
Expand Down
Loading