mirror of
https://github.com/openai/codex.git
synced 2026-05-05 22:01:37 +03:00
respect workspace option for disabling plugins (#18907)
Respects the workspace setting for plugins in Codex Plugins menu disappears Plugins do not load Plugins do not load in composer no plugins loaded <img width="809" height="226" alt="Screenshot 2026-04-23 at 3 20 45 PM" src="https://github.com/user-attachments/assets/3a4dba8e-69c3-4046-a77e-f13ab77f84b4" /> no plugins in menu <img width="293" height="204" alt="Screenshot 2026-04-23 at 3 20 35 PM" src="https://github.com/user-attachments/assets/5cb9bf52-ad72-488f-b90c-5eb457da09a3" />
This commit is contained in:
@@ -37,7 +37,11 @@ pub(crate) async fn chatgpt_get_request_with_timeout<T: DeserializeOwned>(
|
||||
|
||||
// Make direct HTTP request to ChatGPT backend API with the token
|
||||
let client = create_client();
|
||||
let url = format!("{chatgpt_base_url}{path}");
|
||||
let url = format!(
|
||||
"{}/{}",
|
||||
chatgpt_base_url.trim_end_matches('/'),
|
||||
path.trim_start_matches('/')
|
||||
);
|
||||
|
||||
let mut request = client
|
||||
.get(&url)
|
||||
|
||||
@@ -2,3 +2,4 @@ pub mod apply_command;
|
||||
mod chatgpt_client;
|
||||
pub mod connectors;
|
||||
pub mod get_task;
|
||||
pub mod workspace_settings;
|
||||
|
||||
152
codex-rs/chatgpt/src/workspace_settings.rs
Normal file
152
codex-rs/chatgpt/src/workspace_settings.rs
Normal file
@@ -0,0 +1,152 @@
|
||||
use std::collections::HashMap;
|
||||
use std::sync::RwLock;
|
||||
use std::time::Duration;
|
||||
use std::time::Instant;
|
||||
|
||||
use anyhow::Context;
|
||||
use codex_core::config::Config;
|
||||
use codex_login::CodexAuth;
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::chatgpt_client::chatgpt_get_request_with_timeout;
|
||||
|
||||
const WORKSPACE_SETTINGS_TIMEOUT: Duration = Duration::from_secs(10);
|
||||
const WORKSPACE_SETTINGS_CACHE_TTL: Duration = Duration::from_secs(15 * 60);
|
||||
const CODEX_PLUGINS_BETA_SETTING: &str = "plugins";
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct WorkspaceSettingsResponse {
|
||||
#[serde(default)]
|
||||
beta_settings: HashMap<String, bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct WorkspaceSettingsCache {
|
||||
entry: RwLock<Option<CachedWorkspaceSettings>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||
struct WorkspaceSettingsCacheKey {
|
||||
chatgpt_base_url: String,
|
||||
account_id: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct CachedWorkspaceSettings {
|
||||
key: WorkspaceSettingsCacheKey,
|
||||
expires_at: Instant,
|
||||
codex_plugins_enabled: bool,
|
||||
}
|
||||
|
||||
impl WorkspaceSettingsCache {
|
||||
fn get_codex_plugins_enabled(&self, key: &WorkspaceSettingsCacheKey) -> Option<bool> {
|
||||
{
|
||||
let entry = match self.entry.read() {
|
||||
Ok(entry) => entry,
|
||||
Err(err) => err.into_inner(),
|
||||
};
|
||||
let now = Instant::now();
|
||||
if let Some(cached) = entry.as_ref()
|
||||
&& now < cached.expires_at
|
||||
&& cached.key == *key
|
||||
{
|
||||
return Some(cached.codex_plugins_enabled);
|
||||
}
|
||||
}
|
||||
|
||||
let mut entry = match self.entry.write() {
|
||||
Ok(entry) => entry,
|
||||
Err(err) => err.into_inner(),
|
||||
};
|
||||
let now = Instant::now();
|
||||
if entry
|
||||
.as_ref()
|
||||
.is_some_and(|cached| now >= cached.expires_at || cached.key != *key)
|
||||
{
|
||||
*entry = None;
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn set_codex_plugins_enabled(&self, key: WorkspaceSettingsCacheKey, enabled: bool) {
|
||||
let mut entry = match self.entry.write() {
|
||||
Ok(entry) => entry,
|
||||
Err(err) => err.into_inner(),
|
||||
};
|
||||
*entry = Some(CachedWorkspaceSettings {
|
||||
key,
|
||||
expires_at: Instant::now() + WORKSPACE_SETTINGS_CACHE_TTL,
|
||||
codex_plugins_enabled: enabled,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn codex_plugins_enabled_for_workspace(
|
||||
config: &Config,
|
||||
auth: Option<&CodexAuth>,
|
||||
cache: Option<&WorkspaceSettingsCache>,
|
||||
) -> anyhow::Result<bool> {
|
||||
let Some(auth) = auth else {
|
||||
return Ok(true);
|
||||
};
|
||||
if !auth.is_chatgpt_auth() {
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
let token_data = auth
|
||||
.get_token_data()
|
||||
.context("ChatGPT token data is not available")?;
|
||||
if !token_data.id_token.is_workspace_account() {
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
let Some(account_id) = token_data.account_id.as_deref().filter(|id| !id.is_empty()) else {
|
||||
return Ok(true);
|
||||
};
|
||||
|
||||
let cache_key = WorkspaceSettingsCacheKey {
|
||||
chatgpt_base_url: config.chatgpt_base_url.clone(),
|
||||
account_id: account_id.to_string(),
|
||||
};
|
||||
if let Some(cache) = cache
|
||||
&& let Some(enabled) = cache.get_codex_plugins_enabled(&cache_key)
|
||||
{
|
||||
return Ok(enabled);
|
||||
}
|
||||
|
||||
let encoded_account_id = encode_path_segment(account_id);
|
||||
let settings: WorkspaceSettingsResponse = chatgpt_get_request_with_timeout(
|
||||
config,
|
||||
format!("/accounts/{encoded_account_id}/settings"),
|
||||
Some(WORKSPACE_SETTINGS_TIMEOUT),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let codex_plugins_enabled = settings
|
||||
.beta_settings
|
||||
.get(CODEX_PLUGINS_BETA_SETTING)
|
||||
.copied()
|
||||
.unwrap_or(true);
|
||||
|
||||
if let Some(cache) = cache {
|
||||
cache.set_codex_plugins_enabled(cache_key, codex_plugins_enabled);
|
||||
}
|
||||
|
||||
Ok(codex_plugins_enabled)
|
||||
}
|
||||
|
||||
fn encode_path_segment(value: &str) -> String {
|
||||
let mut encoded = String::new();
|
||||
for byte in value.bytes() {
|
||||
if byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'.' | b'_' | b'~') {
|
||||
encoded.push(byte as char);
|
||||
} else {
|
||||
encoded.push_str(&format!("%{byte:02X}"));
|
||||
}
|
||||
}
|
||||
encoded
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "workspace_settings_tests.rs"]
|
||||
mod tests;
|
||||
17
codex-rs/chatgpt/src/workspace_settings_tests.rs
Normal file
17
codex-rs/chatgpt/src/workspace_settings_tests.rs
Normal file
@@ -0,0 +1,17 @@
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn encode_path_segment_leaves_unreserved_ascii_unchanged() {
|
||||
assert_eq!(
|
||||
encode_path_segment("account-123_ABC.~"),
|
||||
"account-123_ABC.~"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encode_path_segment_escapes_path_separators_and_spaces() {
|
||||
assert_eq!(
|
||||
encode_path_segment("account/123 with space"),
|
||||
"account%2F123%20with%20space"
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user