[codex] cover app-owned HTTP requests [ci changed_files]

This commit is contained in:
Cooper Gamble
2026-06-03 23:44:25 +00:00
parent ec2e111e04
commit d766866d3c
52 changed files with 1415 additions and 358 deletions
+26 -9
View File
@@ -1,4 +1,5 @@
use codex_core::config::Config;
use codex_http_state::HttpStateContext;
use codex_login::AuthManager;
use codex_login::default_client::create_client;
@@ -9,26 +10,34 @@ use std::time::Duration;
const OAI_PRODUCT_SKU_HEADER: &str = "OAI-Product-Sku";
const CODEX_PRODUCT_SKU: &str = "codex";
/// Make a GET request to the ChatGPT backend API.
pub(crate) async fn chatgpt_get_request<T: DeserializeOwned>(
pub(crate) async fn chatgpt_get_request_with_http_state<T: DeserializeOwned>(
config: &Config,
path: String,
http_state: HttpStateContext,
) -> anyhow::Result<T> {
chatgpt_get_request_with_timeout(config, path, /*timeout*/ None).await
chatgpt_get_request_with_timeout_and_http_state(
config,
path,
/*timeout*/ None,
Some(http_state),
)
.await
}
pub(crate) async fn chatgpt_get_request_with_timeout<T: DeserializeOwned>(
pub(crate) async fn chatgpt_get_request_with_timeout_and_http_state<T: DeserializeOwned>(
config: &Config,
path: String,
timeout: Option<Duration>,
http_state: Option<HttpStateContext>,
) -> anyhow::Result<T> {
let chatgpt_base_url = &config.chatgpt_base_url;
let auth_manager =
AuthManager::shared_from_config(config, /*enable_codex_api_key_env*/ false).await;
let auth = auth_manager
.auth()
.await
.ok_or_else(|| anyhow::anyhow!("ChatGPT auth not available"))?;
let auth = match http_state.as_ref() {
Some(http_state) => auth_manager.auth_for_surface(http_state.surface()).await,
None => auth_manager.auth().await,
}
.ok_or_else(|| anyhow::anyhow!("ChatGPT auth not available"))?;
anyhow::ensure!(
auth.uses_codex_backend(),
"ChatGPT backend requests require Codex backend auth"
@@ -46,9 +55,16 @@ pub(crate) async fn chatgpt_get_request_with_timeout<T: DeserializeOwned>(
path.trim_start_matches('/')
);
let auth_provider = codex_model_provider::with_native_integrity_state(
codex_model_provider::auth_provider_from_auth(&auth),
Some(&auth),
http_state,
);
let mut request_headers = Default::default();
auth_provider.add_auth_headers_for_url(&url, &mut request_headers);
let mut request = client
.get(&url)
.headers(codex_model_provider::auth_provider_from_auth(&auth).to_auth_headers())
.headers(request_headers.clone())
.header(OAI_PRODUCT_SKU_HEADER, CODEX_PRODUCT_SKU)
.header("Content-Type", "application/json");
@@ -57,6 +73,7 @@ pub(crate) async fn chatgpt_get_request_with_timeout<T: DeserializeOwned>(
}
let response = request.send().await.context("Failed to send request")?;
auth_provider.observe_response_headers(&url, &request_headers, response.headers());
if response.status().is_success() {
let result: T = response
+50 -20
View File
@@ -2,7 +2,7 @@ use std::collections::HashMap;
use std::collections::HashSet;
use std::time::Duration;
use crate::chatgpt_client::chatgpt_get_request_with_timeout;
use crate::chatgpt_client::chatgpt_get_request_with_timeout_and_http_state;
use codex_app_server_protocol::AppInfo;
use codex_connectors::ConnectorDirectoryCacheContext;
@@ -14,11 +14,14 @@ use codex_connectors::merge::merge_plugin_connectors;
use codex_core::config::Config;
pub use codex_core::connectors::list_accessible_connectors_from_mcp_tools;
pub use codex_core::connectors::list_accessible_connectors_from_mcp_tools_with_environment_manager;
pub use codex_core::connectors::list_accessible_connectors_from_mcp_tools_with_environment_manager_and_http_state;
pub use codex_core::connectors::list_accessible_connectors_from_mcp_tools_with_options;
pub use codex_core::connectors::list_accessible_connectors_from_mcp_tools_with_options_and_status;
pub use codex_core::connectors::list_cached_accessible_connectors_from_mcp_tools;
pub use codex_core::connectors::list_cached_accessible_connectors_from_mcp_tools_with_http_state;
pub use codex_core::connectors::with_app_enabled_state;
use codex_core_plugins::PluginsManager;
use codex_http_state::HttpStateContext;
use codex_login::AuthManager;
use codex_login::CodexAuth;
use codex_login::default_client::originator;
@@ -26,20 +29,27 @@ use codex_plugin::AppConnectorId;
const DIRECTORY_CONNECTORS_TIMEOUT: Duration = Duration::from_secs(60);
async fn apps_enabled(config: &Config) -> bool {
async fn auth(config: &Config, http_state: Option<&HttpStateContext>) -> Option<CodexAuth> {
let auth_manager =
AuthManager::shared_from_config(config, /*enable_codex_api_key_env*/ false).await;
let auth = auth_manager.auth().await;
match http_state {
Some(http_state) => auth_manager.auth_for_surface(http_state.surface()).await,
None => auth_manager.auth().await,
}
}
async fn apps_enabled(config: &Config, http_state: Option<&HttpStateContext>) -> bool {
let auth = auth(config, http_state).await;
config
.features
.apps_enabled_for_auth(auth.as_ref().is_some_and(CodexAuth::uses_codex_backend))
}
async fn connector_auth(config: &Config) -> anyhow::Result<CodexAuth> {
let auth_manager =
AuthManager::shared_from_config(config, /*enable_codex_api_key_env*/ false).await;
let auth = auth_manager
.auth()
async fn connector_auth(
config: &Config,
http_state: Option<&HttpStateContext>,
) -> anyhow::Result<CodexAuth> {
let auth = auth(config, http_state)
.await
.ok_or_else(|| anyhow::anyhow!("ChatGPT auth not available"))?;
anyhow::ensure!(
@@ -50,7 +60,7 @@ async fn connector_auth(config: &Config) -> anyhow::Result<CodexAuth> {
}
pub async fn list_connectors(config: &Config) -> anyhow::Result<Vec<AppInfo>> {
if !apps_enabled(config).await {
if !apps_enabled(config, /*http_state*/ None).await {
return Ok(Vec::new());
}
let (connectors_result, accessible_result) = tokio::join!(
@@ -72,11 +82,18 @@ pub async fn list_all_connectors(config: &Config) -> anyhow::Result<Vec<AppInfo>
}
pub async fn list_cached_all_connectors(config: &Config) -> Option<Vec<AppInfo>> {
if !apps_enabled(config).await {
list_cached_all_connectors_with_http_state(config, /*http_state*/ None).await
}
pub async fn list_cached_all_connectors_with_http_state(
config: &Config,
http_state: Option<&HttpStateContext>,
) -> Option<Vec<AppInfo>> {
if !apps_enabled(config, http_state).await {
return Some(Vec::new());
}
let auth = connector_auth(config).await.ok()?;
let auth = connector_auth(config, http_state).await.ok()?;
let cache_context = connector_directory_cache_context(config, &auth);
let connectors = codex_connectors::cached_directory_connectors(&cache_context)?;
let connectors = merge_plugin_connectors(
@@ -96,22 +113,35 @@ pub async fn list_all_connectors_with_options(
config: &Config,
force_refetch: bool,
) -> anyhow::Result<Vec<AppInfo>> {
if !apps_enabled(config).await {
list_all_connectors_with_options_and_http_state(config, force_refetch, /*http_state*/ None)
.await
}
pub async fn list_all_connectors_with_options_and_http_state(
config: &Config,
force_refetch: bool,
http_state: Option<HttpStateContext>,
) -> anyhow::Result<Vec<AppInfo>> {
if !apps_enabled(config, http_state.as_ref()).await {
return Ok(Vec::new());
}
let auth = connector_auth(config).await?;
let auth = connector_auth(config, http_state.as_ref()).await?;
let cache_context = connector_directory_cache_context(config, &auth);
let connectors = codex_connectors::list_all_connectors_with_options(
cache_context,
auth.is_workspace_account(),
force_refetch,
|path| async move {
chatgpt_get_request_with_timeout::<DirectoryListResponse>(
config,
path,
Some(DIRECTORY_CONNECTORS_TIMEOUT),
)
.await
|path| {
let http_state = http_state.clone();
async move {
chatgpt_get_request_with_timeout_and_http_state::<DirectoryListResponse>(
config,
path,
Some(DIRECTORY_CONNECTORS_TIMEOUT),
http_state,
)
.await
}
},
)
.await?;
+9 -2
View File
@@ -1,7 +1,9 @@
use codex_core::config::Config;
use codex_http_state::HttpStateContext;
use codex_http_state::HttpStateSurface;
use serde::Deserialize;
use crate::chatgpt_client::chatgpt_get_request;
use crate::chatgpt_client::chatgpt_get_request_with_http_state;
#[derive(Debug, Deserialize)]
pub struct GetTaskResponse {
@@ -36,5 +38,10 @@ pub struct OutputDiff {
pub(crate) async fn get_task(config: &Config, task_id: String) -> anyhow::Result<GetTaskResponse> {
let path = format!("/wham/tasks/{task_id}");
chatgpt_get_request(config, path).await
chatgpt_get_request_with_http_state(
config,
path,
HttpStateContext::new(config.codex_home.to_path_buf(), HttpStateSurface::CodexCli),
)
.await
}
+16 -2
View File
@@ -5,10 +5,11 @@ use std::time::Instant;
use anyhow::Context;
use codex_core::config::Config;
use codex_http_state::HttpStateContext;
use codex_login::CodexAuth;
use serde::Deserialize;
use crate::chatgpt_client::chatgpt_get_request_with_timeout;
use crate::chatgpt_client::chatgpt_get_request_with_timeout_and_http_state;
const WORKSPACE_SETTINGS_TIMEOUT: Duration = Duration::from_secs(10);
const WORKSPACE_SETTINGS_CACHE_TTL: Duration = Duration::from_secs(15 * 60);
@@ -85,6 +86,18 @@ pub async fn codex_plugins_enabled_for_workspace(
config: &Config,
auth: Option<&CodexAuth>,
cache: Option<&WorkspaceSettingsCache>,
) -> anyhow::Result<bool> {
codex_plugins_enabled_for_workspace_with_http_state(
config, auth, cache, /*http_state*/ None,
)
.await
}
pub async fn codex_plugins_enabled_for_workspace_with_http_state(
config: &Config,
auth: Option<&CodexAuth>,
cache: Option<&WorkspaceSettingsCache>,
http_state: Option<HttpStateContext>,
) -> anyhow::Result<bool> {
let Some(auth) = auth else {
return Ok(true);
@@ -115,10 +128,11 @@ pub async fn codex_plugins_enabled_for_workspace(
}
let encoded_account_id = encode_path_segment(account_id);
let settings: WorkspaceSettingsResponse = chatgpt_get_request_with_timeout(
let settings: WorkspaceSettingsResponse = chatgpt_get_request_with_timeout_and_http_state(
config,
format!("/accounts/{encoded_account_id}/settings"),
Some(WORKSPACE_SETTINGS_TIMEOUT),
http_state,
)
.await?;