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
2 changes: 1 addition & 1 deletion codex-rs/tui/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use crate::app_event::HistoryLookupResponse;
use crate::app_event::PermissionProfileSelection;
use crate::app_event::PluginLocation;
use crate::app_event::RateLimitRefreshOrigin;
use crate::app_event::RateLimitResetCreditsRefreshOrigin;
#[cfg(target_os = "windows")]
use crate::app_event::WindowsSandboxEnableMode;
use crate::app_event_sender::AppEventSender;
Expand Down Expand Up @@ -110,7 +111,6 @@ use codex_app_server_protocol::PluginReadParams;
use codex_app_server_protocol::PluginReadResponse;
use codex_app_server_protocol::PluginUninstallParams;
use codex_app_server_protocol::PluginUninstallResponse;
use codex_app_server_protocol::RateLimitSnapshot;
use codex_app_server_protocol::SandboxMode as AppServerSandboxMode;
use codex_app_server_protocol::SendAddCreditsNudgeEmailParams;
use codex_app_server_protocol::ServerNotification;
Expand Down
104 changes: 96 additions & 8 deletions codex-rs/tui/src/app/background_requests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,16 @@ use crate::app_event::ConnectorsSnapshot;
use crate::config_update::format_config_error;
use codex_app_server_protocol::AppsListParams;
use codex_app_server_protocol::AppsListResponse;
use codex_app_server_protocol::ConsumeAccountRateLimitResetCreditParams;
use codex_app_server_protocol::ConsumeAccountRateLimitResetCreditResponse;
use codex_app_server_protocol::MarketplaceAddParams;
use codex_app_server_protocol::MarketplaceAddResponse;
use codex_app_server_protocol::MarketplaceRemoveParams;
use codex_app_server_protocol::MarketplaceRemoveResponse;
use codex_app_server_protocol::MarketplaceUpgradeParams;
use codex_app_server_protocol::MarketplaceUpgradeResponse;

use codex_app_server_protocol::RateLimitResetCreditsSummary;
use codex_app_server_protocol::RequestId;

use crate::hooks_rpc::fetch_hooks_list;
Expand All @@ -26,6 +29,8 @@ use codex_utils_absolute_path::AbsolutePathBuf;

const TOKEN_ACTIVITY_FETCH_TIMEOUT: std::time::Duration =
std::time::Duration::from_secs(/*secs*/ 15);
const RATE_LIMIT_RESET_REQUEST_TIMEOUT: std::time::Duration =
std::time::Duration::from_secs(/*secs*/ 15);

impl App {
pub(super) fn fetch_mcp_inventory(
Expand Down Expand Up @@ -74,9 +79,19 @@ impl App {
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());
let request = fetch_account_rate_limits(request_handle);
let result = match origin {
RateLimitRefreshOrigin::ResetConsume { .. } => {
tokio::time::timeout(RATE_LIMIT_RESET_REQUEST_TIMEOUT, request)
.await
.map_err(|_| "account/rateLimits/read timed out in TUI".to_string())
.and_then(|result| result.map_err(|err| err.to_string()))
}
RateLimitRefreshOrigin::StartupPrefetch
| RateLimitRefreshOrigin::StatusCommand { .. } => {
request.await.map_err(|err| err.to_string())
}
};
app_event_tx.send(AppEvent::RateLimitsLoaded { origin, result });
});
}
Expand All @@ -100,6 +115,49 @@ impl App {
});
}

pub(super) fn refresh_rate_limit_reset_credits(
&mut self,
app_server: &AppServerSession,
origin: RateLimitResetCreditsRefreshOrigin,
) {
let request_handle = app_server.request_handle();
let app_event_tx = self.app_event_tx.clone();
tokio::spawn(async move {
let result = tokio::time::timeout(
RATE_LIMIT_RESET_REQUEST_TIMEOUT,
fetch_rate_limit_reset_credits(request_handle),
)
.await
.map_err(|_| "account/rateLimits/read timed out in TUI".to_string())
.and_then(|result| result.map_err(|err| err.to_string()));
app_event_tx.send(AppEvent::RateLimitResetCreditsLoaded { origin, result });
});
}

