mirror of
https://github.com/openai/codex.git
synced 2026-10-21 17:48:44 +00:00
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:
@@ -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",
|
||||
)
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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>,
|
||||
|
||||
Reference in New Issue
Block a user