Files
codex/codex-rs/memories/write/src/guard.rs
T
cooper-oai 1c414e0d65 Use thread runtime auth for Guardian Decisions and memory quota checks (#60064)
## Why

Guardian Decisions requests used bearer keys directly, bypassing runtime authentication. Memory startup quota checks used the app-level auth manager instead of the thread's provider auth context.

## What changed

- Bind Decisions credentials to runtime authentication and use route-aware HTTP clients with sensitive header protection. Preserve workload identity credentials, reuse the saved-login runtime binding, and isolate explicit API key bindings.
- Select the staging Decisions endpoint when the ChatGPT environment is staging.
- Use the thread's `ProviderAuthContext` for memory startup and quota checks, preserving account-bound network policy and applying runtime authentication.
- Keep custom destinations independent of runtime assertions.

## Testing

Add regression tests for dual-header and assertion-only authentication, fresh Decisions assertions, saved versus explicit credential isolation, workload identity, staging routing, redirect header protection, and memory quota authentication with custom destinations.

GitOrigin-RevId: 27bc7821a1cad1f5d1d2a71680527ea91ace95db
2026-11-07 05:50:00 +00:00

84 lines
2.6 KiB
Rust

use codex_backend_client::Client as BackendClient;
use codex_core::config::Config;
use codex_model_provider::ProviderAuthContext;
use codex_protocol::protocol::RateLimitSnapshot;
use codex_protocol::protocol::RateLimitWindow;
use tracing::info;
use tracing::warn;
pub(crate) async fn rate_limits_ok(auth_context: &ProviderAuthContext, config: &Config) -> bool {
rate_limits_check(auth_context, config)
.await
.unwrap_or(true)
}
async fn rate_limits_check(auth_context: &ProviderAuthContext, config: &Config) -> Option<bool> {
let (auth, captured_factory) = auth_context
.credentials
.auth_with_http_client_factory()
.await?;
if !auth.uses_codex_backend() {
return None;
}
let factory = config.http_client_factory();
let policy = factory
.network_policy()
.clone()
.with_account_binding(captured_factory.network_policy())?;
let client = BackendClient::from_auth_with_runtime(
config.chatgpt_base_url.clone(),
&auth,
factory.with_network_policy(policy),
auth_context.runtime_auth.as_ref(),
/*headers*/ &Default::default(),
)
.await
.map_err(|error| warn!(%error, "failed to prepare memory rate-limit authentication"))
.ok()?;
let snapshots = client
.get_rate_limits_many()
.await
.map_err(|err| warn!(%err, "failed to fetch rate limits"))
.ok()?;
let snapshot = snapshots
.iter()
.find(|s| s.limit_id.as_deref() == Some(crate::guard_limits::CODEX_LIMIT_ID))
.or_else(|| snapshots.first())?;
let min_remaining_percent = config.memories.min_rate_limit_remaining_percent;
let allowed = snapshot_allows_startup(snapshot, min_remaining_percent);
if !allowed {
info!(
min_remaining_percent,
"skipping memories startup because Codex rate limits are below the configured threshold"
);
}
Some(allowed)
}
fn snapshot_allows_startup(snapshot: &RateLimitSnapshot, min_remaining_percent: i64) -> bool {
if snapshot.rate_limit_reached_type.is_some() {
return false;
}
let max_used_percent = 100.0 - min_remaining_percent.clamp(0, 100) as f64;
window_allows_startup(snapshot.primary.as_ref(), max_used_percent)
&& window_allows_startup(snapshot.secondary.as_ref(), max_used_percent)
}
fn window_allows_startup(window: Option<&RateLimitWindow>, max_used_percent: f64) -> bool {
match window {
Some(window) => window.used_percent <= max_used_percent,
None => true,
}
}
#[cfg(test)]
#[path = "guard_tests.rs"]
mod tests;