mirror of
https://github.com/openai/codex.git
synced 2026-10-20 17:38:45 +00:00
[codex] cover app-owned HTTP requests [ci changed_files]
This commit is contained in:
@@ -15,12 +15,14 @@ workspace = true
|
||||
[dependencies]
|
||||
anyhow = { workspace = true }
|
||||
codex-analytics = { workspace = true }
|
||||
codex-api = { workspace = true }
|
||||
codex-app-server-protocol = { workspace = true }
|
||||
codex-config = { workspace = true }
|
||||
codex-core-skills = { workspace = true }
|
||||
codex-exec-server = { workspace = true }
|
||||
codex-git-utils = { workspace = true }
|
||||
codex-hooks = { workspace = true }
|
||||
codex-http-state = { workspace = true }
|
||||
codex-login = { workspace = true }
|
||||
codex-model-provider = { workspace = true }
|
||||
codex-otel = { workspace = true }
|
||||
|
||||
@@ -58,6 +58,7 @@ use codex_config::types::PluginConfig;
|
||||
use codex_config::version_for_toml;
|
||||
use codex_core_skills::SkillMetadata;
|
||||
use codex_hooks::plugin_hook_declarations;
|
||||
use codex_http_state::HttpStateContext;
|
||||
use codex_login::AuthManager;
|
||||
use codex_login::CodexAuth;
|
||||
use codex_plugin::AppConnectorId;
|
||||
@@ -173,8 +174,17 @@ struct ConfiguredMarketplaceUpgradeState {
|
||||
}
|
||||
|
||||
fn remote_plugin_service_config(config: &PluginsConfigInput) -> RemotePluginServiceConfig {
|
||||
RemotePluginServiceConfig {
|
||||
chatgpt_base_url: config.chatgpt_base_url.clone(),
|
||||
RemotePluginServiceConfig::new(config.chatgpt_base_url.clone())
|
||||
}
|
||||
|
||||
fn remote_plugin_service_config_with_http_state(
|
||||
config: &PluginsConfigInput,
|
||||
http_state: Option<HttpStateContext>,
|
||||
) -> RemotePluginServiceConfig {
|
||||
let service_config = remote_plugin_service_config(config);
|
||||
match http_state {
|
||||
Some(http_state) => service_config.with_http_state(http_state),
|
||||
None => service_config,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -624,11 +634,40 @@ impl PluginsManager {
|
||||
visible_scopes: &[RemotePluginScope],
|
||||
on_effective_plugins_changed: Option<Arc<dyn Fn() + Send + Sync + 'static>>,
|
||||
) -> Result<Vec<crate::remote::RemoteMarketplace>, RemotePluginCatalogError> {
|
||||
let plugins = crate::remote::fetch_remote_installed_plugins(
|
||||
&remote_plugin_service_config(config),
|
||||
self.build_and_cache_remote_installed_plugin_marketplaces_with_service_config(
|
||||
remote_plugin_service_config(config),
|
||||
auth,
|
||||
visible_scopes,
|
||||
on_effective_plugins_changed,
|
||||
)
|
||||
.await?;
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn build_and_cache_remote_installed_plugin_marketplaces_with_http_state(
|
||||
&self,
|
||||
config: &PluginsConfigInput,
|
||||
auth: Option<&CodexAuth>,
|
||||
visible_scopes: &[RemotePluginScope],
|
||||
on_effective_plugins_changed: Option<Arc<dyn Fn() + Send + Sync + 'static>>,
|
||||
http_state: HttpStateContext,
|
||||
) -> Result<Vec<crate::remote::RemoteMarketplace>, RemotePluginCatalogError> {
|
||||
self.build_and_cache_remote_installed_plugin_marketplaces_with_service_config(
|
||||
remote_plugin_service_config(config).with_http_state(http_state),
|
||||
auth,
|
||||
visible_scopes,
|
||||
on_effective_plugins_changed,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn build_and_cache_remote_installed_plugin_marketplaces_with_service_config(
|
||||
&self,
|
||||
service_config: RemotePluginServiceConfig,
|
||||
auth: Option<&CodexAuth>,
|
||||
visible_scopes: &[RemotePluginScope],
|
||||
on_effective_plugins_changed: Option<Arc<dyn Fn() + Send + Sync + 'static>>,
|
||||
) -> Result<Vec<crate::remote::RemoteMarketplace>, RemotePluginCatalogError> {
|
||||
let plugins = crate::remote::fetch_remote_installed_plugins(&service_config, auth).await?;
|
||||
let marketplaces =
|
||||
crate::remote::group_remote_installed_plugins_by_marketplaces(&plugins, visible_scopes);
|
||||
let changed = self.write_remote_installed_plugins_cache(plugins);
|
||||
@@ -671,12 +710,14 @@ impl PluginsManager {
|
||||
config: &PluginsConfigInput,
|
||||
auth: Option<CodexAuth>,
|
||||
on_effective_plugins_changed: Option<Arc<dyn Fn() + Send + Sync + 'static>>,
|
||||
http_state: Option<HttpStateContext>,
|
||||
) {
|
||||
self.maybe_start_remote_installed_plugins_cache_refresh_with_notify(
|
||||
config,
|
||||
auth,
|
||||
RemoteInstalledPluginsCacheRefreshNotify::IfCacheChanged,
|
||||
on_effective_plugins_changed,
|
||||
http_state,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -685,12 +726,14 @@ impl PluginsManager {
|
||||
config: &PluginsConfigInput,
|
||||
auth: Option<CodexAuth>,
|
||||
on_effective_plugins_changed: Option<Arc<dyn Fn() + Send + Sync + 'static>>,
|
||||
http_state: Option<HttpStateContext>,
|
||||
) {
|
||||
self.maybe_start_remote_installed_plugins_cache_refresh_with_notify(
|
||||
config,
|
||||
auth,
|
||||
RemoteInstalledPluginsCacheRefreshNotify::AfterSuccessfulRefresh,
|
||||
on_effective_plugins_changed,
|
||||
http_state,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -700,6 +743,7 @@ impl PluginsManager {
|
||||
auth: Option<CodexAuth>,
|
||||
notify: RemoteInstalledPluginsCacheRefreshNotify,
|
||||
on_effective_plugins_changed: Option<Arc<dyn Fn() + Send + Sync + 'static>>,
|
||||
http_state: Option<HttpStateContext>,
|
||||
) {
|
||||
if !config.plugins_enabled {
|
||||
return;
|
||||
@@ -707,7 +751,7 @@ impl PluginsManager {
|
||||
|
||||
self.schedule_remote_installed_plugins_cache_refresh(
|
||||
RemoteInstalledPluginsCacheRefreshRequest {
|
||||
service_config: remote_plugin_service_config(config),
|
||||
service_config: remote_plugin_service_config_with_http_state(config, http_state),
|
||||
auth,
|
||||
notify,
|
||||
on_effective_plugins_changed,
|
||||
@@ -720,6 +764,7 @@ impl PluginsManager {
|
||||
config: &PluginsConfigInput,
|
||||
auth: Option<CodexAuth>,
|
||||
on_effective_plugins_changed: Option<Arc<dyn Fn() + Send + Sync + 'static>>,
|
||||
http_state: Option<HttpStateContext>,
|
||||
) {
|
||||
if !config.plugins_enabled {
|
||||
return;
|
||||
@@ -728,17 +773,19 @@ impl PluginsManager {
|
||||
let manager = Arc::clone(self);
|
||||
let config_for_refresh = config.clone();
|
||||
let auth_for_refresh = auth.clone();
|
||||
let http_state_for_refresh = http_state.clone();
|
||||
let on_local_cache_changed = Arc::new(move || {
|
||||
manager.maybe_start_remote_installed_plugins_cache_refresh_after_mutation(
|
||||
&config_for_refresh,
|
||||
auth_for_refresh.clone(),
|
||||
on_effective_plugins_changed.clone(),
|
||||
http_state_for_refresh.clone(),
|
||||
);
|
||||
});
|
||||
|
||||
crate::remote::maybe_start_remote_installed_plugin_bundle_sync(
|
||||
self.codex_home.clone(),
|
||||
remote_plugin_service_config(config),
|
||||
remote_plugin_service_config_with_http_state(config, http_state),
|
||||
auth,
|
||||
Some(on_local_cache_changed),
|
||||
);
|
||||
@@ -750,17 +797,20 @@ impl PluginsManager {
|
||||
auth: Option<CodexAuth>,
|
||||
roots: &[AbsolutePathBuf],
|
||||
on_effective_plugins_changed: Option<Arc<dyn Fn() + Send + Sync + 'static>>,
|
||||
http_state: Option<HttpStateContext>,
|
||||
) {
|
||||
self.maybe_start_non_curated_plugin_cache_refresh(roots);
|
||||
self.maybe_start_remote_installed_plugins_cache_refresh(
|
||||
config,
|
||||
auth.clone(),
|
||||
on_effective_plugins_changed.clone(),
|
||||
http_state.clone(),
|
||||
);
|
||||
self.maybe_start_remote_installed_plugin_bundle_sync(
|
||||
config,
|
||||
auth,
|
||||
on_effective_plugins_changed,
|
||||
http_state,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -816,6 +866,34 @@ impl PluginsManager {
|
||||
&self,
|
||||
config: &PluginsConfigInput,
|
||||
auth: Option<&CodexAuth>,
|
||||
) -> Result<Vec<String>, RemotePluginFetchError> {
|
||||
self.featured_plugin_ids_for_config_with_service_config(
|
||||
config,
|
||||
auth,
|
||||
remote_plugin_service_config(config),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn featured_plugin_ids_for_config_with_http_state(
|
||||
&self,
|
||||
config: &PluginsConfigInput,
|
||||
auth: Option<&CodexAuth>,
|
||||
http_state: HttpStateContext,
|
||||
) -> Result<Vec<String>, RemotePluginFetchError> {
|
||||
self.featured_plugin_ids_for_config_with_service_config(
|
||||
config,
|
||||
auth,
|
||||
remote_plugin_service_config(config).with_http_state(http_state),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn featured_plugin_ids_for_config_with_service_config(
|
||||
&self,
|
||||
config: &PluginsConfigInput,
|
||||
auth: Option<&CodexAuth>,
|
||||
service_config: RemotePluginServiceConfig,
|
||||
) -> Result<Vec<String>, RemotePluginFetchError> {
|
||||
if !config.plugins_enabled {
|
||||
return Ok(Vec::new());
|
||||
@@ -826,7 +904,7 @@ impl PluginsManager {
|
||||
return Ok(featured_plugin_ids);
|
||||
}
|
||||
let featured_plugin_ids = crate::remote_legacy::fetch_remote_featured_plugin_ids(
|
||||
&remote_plugin_service_config(config),
|
||||
&service_config,
|
||||
auth,
|
||||
self.restriction_product,
|
||||
)
|
||||
@@ -1570,11 +1648,13 @@ impl PluginsManager {
|
||||
&config_for_remote_sync,
|
||||
auth.clone(),
|
||||
on_effective_plugins_changed.clone(),
|
||||
/*http_state*/ None,
|
||||
);
|
||||
manager.maybe_start_remote_installed_plugin_bundle_sync(
|
||||
&config_for_remote_sync,
|
||||
auth.clone(),
|
||||
on_effective_plugins_changed,
|
||||
/*http_state*/ None,
|
||||
);
|
||||
if config_for_remote_sync.remote_plugin_enabled {
|
||||
match crate::remote::fetch_and_cache_global_remote_plugin_catalog(
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
use crate::store::PLUGINS_CACHE_DIR;
|
||||
use crate::store::PluginStore;
|
||||
use codex_api::SharedAuthProvider;
|
||||
use codex_app_server_protocol::JSONRPCErrorError;
|
||||
use codex_app_server_protocol::PluginAuthPolicy;
|
||||
use codex_app_server_protocol::PluginAvailability;
|
||||
use codex_app_server_protocol::PluginInstallPolicy;
|
||||
use codex_app_server_protocol::PluginInterface;
|
||||
use codex_app_server_protocol::SkillInterface;
|
||||
use codex_http_state::HttpStateContext;
|
||||
use codex_login::CodexAuth;
|
||||
use codex_login::default_client::build_reqwest_client;
|
||||
use codex_plugin::PluginId;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use reqwest::RequestBuilder;
|
||||
use reqwest::header::HeaderMap;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use serde_json::Value as JsonValue;
|
||||
@@ -24,6 +27,7 @@ use std::time::Duration;
|
||||
use url::Url;
|
||||
|
||||
mod catalog_cache;
|
||||
pub(crate) mod http_state;
|
||||
mod remote_installed_plugin_sync;
|
||||
mod share;
|
||||
|
||||
@@ -91,9 +95,24 @@ const REMOTE_INSTALLED_MARKETPLACE_DISPLAY_ORDER: [(&str, &str); 5] = [
|
||||
),
|
||||
];
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RemotePluginServiceConfig {
|
||||
pub chatgpt_base_url: String,
|
||||
http_state: Option<HttpStateContext>,
|
||||
}
|
||||
|
||||
impl RemotePluginServiceConfig {
|
||||
pub fn new(chatgpt_base_url: String) -> Self {
|
||||
Self {
|
||||
chatgpt_base_url,
|
||||
http_state: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_http_state(mut self, http_state: HttpStateContext) -> Self {
|
||||
self.http_state = Some(http_state);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
@@ -871,7 +890,7 @@ pub async fn fetch_remote_plugin_skill_detail(
|
||||
|
||||
let url = remote_plugin_skill_detail_url(config, plugin_id, skill_name)?;
|
||||
let client = build_reqwest_client();
|
||||
let request = authenticated_request(client.get(&url), auth)?;
|
||||
let request = authenticated_request(config, client.get(&url), auth, &url);
|
||||
let response: RemotePluginSkillDetailResponse = send_and_decode(request, &url).await?;
|
||||
if response.plugin_id != plugin_id {
|
||||
return Err(RemotePluginCatalogError::UnexpectedPluginId {
|
||||
@@ -973,7 +992,7 @@ pub async fn install_remote_plugin(
|
||||
let base_url = config.chatgpt_base_url.trim_end_matches('/');
|
||||
let url = format!("{base_url}/ps/plugins/{plugin_id}/install");
|
||||
let client = build_reqwest_client();
|
||||
let request = authenticated_request(client.post(&url), auth)?;
|
||||
let request = authenticated_request(config, client.post(&url), auth, &url);
|
||||
let response: RemotePluginMutationResponse = send_and_decode(request, &url).await?;
|
||||
if response.id != plugin_id {
|
||||
return Err(RemotePluginCatalogError::UnexpectedPluginId {
|
||||
@@ -1009,7 +1028,7 @@ pub async fn uninstall_remote_plugin(
|
||||
let base_url = config.chatgpt_base_url.trim_end_matches('/');
|
||||
let url = format!("{base_url}/plugins/{plugin_id}/uninstall");
|
||||
let client = build_reqwest_client();
|
||||
let request = authenticated_request(client.post(&url), auth)?;
|
||||
let request = authenticated_request(config, client.post(&url), auth, &url);
|
||||
let response: RemotePluginMutationResponse = send_and_decode(request, &url).await?;
|
||||
if response.id != plugin_id {
|
||||
return Err(RemotePluginCatalogError::UnexpectedPluginId {
|
||||
@@ -1399,7 +1418,7 @@ async fn get_remote_plugin_list_page(
|
||||
let base_url = config.chatgpt_base_url.trim_end_matches('/');
|
||||
let url = format!("{base_url}/ps/plugins/list");
|
||||
let client = build_reqwest_client();
|
||||
let mut request = authenticated_request(client.get(&url), auth)?;
|
||||
let mut request = authenticated_request(config, client.get(&url), auth, &url);
|
||||
request = request.query(&[("scope", scope.api_value())]);
|
||||
request = request.query(&[("limit", REMOTE_PLUGIN_LIST_PAGE_LIMIT)]);
|
||||
if let Some(collection) = collection {
|
||||
@@ -1419,7 +1438,7 @@ async fn get_remote_shared_workspace_plugins_page(
|
||||
let base_url = config.chatgpt_base_url.trim_end_matches('/');
|
||||
let url = format!("{base_url}/ps/plugins/workspace/shared");
|
||||
let client = build_reqwest_client();
|
||||
let mut request = authenticated_request(client.get(&url), auth)?;
|
||||
let mut request = authenticated_request(config, client.get(&url), auth, &url);
|
||||
request = request.query(&[("limit", REMOTE_PLUGIN_LIST_PAGE_LIMIT)]);
|
||||
if let Some(page_token) = page_token {
|
||||
request = request.query(&[("pageToken", page_token)]);
|
||||
@@ -1437,7 +1456,7 @@ async fn get_remote_plugin_installed_page(
|
||||
let base_url = config.chatgpt_base_url.trim_end_matches('/');
|
||||
let url = format!("{base_url}/ps/plugins/installed");
|
||||
let client = build_reqwest_client();
|
||||
let mut request = authenticated_request(client.get(&url), auth)?;
|
||||
let mut request = authenticated_request(config, client.get(&url), auth, &url);
|
||||
request = request.query(&[("scope", scope.api_value())]);
|
||||
if include_download_urls {
|
||||
request = request.query(&[("includeDownloadUrls", true)]);
|
||||
@@ -1457,7 +1476,7 @@ async fn fetch_plugin_detail(
|
||||
let base_url = config.chatgpt_base_url.trim_end_matches('/');
|
||||
let url = format!("{base_url}/ps/plugins/{plugin_id}");
|
||||
let client = build_reqwest_client();
|
||||
let mut request = authenticated_request(client.get(&url), auth)?;
|
||||
let mut request = authenticated_request(config, client.get(&url), auth, &url);
|
||||
if include_download_urls {
|
||||
request = request.query(&[("includeDownloadUrls", true)]);
|
||||
}
|
||||
@@ -1496,25 +1515,62 @@ fn ensure_chatgpt_auth(auth: Option<&CodexAuth>) -> Result<&CodexAuth, RemotePlu
|
||||
}
|
||||
|
||||
fn authenticated_request(
|
||||
config: &RemotePluginServiceConfig,
|
||||
request: RequestBuilder,
|
||||
auth: &CodexAuth,
|
||||
) -> Result<RequestBuilder, RemotePluginCatalogError> {
|
||||
Ok(request
|
||||
.timeout(REMOTE_PLUGIN_CATALOG_TIMEOUT)
|
||||
.headers(codex_model_provider::auth_provider_from_auth(auth).to_auth_headers()))
|
||||
url: &str,
|
||||
) -> AuthenticatedRequest {
|
||||
let (auth_provider, request_headers) = http_state::request_headers(config, auth, url);
|
||||
AuthenticatedRequest {
|
||||
request: request
|
||||
.timeout(REMOTE_PLUGIN_CATALOG_TIMEOUT)
|
||||
.headers(request_headers.clone()),
|
||||
auth_provider,
|
||||
request_headers,
|
||||
}
|
||||
}
|
||||
|
||||
struct AuthenticatedRequest {
|
||||
request: RequestBuilder,
|
||||
auth_provider: SharedAuthProvider,
|
||||
request_headers: HeaderMap,
|
||||
}
|
||||
|
||||
impl AuthenticatedRequest {
|
||||
fn query<T: Serialize + ?Sized>(mut self, query: &T) -> Self {
|
||||
self.request = self.request.query(query);
|
||||
self
|
||||
}
|
||||
|
||||
fn json<T: Serialize + ?Sized>(mut self, body: &T) -> Self {
|
||||
self.request = self.request.json(body);
|
||||
self
|
||||
}
|
||||
|
||||
async fn send(self, url: &str) -> Result<reqwest::Response, RemotePluginCatalogError> {
|
||||
let response =
|
||||
self.request
|
||||
.send()
|
||||
.await
|
||||
.map_err(|source| RemotePluginCatalogError::Request {
|
||||
url: url.to_string(),
|
||||
source,
|
||||
})?;
|
||||
http_state::observe_response_headers(
|
||||
&self.auth_provider,
|
||||
url,
|
||||
&self.request_headers,
|
||||
response.headers(),
|
||||
);
|
||||
Ok(response)
|
||||
}
|
||||
}
|
||||
|
||||
async fn send_and_decode<T: for<'de> Deserialize<'de>>(
|
||||
request: RequestBuilder,
|
||||
request: AuthenticatedRequest,
|
||||
url: &str,
|
||||
) -> Result<T, RemotePluginCatalogError> {
|
||||
let response = request
|
||||
.send()
|
||||
.await
|
||||
.map_err(|source| RemotePluginCatalogError::Request {
|
||||
url: url.to_string(),
|
||||
source,
|
||||
})?;
|
||||
let response = request.send(url).await?;
|
||||
let status = response.status();
|
||||
let body = response.text().await.unwrap_or_default();
|
||||
if !status.is_success() {
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
use super::RemotePluginServiceConfig;
|
||||
use codex_api::SharedAuthProvider;
|
||||
use codex_login::CodexAuth;
|
||||
use reqwest::header::HeaderMap;
|
||||
|
||||
pub(crate) fn request_headers(
|
||||
config: &RemotePluginServiceConfig,
|
||||
auth: &CodexAuth,
|
||||
url: &str,
|
||||
) -> (SharedAuthProvider, HeaderMap) {
|
||||
let auth_provider = codex_model_provider::with_native_integrity_state(
|
||||
codex_model_provider::auth_provider_from_auth(auth),
|
||||
Some(auth),
|
||||
config.http_state.clone(),
|
||||
);
|
||||
let mut request_headers = HeaderMap::new();
|
||||
auth_provider.add_auth_headers_for_url(url, &mut request_headers);
|
||||
(auth_provider, request_headers)
|
||||
}
|
||||
|
||||
pub(crate) fn observe_response_headers(
|
||||
auth_provider: &SharedAuthProvider,
|
||||
url: &str,
|
||||
request_headers: &HeaderMap,
|
||||
response_headers: &HeaderMap,
|
||||
) {
|
||||
auth_provider.observe_response_headers(url, request_headers, response_headers);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "http_state_tests.rs"]
|
||||
mod tests;
|
||||
@@ -0,0 +1,42 @@
|
||||
use super::*;
|
||||
use codex_http_state::HttpStateContext;
|
||||
use codex_http_state::HttpStateSurface;
|
||||
use pretty_assertions::assert_eq;
|
||||
use reqwest::header::HeaderValue;
|
||||
use tempfile::TempDir;
|
||||
|
||||
const REQUEST_URL: &str = "https://chatgpt.com/backend-api/ps/plugins/list";
|
||||
|
||||
#[test]
|
||||
fn plugin_request_headers_attach_and_rotate_native_integrity_state() {
|
||||
let codex_home = TempDir::new().expect("create temp dir");
|
||||
let state = HttpStateContext::new(codex_home.path().to_path_buf(), HttpStateSurface::CodexCli);
|
||||
state
|
||||
.set_for_surface(HttpStateSurface::CodexCli, "state-before".to_string())
|
||||
.expect("seed state");
|
||||
let config = RemotePluginServiceConfig::new("https://chatgpt.com/backend-api".to_string())
|
||||
.with_http_state(state.clone());
|
||||
let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing();
|
||||
|
||||
let (auth_provider, request_headers) = request_headers(&config, &auth, REQUEST_URL);
|
||||
assert_eq!(
|
||||
request_headers.get("X-OAI-IS"),
|
||||
Some(&HeaderValue::from_static("state-before"))
|
||||
);
|
||||
|
||||
let mut response_headers = HeaderMap::new();
|
||||
response_headers.insert("X-OAI-IS-Update", HeaderValue::from_static("state-after"));
|
||||
observe_response_headers(
|
||||
&auth_provider,
|
||||
REQUEST_URL,
|
||||
&request_headers,
|
||||
&response_headers,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
state
|
||||
.get_for_surface(HttpStateSurface::CodexCli)
|
||||
.expect("read state"),
|
||||
Some("state-after".to_string())
|
||||
);
|
||||
}
|
||||
@@ -4,7 +4,6 @@ use crate::plugin_bundle_archive::pack_plugin_bundle_tar_gz;
|
||||
use codex_login::CodexAuth;
|
||||
use codex_login::default_client::build_reqwest_client;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use reqwest::RequestBuilder;
|
||||
use reqwest::StatusCode;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
@@ -280,7 +279,7 @@ pub async fn delete_remote_plugin_share(
|
||||
let base_url = config.chatgpt_base_url.trim_end_matches('/');
|
||||
let url = format!("{base_url}/public/plugins/workspace/{remote_plugin_id}");
|
||||
let client = build_reqwest_client();
|
||||
let request = authenticated_request(client.delete(&url), auth)?;
|
||||
let request = authenticated_request(config, client.delete(&url), auth, &url);
|
||||
send_and_expect_status(request, &url, &[StatusCode::NO_CONTENT]).await?;
|
||||
if let Err(err) = local_paths::remove_plugin_share_local_path(codex_home, remote_plugin_id) {
|
||||
warn!(
|
||||
@@ -313,7 +312,7 @@ pub async fn update_remote_plugin_share_targets(
|
||||
let base_url = config.chatgpt_base_url.trim_end_matches('/');
|
||||
let url = format!("{base_url}/ps/plugins/{remote_plugin_id}/shares");
|
||||
let client = build_reqwest_client();
|
||||
let request = authenticated_request(client.put(&url), auth)?.json(
|
||||
let request = authenticated_request(config, client.put(&url), auth, &url).json(
|
||||
&RemotePluginShareUpdateTargetsRequest {
|
||||
discoverability,
|
||||
targets,
|
||||
@@ -379,7 +378,7 @@ async fn get_created_workspace_plugins_page(
|
||||
let base_url = config.chatgpt_base_url.trim_end_matches('/');
|
||||
let url = format!("{base_url}/ps/plugins/workspace/created");
|
||||
let client = build_reqwest_client();
|
||||
let mut request = authenticated_request(client.get(&url), auth)?;
|
||||
let mut request = authenticated_request(config, client.get(&url), auth, &url);
|
||||
request = request.query(&[("limit", REMOTE_PLUGIN_LIST_PAGE_LIMIT)]);
|
||||
if let Some(page_token) = page_token {
|
||||
request = request.query(&[("pageToken", page_token)]);
|
||||
@@ -397,7 +396,7 @@ async fn create_workspace_plugin_upload(
|
||||
let base_url = config.chatgpt_base_url.trim_end_matches('/');
|
||||
let url = format!("{base_url}/public/plugins/workspace/upload-url");
|
||||
let client = build_reqwest_client();
|
||||
let request = authenticated_request(client.post(&url), auth)?.json(
|
||||
let request = authenticated_request(config, client.post(&url), auth, &url).json(
|
||||
&RemoteWorkspacePluginUploadUrlRequest {
|
||||
filename,
|
||||
mime_type: "application/gzip",
|
||||
@@ -451,7 +450,7 @@ async fn finalize_workspace_plugin_upload(
|
||||
format!("{base_url}/public/plugins/workspace")
|
||||
};
|
||||
let client = build_reqwest_client();
|
||||
let request = authenticated_request(client.post(&url), auth)?.json(&body);
|
||||
let request = authenticated_request(config, client.post(&url), auth, &url).json(&body);
|
||||
send_and_decode(request, &url).await
|
||||
}
|
||||
|
||||
@@ -489,17 +488,11 @@ fn archive_plugin_for_upload_with_limit(
|
||||
}
|
||||
|
||||
async fn send_and_expect_status(
|
||||
request: RequestBuilder,
|
||||
request: AuthenticatedRequest,
|
||||
url_for_error: &str,
|
||||
expected_statuses: &[StatusCode],
|
||||
) -> Result<(), RemotePluginCatalogError> {
|
||||
let response = request
|
||||
.send()
|
||||
.await
|
||||
.map_err(|source| RemotePluginCatalogError::Request {
|
||||
url: url_for_error.to_string(),
|
||||
source,
|
||||
})?;
|
||||
let response = request.send(url_for_error).await?;
|
||||
let status = response.status();
|
||||
let body = response.text().await.unwrap_or_default();
|
||||
if !expected_statuses.contains(&status) {
|
||||
|
||||
@@ -23,9 +23,7 @@ use wiremock::matchers::query_param;
|
||||
use wiremock::matchers::query_param_is_missing;
|
||||
|
||||
fn test_config(server: &MockServer) -> RemotePluginServiceConfig {
|
||||
RemotePluginServiceConfig {
|
||||
chatgpt_base_url: format!("{}/backend-api", server.uri()),
|
||||
}
|
||||
RemotePluginServiceConfig::new(format!("{}/backend-api", server.uri()))
|
||||
}
|
||||
|
||||
fn test_auth() -> CodexAuth {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::remote::RemotePluginServiceConfig;
|
||||
use crate::remote::http_state;
|
||||
use codex_login::CodexAuth;
|
||||
use codex_login::default_client::build_reqwest_client;
|
||||
use codex_protocol::protocol::Product;
|
||||
@@ -130,10 +131,11 @@ pub async fn fetch_remote_plugin_status(
|
||||
let base_url = config.chatgpt_base_url.trim_end_matches('/');
|
||||
let url = format!("{base_url}/plugins/list");
|
||||
let client = build_reqwest_client();
|
||||
let (auth_provider, request_headers) = http_state::request_headers(config, auth, &url);
|
||||
let request = client
|
||||
.get(&url)
|
||||
.timeout(REMOTE_PLUGIN_FETCH_TIMEOUT)
|
||||
.headers(codex_model_provider::auth_provider_from_auth(auth).to_auth_headers());
|
||||
.headers(request_headers.clone());
|
||||
|
||||
let response = request
|
||||
.send()
|
||||
@@ -142,6 +144,12 @@ pub async fn fetch_remote_plugin_status(
|
||||
url: url.clone(),
|
||||
source,
|
||||
})?;
|
||||
http_state::observe_response_headers(
|
||||
&auth_provider,
|
||||
&url,
|
||||
&request_headers,
|
||||
response.headers(),
|
||||
);
|
||||
let status = response.status();
|
||||
let body = response.text().await.unwrap_or_default();
|
||||
if !status.is_success() {
|
||||
@@ -170,9 +178,11 @@ pub async fn fetch_remote_featured_plugin_ids(
|
||||
)])
|
||||
.timeout(REMOTE_FEATURED_PLUGIN_FETCH_TIMEOUT);
|
||||
|
||||
if let Some(auth) = auth.filter(|auth| auth.uses_codex_backend()) {
|
||||
request =
|
||||
request.headers(codex_model_provider::auth_provider_from_auth(auth).to_auth_headers());
|
||||
let auth_context = auth
|
||||
.filter(|auth| auth.uses_codex_backend())
|
||||
.map(|auth| http_state::request_headers(config, auth, &url));
|
||||
if let Some((_, request_headers)) = &auth_context {
|
||||
request = request.headers(request_headers.clone());
|
||||
}
|
||||
|
||||
let response = request
|
||||
@@ -182,6 +192,14 @@ pub async fn fetch_remote_featured_plugin_ids(
|
||||
url: url.clone(),
|
||||
source,
|
||||
})?;
|
||||
if let Some((auth_provider, request_headers)) = &auth_context {
|
||||
http_state::observe_response_headers(
|
||||
auth_provider,
|
||||
&url,
|
||||
request_headers,
|
||||
response.headers(),
|
||||
);
|
||||
}
|
||||
let status = response.status();
|
||||
let body = response.text().await.unwrap_or_default();
|
||||
if !status.is_success() {
|
||||
@@ -237,10 +255,11 @@ async fn post_remote_plugin_mutation(
|
||||
let auth = ensure_codex_backend_auth(auth)?;
|
||||
let url = remote_plugin_mutation_url(config, plugin_id, action)?;
|
||||
let client = build_reqwest_client();
|
||||
let (auth_provider, request_headers) = http_state::request_headers(config, auth, &url);
|
||||
let request = client
|
||||
.post(url.clone())
|
||||
.timeout(REMOTE_PLUGIN_MUTATION_TIMEOUT)
|
||||
.headers(codex_model_provider::auth_provider_from_auth(auth).to_auth_headers());
|
||||
.headers(request_headers.clone());
|
||||
|
||||
let response = request
|
||||
.send()
|
||||
@@ -249,6 +268,12 @@ async fn post_remote_plugin_mutation(
|
||||
url: url.clone(),
|
||||
source,
|
||||
})?;
|
||||
http_state::observe_response_headers(
|
||||
&auth_provider,
|
||||
&url,
|
||||
&request_headers,
|
||||
response.headers(),
|
||||
);
|
||||
let status = response.status();
|
||||
let body = response.text().await.unwrap_or_default();
|
||||
if !status.is_success() {
|
||||
|
||||
Reference in New Issue
Block a user