mirror of
https://github.com/openai/codex.git
synced 2026-10-25 18:28:38 +00:00
Merge origin/main into codex/workspace-owner-usage-nudge
Resolve the TUI and config schema conflicts between the workspace owner usage nudge work and the latest main changes without rewriting branch history. Made-with: Cursor
This commit is contained in:
@@ -12,6 +12,7 @@ use codex_protocol::error::RetryLimitReachedError;
|
||||
use codex_protocol::error::UnexpectedResponseError;
|
||||
use codex_protocol::error::UsageLimitReachedError;
|
||||
use http::HeaderMap;
|
||||
use http::HeaderValue;
|
||||
use serde::Deserialize;
|
||||
use serde_json::Value;
|
||||
|
||||
@@ -178,6 +179,7 @@ struct UsageErrorBody {
|
||||
pub struct CoreAuthProvider {
|
||||
pub token: Option<String>,
|
||||
pub account_id: Option<String>,
|
||||
pub is_fedramp_account: bool,
|
||||
}
|
||||
|
||||
impl CoreAuthProvider {
|
||||
@@ -195,16 +197,25 @@ impl CoreAuthProvider {
|
||||
Self {
|
||||
token: token.map(str::to_string),
|
||||
account_id: account_id.map(str::to_string),
|
||||
is_fedramp_account: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ApiAuthProvider for CoreAuthProvider {
|
||||
fn bearer_token(&self) -> Option<String> {
|
||||
self.token.clone()
|
||||
}
|
||||
|
||||
fn account_id(&self) -> Option<String> {
|
||||
self.account_id.clone()
|
||||
fn add_auth_headers(&self, headers: &mut HeaderMap) {
|
||||
if let Some(token) = self.token.as_ref()
|
||||
&& let Ok(header) = HeaderValue::from_str(&format!("Bearer {token}"))
|
||||
{
|
||||
let _ = headers.insert(http::header::AUTHORIZATION, header);
|
||||
}
|
||||
if let Some(account_id) = self.account_id.as_ref()
|
||||
&& let Ok(header) = HeaderValue::from_str(account_id)
|
||||
{
|
||||
let _ = headers.insert("ChatGPT-Account-ID", header);
|
||||
}
|
||||
if self.is_fedramp_account {
|
||||
crate::auth::add_fedramp_routing_header(headers);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,8 +136,49 @@ fn core_auth_provider_reports_when_auth_header_will_attach() {
|
||||
let auth = CoreAuthProvider {
|
||||
token: Some("access-token".to_string()),
|
||||
account_id: None,
|
||||
is_fedramp_account: false,
|
||||
};
|
||||
|
||||
assert!(auth.auth_header_attached());
|
||||
assert_eq!(auth.auth_header_name(), Some("authorization"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn core_auth_provider_adds_auth_headers() {
|
||||
let auth = CoreAuthProvider::for_test(Some("access-token"), Some("workspace-123"));
|
||||
let mut headers = HeaderMap::new();
|
||||
|
||||
crate::AuthProvider::add_auth_headers(&auth, &mut headers);
|
||||
|
||||
assert_eq!(
|
||||
headers
|
||||
.get(http::header::AUTHORIZATION)
|
||||
.and_then(|value| value.to_str().ok()),
|
||||
Some("Bearer access-token")
|
||||
);
|
||||
assert_eq!(
|
||||
headers
|
||||
.get("ChatGPT-Account-ID")
|
||||
.and_then(|value| value.to_str().ok()),
|
||||
Some("workspace-123")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn core_auth_provider_adds_fedramp_routing_header_for_fedramp_accounts() {
|
||||
let auth = CoreAuthProvider {
|
||||
token: Some("access-token".to_string()),
|
||||
account_id: Some("workspace-123".to_string()),
|
||||
is_fedramp_account: true,
|
||||
};
|
||||
let mut headers = HeaderMap::new();
|
||||
|
||||
crate::AuthProvider::add_auth_headers(&auth, &mut headers);
|
||||
|
||||
assert_eq!(
|
||||
headers
|
||||
.get("X-OpenAI-Fedramp")
|
||||
.and_then(|value| value.to_str().ok()),
|
||||
Some("true")
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,33 +1,34 @@
|
||||
use codex_client::Request;
|
||||
use http::HeaderMap;
|
||||
use http::HeaderValue;
|
||||
|
||||
/// Provides bearer and account identity information for API requests.
|
||||
/// Adds authentication headers to API requests.
|
||||
///
|
||||
/// Implementations should be cheap and non-blocking; any asynchronous
|
||||
/// refresh or I/O should be handled by higher layers before requests
|
||||
/// reach this interface.
|
||||
pub trait AuthProvider: Send + Sync {
|
||||
fn bearer_token(&self) -> Option<String>;
|
||||
fn account_id(&self) -> Option<String> {
|
||||
None
|
||||
}
|
||||
fn add_auth_headers(&self, headers: &mut HeaderMap);
|
||||
}
|
||||
|
||||
pub(crate) fn add_auth_headers_to_header_map<A: AuthProvider>(auth: &A, headers: &mut HeaderMap) {
|
||||
if let Some(token) = auth.bearer_token()
|
||||
&& let Ok(header) = HeaderValue::from_str(&format!("Bearer {token}"))
|
||||
{
|
||||
let _ = headers.insert(http::header::AUTHORIZATION, header);
|
||||
}
|
||||
if let Some(account_id) = auth.account_id()
|
||||
&& let Ok(header) = HeaderValue::from_str(&account_id)
|
||||
{
|
||||
let _ = headers.insert("ChatGPT-Account-ID", header);
|
||||
}
|
||||
pub(crate) fn add_fedramp_routing_header(headers: &mut HeaderMap) {
|
||||
headers.insert("X-OpenAI-Fedramp", HeaderValue::from_static("true"));
|
||||
}
|
||||
|
||||
pub(crate) fn add_auth_headers<A: AuthProvider>(auth: &A, mut req: Request) -> Request {
|
||||
add_auth_headers_to_header_map(auth, &mut req.headers);
|
||||
req
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn add_fedramp_routing_header_sets_header() {
|
||||
let mut headers = HeaderMap::new();
|
||||
|
||||
add_fedramp_routing_header(&mut headers);
|
||||
|
||||
assert_eq!(
|
||||
headers
|
||||
.get("X-OpenAI-Fedramp")
|
||||
.and_then(|v| v.to_str().ok()),
|
||||
Some("true")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,9 +90,7 @@ mod tests {
|
||||
struct DummyAuth;
|
||||
|
||||
impl AuthProvider for DummyAuth {
|
||||
fn bearer_token(&self) -> Option<String> {
|
||||
None
|
||||
}
|
||||
fn add_auth_headers(&self, _headers: &mut HeaderMap) {}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -103,9 +103,7 @@ mod tests {
|
||||
struct DummyAuth;
|
||||
|
||||
impl AuthProvider for DummyAuth {
|
||||
fn bearer_token(&self) -> Option<String> {
|
||||
None
|
||||
}
|
||||
fn add_auth_headers(&self, _headers: &mut HeaderMap) {}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
||||
@@ -132,9 +132,7 @@ mod tests {
|
||||
struct DummyAuth;
|
||||
|
||||
impl AuthProvider for DummyAuth {
|
||||
fn bearer_token(&self) -> Option<String> {
|
||||
None
|
||||
}
|
||||
fn add_auth_headers(&self, _headers: &mut HeaderMap) {}
|
||||
}
|
||||
|
||||
fn provider(base_url: &str) -> Provider {
|
||||
|
||||
@@ -284,8 +284,11 @@ mod tests {
|
||||
struct DummyAuth;
|
||||
|
||||
impl AuthProvider for DummyAuth {
|
||||
fn bearer_token(&self) -> Option<String> {
|
||||
Some("test-token".to_string())
|
||||
fn add_auth_headers(&self, headers: &mut HeaderMap) {
|
||||
headers.insert(
|
||||
http::header::AUTHORIZATION,
|
||||
HeaderValue::from_static("Bearer test-token"),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1670,7 +1670,7 @@ mod tests {
|
||||
);
|
||||
assert_eq!(
|
||||
third_json["item"]["output"],
|
||||
Value::String("\"Agent Final Message\":\n\ndelegated result".to_string())
|
||||
Value::String("delegated result".to_string())
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -45,9 +45,11 @@ pub(super) fn conversation_handoff_append_message(
|
||||
handoff_id: String,
|
||||
output_text: String,
|
||||
) -> RealtimeOutboundMessage {
|
||||
let output_text = format!("{AGENT_FINAL_MESSAGE_PREFIX}{output_text}");
|
||||
match event_parser {
|
||||
RealtimeEventParser::V1 => v1_conversation_handoff_append_message(handoff_id, output_text),
|
||||
RealtimeEventParser::V1 => v1_conversation_handoff_append_message(
|
||||
handoff_id,
|
||||
format!("{AGENT_FINAL_MESSAGE_PREFIX}{output_text}"),
|
||||
),
|
||||
RealtimeEventParser::RealtimeV2 => {
|
||||
v2_conversation_handoff_append_message(handoff_id, output_text)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use crate::auth::AuthProvider;
|
||||
use crate::auth::add_auth_headers_to_header_map;
|
||||
use crate::common::ResponseEvent;
|
||||
use crate::common::ResponseStream;
|
||||
use crate::common::ResponsesWsRequest;
|
||||
@@ -310,7 +309,7 @@ impl<A: AuthProvider> ResponsesWebsocketClient<A> {
|
||||
|
||||
let mut headers =
|
||||
merge_request_headers(&self.provider.headers, extra_headers, default_headers);
|
||||
add_auth_headers_to_header_map(&self.auth, &mut headers);
|
||||
self.auth.add_auth_headers(&mut headers);
|
||||
|
||||
let (stream, server_reasoning_included, models_etag, server_model) =
|
||||
connect_websocket(ws_url, headers, turn_state.clone()).await?;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use crate::auth::AuthProvider;
|
||||
use crate::auth::add_auth_headers;
|
||||
use crate::error::ApiError;
|
||||
use crate::provider::Provider;
|
||||
use crate::telemetry::run_with_request_telemetry;
|
||||
@@ -56,7 +55,8 @@ impl<T: HttpTransport, A: AuthProvider> EndpointSession<T, A> {
|
||||
if let Some(body) = body {
|
||||
req.body = Some(RequestBody::Json(body.clone()));
|
||||
}
|
||||
add_auth_headers(&self.auth, req)
|
||||
self.auth.add_auth_headers(&mut req.headers);
|
||||
req
|
||||
}
|
||||
|
||||
pub(crate) async fn execute(
|
||||
|
||||
@@ -256,17 +256,14 @@ fn authorized_request(
|
||||
method: reqwest::Method,
|
||||
url: &str,
|
||||
) -> reqwest::RequestBuilder {
|
||||
let mut headers = http::HeaderMap::new();
|
||||
auth.add_auth_headers(&mut headers);
|
||||
|
||||
let client = build_reqwest_client();
|
||||
let mut request = client
|
||||
client
|
||||
.request(method, url)
|
||||
.timeout(OPENAI_FILE_REQUEST_TIMEOUT);
|
||||
if let Some(token) = auth.bearer_token() {
|
||||
request = request.bearer_auth(token);
|
||||
}
|
||||
if let Some(account_id) = auth.account_id() {
|
||||
request = request.header("chatgpt-account-id", account_id);
|
||||
}
|
||||
request
|
||||
.timeout(OPENAI_FILE_REQUEST_TIMEOUT)
|
||||
.headers(headers)
|
||||
}
|
||||
|
||||
fn build_reqwest_client() -> reqwest::Client {
|
||||
|
||||
@@ -57,7 +57,7 @@ pub use crate::error::ApiError;
|
||||
pub use crate::files::upload_local_file;
|
||||
pub use crate::provider::Provider;
|
||||
pub use crate::provider::RetryConfig;
|
||||
pub use crate::provider::is_azure_responses_wire_base_url;
|
||||
pub use crate::provider::is_azure_responses_provider;
|
||||
pub use crate::requests::Compression;
|
||||
pub use crate::sse::stream_from_fixture;
|
||||
pub use crate::telemetry::SseTelemetry;
|
||||
|
||||
@@ -86,7 +86,7 @@ impl Provider {
|
||||
}
|
||||
|
||||
pub fn is_azure_responses_endpoint(&self) -> bool {
|
||||
is_azure_responses_wire_base_url(&self.name, Some(&self.base_url))
|
||||
is_azure_responses_provider(&self.name, Some(&self.base_url))
|
||||
}
|
||||
|
||||
pub fn websocket_url_for_path(&self, path: &str) -> Result<Url, url::ParseError> {
|
||||
@@ -103,21 +103,20 @@ impl Provider {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_azure_responses_wire_base_url(name: &str, base_url: Option<&str>) -> bool {
|
||||
pub fn is_azure_responses_provider(name: &str, base_url: Option<&str>) -> bool {
|
||||
if name.eq_ignore_ascii_case("azure") {
|
||||
return true;
|
||||
true
|
||||
} else if let Some(base_url) = base_url {
|
||||
matches_azure_responses_base_url(base_url)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
||||
let Some(base_url) = base_url else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let base = base_url.to_ascii_lowercase();
|
||||
base.contains("openai.azure.") || matches_azure_responses_base_url(&base)
|
||||
}
|
||||
|
||||
fn matches_azure_responses_base_url(base_url: &str) -> bool {
|
||||
const AZURE_MARKERS: [&str; 5] = [
|
||||
let base_url = base_url.to_ascii_lowercase();
|
||||
const AZURE_MARKERS: [&str; 6] = [
|
||||
"openai.azure.",
|
||||
"cognitiveservices.azure.",
|
||||
"aoai.azure.",
|
||||
"azure-api.",
|
||||
@@ -144,12 +143,12 @@ mod tests {
|
||||
|
||||
for base_url in positive_cases {
|
||||
assert!(
|
||||
is_azure_responses_wire_base_url("test", Some(base_url)),
|
||||
is_azure_responses_provider("test", Some(base_url)),
|
||||
"expected {base_url} to be detected as Azure"
|
||||
);
|
||||
}
|
||||
|
||||
assert!(is_azure_responses_wire_base_url(
|
||||
assert!(is_azure_responses_provider(
|
||||
"Azure",
|
||||
Some("https://example.com")
|
||||
));
|
||||
@@ -162,7 +161,7 @@ mod tests {
|
||||
|
||||
for base_url in negative_cases {
|
||||
assert!(
|
||||
!is_azure_responses_wire_base_url("test", Some(base_url)),
|
||||
!is_azure_responses_provider("test", Some(base_url)),
|
||||
"expected {base_url} not to be detected as Azure"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -91,9 +91,7 @@ impl HttpTransport for RecordingTransport {
|
||||
struct NoAuth;
|
||||
|
||||
impl AuthProvider for NoAuth {
|
||||
fn bearer_token(&self) -> Option<String> {
|
||||
None
|
||||
}
|
||||
fn add_auth_headers(&self, _headers: &mut HeaderMap) {}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -112,12 +110,14 @@ impl StaticAuth {
|
||||
}
|
||||
|
||||
impl AuthProvider for StaticAuth {
|
||||
fn bearer_token(&self) -> Option<String> {
|
||||
Some(self.token.clone())
|
||||
}
|
||||
|
||||
fn account_id(&self) -> Option<String> {
|
||||
Some(self.account_id.clone())
|
||||
fn add_auth_headers(&self, headers: &mut HeaderMap) {
|
||||
let token = &self.token;
|
||||
if let Ok(header) = HeaderValue::from_str(&format!("Bearer {token}")) {
|
||||
headers.insert(http::header::AUTHORIZATION, header);
|
||||
}
|
||||
if let Ok(header) = HeaderValue::from_str(&self.account_id) {
|
||||
headers.insert("ChatGPT-Account-ID", header);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,9 +24,7 @@ use wiremock::matchers::path;
|
||||
struct DummyAuth;
|
||||
|
||||
impl AuthProvider for DummyAuth {
|
||||
fn bearer_token(&self) -> Option<String> {
|
||||
None
|
||||
}
|
||||
fn add_auth_headers(&self, _headers: &mut HeaderMap) {}
|
||||
}
|
||||
|
||||
fn provider(base_url: &str) -> Provider {
|
||||
|
||||
@@ -53,9 +53,7 @@ impl HttpTransport for FixtureSseTransport {
|
||||
struct NoAuth;
|
||||
|
||||
impl AuthProvider for NoAuth {
|
||||
fn bearer_token(&self) -> Option<String> {
|
||||
None
|
||||
}
|
||||
fn add_auth_headers(&self, _headers: &mut HeaderMap) {}
|
||||
}
|
||||
|
||||
fn provider(name: &str) -> Provider {
|
||||
|
||||
Reference in New Issue
Block a user