Merge remote-tracking branch 'origin/main' into dev/winston/credentialed-routes-discovery-review-20260618

# Conflicts:
#	codex-rs/backend-client/src/lib.rs
#	codex-rs/backend-client/src/types.rs
This commit is contained in:
Winston Howes
2026-06-18 13:39:30 -07:00
1353 changed files with 90743 additions and 26417 deletions
+1 -1
View File
@@ -2,6 +2,6 @@ load("//:defs.bzl", "codex_rust_crate")
codex_rust_crate(
name = "backend-client",
crate_name = "codex_backend_client",
compile_data = glob(["tests/fixtures/**"]),
crate_name = "codex_backend_client",
)
+3 -31
View File
@@ -29,6 +29,8 @@ use serde::Serialize;
use serde::de::DeserializeOwned;
use std::fmt;
mod rate_limit_resets;
#[derive(Debug)]
pub enum RequestError {
UnexpectedStatus {
@@ -295,14 +297,7 @@ impl Client {
}
pub async fn get_rate_limits_many(&self) -> Result<Vec<RateLimitSnapshot>> {
let url = match self.path_style {
PathStyle::CodexApi => format!("{}/api/codex/usage", self.base_url),
PathStyle::ChatGptApi => format!("{}/wham/usage", self.base_url),
};
let req = self.http.get(&url).headers(self.headers());
let (body, ct) = self.exec_request(req, "GET", &url).await?;
let payload: RateLimitStatusPayload = self.decode_json(&url, &ct, &body)?;
Ok(Self::rate_limit_snapshots_from_payload(payload))
Ok(self.get_rate_limits_with_reset_credits().await?.rate_limits)
}
pub async fn get_accounts_check(&self) -> Result<AccountsCheckResponse> {
@@ -948,29 +943,6 @@ mod tests {
);
}
#[test]
fn credential_routes_use_expected_paths() {
let codex_client = test_client("https://example.test", PathStyle::CodexApi);
assert_eq!(
codex_client.credential_routes_url(),
"https://example.test/api/codex/credential_routes"
);
assert_eq!(
codex_client.credential_routes_proxy_url(),
"https://example.test/api/codex/credential_routes/proxy"
);
let chatgpt_client = test_client("https://chatgpt.com/backend-api", PathStyle::ChatGptApi);
assert_eq!(
chatgpt_client.credential_routes_url(),
"https://chatgpt.com/backend-api/ps/credential_routes"
);
assert_eq!(
chatgpt_client.credential_routes_proxy_url(),
"https://chatgpt.com/backend-api/ps/credential_routes/proxy"
);
}
#[test]
fn token_usage_profile_uses_expected_paths() {
let codex_client = test_client("https://example.test", PathStyle::CodexApi);
@@ -0,0 +1,73 @@
//! Backend client operations for reading available rate-limit reset credits and consuming one.
use super::Client;
use super::PathStyle;
use crate::types::ConsumeRateLimitResetCreditResponse;
use crate::types::RateLimitStatusWithResetCredits;
use crate::types::RateLimitsWithResetCredits;
use anyhow::Result;
use reqwest::header::CONTENT_TYPE;
use reqwest::header::HeaderValue;
use serde::Serialize;
#[derive(Serialize)]
struct ConsumeRateLimitResetCreditRequest<'a> {
redeem_request_id: &'a str,
}
impl Client {
pub async fn get_rate_limits_with_reset_credits(&self) -> Result<RateLimitsWithResetCredits> {
let payload = self.get_rate_limit_status().await?;
Ok(RateLimitsWithResetCredits {
rate_limits: Self::rate_limit_snapshots_from_payload(payload.rate_limits),
rate_limit_reset_credits: payload.rate_limit_reset_credits,
})
}
pub(super) async fn get_rate_limit_status(&self) -> Result<RateLimitStatusWithResetCredits> {
let url = self.rate_limit_status_url();
let req = self.http.get(&url).headers(self.headers());
let (body, ct) = self.exec_request(req, "GET", &url).await?;
self.decode_json(&url, &ct, &body)
}
pub async fn consume_rate_limit_reset_credit(
&self,
redeem_request_id: &str,
) -> Result<ConsumeRateLimitResetCreditResponse> {
let url = self.consume_rate_limit_reset_credit_url();
let req = self
.http
.post(&url)
.headers(self.headers())
.header(CONTENT_TYPE, HeaderValue::from_static("application/json"))
.json(&ConsumeRateLimitResetCreditRequest { redeem_request_id });
let (body, ct) = self.exec_request(req, "POST", &url).await?;
self.decode_json(&url, &ct, &body)
}
fn rate_limit_status_url(&self) -> String {
match self.path_style {
PathStyle::CodexApi => format!("{}/api/codex/usage", self.base_url),
PathStyle::ChatGptApi => format!("{}/wham/usage", self.base_url),
}
}
fn consume_rate_limit_reset_credit_url(&self) -> String {
match self.path_style {
PathStyle::CodexApi => {
format!(
"{}/api/codex/rate-limit-reset-credits/consume",
self.base_url
)
}
PathStyle::ChatGptApi => {
format!("{}/wham/rate-limit-reset-credits/consume", self.base_url)
}
}
}
}
#[cfg(test)]
#[path = "rate_limit_resets_tests.rs"]
mod tests;
@@ -0,0 +1,71 @@
use super::*;
use crate::types::ConsumeRateLimitResetCreditCode;
use crate::types::RateLimitResetCreditsSummary;
use pretty_assertions::assert_eq;
#[test]
fn rate_limit_reset_contract_uses_expected_paths_and_payloads() {
assert_eq!(
test_client("https://example.test", PathStyle::CodexApi).rate_limit_status_url(),
"https://example.test/api/codex/usage"
);
assert_eq!(
test_client("https://example.test", PathStyle::CodexApi)
.consume_rate_limit_reset_credit_url(),
"https://example.test/api/codex/rate-limit-reset-credits/consume"
);
assert_eq!(
test_client("https://chatgpt.com/backend-api", PathStyle::ChatGptApi)
.rate_limit_status_url(),
"https://chatgpt.com/backend-api/wham/usage"
);
assert_eq!(
test_client("https://chatgpt.com/backend-api", PathStyle::ChatGptApi)
.consume_rate_limit_reset_credit_url(),
"https://chatgpt.com/backend-api/wham/rate-limit-reset-credits/consume"
);
assert_eq!(
serde_json::to_value(ConsumeRateLimitResetCreditRequest {
redeem_request_id: "redeem-123",
})
.unwrap(),
serde_json::json!({ "redeem_request_id": "redeem-123" })
);
let status: RateLimitStatusWithResetCredits = serde_json::from_value(serde_json::json!({
"plan_type": "plus",
"rate_limit_reset_credits": { "available_count": 3 }
}))
.unwrap();
assert_eq!(
status.rate_limit_reset_credits,
Some(RateLimitResetCreditsSummary { available_count: 3 })
);
let response: ConsumeRateLimitResetCreditResponse = serde_json::from_value(serde_json::json!({
"code": "reset",
"credit": { "id": "ignored-by-cli" },
"windows_reset": 2
}))
.unwrap();
assert_eq!(
response,
ConsumeRateLimitResetCreditResponse {
code: ConsumeRateLimitResetCreditCode::Reset,
windows_reset: 2,
}
);
}
fn test_client(base_url: &str, path_style: PathStyle) -> Client {
Client {
base_url: base_url.to_string(),
http: reqwest::Client::new(),
auth_provider: codex_model_provider::unauthenticated_auth_provider(),
user_agent: None,
chatgpt_account_id: None,
chatgpt_account_is_fedramp: false,
path_style,
}
}
+4
View File
@@ -10,12 +10,16 @@ pub use types::CodeTaskDetailsResponse;
pub use types::CodeTaskDetailsResponseExt;
pub use types::ConfigBundleResponse;
pub use types::CredentialRouteAuthType;
pub use types::ConsumeRateLimitResetCreditCode;
pub use types::ConsumeRateLimitResetCreditResponse;
pub use types::DeliveredConfigToml;
pub use types::DeliveredRequirementsToml;
pub use types::DeliveredTomlFragment;
pub use types::ListCredentialRoutesResponse;
pub use types::PaginatedListTaskListItem;
pub use types::ResolvedCredentialRoute;
pub use types::RateLimitResetCreditsSummary;
pub use types::RateLimitsWithResetCredits;
pub use types::TaskListItem;
pub use types::TokenUsageProfile;
pub use types::TokenUsageProfileDailyBucket;
+35
View File
@@ -12,6 +12,7 @@ pub use codex_backend_openapi_models::models::RateLimitWindowSnapshot;
pub use codex_backend_openapi_models::models::SpendControlLimitDetails;
pub use codex_backend_openapi_models::models::TaskListItem;
use codex_protocol::protocol::RateLimitSnapshot;
use serde::Deserialize;
use serde::de::Deserializer;
use serde_json::Value;
@@ -40,6 +41,40 @@ pub enum CredentialRouteAuthType {
None,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
pub struct RateLimitResetCreditsSummary {
pub available_count: i64,
}
#[derive(Clone, Debug, PartialEq)]
pub struct RateLimitsWithResetCredits {
pub rate_limits: Vec<RateLimitSnapshot>,
pub rate_limit_reset_credits: Option<RateLimitResetCreditsSummary>,
}
#[derive(Clone, Debug, Deserialize, PartialEq)]
pub(crate) struct RateLimitStatusWithResetCredits {
#[serde(flatten)]
pub rate_limits: RateLimitStatusPayload,
pub rate_limit_reset_credits: Option<RateLimitResetCreditsSummary>,
}
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum ConsumeRateLimitResetCreditCode {
Reset,
NothingToReset,
NoCredit,
AlreadyRedeemed,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
pub struct ConsumeRateLimitResetCreditResponse {
pub code: ConsumeRateLimitResetCreditCode,
#[serde(default)]
pub windows_reset: i64,
}
#[derive(Clone, Debug)]
pub struct AccountsCheckResponse {
pub accounts: Vec<AccountEntry>,