mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
02dce8eb8d
## Summary - choose the local OpenAI curated marketplace manifest based on auth: Codex backend auth gets the existing marketplace, direct provider auth gets `api_marketplace.json` - include Bedrock API key auth in the direct-provider API marketplace path - safely skip the API marketplace when `api_marketplace.json` is absent ## Validation - `just fmt` - `git diff --check origin/main...HEAD` - CI should run the full validation ## Manual Testing ### - New api marketplace not available for API key sign 1. Safely not display anything from api marketplace <img width="1161" height="289" alt="Screenshot 2026-06-15 at 21 37 43" src="https://github.com/user-attachments/assets/a5f16642-8a20-4ac1-a0de-1274a4c7b5b2" /> ### - New api marketplace for API key sign in 1. Setup api_marketplace.json ``` { "name": "openai-curated", "interface": { "displayName": "Codex official" }, "plugins": [ { "name": "linear", "source": { "source": "local", "path": "./plugins/linear" }, "policy": { "installation": "AVAILABLE", "authentication": "ON_INSTALL" }, "category": "Productivity" } ] } ``` 2. Log in with API key, observe that only the defined plugin from api_marketplace.json is available from "Codex Official" (outside of local testing marketplaces) <img width="1167" height="446" alt="Screenshot 2026-06-15 at 21 16 53" src="https://github.com/user-attachments/assets/7cf61477-d826-4ef6-bc05-0a23ac1c0259" /> also checked functionality on codex app ### - SiWC users Still uses 'default' marketplace.json and renders all plugins <img width="1171" height="502" alt="Screenshot 2026-06-15 at 21 40 25" src="https://github.com/user-attachments/assets/d212ea9b-0aa5-470b-8ea4-450efe65bb2b" /> also checked functionality on codex app ## Notes - `just test -p codex-core-plugins` was started locally before splitting branches, but I stopped relying on local tests per follow-up and left final validation to PR CI.
166 lines
4.4 KiB
Rust
166 lines
4.4 KiB
Rust
use std::fs;
|
|
use std::path::Path;
|
|
|
|
use crate::OPENAI_API_CURATED_MARKETPLACE_NAME;
|
|
use crate::OPENAI_CURATED_MARKETPLACE_NAME;
|
|
use crate::PluginsConfigInput;
|
|
use codex_config::LoaderOverrides;
|
|
use codex_config::NoopThreadConfigLoader;
|
|
use codex_config::loader::load_config_layers_state;
|
|
use codex_exec_server::LOCAL_FS;
|
|
use codex_utils_absolute_path::AbsolutePathBuf;
|
|
use toml::Value;
|
|
|
|
pub(crate) const TEST_CURATED_PLUGIN_SHA: &str = "0123456789abcdef0123456789abcdef01234567";
|
|
pub(crate) const TEST_CURATED_PLUGIN_CACHE_VERSION: &str = "01234567";
|
|
|
|
pub(crate) fn write_file(path: &Path, contents: &str) {
|
|
fs::create_dir_all(path.parent().expect("file should have a parent")).unwrap();
|
|
fs::write(path, contents).unwrap();
|
|
}
|
|
|
|
pub(crate) fn write_curated_plugin(root: &Path, plugin_name: &str) {
|
|
let plugin_root = root.join("plugins").join(plugin_name);
|
|
write_file(
|
|
&plugin_root.join(".codex-plugin/plugin.json"),
|
|
&format!(
|
|
r#"{{
|
|
"name": "{plugin_name}",
|
|
"description": "Plugin that includes skills, MCP servers, and app connectors"
|
|
}}"#
|
|
),
|
|
);
|
|
write_file(
|
|
&plugin_root.join("skills/SKILL.md"),
|
|
"---\nname: sample\ndescription: sample\n---\n",
|
|
);
|
|
write_file(
|
|
&plugin_root.join(".mcp.json"),
|
|
r#"{
|
|
"mcpServers": {
|
|
"sample-docs": {
|
|
"type": "http",
|
|
"url": "https://sample.example/mcp"
|
|
}
|
|
}
|
|
}"#,
|
|
);
|
|
write_file(
|
|
&plugin_root.join(".app.json"),
|
|
r#"{
|
|
"apps": {
|
|
"calendar": {
|
|
"id": "connector_calendar"
|
|
}
|
|
}
|
|
}"#,
|
|
);
|
|
}
|
|
|
|
pub(crate) fn write_openai_curated_marketplace(root: &Path, plugin_names: &[&str]) {
|
|
write_curated_marketplace(
|
|
root,
|
|
"marketplace.json",
|
|
OPENAI_CURATED_MARKETPLACE_NAME,
|
|
/*display_name*/ None,
|
|
plugin_names,
|
|
);
|
|
}
|
|
|
|
pub(crate) fn write_openai_api_curated_marketplace(root: &Path, plugin_names: &[&str]) {
|
|
write_curated_marketplace(
|
|
root,
|
|
"api_marketplace.json",
|
|
OPENAI_API_CURATED_MARKETPLACE_NAME,
|
|
Some("OpenAI Curated"),
|
|
plugin_names,
|
|
);
|
|
}
|
|
|
|
fn write_curated_marketplace(
|
|
root: &Path,
|
|
manifest_name: &str,
|
|
marketplace_name: &str,
|
|
display_name: Option<&str>,
|
|
plugin_names: &[&str],
|
|
) {
|
|
let plugins = plugin_names
|
|
.iter()
|
|
.map(|plugin_name| {
|
|
format!(
|
|
r#"{{
|
|
"name": "{plugin_name}",
|
|
"source": {{
|
|
"source": "local",
|
|
"path": "./plugins/{plugin_name}"
|
|
}}
|
|
}}"#
|
|
)
|
|
})
|
|
.collect::<Vec<_>>()
|
|
.join(",\n");
|
|
let interface = display_name
|
|
.map(|display_name| {
|
|
format!(
|
|
r#"
|
|
"interface": {{
|
|
"displayName": "{display_name}"
|
|
}},"#
|
|
)
|
|
})
|
|
.unwrap_or_default();
|
|
write_file(
|
|
&root.join(".agents/plugins").join(manifest_name),
|
|
&format!(
|
|
r#"{{
|
|
"name": "{marketplace_name}",{interface}
|
|
"plugins": [
|
|
{plugins}
|
|
]
|
|
}}"#
|
|
),
|
|
);
|
|
for plugin_name in plugin_names {
|
|
write_curated_plugin(root, plugin_name);
|
|
}
|
|
}
|
|
|
|
pub(crate) fn write_curated_plugin_sha_with(codex_home: &Path, sha: &str) {
|
|
write_file(&codex_home.join(".tmp/plugins.sha"), &format!("{sha}\n"));
|
|
}
|
|
|
|
pub(crate) async fn load_plugins_config(codex_home: &Path, cwd: &Path) -> PluginsConfigInput {
|
|
let codex_home = AbsolutePathBuf::try_from(codex_home).expect("codex home should be absolute");
|
|
let cwd = AbsolutePathBuf::try_from(cwd).expect("cwd should be absolute");
|
|
let config_layer_stack = load_config_layers_state(
|
|
LOCAL_FS.as_ref(),
|
|
codex_home.as_path(),
|
|
Some(cwd),
|
|
&[],
|
|
LoaderOverrides::without_managed_config_for_tests(),
|
|
&NoopThreadConfigLoader,
|
|
)
|
|
.await
|
|
.expect("config should load");
|
|
let effective_config = config_layer_stack.effective_config();
|
|
PluginsConfigInput::new(
|
|
config_layer_stack,
|
|
feature_enabled(&effective_config, "plugins", /*default_enabled*/ true),
|
|
feature_enabled(
|
|
&effective_config,
|
|
"remote_plugin",
|
|
/*default_enabled*/ false,
|
|
),
|
|
"https://chatgpt.com/backend-api/".to_string(),
|
|
)
|
|
}
|
|
|
|
fn feature_enabled(config: &Value, key: &str, default_enabled: bool) -> bool {
|
|
config
|
|
.get("features")
|
|
.and_then(Value::as_table)
|
|
.and_then(|features| features.get(key))
|
|
.and_then(Value::as_bool)
|
|
.unwrap_or(default_enabled)
|
|
}
|