[codex] add remote plugin HTTP state adapter [ci changed_files]

This commit is contained in:
Cooper Gamble
2026-06-03 09:54:55 +00:00
parent fb9d91415a
commit b766a57ff1
11 changed files with 220 additions and 84 deletions
+2
View File
@@ -2660,12 +2660,14 @@ dependencies = [
"anyhow",
"chrono",
"codex-analytics",
"codex-api",
"codex-app-server-protocol",
"codex-config",
"codex-core-skills",
"codex-exec-server",
"codex-git-utils",
"codex-hooks",
"codex-http-state",
"codex-login",
"codex-model-provider",
"codex-otel",
@@ -616,9 +616,8 @@ impl PluginRequestProcessor {
// TODO(remote plugins): Remove this once remote plugins are ready and vertical plugins are
// served directly from the normal remote catalog.
if include_vertical && !config.features.enabled(Feature::RemotePlugin) {
let remote_plugin_service_config = RemotePluginServiceConfig {
chatgpt_base_url: config.chatgpt_base_url.clone(),
};
let remote_plugin_service_config =
RemotePluginServiceConfig::new(config.chatgpt_base_url.clone());
match codex_core_plugins::remote::fetch_openai_curated_remote_collection_marketplace(
&remote_plugin_service_config,
auth.as_ref(),
@@ -655,9 +654,8 @@ impl PluginRequestProcessor {
remote_sources.push(RemoteMarketplaceSource::SharedWithMe);
}
if !remote_sources.is_empty() {
let remote_plugin_service_config = RemotePluginServiceConfig {
chatgpt_base_url: config.chatgpt_base_url.clone(),
};
let remote_plugin_service_config =
RemotePluginServiceConfig::new(config.chatgpt_base_url.clone());
match codex_core_plugins::remote::fetch_remote_marketplaces(
&remote_plugin_service_config,
auth.as_ref(),
@@ -966,9 +964,8 @@ impl PluginRequestProcessor {
let share_context = match share_context {
Some(context) => {
let auth = self.auth_manager.auth().await;
let remote_plugin_service_config = RemotePluginServiceConfig {
chatgpt_base_url: config.chatgpt_base_url.clone(),
};
let remote_plugin_service_config =
RemotePluginServiceConfig::new(config.chatgpt_base_url.clone());
match codex_core_plugins::remote::fetch_remote_plugin_share_context(
&remote_plugin_service_config,
auth.as_ref(),
@@ -1072,9 +1069,8 @@ impl PluginRequestProcessor {
)));
}
let auth = self.auth_manager.auth().await;
let remote_plugin_service_config = RemotePluginServiceConfig {
chatgpt_base_url: config.chatgpt_base_url.clone(),
};
let remote_plugin_service_config =
RemotePluginServiceConfig::new(config.chatgpt_base_url.clone());
validate_remote_plugin_id(&plugin_name)?;
let remote_detail = codex_core_plugins::remote::fetch_remote_plugin_detail(
&remote_plugin_service_config,
@@ -1130,9 +1126,8 @@ impl PluginRequestProcessor {
}
let auth = self.auth_manager.auth().await;
let remote_plugin_service_config = RemotePluginServiceConfig {
chatgpt_base_url: config.chatgpt_base_url.clone(),
};
let remote_plugin_service_config =
RemotePluginServiceConfig::new(config.chatgpt_base_url.clone());
let remote_skill_detail = codex_core_plugins::remote::fetch_remote_plugin_skill_detail(
&remote_plugin_service_config,
auth.as_ref(),
@@ -1183,9 +1178,8 @@ impl PluginRequestProcessor {
validate_client_plugin_share_targets(share_targets)?;
}
let remote_plugin_service_config = RemotePluginServiceConfig {
chatgpt_base_url: config.chatgpt_base_url.clone(),
};
let remote_plugin_service_config =
RemotePluginServiceConfig::new(config.chatgpt_base_url.clone());
let access_policy = codex_core_plugins::remote::RemotePluginShareAccessPolicy {
discoverability: discoverability.map(remote_plugin_share_discoverability),
share_targets: share_targets.map(remote_plugin_share_targets),
@@ -1226,9 +1220,8 @@ impl PluginRequestProcessor {
}
validate_client_plugin_share_targets(&share_targets)?;
let remote_plugin_service_config = RemotePluginServiceConfig {
chatgpt_base_url: config.chatgpt_base_url.clone(),
};
let remote_plugin_service_config =
RemotePluginServiceConfig::new(config.chatgpt_base_url.clone());
let result = codex_core_plugins::remote::update_remote_plugin_share_targets(
&remote_plugin_service_config,
auth.as_ref(),
@@ -1256,9 +1249,8 @@ impl PluginRequestProcessor {
_params: PluginShareListParams,
) -> Result<PluginShareListResponse, JSONRPCErrorError> {
let (config, auth) = self.load_plugin_share_config_and_auth().await?;
let remote_plugin_service_config = RemotePluginServiceConfig {
chatgpt_base_url: config.chatgpt_base_url.clone(),
};
let remote_plugin_service_config =
RemotePluginServiceConfig::new(config.chatgpt_base_url.clone());
let data = codex_core_plugins::remote::list_remote_plugin_shares(
&remote_plugin_service_config,
auth.as_ref(),
@@ -1295,9 +1287,8 @@ impl PluginRequestProcessor {
return Err(invalid_request("invalid remote plugin id"));
}
let remote_plugin_service_config = RemotePluginServiceConfig {
chatgpt_base_url: config.chatgpt_base_url.clone(),
};
let remote_plugin_service_config =
RemotePluginServiceConfig::new(config.chatgpt_base_url.clone());
let result = codex_core_plugins::remote::checkout_remote_plugin_share(
&remote_plugin_service_config,
auth.as_ref(),
@@ -1328,9 +1319,8 @@ impl PluginRequestProcessor {
return Err(invalid_request("invalid remote plugin id"));
}
let remote_plugin_service_config = RemotePluginServiceConfig {
chatgpt_base_url: config.chatgpt_base_url.clone(),
};
let remote_plugin_service_config =
RemotePluginServiceConfig::new(config.chatgpt_base_url.clone());
codex_core_plugins::remote::delete_remote_plugin_share(
&remote_plugin_service_config,
auth.as_ref(),
@@ -1448,9 +1438,8 @@ impl PluginRequestProcessor {
validate_remote_plugin_id(&remote_plugin_id)?;
let auth = self.auth_manager.auth().await;
let remote_plugin_service_config = RemotePluginServiceConfig {
chatgpt_base_url: config.chatgpt_base_url.clone(),
};
let remote_plugin_service_config =
RemotePluginServiceConfig::new(config.chatgpt_base_url.clone());
let remote_detail =
codex_core_plugins::remote::fetch_remote_plugin_detail_with_download_urls(
&remote_plugin_service_config,
@@ -1796,9 +1785,8 @@ impl PluginRequestProcessor {
validate_remote_plugin_id(&plugin_id)?;
let auth = self.auth_manager.auth().await;
let remote_plugin_service_config = RemotePluginServiceConfig {
chatgpt_base_url: config.chatgpt_base_url.clone(),
};
let remote_plugin_service_config =
RemotePluginServiceConfig::new(config.chatgpt_base_url.clone());
let uninstall_result = codex_core_plugins::remote::uninstall_remote_plugin(
&remote_plugin_service_config,
auth.as_ref(),
+2
View File
@@ -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 }
+1 -3
View File
@@ -173,9 +173,7 @@ 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 featured_plugin_ids_cache_key(
+76 -20
View File
@@ -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())
);
}
+7 -14
View File
@@ -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 {
+30 -5
View File
@@ -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() {
@@ -320,11 +320,11 @@ remote_plugin = true
let mut config = load_plugins_config(codex_home.path()).await;
config.chatgpt_base_url = format!("{}/backend-api", server.uri());
let plugins_manager = PluginsManager::new(config.codex_home.to_path_buf());
let remote_plugin_service_config =
RemotePluginServiceConfig::new(config.chatgpt_base_url.clone());
fetch_and_cache_global_remote_plugin_catalog(
codex_home.path(),
&RemotePluginServiceConfig {
chatgpt_base_url: config.chatgpt_base_url.clone(),
},
&remote_plugin_service_config,
Some(&auth),
)
.await