pub(super) fn consume_rate_limit_reset_credit(
&mut self,
app_server: &AppServerSession,
request_id: u64,
redeem_request_id: String,
) {
let request_handle = app_server.request_handle();
let app_event_tx = self.app_event_tx.clone();
tokio::spawn(async move {
let result = tokio::time::timeout(
RATE_LIMIT_RESET_REQUEST_TIMEOUT,
consume_rate_limit_reset_credit_request(request_handle, redeem_request_id.clone()),
)
.await
.map_err(|_| "account/rateLimitResetCredit/consume timed out in TUI".to_string())
.and_then(|result| result.map_err(|err| err.to_string()));
app_event_tx.send(AppEvent::RateLimitResetCreditConsumed {
request_id,
redeem_request_id,
result,
});
});
}

pub(super) fn send_add_credits_nudge_email(
&mut self,
app_server: &AppServerSession,
Expand Down Expand Up @@ -661,17 +719,15 @@ pub(super) async fn fetch_all_mcp_server_statuses(

pub(super) async fn fetch_account_rate_limits(
request_handle: AppServerRequestHandle,
) -> Result<Vec<RateLimitSnapshot>> {
) -> Result<GetAccountRateLimitsResponse> {
let request_id = RequestId::String(format!("account-rate-limits-{}", Uuid::new_v4()));
let response: GetAccountRateLimitsResponse = request_handle
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(response))
.wrap_err("account/rateLimits/read failed in TUI")
}

pub(super) async fn fetch_account_token_activity(
Expand All @@ -687,6 +743,38 @@ pub(super) async fn fetch_account_token_activity(
.wrap_err("account/usage/read failed in TUI")
}

pub(super) async fn fetch_rate_limit_reset_credits(
request_handle: AppServerRequestHandle,
) -> Result<RateLimitResetCreditsSummary> {
let request_id = RequestId::String(format!("account-rate-limit-resets-{}", 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")?;
response.rate_limit_reset_credits.ok_or_else(|| {
color_eyre::eyre::eyre!(
"account/rateLimits/read response did not include rateLimitResetCredits"
)
})
}

pub(super) async fn consume_rate_limit_reset_credit_request(
request_handle: AppServerRequestHandle,
redeem_request_id: String,
) -> Result<ConsumeAccountRateLimitResetCreditResponse> {
let request_id = RequestId::String(format!("consume-rate-limit-reset-{}", Uuid::new_v4()));
request_handle
.request_typed(ClientRequest::ConsumeAccountRateLimitResetCredit {
request_id,
params: ConsumeAccountRateLimitResetCreditParams { redeem_request_id },
})
.await
.wrap_err("account/rateLimitResetCredit/consume failed in TUI")
}

pub(super) async fn send_add_credits_nudge_email(
request_handle: AppServerRequestHandle,
credit_type: AddCreditsNudgeCreditType,
Expand Down
109 changes: 98 additions & 11 deletions codex-rs/tui/src/app/event_dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -246,15 +246,15 @@ impl App {
deferred_history_cell,
)?;
self.chat_widget.note_stream_consolidation_completed();
self.insert_completed_token_activity_output_after_stream_shutdown(tui);
self.insert_pending_usage_output_after_stream_shutdown(tui);
}
AppEvent::ConsolidateProposedPlan(source) => {
if !self.terminal_resize_reflow_enabled() {
if !self.transcript_reflow.history_cell_refresh_requested() {
self.transcript_reflow.clear();
}
self.chat_widget.note_stream_consolidation_completed();
self.insert_completed_token_activity_output_after_stream_shutdown(tui);
self.insert_pending_usage_output_after_stream_shutdown(tui);
return Ok(AppRunControl::Continue);
}
let end = self.transcript_cells.len();
Expand Down Expand Up @@ -290,7 +290,7 @@ impl App {
self.maybe_finish_stream_reflow(tui)?;
}
self.chat_widget.note_stream_consolidation_completed();
self.insert_completed_token_activity_output_after_stream_shutdown(tui);
self.insert_pending_usage_output_after_stream_shutdown(tui);
}
AppEvent::ApplyThreadRollback { num_turns } => {
if self.apply_non_pending_thread_rollback(num_turns) {
Expand Down Expand Up @@ -747,14 +747,25 @@ impl App {
.finish_add_credits_nudge_email_request(result);
}
AppEvent::RateLimitsLoaded { origin, result } => match result {
Ok(snapshots) => {
for snapshot in snapshots {
Ok(response) => {
let rate_limit_reset_credits = response.rate_limit_reset_credits.clone();
for snapshot in app_server_rate_limit_snapshots(response) {
self.chat_widget.on_rate_limit_snapshot(Some(snapshot));
}
match origin {
RateLimitRefreshOrigin::StartupPrefetch => {
tui.frame_requester().schedule_frame();
}
RateLimitRefreshOrigin::ResetConsume { request_id } => {
self.chat_widget.finish_post_consume_reset_credits_refresh(
request_id,
rate_limit_reset_credits.ok_or_else(|| {
"account/rateLimits/read response did not include rateLimitResetCredits"
.to_string()
}),
);
tui.frame_requester().schedule_frame();
}
RateLimitRefreshOrigin::StatusCommand { request_id } => {
self.chat_widget
.finish_status_rate_limit_refresh(request_id);
Expand All @@ -763,12 +774,85 @@ impl App {
}
Err(err) => {
tracing::warn!("account/rateLimits/read failed during TUI refresh: {err}");
if let RateLimitRefreshOrigin::StatusCommand { request_id } = origin {
self.chat_widget
.finish_status_rate_limit_refresh(request_id);
match origin {
RateLimitRefreshOrigin::StartupPrefetch => {}
RateLimitRefreshOrigin::ResetConsume { request_id } => {
self.chat_widget
.finish_post_consume_reset_credits_refresh(request_id, Err(err));
}
RateLimitRefreshOrigin::StatusCommand { request_id } => {
self.chat_widget
.finish_status_rate_limit_refresh(request_id);
}
}
}
},
AppEvent::OpenTokenActivity => {
self.chat_widget
.add_token_activity_output(crate::chatwidget::TokenActivityView::Daily);
}
AppEvent::OpenRateLimitResetCredits => {
let request_id = self.chat_widget.show_rate_limit_reset_loading_popup();
self.refresh_rate_limit_reset_credits(
app_server,
RateLimitResetCreditsRefreshOrigin::UsageMenu { request_id },
);
}
AppEvent::CheckRateLimitResetCredits { request_id } => {
self.refresh_rate_limit_reset_credits(
app_server,
RateLimitResetCreditsRefreshOrigin::UsageLimitHint { request_id },
);
}
AppEvent::RateLimitResetCreditsLoaded { origin, result } => match origin {
RateLimitResetCreditsRefreshOrigin::UsageMenu { request_id } => {
if let Err(err) = &result {
tracing::warn!(
"account/rateLimits/read failed during reset-credit refresh: {err}"
);
}
self.chat_widget
.finish_rate_limit_reset_credits_refresh(request_id, result);
}
RateLimitResetCreditsRefreshOrigin::UsageLimitHint { request_id } => {
if let Err(err) = &result {
tracing::warn!(
"account/rateLimits/read failed while checking usage-limit hint: {err}"
);
}
if self
.chat_widget
.finish_rate_limit_reset_hint_refresh(request_id, result)
{
self.insert_pending_usage_output_if_ready(tui);
}
}
},
AppEvent::ConsumeRateLimitResetCredit { redeem_request_id } => {
let request_id = self.chat_widget.show_rate_limit_reset_consuming_popup();
self.consume_rate_limit_reset_credit(app_server, request_id, redeem_request_id);
}
AppEvent::RateLimitResetCreditConsumed {
request_id,
redeem_request_id,
result,
} => {
if let Err(err) = &result {
tracing::warn!(
"account/rateLimitResetCredit/consume failed during TUI request: {err}"
);
}
if self.chat_widget.finish_rate_limit_reset_consume(
request_id,
redeem_request_id,
result,
) {
self.refresh_rate_limits(
app_server,
RateLimitRefreshOrigin::ResetConsume { request_id },
);
}
}
AppEvent::TokenActivityLoaded { request_id, result } => {
if let Err(err) = &result {
tracing::warn!("account/usage/read failed during TUI refresh: {err}");
Expand All @@ -782,11 +866,14 @@ impl App {
// active work, and flushing an in-progress tool cell would corrupt its lifecycle.
// If an answer stream is active, keep the settled card transient until its
// provisional transcript cells have been consolidated.
self.insert_completed_token_activity_output_if_ready(tui);
self.insert_pending_usage_output_if_ready(tui);
}
}
AppEvent::CommitCompletedTokenActivityOutput => {
self.insert_completed_token_activity_output_after_stream_shutdown(tui);
AppEvent::CommitPendingUsageOutput => {
self.insert_pending_usage_output_if_ready(tui);
}
AppEvent::CommitPendingUsageOutputAfterStreamShutdown => {
self.insert_pending_usage_output_after_stream_shutdown(tui);
}
AppEvent::ConnectorsLoaded { result, is_final } => {
self.chat_widget.on_connectors_loaded(result, is_final);
Expand Down
Loading
Loading