mirror of
https://github.com/openai/codex.git
synced 2026-11-05 20:10:33 +00:00
[codex] app-server: expose plugin install policy source (#31293)
## Summary - Add `PluginInstallPolicySource` to the app-server v2 `PluginSummary` payload. - Propagate remote `installation_policy_source` through plugin summaries, details, installed-plugin caching, and app-server responses. - Preserve the supported source values and map unrecognized backend values to `null`. - Return `null` for local plugins and expose the field in generated JSON and TypeScript schemas. - Update app-server API documentation and plugin list/read coverage. ## Testing - `just test -p codex-app-server-protocol` (251 passed) - Targeted `codex-app-server` plugin list, installed, and read tests (4 passed) - Targeted `codex-core-plugins` known and unknown policy source tests (2 passed) - `just fmt` - `git diff --check`
This commit is contained in:
+17
@@ -13423,6 +13423,13 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"PluginInstallPolicySource": {
|
||||
"enum": [
|
||||
"WORKSPACE_SETTING",
|
||||
"IMPLICIT_CANONICAL_APP"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"PluginInstallResponse": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
@@ -14306,6 +14313,16 @@
|
||||
"installPolicy": {
|
||||
"$ref": "#/definitions/v2/PluginInstallPolicy"
|
||||
},
|
||||
"installPolicySource": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/v2/PluginInstallPolicySource"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"installed": {
|
||||
"type": "boolean"
|
||||
},
|
||||
|
||||
+17
@@ -9827,6 +9827,13 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"PluginInstallPolicySource": {
|
||||
"enum": [
|
||||
"WORKSPACE_SETTING",
|
||||
"IMPLICIT_CANONICAL_APP"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"PluginInstallResponse": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
@@ -10710,6 +10717,16 @@
|
||||
"installPolicy": {
|
||||
"$ref": "#/definitions/PluginInstallPolicy"
|
||||
},
|
||||
"installPolicySource": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/PluginInstallPolicySource"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"installed": {
|
||||
"type": "boolean"
|
||||
},
|
||||
|
||||
@@ -63,6 +63,13 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"PluginInstallPolicySource": {
|
||||
"enum": [
|
||||
"WORKSPACE_SETTING",
|
||||
"IMPLICIT_CANONICAL_APP"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"PluginInterface": {
|
||||
"properties": {
|
||||
"brandColor": {
|
||||
@@ -489,6 +496,16 @@
|
||||
"installPolicy": {
|
||||
"$ref": "#/definitions/PluginInstallPolicy"
|
||||
},
|
||||
"installPolicySource": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/PluginInstallPolicySource"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"installed": {
|
||||
"type": "boolean"
|
||||
},
|
||||
|
||||
@@ -63,6 +63,13 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"PluginInstallPolicySource": {
|
||||
"enum": [
|
||||
"WORKSPACE_SETTING",
|
||||
"IMPLICIT_CANONICAL_APP"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"PluginInterface": {
|
||||
"properties": {
|
||||
"brandColor": {
|
||||
@@ -489,6 +496,16 @@
|
||||
"installPolicy": {
|
||||
"$ref": "#/definitions/PluginInstallPolicy"
|
||||
},
|
||||
"installPolicySource": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/PluginInstallPolicySource"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"installed": {
|
||||
"type": "boolean"
|
||||
},
|
||||
|
||||
@@ -242,6 +242,13 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"PluginInstallPolicySource": {
|
||||
"enum": [
|
||||
"WORKSPACE_SETTING",
|
||||
"IMPLICIT_CANONICAL_APP"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"PluginInterface": {
|
||||
"properties": {
|
||||
"brandColor": {
|
||||
@@ -629,6 +636,16 @@
|
||||
"installPolicy": {
|
||||
"$ref": "#/definitions/PluginInstallPolicy"
|
||||
},
|
||||
"installPolicySource": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/PluginInstallPolicySource"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"installed": {
|
||||
"type": "boolean"
|
||||
},
|
||||
|
||||
@@ -37,6 +37,13 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"PluginInstallPolicySource": {
|
||||
"enum": [
|
||||
"WORKSPACE_SETTING",
|
||||
"IMPLICIT_CANONICAL_APP"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"PluginInterface": {
|
||||
"properties": {
|
||||
"brandColor": {
|
||||
@@ -445,6 +452,16 @@
|
||||
"installPolicy": {
|
||||
"$ref": "#/definitions/PluginInstallPolicy"
|
||||
},
|
||||
"installPolicySource": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/PluginInstallPolicySource"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"installed": {
|
||||
"type": "boolean"
|
||||
},
|
||||
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
// GENERATED CODE! DO NOT MODIFY BY HAND!
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type PluginInstallPolicySource = "WORKSPACE_SETTING" | "IMPLICIT_CANONICAL_APP";
|
||||
@@ -4,6 +4,7 @@
|
||||
import type { PluginAuthPolicy } from "./PluginAuthPolicy";
|
||||
import type { PluginAvailability } from "./PluginAvailability";
|
||||
import type { PluginInstallPolicy } from "./PluginInstallPolicy";
|
||||
import type { PluginInstallPolicySource } from "./PluginInstallPolicySource";
|
||||
import type { PluginInterface } from "./PluginInterface";
|
||||
import type { PluginShareContext } from "./PluginShareContext";
|
||||
import type { PluginSource } from "./PluginSource";
|
||||
@@ -24,7 +25,7 @@ localVersion: string | null, name: string,
|
||||
/**
|
||||
* Remote sharing context associated with this plugin when available.
|
||||
*/
|
||||
shareContext: PluginShareContext | null, source: PluginSource, installed: boolean, enabled: boolean, installPolicy: PluginInstallPolicy, authPolicy: PluginAuthPolicy,
|
||||
shareContext: PluginShareContext | null, source: PluginSource, installed: boolean, enabled: boolean, installPolicy: PluginInstallPolicy, installPolicySource: PluginInstallPolicySource | null, authPolicy: PluginAuthPolicy,
|
||||
/**
|
||||
* Availability state for installing and using the plugin.
|
||||
*/
|
||||
|
||||
@@ -294,6 +294,7 @@ export type { PluginDetail } from "./PluginDetail";
|
||||
export type { PluginHookSummary } from "./PluginHookSummary";
|
||||
export type { PluginInstallParams } from "./PluginInstallParams";
|
||||
export type { PluginInstallPolicy } from "./PluginInstallPolicy";
|
||||
export type { PluginInstallPolicySource } from "./PluginInstallPolicySource";
|
||||
export type { PluginInstallResponse } from "./PluginInstallResponse";
|
||||
export type { PluginInstalledParams } from "./PluginInstalledParams";
|
||||
export type { PluginInstalledResponse } from "./PluginInstalledResponse";
|
||||
|
||||
@@ -567,6 +567,17 @@ pub enum PluginInstallPolicy {
|
||||
InstalledByDefault,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum PluginInstallPolicySource {
|
||||
#[serde(rename = "WORKSPACE_SETTING")]
|
||||
#[ts(rename = "WORKSPACE_SETTING")]
|
||||
WorkspaceSetting,
|
||||
#[serde(rename = "IMPLICIT_CANONICAL_APP")]
|
||||
#[ts(rename = "IMPLICIT_CANONICAL_APP")]
|
||||
ImplicitCanonicalApp,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub enum PluginAuthPolicy {
|
||||
@@ -613,6 +624,7 @@ pub struct PluginSummary {
|
||||
pub installed: bool,
|
||||
pub enabled: bool,
|
||||
pub install_policy: PluginInstallPolicy,
|
||||
pub install_policy_source: Option<PluginInstallPolicySource>,
|
||||
pub auth_policy: PluginAuthPolicy,
|
||||
/// Availability state for installing and using the plugin.
|
||||
#[serde(default)]
|
||||
|
||||
@@ -3650,6 +3650,7 @@ fn plugin_share_list_response_serializes_share_items() {
|
||||
installed: false,
|
||||
enabled: false,
|
||||
install_policy: PluginInstallPolicy::Available,
|
||||
install_policy_source: Some(PluginInstallPolicySource::WorkspaceSetting),
|
||||
auth_policy: PluginAuthPolicy::OnUse,
|
||||
availability: PluginAvailability::Available,
|
||||
interface: None,
|
||||
@@ -3672,6 +3673,7 @@ fn plugin_share_list_response_serializes_share_items() {
|
||||
"installed": false,
|
||||
"enabled": false,
|
||||
"installPolicy": "AVAILABLE",
|
||||
"installPolicySource": "WORKSPACE_SETTING",
|
||||
"authPolicy": "ON_USE",
|
||||
"availability": "AVAILABLE",
|
||||
"interface": null,
|
||||
|
||||
@@ -213,8 +213,8 @@ Example with notification opt-out:
|
||||
- `marketplace/add` — add a remote plugin marketplace from an HTTP(S) Git URL, SSH Git URL, or GitHub `owner/repo` shorthand, then persist it into the user marketplace config. Returns the installed root path plus whether the marketplace was already present.
|
||||
- `marketplace/remove` — remove a configured marketplace by name from the user marketplace config, and delete its installed marketplace root when one exists.
|
||||
- `marketplace/upgrade` — upgrade all configured Git plugin marketplaces, or one named marketplace when `marketplaceName` is provided. Returns selected marketplace names, upgraded roots, and per-marketplace errors.
|
||||
- `plugin/list` — list discovered plugin marketplaces and plugin state, including effective marketplace install/auth policy metadata, the remote marketplace `version` and locally materialized `localVersion` when available, plugin `availability` (`AVAILABLE` by default or `DISABLED_BY_ADMIN` for remote plugins blocked upstream), fail-open `marketplaceLoadErrors` entries for marketplace files that could not be parsed or loaded, and best-effort `featuredPluginIds` for the official curated marketplace. Clients can explicitly request the remote `workspace-directory`, `shared-with-me`, or `created-by-me-remote` marketplace kinds. `interface.category` uses the marketplace category when present; otherwise it falls back to the plugin manifest category (**under development; do not call from production clients yet**).
|
||||
- `plugin/installed` — list installed plugin rows plus any explicitly requested local install-suggestion plugin names, without fetching the broader remote catalog. Mention surfaces can use this narrower view when they need plugin mention payloads rather than plugin-page discovery data (**under development; do not call from production clients yet**).
|
||||
- `plugin/list` — list discovered plugin marketplaces and plugin state, including effective marketplace install/auth policy metadata, nullable remote install-policy provenance in `installPolicySource` (`WORKSPACE_SETTING` or `IMPLICIT_CANONICAL_APP`), the remote marketplace `version` and locally materialized `localVersion` when available, plugin `availability` (`AVAILABLE` by default or `DISABLED_BY_ADMIN` for remote plugins blocked upstream), fail-open `marketplaceLoadErrors` entries for marketplace files that could not be parsed or loaded, and best-effort `featuredPluginIds` for the official curated marketplace. Clients can explicitly request the remote `workspace-directory`, `shared-with-me`, or `created-by-me-remote` marketplace kinds. `interface.category` uses the marketplace category when present; otherwise it falls back to the plugin manifest category (**under development; do not call from production clients yet**).
|
||||
- `plugin/installed` — list installed plugin rows plus any explicitly requested local install-suggestion plugin names, without fetching the broader remote catalog. Remote rows include nullable `installPolicySource`; local rows return `null`. Mention surfaces can use this narrower view when they need plugin mention payloads rather than plugin-page discovery data (**under development; do not call from production clients yet**).
|
||||
- `plugin/read` — read one plugin by `marketplacePath` plus `pluginName`, returning marketplace info, a list-style `summary`, manifest descriptions/interface metadata, and bundled skills/hooks/apps/MCP server names. Remote plugin details expose the canonical `shareUrl` supplied by the remote catalog when available; it is `null` for local plugins or when the catalog omits it. This field is separate from `summary.shareContext`, which continues to describe user and workspace sharing state. Returned plugin skills include their current `enabled` state after local config filtering; bundled hooks are returned as lightweight declaration summaries keyed for correlation with `hooks/list`. Use `plugin/install`'s `appsNeedingAuth` to drive post-install authentication and `app/list`'s `isAccessible` to determine current connector accessibility (**under development; do not call from production clients yet**).
|
||||
- `plugin/skill/read` — read remote plugin skill markdown on demand by `remoteMarketplaceName`, `remotePluginId`, and `skillName`. This lets clients preview uninstalled remote plugin skills without downloading the plugin bundle.
|
||||
- `skills/changed` — notification emitted when watched local skill files change.
|
||||
|
||||
@@ -163,6 +163,7 @@ fn convert_configured_marketplace_plugin_to_plugin_summary(
|
||||
share_context,
|
||||
source: marketplace_plugin_source_to_info(plugin.source),
|
||||
install_policy: plugin.policy.installation.into(),
|
||||
install_policy_source: None,
|
||||
auth_policy: plugin.policy.authentication.into(),
|
||||
availability: PluginAvailability::Available,
|
||||
interface: plugin.interface.map(local_plugin_interface_to_info),
|
||||
@@ -1104,6 +1105,7 @@ impl PluginRequestProcessor {
|
||||
installed: outcome.plugin.installed,
|
||||
enabled: outcome.plugin.enabled,
|
||||
install_policy: outcome.plugin.policy.installation.into(),
|
||||
install_policy_source: None,
|
||||
auth_policy: outcome.plugin.policy.authentication.into(),
|
||||
availability: PluginAvailability::Available,
|
||||
interface: outcome.plugin.interface.map(local_plugin_interface_to_info),
|
||||
@@ -2180,6 +2182,7 @@ fn remote_plugin_summary_to_info(summary: RemoteCatalogPluginSummary) -> PluginS
|
||||
installed: summary.installed,
|
||||
enabled: summary.enabled,
|
||||
install_policy: summary.install_policy,
|
||||
install_policy_source: summary.install_policy_source,
|
||||
auth_policy: summary.auth_policy,
|
||||
availability: summary.availability,
|
||||
interface: summary.interface,
|
||||
|
||||
@@ -9,6 +9,7 @@ use app_test_support::write_chatgpt_auth;
|
||||
use codex_app_server_protocol::JSONRPCResponse;
|
||||
use codex_app_server_protocol::PluginAuthPolicy;
|
||||
use codex_app_server_protocol::PluginInstallPolicy;
|
||||
use codex_app_server_protocol::PluginInstallPolicySource;
|
||||
use codex_app_server_protocol::PluginInstalledParams;
|
||||
use codex_app_server_protocol::PluginInstalledResponse;
|
||||
use codex_app_server_protocol::PluginListMarketplaceKind;
|
||||
@@ -196,6 +197,12 @@ enabled = true
|
||||
]
|
||||
);
|
||||
assert_eq!(response.marketplace_load_errors, Vec::new());
|
||||
assert!(
|
||||
response.marketplaces[0]
|
||||
.plugins
|
||||
.iter()
|
||||
.all(|plugin| plugin.install_policy_source.is_none())
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -289,11 +296,17 @@ enabled = true
|
||||
remote_marketplace
|
||||
.plugins
|
||||
.iter()
|
||||
.map(|plugin| plugin.id.clone())
|
||||
.map(|plugin| (plugin.id.clone(), plugin.install_policy_source))
|
||||
.collect::<Vec<_>>(),
|
||||
vec![
|
||||
"linear@openai-curated-remote".to_string(),
|
||||
"remote-only@openai-curated-remote".to_string(),
|
||||
(
|
||||
"linear@openai-curated-remote".to_string(),
|
||||
Some(PluginInstallPolicySource::WorkspaceSetting),
|
||||
),
|
||||
(
|
||||
"remote-only@openai-curated-remote".to_string(),
|
||||
Some(PluginInstallPolicySource::WorkspaceSetting),
|
||||
),
|
||||
]
|
||||
);
|
||||
assert_eq!(response.marketplace_load_errors, Vec::new());
|
||||
@@ -462,6 +475,7 @@ async fn plugin_list_keeps_valid_marketplaces_when_another_marketplace_fails_to_
|
||||
installed: false,
|
||||
enabled: false,
|
||||
install_policy: PluginInstallPolicy::Available,
|
||||
install_policy_source: None,
|
||||
auth_policy: PluginAuthPolicy::OnInstall,
|
||||
availability: codex_app_server_protocol::PluginAvailability::Available,
|
||||
interface: None,
|
||||
@@ -754,6 +768,7 @@ async fn plugin_list_uses_alternate_discoverable_manifest_and_keeps_undiscoverab
|
||||
installed: false,
|
||||
enabled: false,
|
||||
install_policy: PluginInstallPolicy::Available,
|
||||
install_policy_source: None,
|
||||
auth_policy: PluginAuthPolicy::OnInstall,
|
||||
availability: codex_app_server_protocol::PluginAvailability::Available,
|
||||
interface: Some(codex_app_server_protocol::PluginInterface {
|
||||
@@ -794,6 +809,7 @@ async fn plugin_list_uses_alternate_discoverable_manifest_and_keeps_undiscoverab
|
||||
installed: false,
|
||||
enabled: false,
|
||||
install_policy: PluginInstallPolicy::Available,
|
||||
install_policy_source: None,
|
||||
auth_policy: PluginAuthPolicy::OnInstall,
|
||||
availability: codex_app_server_protocol::PluginAvailability::Available,
|
||||
interface: None,
|
||||
@@ -1683,6 +1699,7 @@ async fn plugin_list_includes_remote_marketplaces_when_remote_plugin_enabled() -
|
||||
"name": "linear",
|
||||
"scope": "GLOBAL",
|
||||
"installation_policy": "AVAILABLE",
|
||||
"installation_policy_source": "IMPLICIT_CANONICAL_APP",
|
||||
"authentication_policy": "ON_USE",
|
||||
"status": "ENABLED",
|
||||
"release": {
|
||||
@@ -1722,6 +1739,7 @@ async fn plugin_list_includes_remote_marketplaces_when_remote_plugin_enabled() -
|
||||
"name": "linear",
|
||||
"scope": "GLOBAL",
|
||||
"installation_policy": "AVAILABLE",
|
||||
"installation_policy_source": "WORKSPACE_SETTING",
|
||||
"authentication_policy": "ON_USE",
|
||||
"status": "ENABLED",
|
||||
"release": {
|
||||
@@ -1845,6 +1863,10 @@ async fn plugin_list_includes_remote_marketplaces_when_remote_plugin_enabled() -
|
||||
);
|
||||
assert_eq!(remote_marketplace.plugins[0].installed, true);
|
||||
assert_eq!(remote_marketplace.plugins[0].enabled, true);
|
||||
assert_eq!(
|
||||
remote_marketplace.plugins[0].install_policy_source,
|
||||
Some(PluginInstallPolicySource::ImplicitCanonicalApp)
|
||||
);
|
||||
assert_eq!(
|
||||
remote_marketplace.plugins[0].availability,
|
||||
codex_app_server_protocol::PluginAvailability::Available
|
||||
@@ -1880,6 +1902,10 @@ async fn plugin_list_includes_remote_marketplaces_when_remote_plugin_enabled() -
|
||||
let cached_catalog: serde_json::Value =
|
||||
serde_json::from_slice(&std::fs::read(&cache_files[0])?)?;
|
||||
assert_eq!(cached_catalog["schema_version"], serde_json::json!(1));
|
||||
assert_eq!(
|
||||
cached_catalog["plugins"][0]["installation_policy_source"],
|
||||
serde_json::json!("IMPLICIT_CANONICAL_APP")
|
||||
);
|
||||
assert_eq!(
|
||||
cached_catalog["plugins"][0]["release"]["interface"]["default_prompts"],
|
||||
serde_json::json!(["Create a Linear issue", "Review my Linear projects"])
|
||||
@@ -3938,6 +3964,7 @@ fn remote_installed_plugin_body_with_optional_app_manifest(
|
||||
"name": "linear",
|
||||
"scope": "GLOBAL",
|
||||
"installation_policy": "AVAILABLE",
|
||||
"installation_policy_source": "WORKSPACE_SETTING",
|
||||
"authentication_policy": "ON_USE",
|
||||
"release": {{
|
||||
"version": "{release_version}",
|
||||
|
||||
@@ -25,6 +25,7 @@ use codex_app_server_protocol::JSONRPCResponse;
|
||||
use codex_app_server_protocol::PluginAuthPolicy;
|
||||
use codex_app_server_protocol::PluginAvailability;
|
||||
use codex_app_server_protocol::PluginInstallPolicy;
|
||||
use codex_app_server_protocol::PluginInstallPolicySource;
|
||||
use codex_app_server_protocol::PluginReadParams;
|
||||
use codex_app_server_protocol::PluginReadResponse;
|
||||
use codex_app_server_protocol::PluginShareDiscoverability;
|
||||
@@ -144,6 +145,7 @@ apps = true
|
||||
"name": "example-plugin",
|
||||
"scope": "GLOBAL",
|
||||
"installation_policy": "AVAILABLE",
|
||||
"installation_policy_source": "IMPLICIT_CANONICAL_APP",
|
||||
"authentication_policy": "ON_USE",
|
||||
"release": {
|
||||
"version": "1.2.1",
|
||||
@@ -267,6 +269,10 @@ apps = true
|
||||
assert_eq!(response.plugin.summary.name, "example-plugin");
|
||||
assert_eq!(response.plugin.summary.source, PluginSource::Remote);
|
||||
assert_eq!(response.plugin.summary.share_context, None);
|
||||
assert_eq!(
|
||||
response.plugin.summary.install_policy_source,
|
||||
Some(PluginInstallPolicySource::ImplicitCanonicalApp)
|
||||
);
|
||||
assert_eq!(
|
||||
response
|
||||
.plugin
|
||||
|
||||
@@ -179,6 +179,7 @@ async fn plugin_share_save_uploads_local_plugin() -> Result<()> {
|
||||
installed: true,
|
||||
enabled: true,
|
||||
install_policy: PluginInstallPolicy::Available,
|
||||
install_policy_source: None,
|
||||
auth_policy: PluginAuthPolicy::OnUse,
|
||||
availability: codex_app_server_protocol::PluginAvailability::Available,
|
||||
interface: Some(expected_plugin_interface()),
|
||||
@@ -583,6 +584,7 @@ async fn plugin_share_list_returns_created_workspace_plugins() -> Result<()> {
|
||||
installed: true,
|
||||
enabled: true,
|
||||
install_policy: PluginInstallPolicy::Available,
|
||||
install_policy_source: None,
|
||||
auth_policy: PluginAuthPolicy::OnUse,
|
||||
availability: codex_app_server_protocol::PluginAvailability::Available,
|
||||
interface: Some(expected_plugin_interface()),
|
||||
@@ -1184,6 +1186,7 @@ async fn plugin_share_delete_removes_created_workspace_plugin() -> Result<()> {
|
||||
installed: true,
|
||||
enabled: true,
|
||||
install_policy: PluginInstallPolicy::Available,
|
||||
install_policy_source: None,
|
||||
auth_policy: PluginAuthPolicy::OnUse,
|
||||
availability: codex_app_server_protocol::PluginAvailability::Available,
|
||||
interface: Some(expected_plugin_interface()),
|
||||
|
||||
@@ -781,6 +781,7 @@ fn remote_installed_plugin_in_marketplace(
|
||||
name: name.to_string(),
|
||||
enabled: true,
|
||||
install_policy: codex_app_server_protocol::PluginInstallPolicy::Available,
|
||||
install_policy_source: None,
|
||||
auth_policy: codex_app_server_protocol::PluginAuthPolicy::OnUse,
|
||||
availability: codex_app_server_protocol::PluginAvailability::Available,
|
||||
interface: None,
|
||||
|
||||
@@ -6,6 +6,7 @@ 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::PluginInstallPolicySource;
|
||||
use codex_app_server_protocol::PluginInterface;
|
||||
use codex_app_server_protocol::SkillInterface;
|
||||
use codex_login::CodexAuth;
|
||||
@@ -152,6 +153,7 @@ pub struct RemoteInstalledPlugin {
|
||||
pub name: String,
|
||||
pub enabled: bool,
|
||||
pub install_policy: PluginInstallPolicy,
|
||||
pub install_policy_source: Option<PluginInstallPolicySource>,
|
||||
pub auth_policy: PluginAuthPolicy,
|
||||
pub availability: PluginAvailability,
|
||||
pub interface: Option<PluginInterface>,
|
||||
@@ -169,6 +171,7 @@ pub struct RemotePluginSummary {
|
||||
pub installed: bool,
|
||||
pub enabled: bool,
|
||||
pub install_policy: PluginInstallPolicy,
|
||||
pub install_policy_source: Option<PluginInstallPolicySource>,
|
||||
pub auth_policy: PluginAuthPolicy,
|
||||
pub availability: PluginAvailability,
|
||||
pub interface: Option<PluginInterface>,
|
||||
@@ -528,6 +531,26 @@ struct RemoteAppTemplateResponse {
|
||||
reason: Option<RemoteAppTemplateUnavailableReason>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
|
||||
enum RemotePluginInstallPolicySource {
|
||||
#[serde(rename = "WORKSPACE_SETTING")]
|
||||
WorkspaceSetting,
|
||||
#[serde(rename = "IMPLICIT_CANONICAL_APP")]
|
||||
ImplicitCanonicalApp,
|
||||
#[serde(other)]
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl RemotePluginInstallPolicySource {
|
||||
fn into_protocol(self) -> Option<PluginInstallPolicySource> {
|
||||
match self {
|
||||
Self::WorkspaceSetting => Some(PluginInstallPolicySource::WorkspaceSetting),
|
||||
Self::ImplicitCanonicalApp => Some(PluginInstallPolicySource::ImplicitCanonicalApp),
|
||||
Self::Unknown => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
|
||||
struct RemotePluginDirectoryItem {
|
||||
id: String,
|
||||
@@ -544,6 +567,7 @@ struct RemotePluginDirectoryItem {
|
||||
#[serde(default)]
|
||||
share_principals: Option<Vec<RemotePluginDirectorySharePrincipal>>,
|
||||
installation_policy: PluginInstallPolicy,
|
||||
installation_policy_source: Option<RemotePluginInstallPolicySource>,
|
||||
authentication_policy: PluginAuthPolicy,
|
||||
#[serde(rename = "status", default)]
|
||||
availability: PluginAvailability,
|
||||
@@ -1050,6 +1074,7 @@ pub fn group_remote_installed_plugins_by_marketplaces(
|
||||
installed: true,
|
||||
enabled: plugin.enabled,
|
||||
install_policy: plugin.install_policy,
|
||||
install_policy_source: plugin.install_policy_source,
|
||||
auth_policy: plugin.auth_policy,
|
||||
availability: plugin.availability,
|
||||
interface: plugin.interface.clone(),
|
||||
@@ -1483,6 +1508,9 @@ fn build_remote_plugin_summary(
|
||||
installed: installed_plugin.is_some(),
|
||||
enabled: installed_plugin.is_some_and(|plugin| plugin.enabled),
|
||||
install_policy: plugin.installation_policy,
|
||||
install_policy_source: plugin
|
||||
.installation_policy_source
|
||||
.and_then(RemotePluginInstallPolicySource::into_protocol),
|
||||
auth_policy: plugin.authentication_policy,
|
||||
availability: plugin.availability,
|
||||
interface: remote_plugin_interface_to_info(plugin),
|
||||
@@ -1562,6 +1590,9 @@ fn remote_installed_plugin_to_cache_entry(
|
||||
name: plugin.name.clone(),
|
||||
enabled: installed_plugin.enabled,
|
||||
install_policy: plugin.installation_policy,
|
||||
install_policy_source: plugin
|
||||
.installation_policy_source
|
||||
.and_then(RemotePluginInstallPolicySource::into_protocol),
|
||||
auth_policy: plugin.authentication_policy,
|
||||
availability: plugin.availability,
|
||||
interface: remote_plugin_interface_to_info(plugin),
|
||||
|
||||
@@ -648,6 +648,7 @@ async fn list_remote_plugin_shares_fetches_created_workspace_plugins() {
|
||||
installed: false,
|
||||
enabled: false,
|
||||
install_policy: PluginInstallPolicy::Available,
|
||||
install_policy_source: None,
|
||||
auth_policy: PluginAuthPolicy::OnUse,
|
||||
availability: PluginAvailability::Available,
|
||||
interface: Some(expected_plugin_interface()),
|
||||
@@ -687,6 +688,7 @@ async fn list_remote_plugin_shares_fetches_created_workspace_plugins() {
|
||||
installed: true,
|
||||
enabled: true,
|
||||
install_policy: PluginInstallPolicy::Available,
|
||||
install_policy_source: None,
|
||||
auth_policy: PluginAuthPolicy::OnUse,
|
||||
availability: PluginAvailability::Available,
|
||||
interface: Some(expected_plugin_interface()),
|
||||
|
||||
@@ -33,6 +33,69 @@ fn build_remote_marketplace_preserves_directory_order_and_appends_installed_only
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn installation_policy_source_is_preserved_across_remote_summary_paths() {
|
||||
let mut directory_plugin = directory_plugin("plugin-linear", "linear");
|
||||
directory_plugin.installation_policy_source =
|
||||
Some(RemotePluginInstallPolicySource::ImplicitCanonicalApp);
|
||||
let installed_plugin = RemotePluginInstalledItem {
|
||||
plugin: directory_plugin.clone(),
|
||||
enabled: true,
|
||||
disabled_skill_names: Vec::new(),
|
||||
};
|
||||
|
||||
let marketplace = build_remote_marketplace(
|
||||
REMOTE_GLOBAL_MARKETPLACE_NAME,
|
||||
REMOTE_GLOBAL_MARKETPLACE_DISPLAY_NAME,
|
||||
vec![directory_plugin],
|
||||
vec![installed_plugin.clone()],
|
||||
/*include_installed_only*/ false,
|
||||
)
|
||||
.expect("marketplace should be valid")
|
||||
.expect("marketplace should not be empty");
|
||||
assert_eq!(
|
||||
marketplace
|
||||
.plugins
|
||||
.into_iter()
|
||||
.map(|plugin| plugin.install_policy_source)
|
||||
.collect::<Vec<_>>(),
|
||||
vec![Some(PluginInstallPolicySource::ImplicitCanonicalApp)]
|
||||
);
|
||||
|
||||
let mut installed_plugin = installed_plugin;
|
||||
installed_plugin.plugin.installation_policy_source =
|
||||
Some(RemotePluginInstallPolicySource::WorkspaceSetting);
|
||||
let installed_plugin = remote_installed_plugin_to_cache_entry(&installed_plugin)
|
||||
.expect("installed plugin should be valid");
|
||||
let marketplaces = group_remote_installed_plugins_by_marketplaces(
|
||||
&[installed_plugin],
|
||||
&[REMOTE_GLOBAL_MARKETPLACE_NAME],
|
||||
);
|
||||
assert_eq!(
|
||||
marketplaces
|
||||
.into_iter()
|
||||
.flat_map(|marketplace| marketplace.plugins)
|
||||
.map(|plugin| plugin.install_policy_source)
|
||||
.collect::<Vec<_>>(),
|
||||
vec![Some(PluginInstallPolicySource::WorkspaceSetting)]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unknown_installation_policy_source_maps_to_none() {
|
||||
let plugin = directory_plugin("plugin-linear", "linear");
|
||||
let mut plugin_json = serde_json::to_value(plugin).expect("plugin should serialize");
|
||||
plugin_json["installation_policy_source"] =
|
||||
serde_json::Value::String("FUTURE_POLICY_SOURCE".to_string());
|
||||
let plugin: RemotePluginDirectoryItem =
|
||||
serde_json::from_value(plugin_json).expect("unknown source should deserialize");
|
||||
|
||||
let summary = build_remote_plugin_summary(&plugin, /*installed_plugin*/ None)
|
||||
.expect("summary should be valid");
|
||||
|
||||
assert_eq!(summary.install_policy_source, None);
|
||||
}
|
||||
|
||||
fn directory_plugin(id: &str, name: &str) -> RemotePluginDirectoryItem {
|
||||
RemotePluginDirectoryItem {
|
||||
id: id.to_string(),
|
||||
@@ -44,6 +107,7 @@ fn directory_plugin(id: &str, name: &str) -> RemotePluginDirectoryItem {
|
||||
share_url: None,
|
||||
share_principals: None,
|
||||
installation_policy: PluginInstallPolicy::Available,
|
||||
installation_policy_source: None,
|
||||
authentication_policy: PluginAuthPolicy::OnUse,
|
||||
availability: PluginAvailability::Available,
|
||||
release: RemotePluginReleaseResponse {
|
||||
|
||||
@@ -173,6 +173,7 @@ mod tests {
|
||||
installed: true,
|
||||
enabled: true,
|
||||
install_policy: PluginInstallPolicy::Available,
|
||||
install_policy_source: None,
|
||||
auth_policy: PluginAuthPolicy::OnInstall,
|
||||
availability: PluginAvailability::Available,
|
||||
interface: None,
|
||||
|
||||
@@ -1341,6 +1341,7 @@ pub(super) fn plugins_test_summary(
|
||||
installed,
|
||||
enabled,
|
||||
install_policy,
|
||||
install_policy_source: None,
|
||||
auth_policy: PluginAuthPolicy::OnInstall,
|
||||
availability: PluginAvailability::Available,
|
||||
interface: Some(plugins_test_interface(
|
||||
@@ -1370,6 +1371,7 @@ pub(super) fn plugins_test_remote_summary(
|
||||
installed,
|
||||
enabled: true,
|
||||
install_policy: PluginInstallPolicy::Available,
|
||||
install_policy_source: None,
|
||||
auth_policy: PluginAuthPolicy::OnInstall,
|
||||
availability: PluginAvailability::Available,
|
||||
interface: Some(plugins_test_interface(
|
||||
|
||||
Reference in New Issue
Block a user