mirror of
https://github.com/openai/codex.git
synced 2026-11-13 14:46:14 +00:00
[codex] Cache plugin metadata for tool suggestions (#27812)
## Why `built_tools` runs for every sampling request, and local plugin discovery was repeatedly rereading plugin manifests, skills, MCP configuration, and app declarations to build the same tool-suggest metadata. That source-derived metadata is stable until the existing plugin manager reloads its cache. Runtime eligibility still needs to reflect the current install, disable, policy, app-overlap, and authentication state. ## What changed - Add a bounded, in-memory tool-suggest metadata cache owned by `PluginsManager`. - Key cached metadata by plugin identity and source, while applying authentication routing each time the metadata is projected. - Invalidate the metadata alongside the existing loaded-plugin cache, including its normal configuration, marketplace refresh, and remote-installed-plugin invalidation paths. - Guard against an in-flight load repopulating stale metadata after invalidation. - Keep marketplace membership and all runtime eligibility filtering live rather than introducing a separate catalog or revision model. ## Impact Repeated sampling requests reuse already-loaded plugin capability metadata while retaining the existing plugin-manager lifecycle as the single freshness boundary. ## Validation - `just test -p codex-core-plugins` — 252 passed - Added focused coverage for cache invalidation and authentication reprojection.
This commit is contained in:
1 parent
752ed90d78
commit
a52a3b5197
8 files changed
+590
-30
No files matched your search
@@ -3,6 +3,8 @@ use crate::LoadedPlugin;
|
||||
use crate::OPENAI_API_CURATED_MARKETPLACE_NAME;
|
||||
use crate::OPENAI_CURATED_MARKETPLACE_NAME;
|
||||
use crate::PluginLoadOutcome;
|
||||
use crate::ToolSuggestDiscoverablePlugin;
|
||||
use crate::ToolSuggestPluginDiscoveryInput;
|
||||
use crate::installed_marketplaces::marketplace_install_root;
|
||||
use crate::loader::load_plugin_skills;
|
||||
use crate::loader::load_plugins_from_layer_stack;
|
||||
@@ -18,6 +20,7 @@ use crate::startup_sync::curated_plugins_repo_path;
|
||||
use crate::test_support::TEST_CURATED_PLUGIN_CACHE_VERSION;
|
||||
use crate::test_support::TEST_CURATED_PLUGIN_SHA;
|
||||
use crate::test_support::load_plugins_config as load_plugins_config_input;
|
||||
use crate::test_support::write_curated_plugin;
|
||||
use crate::test_support::write_curated_plugin_sha_with as write_curated_plugin_sha;
|
||||
use crate::test_support::write_file;
|
||||
use crate::test_support::write_openai_api_curated_marketplace;
|
||||
@@ -3497,6 +3500,103 @@ source = "/tmp/debug"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn configured_marketplace_upgrade_invalidates_cached_tool_suggest_metadata() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let remote_repo = tmp.path().join("remote-marketplace");
|
||||
let remote_repo_url = url::Url::from_directory_path(&remote_repo)
|
||||
.unwrap()
|
||||
.to_string();
|
||||
write_file(
|
||||
&remote_repo.join(".agents/plugins/marketplace.json"),
|
||||
r#"{
|
||||
"name": "debug",
|
||||
"plugins": [
|
||||
{
|
||||
"name": "sample",
|
||||
"source": {
|
||||
"source": "local",
|
||||
"path": "./plugins/sample"
|
||||
}
|
||||
}
|
||||
]
|
||||
}"#,
|
||||
);
|
||||
write_curated_plugin(&remote_repo, "sample");
|
||||
write_file(
|
||||
&remote_repo.join("plugins/sample/.codex-plugin/plugin.json"),
|
||||
r#"{"name":"sample","description":"Before upgrade"}"#,
|
||||
);
|
||||
init_git_repo(&remote_repo);
|
||||
write_file(
|
||||
&tmp.path().join(CONFIG_TOML_FILE),
|
||||
&format!(
|
||||
r#"[features]
|
||||
plugins = true
|
||||
|
||||
[marketplaces.debug]
|
||||
source_type = "git"
|
||||
source = "{remote_repo_url}"
|
||||
"#
|
||||
),
|
||||
);
|
||||
|
||||
let manager = PluginsManager::new(tmp.path().to_path_buf());
|
||||
let config = load_config(tmp.path(), tmp.path()).await;
|
||||
let initial_upgrade = manager
|
||||
.upgrade_configured_marketplaces_for_config(&config, /*marketplace_name*/ None)
|
||||
.expect("initial marketplace install should succeed");
|
||||
assert_eq!(initial_upgrade.errors, Vec::new());
|
||||
assert_eq!(initial_upgrade.upgraded_roots.len(), 1);
|
||||
|
||||
let config = load_config(tmp.path(), tmp.path()).await;
|
||||
let input = ToolSuggestPluginDiscoveryInput {
|
||||
plugins: config.clone(),
|
||||
configured_plugin_ids: HashSet::from(["sample@debug".to_string()]),
|
||||
disabled_plugin_ids: HashSet::new(),
|
||||
loaded_plugin_app_connector_ids: HashSet::new(),
|
||||
};
|
||||
let expected = ToolSuggestDiscoverablePlugin {
|
||||
id: "sample@debug".to_string(),
|
||||
remote_plugin_id: None,
|
||||
name: "sample".to_string(),
|
||||
description: Some("Before upgrade".to_string()),
|
||||
has_skills: true,
|
||||
mcp_server_names: vec!["sample-docs".to_string()],
|
||||
app_connector_ids: vec!["connector_calendar".to_string()],
|
||||
};
|
||||
assert_eq!(
|
||||
manager
|
||||
.list_tool_suggest_discoverable_plugins(&input, /*auth*/ None)
|
||||
.await
|
||||
.expect("initial tool-suggest metadata should load"),
|
||||
vec![expected.clone()]
|
||||
);
|
||||
|
||||
write_file(
|
||||
&remote_repo.join("plugins/sample/.codex-plugin/plugin.json"),
|
||||
r#"{"name":"sample","description":"After upgrade"}"#,
|
||||
);
|
||||
run_git(&remote_repo, &["add", "."]);
|
||||
run_git(&remote_repo, &["commit", "-m", "update plugin"]);
|
||||
let upgrade = manager
|
||||
.upgrade_configured_marketplaces_for_config(&config, /*marketplace_name*/ None)
|
||||
.expect("marketplace upgrade should succeed");
|
||||
assert_eq!(upgrade.errors, Vec::new());
|
||||
assert_eq!(upgrade.upgraded_roots.len(), 1);
|
||||
|
||||
assert_eq!(
|
||||
manager
|
||||
.list_tool_suggest_discoverable_plugins(&input, /*auth*/ None)
|
||||
.await
|
||||
.expect("refreshed tool-suggest metadata should load"),
|
||||
vec![ToolSuggestDiscoverablePlugin {
|
||||
description: Some("After upgrade".to_string()),
|
||||
..expected
|
||||
}]
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn list_marketplaces_uses_config_when_known_registry_is_malformed() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
|
||||
Reference in new issue
Block a user