mirror of
https://github.com/openai/codex.git
synced 2026-10-20 17:38:45 +00:00
http-client: extract shared HTTP transport
This commit is contained in:
Generated
+38
-27
@@ -1936,6 +1936,7 @@ dependencies = [
|
||||
"bytes",
|
||||
"chrono",
|
||||
"codex-client",
|
||||
"codex-http-client",
|
||||
"codex-protocol",
|
||||
"codex-utils-rustls-provider",
|
||||
"eventsource-stream",
|
||||
@@ -2262,7 +2263,7 @@ dependencies = [
|
||||
"anyhow",
|
||||
"codex-api",
|
||||
"codex-backend-openapi-models",
|
||||
"codex-client",
|
||||
"codex-http-client",
|
||||
"codex-login",
|
||||
"codex-model-provider",
|
||||
"codex-protocol",
|
||||
@@ -2392,33 +2393,12 @@ dependencies = [
|
||||
name = "codex-client"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"codex-utils-cargo-bin",
|
||||
"codex-utils-rustls-provider",
|
||||
"codex-http-client",
|
||||
"eventsource-stream",
|
||||
"futures",
|
||||
"http 1.4.0",
|
||||
"opentelemetry",
|
||||
"opentelemetry_sdk",
|
||||
"pretty_assertions",
|
||||
"rand 0.9.3",
|
||||
"rcgen",
|
||||
"reqwest 0.12.28",
|
||||
"rustls",
|
||||
"rustls-native-certs",
|
||||
"rustls-pki-types",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2 0.10.9",
|
||||
"system-configuration",
|
||||
"tempfile",
|
||||
"thiserror 2.0.18",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"tracing-opentelemetry",
|
||||
"tracing-subscriber",
|
||||
"windows-sys 0.52.0",
|
||||
"zstd 0.13.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2452,11 +2432,11 @@ dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
"clap",
|
||||
"codex-client",
|
||||
"codex-cloud-tasks-client",
|
||||
"codex-cloud-tasks-mock-client",
|
||||
"codex-core",
|
||||
"codex-git-utils",
|
||||
"codex-http-client",
|
||||
"codex-login",
|
||||
"codex-model-provider",
|
||||
"codex-tui",
|
||||
@@ -2916,9 +2896,9 @@ dependencies = [
|
||||
"bytes",
|
||||
"clatter",
|
||||
"codex-api",
|
||||
"codex-client",
|
||||
"codex-exec-server-protocol",
|
||||
"codex-file-system",
|
||||
"codex-http-client",
|
||||
"codex-network-proxy",
|
||||
"codex-otel",
|
||||
"codex-protocol",
|
||||
@@ -3214,6 +3194,37 @@ dependencies = [
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codex-http-client"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"codex-utils-cargo-bin",
|
||||
"codex-utils-rustls-provider",
|
||||
"futures",
|
||||
"http 1.4.0",
|
||||
"opentelemetry",
|
||||
"opentelemetry_sdk",
|
||||
"pretty_assertions",
|
||||
"rcgen",
|
||||
"reqwest 0.12.28",
|
||||
"rustls",
|
||||
"rustls-native-certs",
|
||||
"rustls-pki-types",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2 0.10.9",
|
||||
"system-configuration",
|
||||
"tempfile",
|
||||
"thiserror 2.0.18",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"tracing-opentelemetry",
|
||||
"tracing-subscriber",
|
||||
"windows-sys 0.52.0",
|
||||
"zstd 0.13.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codex-image-generation-extension"
|
||||
version = "0.0.0"
|
||||
@@ -3305,8 +3316,8 @@ dependencies = [
|
||||
"base64 0.22.1",
|
||||
"chrono",
|
||||
"codex-agent-identity",
|
||||
"codex-client",
|
||||
"codex-config",
|
||||
"codex-http-client",
|
||||
"codex-keyring-store",
|
||||
"codex-model-provider-info",
|
||||
"codex-otel",
|
||||
@@ -3520,8 +3531,8 @@ dependencies = [
|
||||
"codex-agent-identity",
|
||||
"codex-api",
|
||||
"codex-aws-auth",
|
||||
"codex-client",
|
||||
"codex-feedback",
|
||||
"codex-http-client",
|
||||
"codex-login",
|
||||
"codex-model-provider-info",
|
||||
"codex-models-manager",
|
||||
|
||||
@@ -41,6 +41,7 @@ members = [
|
||||
"core-plugins",
|
||||
"core-skills",
|
||||
"hooks",
|
||||
"http-client",
|
||||
"secrets",
|
||||
"exec",
|
||||
"file-system",
|
||||
@@ -164,6 +165,7 @@ codex-cloud-tasks-mock-client = { path = "cloud-tasks-mock-client" }
|
||||
codex-code-mode = { path = "code-mode" }
|
||||
codex-code-mode-protocol = { path = "code-mode-protocol" }
|
||||
codex-home = { path = "codex-home" }
|
||||
codex-http-client = { path = "http-client" }
|
||||
codex-config = { path = "config" }
|
||||
codex-connectors = { path = "connectors" }
|
||||
codex-connectors-extension = { path = "ext/connectors" }
|
||||
|
||||
@@ -19,7 +19,7 @@ serde_json = "1"
|
||||
reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] }
|
||||
codex-backend-openapi-models = { path = "../codex-backend-openapi-models" }
|
||||
codex-api = { workspace = true }
|
||||
codex-client = { workspace = true }
|
||||
codex-http-client = { workspace = true }
|
||||
codex-login = { workspace = true }
|
||||
codex-model-provider = { workspace = true }
|
||||
codex-protocol = { workspace = true }
|
||||
|
||||
@@ -9,8 +9,8 @@ use crate::types::TokenUsageProfile;
|
||||
use crate::types::TurnAttemptsSiblingTurnsResponse;
|
||||
use anyhow::Result;
|
||||
use codex_api::SharedAuthProvider;
|
||||
use codex_client::build_reqwest_client_with_custom_ca;
|
||||
use codex_client::with_chatgpt_cloudflare_cookie_store;
|
||||
use codex_http_client::build_reqwest_client_with_custom_ca;
|
||||
use codex_http_client::with_chatgpt_cloudflare_cookie_store;
|
||||
use codex_login::CodexAuth;
|
||||
use codex_login::default_client::get_codex_user_agent;
|
||||
use codex_protocol::account::PlanType as AccountPlanType;
|
||||
|
||||
@@ -16,7 +16,7 @@ workspace = true
|
||||
anyhow = { workspace = true }
|
||||
chrono = { workspace = true, features = ["serde"] }
|
||||
clap = { workspace = true, features = ["derive"] }
|
||||
codex-client = { workspace = true }
|
||||
codex-http-client = { workspace = true }
|
||||
codex-cloud-tasks-client = { workspace = true }
|
||||
# TODO: codex-cloud-tasks-mock-client should be in dev-dependencies.
|
||||
codex-cloud-tasks-mock-client = { workspace = true }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use codex_client::build_reqwest_client_with_custom_ca;
|
||||
use codex_http_client::build_reqwest_client_with_custom_ca;
|
||||
use reqwest::header::CONTENT_TYPE;
|
||||
use reqwest::header::HeaderMap;
|
||||
use std::collections::HashMap;
|
||||
|
||||
@@ -10,6 +10,7 @@ base64 = { workspace = true }
|
||||
bytes = { workspace = true }
|
||||
chrono = { workspace = true }
|
||||
codex-client = { workspace = true }
|
||||
codex-http-client = { workspace = true }
|
||||
codex-protocol = { workspace = true }
|
||||
codex-utils-rustls-provider = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
|
||||
@@ -16,7 +16,7 @@ use crate::endpoint::realtime_websocket::protocol::parse_realtime_event;
|
||||
use crate::error::ApiError;
|
||||
use crate::provider::Provider;
|
||||
use codex_client::backoff;
|
||||
use codex_client::maybe_build_rustls_client_config_with_custom_ca;
|
||||
use codex_http_client::maybe_build_rustls_client_config_with_custom_ca;
|
||||
use codex_protocol::protocol::ConversationTextRole;
|
||||
use codex_protocol::protocol::RealtimeTranscriptDelta;
|
||||
use codex_utils_rustls_provider::ensure_rustls_crypto_provider;
|
||||
|
||||
@@ -11,7 +11,7 @@ use crate::sse::ResponsesStreamEvent;
|
||||
use crate::sse::process_responses_event;
|
||||
use crate::telemetry::WebsocketTelemetry;
|
||||
use codex_client::TransportError;
|
||||
use codex_client::maybe_build_rustls_client_config_with_custom_ca;
|
||||
use codex_http_client::maybe_build_rustls_client_config_with_custom_ca;
|
||||
use codex_utils_rustls_provider::ensure_rustls_crypto_provider;
|
||||
use futures::SinkExt;
|
||||
use futures::StreamExt;
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::time::Duration;
|
||||
|
||||
use crate::AuthProvider;
|
||||
use bytes::Bytes;
|
||||
use codex_client::build_reqwest_client_with_custom_ca;
|
||||
use codex_http_client::build_reqwest_client_with_custom_ca;
|
||||
use futures::Stream;
|
||||
use reqwest::StatusCode;
|
||||
use reqwest::header::CONTENT_LENGTH;
|
||||
|
||||
@@ -2,6 +2,5 @@ load("//:defs.bzl", "codex_rust_crate")
|
||||
|
||||
codex_rust_crate(
|
||||
name = "codex-client",
|
||||
compile_data = glob(["tests/fixtures/**"]),
|
||||
crate_name = "codex_client",
|
||||
)
|
||||
|
||||
@@ -5,47 +5,16 @@ name = "codex-client"
|
||||
version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
bytes = { workspace = true }
|
||||
codex-http-client = { workspace = true }
|
||||
eventsource-stream = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
http = { workspace = true }
|
||||
opentelemetry = { workspace = true }
|
||||
rand = { workspace = true }
|
||||
reqwest = { workspace = true, features = ["json", "rustls-tls-native-roots", "stream"] }
|
||||
rustls = { workspace = true }
|
||||
rustls-native-certs = { workspace = true }
|
||||
rustls-pki-types = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tokio = { workspace = true, features = ["macros", "rt", "time", "sync"] }
|
||||
tracing = { workspace = true }
|
||||
tracing-opentelemetry = { workspace = true }
|
||||
codex-utils-rustls-provider = { workspace = true }
|
||||
zstd = { workspace = true }
|
||||
|
||||
[target.'cfg(any(target_os = "windows", target_os = "macos"))'.dependencies]
|
||||
sha2 = { workspace = true }
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
system-configuration = { workspace = true }
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
windows-sys = { version = "0.52", features = [
|
||||
"Win32_Foundation",
|
||||
"Win32_Networking_WinHttp",
|
||||
] }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
codex-utils-cargo-bin = { workspace = true }
|
||||
opentelemetry_sdk = { workspace = true }
|
||||
pretty_assertions = { workspace = true }
|
||||
rcgen = { workspace = true }
|
||||
tempfile = { workspace = true }
|
||||
tracing-subscriber = { workspace = true }
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
test = false
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# codex-client
|
||||
|
||||
Generic transport layer that wraps HTTP requests, retries, and streaming primitives without any Codex/OpenAI awareness.
|
||||
Higher-level request policy layered on `codex-http-client` without any Codex/OpenAI API awareness.
|
||||
|
||||
- Defines `HttpTransport` and a default `ReqwestTransport` plus thin `Request`/`Response` types.
|
||||
- Provides retry utilities (`RetryPolicy`, `RetryOn`, `run_with_retry`, `backoff`) that callers plug into for unary and streaming calls.
|
||||
- Supplies the `sse_stream` helper to turn byte streams into raw SSE `data:` frames with idle timeouts and surfaced stream errors.
|
||||
- Consumed by higher-level crates like `codex-api`; it stays neutral on endpoints, headers, or API-specific error shapes.
|
||||
- Defines the request telemetry callback used by higher-level clients.
|
||||
- Re-exports the low-level HTTP types temporarily so consumers can migrate to `codex-http-client` incrementally.
|
||||
|
||||
@@ -1,49 +1,13 @@
|
||||
mod chatgpt_cloudflare_cookies;
|
||||
mod chatgpt_hosts;
|
||||
mod custom_ca;
|
||||
mod default_client;
|
||||
mod error;
|
||||
mod outbound_proxy;
|
||||
mod request;
|
||||
mod retry;
|
||||
mod sse;
|
||||
mod telemetry;
|
||||
mod transport;
|
||||
|
||||
pub use crate::chatgpt_cloudflare_cookies::with_chatgpt_cloudflare_cookie_store;
|
||||
pub use crate::chatgpt_hosts::is_allowed_chatgpt_host;
|
||||
pub use crate::custom_ca::BuildCustomCaTransportError;
|
||||
/// Test-only subprocess hook for custom CA coverage.
|
||||
///
|
||||
/// This stays public only so the `custom_ca_probe` binary target can reuse the shared helper. It
|
||||
/// is hidden from normal docs because ordinary callers should use
|
||||
/// [`build_reqwest_client_with_custom_ca`] instead.
|
||||
#[doc(hidden)]
|
||||
pub use crate::custom_ca::build_reqwest_client_for_subprocess_tests;
|
||||
pub use crate::custom_ca::build_reqwest_client_with_custom_ca;
|
||||
pub use crate::custom_ca::maybe_build_rustls_client_config_with_custom_ca;
|
||||
pub use crate::default_client::CodexHttpClient;
|
||||
pub use crate::default_client::CodexRequestBuilder;
|
||||
pub use crate::error::StreamError;
|
||||
pub use crate::error::TransportError;
|
||||
pub use crate::outbound_proxy::BuildRouteAwareHttpClientError;
|
||||
pub use crate::outbound_proxy::ClientRouteClass;
|
||||
pub use crate::outbound_proxy::OutboundProxyConfig;
|
||||
pub use crate::outbound_proxy::RouteFailureClass;
|
||||
pub use crate::outbound_proxy::build_reqwest_client_for_route;
|
||||
pub use crate::request::EncodedJsonBody;
|
||||
pub use crate::request::PreparedRequestBody;
|
||||
pub use crate::request::Request;
|
||||
pub use crate::request::RequestBody;
|
||||
pub use crate::request::RequestCompression;
|
||||
pub use crate::request::Response;
|
||||
pub use crate::retry::RetryOn;
|
||||
pub use crate::retry::RetryPolicy;
|
||||
pub use crate::retry::backoff;
|
||||
pub use crate::retry::run_with_retry;
|
||||
pub use crate::sse::sse_stream;
|
||||
pub use crate::telemetry::RequestTelemetry;
|
||||
pub use crate::transport::ByteStream;
|
||||
pub use crate::transport::HttpTransport;
|
||||
pub use crate::transport::ReqwestTransport;
|
||||
pub use crate::transport::StreamResponse;
|
||||
pub use codex_http_client::HttpClient as CodexHttpClient;
|
||||
pub use codex_http_client::RequestBuilder as CodexRequestBuilder;
|
||||
pub use codex_http_client::*;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::error::TransportError;
|
||||
use crate::request::Request;
|
||||
use codex_http_client::Request;
|
||||
use codex_http_client::TransportError;
|
||||
use rand::Rng;
|
||||
use std::future::Future;
|
||||
use std::time::Duration;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::error::StreamError;
|
||||
use crate::transport::ByteStream;
|
||||
use codex_http_client::ByteStream;
|
||||
use codex_http_client::StreamError;
|
||||
use eventsource_stream::Eventsource;
|
||||
use futures::StreamExt;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::error::TransportError;
|
||||
use codex_http_client::TransportError;
|
||||
use http::StatusCode;
|
||||
use std::time::Duration;
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ base64 = { workspace = true }
|
||||
bytes = { workspace = true }
|
||||
clatter = { workspace = true }
|
||||
codex-api = { workspace = true }
|
||||
codex-client = { workspace = true }
|
||||
codex-http-client = { workspace = true }
|
||||
codex-exec-server-protocol = { workspace = true }
|
||||
codex-file-system = { workspace = true }
|
||||
codex-network-proxy = { workspace = true }
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
use std::error::Error as StdError;
|
||||
use std::time::Duration;
|
||||
|
||||
use codex_client::build_reqwest_client_with_custom_ca;
|
||||
use codex_client::with_chatgpt_cloudflare_cookie_store;
|
||||
use codex_exec_server_protocol::JSONRPCErrorError;
|
||||
use codex_http_client::build_reqwest_client_with_custom_ca;
|
||||
use codex_http_client::with_chatgpt_cloudflare_cookie_store;
|
||||
use futures::FutureExt;
|
||||
use futures::StreamExt;
|
||||
use futures::future::BoxFuture;
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
load("//:defs.bzl", "codex_rust_crate")
|
||||
|
||||
codex_rust_crate(
|
||||
name = "http-client",
|
||||
compile_data = glob(["tests/fixtures/**"]),
|
||||
crate_name = "codex_http_client",
|
||||
)
|
||||
@@ -0,0 +1,49 @@
|
||||
[package]
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
name = "codex-http-client"
|
||||
version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
bytes = { workspace = true }
|
||||
codex-utils-rustls-provider = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
http = { workspace = true }
|
||||
opentelemetry = { workspace = true }
|
||||
reqwest = { workspace = true, features = ["json", "rustls-tls-native-roots", "stream"] }
|
||||
rustls = { workspace = true }
|
||||
rustls-native-certs = { workspace = true }
|
||||
rustls-pki-types = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tokio = { workspace = true, features = ["macros", "rt", "time", "sync"] }
|
||||
tracing = { workspace = true }
|
||||
tracing-opentelemetry = { workspace = true }
|
||||
zstd = { workspace = true }
|
||||
|
||||
[target.'cfg(any(target_os = "windows", target_os = "macos"))'.dependencies]
|
||||
sha2 = { workspace = true }
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
system-configuration = { workspace = true }
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
windows-sys = { version = "0.52", features = [
|
||||
"Win32_Foundation",
|
||||
"Win32_Networking_WinHttp",
|
||||
] }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
codex-utils-cargo-bin = { workspace = true }
|
||||
opentelemetry_sdk = { workspace = true }
|
||||
pretty_assertions = { workspace = true }
|
||||
rcgen = { workspace = true }
|
||||
tempfile = { workspace = true }
|
||||
tracing-subscriber = { workspace = true }
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
@@ -0,0 +1,9 @@
|
||||
# codex-http-client
|
||||
|
||||
Low-level HTTP transport shared by Codex crates.
|
||||
|
||||
- Defines the request, response, streaming, and transport types used for outbound HTTP calls.
|
||||
- Owns the `reqwest` implementation, custom CA handling, and ChatGPT Cloudflare cookie policy.
|
||||
- Resolves system, PAC/WPAD, environment, and direct proxy routes for supported clients.
|
||||
|
||||
Higher-level retry, SSE, and request telemetry policy remains in `codex-client`.
|
||||
+4
-4
@@ -10,9 +10,9 @@
|
||||
//! - error messages guide users when CA files are invalid.
|
||||
//! - optional HTTPS probes can complete a request through the constructed client.
|
||||
//!
|
||||
//! The detailed explanation of what "hermetic" means here lives in `codex_client::custom_ca`.
|
||||
//! The detailed explanation of what "hermetic" means here lives in `codex_http_client::custom_ca`.
|
||||
//! This binary exists so the tests can exercise
|
||||
//! [`codex_client::build_reqwest_client_for_subprocess_tests`] in a separate process without
|
||||
//! [`codex_http_client::build_reqwest_client_for_subprocess_tests`] in a separate process without
|
||||
//! duplicating client-construction logic.
|
||||
|
||||
use std::env;
|
||||
@@ -69,11 +69,11 @@ fn build_probe_client(
|
||||
if let Some(proxy_url) = proxy_url {
|
||||
let proxy = reqwest::Proxy::https(proxy_url)
|
||||
.map_err(|error| format!("failed to configure probe proxy {proxy_url}: {error}"))?;
|
||||
return codex_client::build_reqwest_client_with_custom_ca(builder.proxy(proxy))
|
||||
return codex_http_client::build_reqwest_client_with_custom_ca(builder.proxy(proxy))
|
||||
.map_err(|error| error.to_string());
|
||||
}
|
||||
|
||||
codex_client::build_reqwest_client_for_subprocess_tests(builder)
|
||||
codex_http_client::build_reqwest_client_for_subprocess_tests(builder)
|
||||
.map_err(|error| error.to_string())
|
||||
}
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ use reqwest::header::HeaderValue;
|
||||
|
||||
use crate::chatgpt_hosts::is_allowed_chatgpt_host;
|
||||
|
||||
// WARNING: this store is process-global and may be shared across auth contexts.
|
||||
// WARNING: this HTTP cookie store is process-global and may be shared across auth contexts.
|
||||
// It must only ever contain Cloudflare infrastructure cookies. Never extend this
|
||||
// store to persist ChatGPT account, session, auth, or other user-specific cookie
|
||||
// data.
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/// Returns whether `host` is one of the ChatGPT hosts Codex is allowed to treat
|
||||
/// as first-party ChatGPT traffic.
|
||||
/// as first-party ChatGPT HTTP traffic.
|
||||
pub fn is_allowed_chatgpt_host(host: &str) -> bool {
|
||||
const EXACT_HOSTS: &[&str] = &["chatgpt.com", "chat.openai.com", "chatgpt-staging.com"];
|
||||
const SUBDOMAIN_SUFFIXES: &[&str] = &[".chatgpt.com", ".chatgpt-staging.com"];
|
||||
@@ -1,4 +1,4 @@
|
||||
//! Custom CA handling for Codex outbound HTTP and websocket clients.
|
||||
//! Custom CA handling shared by Codex outbound HTTP and websocket clients.
|
||||
//!
|
||||
//! Codex constructs outbound reqwest clients and secure websocket connections in a few crates, but
|
||||
//! they all need the same trust-store policy when enterprise proxies or gateways intercept TLS.
|
||||
+8
-8
@@ -14,47 +14,47 @@ use tracing::Span;
|
||||
use tracing_opentelemetry::OpenTelemetrySpanExt;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CodexHttpClient {
|
||||
pub struct HttpClient {
|
||||
inner: reqwest::Client,
|
||||
}
|
||||
|
||||
impl CodexHttpClient {
|
||||
impl HttpClient {
|
||||
pub fn new(inner: reqwest::Client) -> Self {
|
||||
Self { inner }
|
||||
}
|
||||
|
||||
pub fn get<U>(&self, url: U) -> CodexRequestBuilder
|
||||
pub fn get<U>(&self, url: U) -> RequestBuilder
|
||||
where
|
||||
U: IntoUrl,
|
||||
{
|
||||
self.request(Method::GET, url)
|
||||
}
|
||||
|
||||
pub fn post<U>(&self, url: U) -> CodexRequestBuilder
|
||||
pub fn post<U>(&self, url: U) -> RequestBuilder
|
||||
where
|
||||
U: IntoUrl,
|
||||
{
|
||||
self.request(Method::POST, url)
|
||||
}
|
||||
|
||||
pub fn request<U>(&self, method: Method, url: U) -> CodexRequestBuilder
|
||||
pub fn request<U>(&self, method: Method, url: U) -> RequestBuilder
|
||||
where
|
||||
U: IntoUrl,
|
||||
{
|
||||
let url_str = url.as_str().to_string();
|
||||
CodexRequestBuilder::new(self.inner.request(method.clone(), url), method, url_str)
|
||||
RequestBuilder::new(self.inner.request(method.clone(), url), method, url_str)
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use = "requests are not sent unless `send` is awaited"]
|
||||
#[derive(Debug)]
|
||||
pub struct CodexRequestBuilder {
|
||||
pub struct RequestBuilder {
|
||||
builder: reqwest::RequestBuilder,
|
||||
method: Method,
|
||||
url: String,
|
||||
}
|
||||
|
||||
impl CodexRequestBuilder {
|
||||
impl RequestBuilder {
|
||||
fn new(builder: reqwest::RequestBuilder, method: Method, url: String) -> Self {
|
||||
Self {
|
||||
builder,
|
||||
@@ -1,3 +1,5 @@
|
||||
//! Errors returned by the shared Codex HTTP transport.
|
||||
|
||||
use http::HeaderMap;
|
||||
use http::StatusCode;
|
||||
use thiserror::Error;
|
||||
@@ -0,0 +1,40 @@
|
||||
mod chatgpt_cloudflare_cookies;
|
||||
mod chatgpt_hosts;
|
||||
mod custom_ca;
|
||||
mod default_client;
|
||||
mod error;
|
||||
mod outbound_proxy;
|
||||
mod request;
|
||||
mod transport;
|
||||
|
||||
pub use crate::chatgpt_cloudflare_cookies::with_chatgpt_cloudflare_cookie_store;
|
||||
pub use crate::chatgpt_hosts::is_allowed_chatgpt_host;
|
||||
pub use crate::custom_ca::BuildCustomCaTransportError;
|
||||
/// Test-only subprocess hook for custom CA coverage.
|
||||
///
|
||||
/// This stays public only so the `custom_ca_probe` binary target can reuse the shared helper. It
|
||||
/// is hidden from normal docs because ordinary callers should use
|
||||
/// [`build_reqwest_client_with_custom_ca`] instead.
|
||||
#[doc(hidden)]
|
||||
pub use crate::custom_ca::build_reqwest_client_for_subprocess_tests;
|
||||
pub use crate::custom_ca::build_reqwest_client_with_custom_ca;
|
||||
pub use crate::custom_ca::maybe_build_rustls_client_config_with_custom_ca;
|
||||
pub use crate::default_client::HttpClient;
|
||||
pub use crate::default_client::RequestBuilder;
|
||||
pub use crate::error::StreamError;
|
||||
pub use crate::error::TransportError;
|
||||
pub use crate::outbound_proxy::BuildRouteAwareHttpClientError;
|
||||
pub use crate::outbound_proxy::ClientRouteClass;
|
||||
pub use crate::outbound_proxy::OutboundProxyConfig;
|
||||
pub use crate::outbound_proxy::RouteFailureClass;
|
||||
pub use crate::outbound_proxy::build_reqwest_client_for_route;
|
||||
pub use crate::request::EncodedJsonBody;
|
||||
pub use crate::request::PreparedRequestBody;
|
||||
pub use crate::request::Request;
|
||||
pub use crate::request::RequestBody;
|
||||
pub use crate::request::RequestCompression;
|
||||
pub use crate::request::Response;
|
||||
pub use crate::transport::ByteStream;
|
||||
pub use crate::transport::HttpTransport;
|
||||
pub use crate::transport::ReqwestTransport;
|
||||
pub use crate::transport::StreamResponse;
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
//! Conservative outbound proxy selection for resolver-aware clients.
|
||||
//! Conservative outbound proxy selection for resolver-aware HTTP clients.
|
||||
//!
|
||||
//! When enabled, platform system discovery is tried first, explicit environment
|
||||
//! proxies are the fallback, and the final fallback is a direct connection.
|
||||
+2
@@ -1,3 +1,5 @@
|
||||
//! macOS system proxy resolution through SystemConfiguration and CFNetwork.
|
||||
|
||||
use std::ffi::c_void;
|
||||
use std::ptr;
|
||||
use std::time::Duration;
|
||||
+2
@@ -1,3 +1,5 @@
|
||||
//! Windows system proxy resolution through WinHTTP.
|
||||
|
||||
use std::ffi::c_void;
|
||||
use std::ptr;
|
||||
|
||||
+2
@@ -1,3 +1,5 @@
|
||||
//! Windows proxy parsing tests.
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
+2
@@ -1,3 +1,5 @@
|
||||
//! Shared outbound proxy policy tests.
|
||||
|
||||
use super::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::io::Read;
|
||||
@@ -1,7 +1,7 @@
|
||||
use bytes::Bytes;
|
||||
use http::HeaderMap;
|
||||
use http::HeaderValue;
|
||||
use http::Method;
|
||||
use reqwest::header::HeaderMap;
|
||||
use reqwest::header::HeaderValue;
|
||||
use serde::Serialize;
|
||||
use serde_json::Value;
|
||||
use std::time::Duration;
|
||||
@@ -121,7 +121,7 @@ impl Request {
|
||||
Some(RequestBody::Json(_) | RequestBody::EncodedJson(_))
|
||||
);
|
||||
let trace_bytes = if self.compression != RequestCompression::None
|
||||
&& tracing::enabled!(target: "codex_client::transport", tracing::Level::TRACE)
|
||||
&& tracing::enabled!(target: "codex_http_client::transport", tracing::Level::TRACE)
|
||||
{
|
||||
match self.body.as_ref() {
|
||||
Some(RequestBody::Json(body)) => Some(Bytes::from(
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::default_client::CodexHttpClient;
|
||||
use crate::default_client::CodexRequestBuilder;
|
||||
use crate::default_client::HttpClient;
|
||||
use crate::default_client::RequestBuilder;
|
||||
use crate::error::TransportError;
|
||||
use crate::request::Request;
|
||||
use crate::request::RequestBody;
|
||||
@@ -35,17 +35,17 @@ pub trait HttpTransport: Send + Sync {
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ReqwestTransport {
|
||||
client: CodexHttpClient,
|
||||
client: HttpClient,
|
||||
}
|
||||
|
||||
impl ReqwestTransport {
|
||||
pub fn new(client: reqwest::Client) -> Self {
|
||||
Self {
|
||||
client: CodexHttpClient::new(client),
|
||||
client: HttpClient::new(client),
|
||||
}
|
||||
}
|
||||
|
||||
fn build(&self, req: Request) -> Result<CodexRequestBuilder, TransportError> {
|
||||
fn build(&self, req: Request) -> Result<RequestBuilder, TransportError> {
|
||||
let prepared = req.prepare_body_for_send().map_err(TransportError::Build)?;
|
||||
|
||||
let Request {
|
||||
@@ -3,7 +3,7 @@
|
||||
//!
|
||||
//! These tests intentionally run through `custom_ca_probe` and
|
||||
//! `build_reqwest_client_for_subprocess_tests` instead of calling the helper in-process. The
|
||||
//! detailed explanation of what "hermetic" means here lives in `codex_client::custom_ca`; these
|
||||
//! detailed explanation of what "hermetic" means here lives in `codex_http_client::custom_ca`; these
|
||||
//! tests add the process-level half of that contract by scrubbing inherited CA environment
|
||||
//! variables before each subprocess launch. Most assertions here cover CA file selection, PEM
|
||||
//! parsing, and user-facing errors. The HTTPS probes go further and perform real POSTs against
|
||||
Vendored
@@ -11,7 +11,7 @@ workspace = true
|
||||
base64 = { workspace = true }
|
||||
chrono = { workspace = true, features = ["serde"] }
|
||||
codex-agent-identity = { workspace = true }
|
||||
codex-client = { workspace = true }
|
||||
codex-http-client = { workspace = true }
|
||||
codex-config = { workspace = true }
|
||||
codex-keyring-store = { workspace = true }
|
||||
codex-model-provider-info = { workspace = true }
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
//! Default Codex HTTP client: shared `User-Agent`, `originator`, optional residency header, and
|
||||
//! reqwest/`CodexHttpClient` construction.
|
||||
//! reqwest/`HttpClient` construction.
|
||||
//!
|
||||
//! Use [`crate::default_client`] or [`codex_login::default_client`] from other crates in this
|
||||
//! workspace.
|
||||
|
||||
use codex_client::BuildCustomCaTransportError;
|
||||
use codex_client::BuildRouteAwareHttpClientError;
|
||||
use codex_client::ClientRouteClass;
|
||||
use codex_client::CodexHttpClient;
|
||||
pub use codex_client::CodexRequestBuilder;
|
||||
use codex_client::build_reqwest_client_for_route;
|
||||
use codex_client::build_reqwest_client_with_custom_ca;
|
||||
use codex_client::with_chatgpt_cloudflare_cookie_store;
|
||||
use codex_http_client::BuildCustomCaTransportError;
|
||||
use codex_http_client::BuildRouteAwareHttpClientError;
|
||||
use codex_http_client::ClientRouteClass;
|
||||
use codex_http_client::HttpClient;
|
||||
pub use codex_http_client::RequestBuilder as CodexRequestBuilder;
|
||||
use codex_http_client::build_reqwest_client_for_route;
|
||||
use codex_http_client::build_reqwest_client_with_custom_ca;
|
||||
use codex_http_client::with_chatgpt_cloudflare_cookie_store;
|
||||
use codex_terminal_detection::user_agent;
|
||||
use reqwest::header::HeaderMap;
|
||||
use reqwest::header::HeaderValue;
|
||||
@@ -197,9 +197,9 @@ fn sanitize_user_agent(candidate: String, fallback: &str) -> String {
|
||||
///
|
||||
/// This supported default path preserves reqwest's existing proxy behavior and does not opt into
|
||||
/// Codex's route-aware system/PAC resolution.
|
||||
pub fn create_client() -> CodexHttpClient {
|
||||
pub fn create_client() -> HttpClient {
|
||||
let inner = build_reqwest_client();
|
||||
CodexHttpClient::new(inner)
|
||||
HttpClient::new(inner)
|
||||
}
|
||||
|
||||
/// Builds the default reqwest client used for ordinary Codex HTTP traffic.
|
||||
@@ -282,8 +282,8 @@ pub(crate) fn build_default_auth_reqwest_client(
|
||||
pub(crate) fn create_default_auth_client(
|
||||
endpoint: &str,
|
||||
auth_route_config: Option<&AuthRouteConfig>,
|
||||
) -> Result<CodexHttpClient, BuildRouteAwareHttpClientError> {
|
||||
build_default_auth_reqwest_client(endpoint, auth_route_config).map(CodexHttpClient::new)
|
||||
) -> Result<HttpClient, BuildRouteAwareHttpClientError> {
|
||||
build_default_auth_reqwest_client(endpoint, auth_route_config).map(HttpClient::new)
|
||||
}
|
||||
|
||||
pub fn default_headers() -> HeaderMap {
|
||||
|
||||
@@ -55,8 +55,8 @@ use crate::outbound_proxy::AuthRouteConfig;
|
||||
use crate::token_data::TokenData;
|
||||
use crate::token_data::parse_chatgpt_jwt_claims;
|
||||
use crate::token_data::parse_jwt_expiration;
|
||||
use codex_client::CodexHttpClient;
|
||||
use codex_config::types::AuthCredentialsStoreMode;
|
||||
use codex_http_client::HttpClient;
|
||||
use codex_protocol::account::PlanType as AccountPlanType;
|
||||
use codex_protocol::auth::PlanType as InternalPlanType;
|
||||
use codex_protocol::auth::RefreshTokenFailedError;
|
||||
@@ -171,7 +171,7 @@ pub struct ChatgptAuthTokens {
|
||||
#[derive(Debug, Clone)]
|
||||
struct ChatgptAuthState {
|
||||
auth_dot_json: Arc<Mutex<Option<AuthDotJson>>>,
|
||||
client: CodexHttpClient,
|
||||
client: HttpClient,
|
||||
}
|
||||
|
||||
const TOKEN_REFRESH_INTERVAL: i64 = 8;
|
||||
@@ -805,7 +805,7 @@ impl ChatgptAuth {
|
||||
&self.storage
|
||||
}
|
||||
|
||||
fn client(&self) -> &CodexHttpClient {
|
||||
fn client(&self) -> &HttpClient {
|
||||
&self.state.client
|
||||
}
|
||||
|
||||
@@ -1331,7 +1331,7 @@ fn persist_tokens(
|
||||
// The caller is responsible for persisting any returned tokens.
|
||||
async fn request_chatgpt_token_refresh(
|
||||
refresh_token: String,
|
||||
client: &CodexHttpClient,
|
||||
client: &HttpClient,
|
||||
) -> Result<RefreshResponse, RefreshTokenError> {
|
||||
let refresh_request = RefreshRequest {
|
||||
client_id: oauth_client_id(),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use codex_client::CodexHttpClient;
|
||||
use codex_http_client::HttpClient;
|
||||
use codex_protocol::account::PlanType as AccountPlanType;
|
||||
use codex_protocol::auth::PlanType as InternalPlanType;
|
||||
use serde::Deserialize;
|
||||
@@ -77,7 +77,7 @@ impl PersonalAccessTokenAuth {
|
||||
}
|
||||
|
||||
async fn hydrate_personal_access_token(
|
||||
client: &CodexHttpClient,
|
||||
client: &HttpClient,
|
||||
endpoint: &str,
|
||||
access_token: &str,
|
||||
) -> std::io::Result<PersonalAccessTokenAuth> {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
use serde::Serialize;
|
||||
use std::time::Duration;
|
||||
|
||||
use codex_client::CodexHttpClient;
|
||||
use codex_http_client::HttpClient;
|
||||
use codex_protocol::auth::AuthMode;
|
||||
|
||||
use super::manager::REFRESH_TOKEN_URL_OVERRIDE_ENV_VAR;
|
||||
@@ -95,7 +95,7 @@ fn resolved_auth_mode(auth_dot_json: &AuthDotJson) -> AuthMode {
|
||||
}
|
||||
|
||||
async fn revoke_oauth_token(
|
||||
client: &CodexHttpClient,
|
||||
client: &HttpClient,
|
||||
endpoint: &str,
|
||||
token: &str,
|
||||
kind: RevokeTokenKind,
|
||||
@@ -181,7 +181,7 @@ mod tests {
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
let client = CodexHttpClient::new(reqwest::Client::new());
|
||||
let client = HttpClient::new(reqwest::Client::new());
|
||||
let endpoint = format!("{}/oauth/revoke", server.uri());
|
||||
let error = revoke_oauth_token(
|
||||
&client,
|
||||
|
||||
@@ -7,8 +7,8 @@ mod outbound_proxy;
|
||||
mod pkce;
|
||||
mod server;
|
||||
|
||||
pub use codex_client::BuildCustomCaTransportError as BuildLoginHttpClientError;
|
||||
pub use codex_config::types::AuthCredentialsStoreMode;
|
||||
pub use codex_http_client::BuildCustomCaTransportError as BuildLoginHttpClientError;
|
||||
pub use device_code_auth::DeviceCode;
|
||||
pub use device_code_auth::complete_device_code_login;
|
||||
pub use device_code_auth::request_device_code;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use codex_client::OutboundProxyConfig;
|
||||
use codex_http_client::OutboundProxyConfig;
|
||||
|
||||
/// Auth-layer adapter around client-owned proxy policy.
|
||||
///
|
||||
|
||||
@@ -16,7 +16,7 @@ workspace = true
|
||||
codex-api = { workspace = true }
|
||||
codex-agent-identity = { workspace = true }
|
||||
codex-aws-auth = { workspace = true }
|
||||
codex-client = { workspace = true }
|
||||
codex-http-client = { workspace = true }
|
||||
codex-feedback = { workspace = true }
|
||||
codex-login = { workspace = true }
|
||||
codex-model-provider-info = { workspace = true }
|
||||
|
||||
@@ -6,9 +6,9 @@ use codex_api::SharedAuthProvider;
|
||||
use codex_aws_auth::AwsAuthContext;
|
||||
use codex_aws_auth::AwsAuthError;
|
||||
use codex_aws_auth::AwsRequestToSign;
|
||||
use codex_client::Request;
|
||||
use codex_client::RequestBody;
|
||||
use codex_client::RequestCompression;
|
||||
use codex_http_client::Request;
|
||||
use codex_http_client::RequestBody;
|
||||
use codex_http_client::RequestCompression;
|
||||
use codex_login::auth::BedrockApiKeyAuth;
|
||||
use codex_model_provider_info::ModelProviderAwsAuthInfo;
|
||||
use codex_protocol::error::CodexErr;
|
||||
|
||||
Reference in New Issue
Block a user