Honor the configured product SKU in remote plugin requests (#47116)

## Why

Remote plugin requests always sent `OAI-Product-Sku: codex`, ignoring the existing `apps_mcp_product_sku` setting.

## What changed

Pass `apps_mcp_product_sku` through plugin configuration and use it for authenticated remote plugin requests, including discovery, search, installation, and sharing. Keep `codex` as the default and include the SKU when comparing remote service configurations.

## Testing

Extend app-server plugin listing and core agent-turn tests to check default and configured SKU headers. Update the CLI worktree test to wait for the user prompt's model request, skipping background title generation requests.

GitOrigin-RevId: 508c9ef9aa3e41b87c92383c3088eb1eb9227fd8
This commit is contained in:
Jesse Du
2026-09-21 22:30:43 +00:00
committed by copyberry
parent 772abc9425
commit 3fd5160cd6
19 changed files with 157 additions and 49 deletions
@@ -157,10 +157,7 @@ fn load_shared_plugin_ids_by_local_path(
}
fn remote_plugin_service_config(config: &Config) -> RemotePluginServiceConfig {
RemotePluginServiceConfig::new(
config.chatgpt_base_url.clone(),
config.http_client_factory(),
)
config.plugins_config_input().remote_plugin_service_config()
}
fn share_context_for_source(
@@ -2088,12 +2088,14 @@ enum ProjectPluginConfiguration {
Invalid,
}
#[test_case(ProjectPluginConfiguration::None; "without project override")]
#[test_case(ProjectPluginConfiguration::Disabled; "project disables local plugins")]
#[test_case(ProjectPluginConfiguration::Invalid; "invalid project preserves remote plugins")]
#[test_case(ProjectPluginConfiguration::None, None; "without project override")]
#[test_case(ProjectPluginConfiguration::Disabled, None; "project disables local plugins")]
#[test_case(ProjectPluginConfiguration::Invalid, None; "invalid project preserves remote plugins")]
#[test_case(ProjectPluginConfiguration::None, Some("tpp"); "configured product sku")]
#[tokio::test]
async fn plugin_list_includes_remote_marketplaces_when_remote_plugin_enabled(
project_configuration: ProjectPluginConfiguration,
product_sku: Option<&str>,
) -> Result<()> {
let codex_home = TempDir::new()?;
let server = MockServer::start().await;
@@ -2208,13 +2210,14 @@ async fn plugin_list_includes_remote_marketplaces_when_remote_plugin_enabled(
}
}"#;
let expected_product_sku = product_sku.unwrap_or("codex");
Mock::given(method("GET"))
.and(path("/backend-api/ps/plugins/list"))
.and(query_param("scope", "GLOBAL"))
.and(query_param("limit", "200"))
.and(header("authorization", "Bearer chatgpt-token"))
.and(header("chatgpt-account-id", "account-123"))
.and(header("oai-product-sku", "codex"))
.and(header("oai-product-sku", expected_product_sku))
.respond_with(ResponseTemplate::new(200).set_body_string(global_directory_body))
.mount(&server)
.await;
@@ -2224,7 +2227,7 @@ async fn plugin_list_includes_remote_marketplaces_when_remote_plugin_enabled(
.and(query_param("limit", "200"))
.and(header("authorization", "Bearer chatgpt-token"))
.and(header("chatgpt-account-id", "account-123"))
.and(header("oai-product-sku", "codex"))
.and(header("oai-product-sku", expected_product_sku))
.respond_with(ResponseTemplate::new(200).set_body_string(empty_page_body))
.mount(&server)
.await;
@@ -2233,7 +2236,7 @@ async fn plugin_list_includes_remote_marketplaces_when_remote_plugin_enabled(
.and(query_param("scope", "GLOBAL"))
.and(header("authorization", "Bearer chatgpt-token"))
.and(header("chatgpt-account-id", "account-123"))
.and(header("oai-product-sku", "codex"))
.and(header("oai-product-sku", expected_product_sku))
.respond_with(ResponseTemplate::new(200).set_body_string(global_installed_body))
.mount(&server)
.await;
@@ -2242,7 +2245,7 @@ async fn plugin_list_includes_remote_marketplaces_when_remote_plugin_enabled(
.and(query_param("scope", "WORKSPACE"))
.and(header("authorization", "Bearer chatgpt-token"))
.and(header("chatgpt-account-id", "account-123"))
.and(header("oai-product-sku", "codex"))
.and(header("oai-product-sku", expected_product_sku))
.respond_with(ResponseTemplate::new(200).set_body_string(empty_page_body))
.mount(&server)
.await;
@@ -2255,8 +2258,11 @@ async fn plugin_list_includes_remote_marketplaces_when_remote_plugin_enabled(
.mount(&server)
.await;
let mut mcp = TestAppServer::builder()
.with_codex_home(codex_home.path())
let mut builder = TestAppServer::builder().with_codex_home(codex_home.path());
if let Some(product_sku) = product_sku {
builder = builder.with_args(&["-c", &format!("apps_mcp_product_sku=\"{product_sku}\"")]);
}
let mut mcp = builder
.build_initialized_with_timeout(DEFAULT_TIMEOUT)
.await?;
+14 -1
View File
@@ -293,6 +293,7 @@ trust_level = "trusted"
];
let mut owner = None;
let mut previous: Vec<String> = Vec::new();
let prompt = "describe checkout";
for (fork, explicit_cd, analytics, auth_failure) in [
(false, false, false, false),
(true, false, false, false),
@@ -393,7 +394,7 @@ trust_level = "trusted"
if analytics {
args.extend(["-c".into(), "analytics.enabled=true".into()]);
}
args.push("describe checkout".into());
args.push(prompt.into());
if previous.is_empty() {
let mut untrusted_args = args.clone();
untrusted_args.extend([
@@ -436,6 +437,18 @@ trust_level = "trusted"
body = rx.recv() => {
let (body, checkout, metadata) = body.context("model request")??;
let body: Value = serde_json::from_slice(&body)?;
// Background title generation can reach the model before the user turn.
let is_user_turn = body["input"].as_array().is_some_and(|input| {
input.iter().any(|item| {
item["role"] == "user"
&& item["content"].as_array().is_some_and(|content| {
content.iter().any(|part| part["text"] == prompt)
})
})
});
if !is_user_turn {
continue;
}
return Ok::<_, anyhow::Error>((body, checkout, metadata));
}
bytes = stdout.recv() => {
@@ -956,6 +956,7 @@ plugins = true
&RemotePluginServiceConfig::new(
plugins.chatgpt_base_url.clone(),
crate::test_support::test_http_client_factory(),
/*product_sku*/ None,
),
Some(&auth),
)
+4
View File
@@ -157,6 +157,7 @@ pub struct PluginsConfigInput {
pub plugins_enabled: bool,
pub remote_plugin_enabled: bool,
pub chatgpt_base_url: String,
pub product_sku: Option<String>,
http_client_factory: HttpClientFactory,
}
@@ -168,6 +169,7 @@ impl PluginsConfigInput {
remote_plugin_enabled: bool,
chatgpt_base_url: String,
http_client_factory: HttpClientFactory,
product_sku: Option<String>,
) -> Self {
Self {
config_layer_stack,
@@ -176,6 +178,7 @@ impl PluginsConfigInput {
remote_plugin_enabled,
chatgpt_base_url,
http_client_factory,
product_sku,
}
}
@@ -184,6 +187,7 @@ impl PluginsConfigInput {
RemotePluginServiceConfig::new(
self.chatgpt_base_url.clone(),
self.http_client_factory.clone(),
self.product_sku.clone(),
)
}
}
@@ -126,6 +126,7 @@ fn plugins_config_input_with_requirements(
/*remote_plugin_enabled*/ false,
String::new(),
test_http_client_factory(),
/*product_sku*/ None,
)
}
@@ -166,6 +167,7 @@ fn curated_repo_sync_stays_deferred_for_remote_chatgpt_catalog() {
/*remote_plugin_enabled*/ true,
"https://chatgpt.com".to_string(),
test_http_client_factory(),
/*product_sku*/ None,
);
let manager = Arc::new(test_plugins_manager_with_options(
tmp.path().to_path_buf(),
@@ -2969,6 +2971,7 @@ async fn plugin_cache_reuses_effective_configurations() {
/*remote_plugin_enabled*/ false,
"https://chatgpt.com".to_string(),
test_http_client_factory(),
/*product_sku*/ None,
)
};
let manager = test_plugins_manager(codex_home.path().to_path_buf());
@@ -3233,6 +3236,7 @@ async fn plugins_for_config_discards_in_flight_load_after_account_change() {
/*remote_plugin_enabled*/ true,
String::new(),
test_http_client_factory(),
/*product_sku*/ None,
);
let auth_manager = test_auth_manager(Some(AuthMode::ChatgptAuthTokens));
let manager = Arc::new(test_plugins_manager_with_auth_manager(
@@ -7182,6 +7186,7 @@ fn remote_installed_plugins_cache_refresh_coalesces_materializations() {
service_config: RemotePluginServiceConfig::new(
"https://example.com".to_string(),
test_http_client_factory(),
/*product_sku*/ None,
),
auth: None,
notify: RemoteInstalledPluginsCacheRefreshNotify::IfCacheChanged,
@@ -87,6 +87,7 @@ async fn curated_git_requirements_control_plugin_skills() -> Result<()> {
/*remote_plugin_enabled*/ false,
"https://chatgpt.com/backend-api/".to_string(),
HttpClientFactory::new(OutboundProxyPolicy::ReqwestDefault),
/*product_sku*/ None,
);
let auth = if name == "openai-curated" {
CodexAuth::create_dummy_chatgpt_auth_for_testing()
+53 -13
View File
@@ -107,7 +107,7 @@ pub const REMOTE_WORKSPACE_SHARED_WITH_ME_UNLISTED_MARKETPLACE_DISPLAY_NAME: &st
const OPENAI_CURATED_REMOTE_COLLECTION_KEY: &str = "vertical";
const OAI_PRODUCT_SKU_HEADER: &str = "OAI-Product-Sku";
const CODEX_PRODUCT_SKU: &str = "codex";
pub(crate) const CODEX_PRODUCT_SKU: &str = "codex";
const REMOTE_PLUGIN_CATALOG_TIMEOUT: Duration = Duration::from_secs(30);
const RECOMMENDED_PLUGINS_TIMEOUT: Duration = Duration::from_secs(5);
const REMOTE_PLUGIN_LIST_PAGE_LIMIT: u32 = 200;
@@ -147,6 +147,7 @@ const REMOTE_INSTALLED_MARKETPLACE_DISPLAY_ORDER: [(&str, &str); 6] = [
#[derive(Debug, Clone)]
pub struct RemotePluginServiceConfig {
pub chatgpt_base_url: String,
pub(crate) product_sku: String,
pub(crate) http_clients: Arc<dyn HttpClientSelector>,
}
@@ -155,7 +156,11 @@ impl RemotePluginServiceConfig {
///
/// Keeping the factory mandatory ensures every catalog, mutation, upload, and bundle request
/// follows the same outbound proxy policy.
pub fn new(chatgpt_base_url: String, http_client_factory: HttpClientFactory) -> Self {
pub fn new(
chatgpt_base_url: String,
http_client_factory: HttpClientFactory,
product_sku: Option<String>,
) -> Self {
let http_clients =
RouteAwareClientPool::with_chatgpt_cloudflare_cookies_without_request_logging(
http_client_factory,
@@ -163,6 +168,7 @@ impl RemotePluginServiceConfig {
);
Self {
chatgpt_base_url,
product_sku: product_sku.unwrap_or_else(|| CODEX_PRODUCT_SKU.to_string()),
http_clients: Arc::new(http_clients),
}
}
@@ -177,6 +183,7 @@ impl RemotePluginServiceConfig {
impl PartialEq for RemotePluginServiceConfig {
fn eq(&self, other: &Self) -> bool {
self.chatgpt_base_url == other.chatgpt_base_url
&& self.product_sku == other.product_sku
&& self.http_clients.outbound_proxy_policy()
== other.http_clients.outbound_proxy_policy()
}
@@ -1032,8 +1039,12 @@ pub async fn fetch_recommended_plugins(
.map_err(RemotePluginCatalogError::InvalidBaseUrl)?;
url.query_pairs_mut().append_pair("scope", "GLOBAL");
let url = url.to_string();
let request = authenticated_request(config.http_request(Method::GET, &url), auth)
.timeout(RECOMMENDED_PLUGINS_TIMEOUT);
let request = authenticated_request(
config.http_request(Method::GET, &url),
auth,
&config.product_sku,
)
.timeout(RECOMMENDED_PLUGINS_TIMEOUT);
let response: RecommendedPluginsResponse = send_and_decode(request, &url).await?;
Ok(recommended_plugins_mode(response))
}
@@ -1400,7 +1411,11 @@ pub async fn fetch_remote_plugin_skill_detail(
}
let url = remote_plugin_skill_detail_url(config, plugin_id, skill_name)?;
let request = authenticated_request(config.http_request(Method::GET, &url), auth);
let request = authenticated_request(
config.http_request(Method::GET, &url),
auth,
&config.product_sku,
);
let response: RemotePluginSkillDetailResponse = send_and_decode(request, &url).await?;
if response.plugin_id != plugin_id {
return Err(RemotePluginCatalogError::UnexpectedPluginId {
@@ -1607,7 +1622,11 @@ async fn install_remote_plugin_inner(
url.query_pairs_mut()
.append_pair("includeAppsNeedingAuth", "true");
let url = url.to_string();
let request = authenticated_request(config.http_request(Method::POST, &url), auth);
let request = authenticated_request(
config.http_request(Method::POST, &url),
auth,
&config.product_sku,
);
let request = if let Some(install_attempt_id) = install_attempt_id {
request.json(&RemotePluginInstallRequest { install_attempt_id })
} else {
@@ -1700,7 +1719,11 @@ pub async fn uninstall_remote_plugin(
let base_url = config.chatgpt_base_url.trim_end_matches('/');
let url = format!("{base_url}/ps/plugins/{remote_plugin_id}/uninstall");
let request = authenticated_request(config.http_request(Method::POST, &url), auth);
let request = authenticated_request(
config.http_request(Method::POST, &url),
auth,
&config.product_sku,
);
let response: RemotePluginMutationResponse = send_and_decode(request, &url).await?;
if response.id != remote_plugin_id {
return Err(RemotePluginCatalogError::UnexpectedPluginId {
@@ -2194,7 +2217,11 @@ async fn get_remote_plugin_list_page(
url.query_pairs_mut().append_pair("pageToken", page_token);
}
let url = url.to_string();
let request = authenticated_request(config.http_request(Method::GET, &url), auth);
let request = authenticated_request(
config.http_request(Method::GET, &url),
auth,
&config.product_sku,
);
send_and_decode(request, &url).await
}
@@ -2212,7 +2239,11 @@ async fn get_remote_shared_workspace_plugins_page(
url.query_pairs_mut().append_pair("pageToken", page_token);
}
let url = url.to_string();
let request = authenticated_request(config.http_request(Method::GET, &url), auth);
let request = authenticated_request(
config.http_request(Method::GET, &url),
auth,
&config.product_sku,
);
send_and_decode(request, &url).await
}
@@ -2244,7 +2275,11 @@ async fn get_remote_plugin_installed_page(
url.query_pairs_mut().append_pair("pageToken", page_token);
}
let url = url.to_string();
let request = authenticated_request(config.http_request(Method::GET, &url), auth);
let request = authenticated_request(
config.http_request(Method::GET, &url),
auth,
&config.product_sku,
);
send_and_decode(request, &url).await
}
@@ -2279,8 +2314,12 @@ async fn fetch_plugin_detail_with_timeout(
.append_pair("includeDownloadUrls", "true");
}
let url = url.to_string();
let request =
authenticated_request(config.http_request(Method::GET, &url), auth).timeout(timeout);
let request = authenticated_request(
config.http_request(Method::GET, &url),
auth,
&config.product_sku,
)
.timeout(timeout);
send_and_decode(request, &url).await
}
@@ -2318,11 +2357,12 @@ fn ensure_chatgpt_auth(auth: Option<&CodexAuth>) -> Result<&CodexAuth, RemotePlu
fn authenticated_request(
request: RouteAwareRequestBuilder,
auth: &CodexAuth,
product_sku: &str,
) -> RouteAwareRequestBuilder {
request
.timeout(REMOTE_PLUGIN_CATALOG_TIMEOUT)
.headers(codex_model_provider::auth_provider_from_auth(auth).to_auth_headers())
.header(OAI_PRODUCT_SKU_HEADER, CODEX_PRODUCT_SKU)
.header(OAI_PRODUCT_SKU_HEADER, product_sku)
}
async fn send_and_decode<T: for<'de> Deserialize<'de>>(
@@ -51,6 +51,7 @@ fn global_catalog_cache_reuses_legacy_cache_file() {
let config = RemotePluginServiceConfig::new(
"https://chatgpt.com/backend-api".to_string(),
crate::test_support::test_http_client_factory(),
/*product_sku*/ None,
);
let auth = CodexAuth::Headers(AuthHeaders::new(http::HeaderMap::new()));
let legacy_cache_path = codex_home
@@ -96,6 +97,7 @@ fn header_auth_does_not_cache_private_catalogs_without_a_stable_identity() {
let config = RemotePluginServiceConfig::new(
"https://chatgpt.com/backend-api".to_string(),
crate::test_support::test_http_client_factory(),
/*product_sku*/ None,
);
let auth = CodexAuth::Headers(AuthHeaders::new(http::HeaderMap::new()));
@@ -119,6 +119,7 @@ async fn capabilities_union_cached_versions_and_sync_reports_removal() -> anyhow
let config = RemotePluginServiceConfig::new(
format!("{}/backend-api", server.uri()),
crate::test_support::test_http_client_factory(),
/*product_sku*/ None,
);
let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing();
Mock::given(method("GET"))
@@ -570,6 +570,7 @@ mod tests {
let config = RemotePluginServiceConfig::new(
format!("{}/backend-api", server.uri()),
crate::test_support::test_http_client_factory(),
/*product_sku*/ None,
);
let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing();
+5 -1
View File
@@ -60,7 +60,11 @@ pub async fn search_remote_plugins(
}
let url = url.to_string();
let request = authenticated_request(config.http_request(Method::GET, &url), auth);
let request = authenticated_request(
config.http_request(Method::GET, &url),
auth,
&config.product_sku,
);
let response: RemotePluginListResponse = send_and_decode(request, &url_for_error)
.await
.map_err(|error| match error {
+36 -17
View File
@@ -285,7 +285,11 @@ pub async fn delete_remote_plugin_share(
let auth = ensure_chatgpt_auth(auth)?;
let base_url = config.chatgpt_base_url.trim_end_matches('/');
let url = format!("{base_url}/public/plugins/workspace/{remote_plugin_id}");
let request = authenticated_request(config.http_request(Method::DELETE, &url), auth);
let request = authenticated_request(
config.http_request(Method::DELETE, &url),
auth,
&config.product_sku,
);
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!(
@@ -318,12 +322,15 @@ pub async fn update_remote_plugin_share_targets(
.unwrap_or_default();
let base_url = config.chatgpt_base_url.trim_end_matches('/');
let url = format!("{base_url}/ps/plugins/{remote_plugin_id}/shares");
let request = authenticated_request(config.http_request(Method::PUT, &url), auth).json(
&RemotePluginShareUpdateTargetsRequest {
discoverability,
targets,
},
);
let request = authenticated_request(
config.http_request(Method::PUT, &url),
auth,
&config.product_sku,
)
.json(&RemotePluginShareUpdateTargetsRequest {
discoverability,
targets,
});
let response: RemotePluginShareUpdateTargetsResponse = send_and_decode(request, &url).await?;
Ok(RemotePluginShareUpdateTargetsResult {
principals: response.principals,
@@ -390,7 +397,11 @@ async fn get_created_workspace_plugins_page(
url.query_pairs_mut().append_pair("pageToken", page_token);
}
let url = url.to_string();
let request = authenticated_request(config.http_request(Method::GET, &url), auth);
let request = authenticated_request(
config.http_request(Method::GET, &url),
auth,
&config.product_sku,
);
send_and_decode(request, &url).await
}
@@ -403,14 +414,17 @@ async fn create_workspace_plugin_upload(
) -> Result<RemoteWorkspacePluginUploadUrlResponse, RemotePluginCatalogError> {
let base_url = config.chatgpt_base_url.trim_end_matches('/');
let url = format!("{base_url}/public/plugins/workspace/upload-url");
let request = authenticated_request(config.http_request(Method::POST, &url), auth).json(
&RemoteWorkspacePluginUploadUrlRequest {
filename,
mime_type: "application/gzip",
size_bytes,
plugin_id: remote_plugin_id,
},
);
let request = authenticated_request(
config.http_request(Method::POST, &url),
auth,
&config.product_sku,
)
.json(&RemoteWorkspacePluginUploadUrlRequest {
filename,
mime_type: "application/gzip",
size_bytes,
plugin_id: remote_plugin_id,
});
send_and_decode(request, &url).await
}
@@ -456,7 +470,12 @@ async fn finalize_workspace_plugin_upload(
} else {
format!("{base_url}/public/plugins/workspace")
};
let request = authenticated_request(config.http_request(Method::POST, &url), auth).json(&body);
let request = authenticated_request(
config.http_request(Method::POST, &url),
auth,
&config.product_sku,
)
.json(&body);
send_and_decode(request, &url).await
}
@@ -28,6 +28,7 @@ fn test_config(server: &MockServer) -> RemotePluginServiceConfig {
RemotePluginServiceConfig::new(
format!("{}/backend-api", server.uri()),
crate::test_support::test_http_client_factory(),
/*product_sku*/ None,
)
}
@@ -276,6 +276,7 @@ fn catalog_cache_invalidation_clears_global_collections_and_preserves_other_scop
let config = RemotePluginServiceConfig::new(
"https://chatgpt.com/backend-api".to_string(),
crate::test_support::test_http_client_factory(),
/*product_sku*/ None,
);
let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing();
for scope in [RemotePluginScope::Global, RemotePluginScope::Workspace] {
@@ -339,6 +339,7 @@ pub(crate) fn recording_remote_plugin_service_config(
(
RemotePluginServiceConfig {
chatgpt_base_url,
product_sku: crate::remote::CODEX_PRODUCT_SKU.to_string(),
http_clients,
},
selected_urls,
@@ -497,6 +498,7 @@ pub(crate) async fn load_plugins_config(codex_home: &Path, cwd: &Path) -> Plugin
),
"https://chatgpt.com/backend-api/".to_string(),
test_http_client_factory(),
/*product_sku*/ None,
)
}
+1
View File
@@ -2023,6 +2023,7 @@ respect_system_proxy = true
codex_http_client::HttpClientFactory::new(
codex_http_client::OutboundProxyPolicy::RespectSystemProxy,
),
/*product_sku*/ None,
)
);
Ok(())
+1
View File
@@ -1676,6 +1676,7 @@ impl Config {
self.features.enabled(Feature::RemotePlugin),
self.chatgpt_base_url.clone(),
self.http_client_factory(),
self.apps_mcp_product_sku.clone(),
)
}
+12 -4
View File
@@ -68,6 +68,7 @@ use test_case::test_case;
use wiremock::Mock;
use wiremock::MockServer;
use wiremock::ResponseTemplate;
use wiremock::matchers::header;
use wiremock::matchers::method;
use wiremock::matchers::path;
@@ -999,11 +1000,13 @@ async fn legacy_plugin_skill_prompt_remains_complete() -> Result<()> {
Ok(())
}
#[test_case(true, true, (true, false); "enabled remote supersedes bundled")]
#[test_case(false, true, (false, false); "disabled remote does not reactivate bundled")]
#[test_case(true, false, (false, true); "missing remote bundle preserves bundled")]
#[test_case(None, true, true, (true, false); "enabled remote supersedes bundled")]
#[test_case(Some("tpp"), true, true, (true, false); "configured work sku")]
#[test_case(None, false, true, (false, false); "disabled remote does not reactivate bundled")]
#[test_case(None, true, false, (false, true); "missing remote bundle preserves bundled")]
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn sites_compatibility_guard_in_agent_turn(
product_sku: Option<&str>,
remote_enabled: bool,
cache_remote_sites: bool,
expected_skills: (bool, bool),
@@ -1017,6 +1020,7 @@ async fn sites_compatibility_guard_in_agent_turn(
.await;
Mock::given(method("GET"))
.and(path("/ps/plugins/installed"))
.and(header("OAI-Product-Sku", product_sku.unwrap_or("codex")))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"plugins": [{
"id": "plugins~plugin_connector_1p_689987207de08191979cf68eca2941c6",
@@ -1073,11 +1077,15 @@ enabled = true
}
let chatgpt_base_url = server.uri();
let product_sku = product_sku.map(str::to_owned);
let mut builder = test_codex()
.with_home(codex_home)
.with_extensions(skills_extensions())
.with_auth(CodexAuth::create_dummy_chatgpt_auth_for_testing())
.with_config(move |config| config.chatgpt_base_url = chatgpt_base_url);
.with_config(move |config| {
config.chatgpt_base_url = chatgpt_base_url;
config.apps_mcp_product_sku = product_sku;
});
let test = builder.build_with_auto_env(&server).await?;
let auth = test.thread_manager.auth_manager().auth().await;