mirror of
https://github.com/openai/codex.git
synced 2026-10-19 17:28:46 +00:00
Expose remote skill icon URLs through app server (#35012)
## What changed - Add nullable `iconSmallUrl` and `iconLargeUrl` fields to the v2 `SkillInterface` protocol and generated schemas. - Populate the fields from remote plugin catalog metadata while returning null values for local skill interfaces. - Document the new `plugin/read` response fields. ## Testing - Extend the remote `plugin/read` test to verify both icon URLs are returned. GitOrigin-RevId: 0a631a0121627d944a84e542728a506823ff2738
This commit is contained in:
+14
@@ -17458,6 +17458,13 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"iconLargeUrl": {
|
||||
"description": "Remote large icon URL from the plugin catalog.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"iconSmall": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -17468,6 +17475,13 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"iconSmallUrl": {
|
||||
"description": "Remote small icon URL from the plugin catalog.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"shortDescription": {
|
||||
"type": [
|
||||
"string",
|
||||
|
||||
+14
@@ -15222,6 +15222,13 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"iconLargeUrl": {
|
||||
"description": "Remote large icon URL from the plugin catalog.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"iconSmall": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -15232,6 +15239,13 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"iconSmallUrl": {
|
||||
"description": "Remote small icon URL from the plugin catalog.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"shortDescription": {
|
||||
"type": [
|
||||
"string",
|
||||
|
||||
@@ -902,6 +902,13 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"iconLargeUrl": {
|
||||
"description": "Remote large icon URL from the plugin catalog.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"iconSmall": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -912,6 +919,13 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"iconSmallUrl": {
|
||||
"description": "Remote small icon URL from the plugin catalog.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"shortDescription": {
|
||||
"type": [
|
||||
"string",
|
||||
|
||||
@@ -64,6 +64,13 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"iconLargeUrl": {
|
||||
"description": "Remote large icon URL from the plugin catalog.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"iconSmall": {
|
||||
"anyOf": [
|
||||
{
|
||||
@@ -74,6 +81,13 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"iconSmallUrl": {
|
||||
"description": "Remote small icon URL from the plugin catalog.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"shortDescription": {
|
||||
"type": [
|
||||
"string",
|
||||
|
||||
@@ -3,4 +3,12 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { AbsolutePathBuf } from "../AbsolutePathBuf";
|
||||
|
||||
export type SkillInterface = { displayName?: string, shortDescription?: string, iconSmall?: AbsolutePathBuf, iconLarge?: AbsolutePathBuf, brandColor?: string, defaultPrompt?: string, };
|
||||
export type SkillInterface = { displayName?: string, shortDescription?: string, iconSmall?: AbsolutePathBuf, iconLarge?: AbsolutePathBuf,
|
||||
/**
|
||||
* Remote small icon URL from the plugin catalog.
|
||||
*/
|
||||
iconSmallUrl: string | null,
|
||||
/**
|
||||
* Remote large icon URL from the plugin catalog.
|
||||
*/
|
||||
iconLargeUrl: string | null, brandColor?: string, defaultPrompt?: string, };
|
||||
|
||||
@@ -449,6 +449,10 @@ pub struct SkillInterface {
|
||||
pub icon_small: Option<AbsolutePathBuf>,
|
||||
#[ts(optional)]
|
||||
pub icon_large: Option<AbsolutePathBuf>,
|
||||
/// Remote small icon URL from the plugin catalog.
|
||||
pub icon_small_url: Option<String>,
|
||||
/// Remote large icon URL from the plugin catalog.
|
||||
pub icon_large_url: Option<String>,
|
||||
#[ts(optional)]
|
||||
pub brand_color: Option<String>,
|
||||
#[ts(optional)]
|
||||
@@ -908,6 +912,8 @@ impl From<CoreSkillInterface> for SkillInterface {
|
||||
default_prompt: value.default_prompt,
|
||||
icon_small: value.icon_small,
|
||||
icon_large: value.icon_large,
|
||||
icon_small_url: None,
|
||||
icon_large_url: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,7 +218,7 @@ Example with notification opt-out:
|
||||
- `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, 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. Every `PluginSummary` returned by plugin list, installed, read, and share-list methods includes `mustShowInstallationInterstitial`: remote service values preserve `true` or `false`, while local plugins and remote responses that omit the policy return `null`. Clients should fail closed when the value is `null`. Clients can explicitly request the remote `workspace-directory`, `shared-with-me`, or `created-by-me-remote` marketplace kinds. Set `forceRefetch: true` to bypass TTL-backed remote catalog caches for the requested marketplaces and wait for fresh data; cache entries are replaced only after a successful fetch. When local marketplaces are included, the request also waits for configured plugin caches to reconcile before marketplace summaries are returned. At app-server startup, existing cached catalogs remain available to `plugin/list` while they refresh in the background. `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 can include scheduled task summaries from the catalog; `scheduledTasks: null` means the metadata is unavailable, while an empty array means the catalog found no scheduled tasks. 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/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 can include scheduled task summaries from the catalog; `scheduledTasks: null` means the metadata is unavailable, while an empty array means the catalog found no scheduled tasks. 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. Remote skill interfaces expose `iconSmallUrl` and `iconLargeUrl` when the catalog supplies icon URLs. 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.
|
||||
- `app/installed` — read installed connector runtime state from the last committed snapshot, optionally refreshing it first.
|
||||
|
||||
@@ -33,6 +33,8 @@ fn skills_to_info(
|
||||
short_description: interface.short_description,
|
||||
icon_small: interface.icon_small,
|
||||
icon_large: interface.icon_large,
|
||||
icon_small_url: None,
|
||||
icon_large_url: None,
|
||||
brand_color: interface.brand_color,
|
||||
default_prompt: interface.default_prompt,
|
||||
}
|
||||
|
||||
@@ -59,6 +59,8 @@ fn plugin_skills_to_info(
|
||||
short_description: interface.short_description,
|
||||
icon_small: interface.icon_small,
|
||||
icon_large: interface.icon_large,
|
||||
icon_small_url: None,
|
||||
icon_large_url: None,
|
||||
brand_color: interface.brand_color,
|
||||
default_prompt: interface.default_prompt,
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ use codex_app_server_protocol::RequestId;
|
||||
use codex_app_server_protocol::ScheduledTaskSchedule;
|
||||
use codex_app_server_protocol::ScheduledTaskSummary;
|
||||
use codex_app_server_protocol::ScheduledTaskWeekday;
|
||||
use codex_app_server_protocol::SkillInterface;
|
||||
use codex_config::types::AuthCredentialsStoreMode;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use pretty_assertions::assert_eq;
|
||||
@@ -575,7 +576,9 @@ async fn plugin_read_includes_share_url_for_admin_disabled_remote_plugin() -> Re
|
||||
"plugin_release_skill_id": "skill-1",
|
||||
"interface": {
|
||||
"display_name": "Plan Work",
|
||||
"short_description": "Create a plan from issues"
|
||||
"short_description": "Create a plan from issues",
|
||||
"icon_small_url": "https://example.com/plan-work-small.svg",
|
||||
"icon_large_url": "https://example.com/plan-work-large.png"
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -703,6 +706,19 @@ async fn plugin_read_includes_share_url_for_admin_disabled_remote_plugin() -> Re
|
||||
assert_eq!(response.plugin.skills[0].name, "plan-work");
|
||||
assert_eq!(response.plugin.skills[0].path, None);
|
||||
assert_eq!(response.plugin.skills[0].enabled, false);
|
||||
assert_eq!(
|
||||
response.plugin.skills[0].interface,
|
||||
Some(SkillInterface {
|
||||
display_name: Some("Plan Work".to_string()),
|
||||
short_description: Some("Create a plan from issues".to_string()),
|
||||
icon_small: None,
|
||||
icon_large: None,
|
||||
icon_small_url: Some("https://example.com/plan-work-small.svg".to_string()),
|
||||
icon_large_url: Some("https://example.com/plan-work-large.png".to_string()),
|
||||
brand_color: None,
|
||||
default_prompt: None,
|
||||
})
|
||||
);
|
||||
assert_eq!(response.plugin.apps.len(), 0);
|
||||
assert_eq!(
|
||||
response.plugin.app_templates,
|
||||
|
||||
@@ -1782,11 +1782,15 @@ fn remote_skill_interface_to_info(
|
||||
short_description: interface.short_description,
|
||||
icon_small: None,
|
||||
icon_large: None,
|
||||
icon_small_url: interface.icon_small_url,
|
||||
icon_large_url: interface.icon_large_url,
|
||||
brand_color: interface.brand_color,
|
||||
default_prompt: interface.default_prompt,
|
||||
};
|
||||
let has_fields = result.display_name.is_some()
|
||||
|| result.short_description.is_some()
|
||||
|| result.icon_small_url.is_some()
|
||||
|| result.icon_large_url.is_some()
|
||||
|| result.brand_color.is_some()
|
||||
|| result.default_prompt.is_some();
|
||||
has_fields.then_some(result)
|
||||
|
||||
@@ -7329,6 +7329,8 @@ mod tests {
|
||||
short_description: None,
|
||||
icon_small: None,
|
||||
icon_large: None,
|
||||
icon_small_url: None,
|
||||
icon_large_url: None,
|
||||
brand_color: None,
|
||||
default_prompt: None,
|
||||
}),
|
||||
@@ -7444,6 +7446,8 @@ mod tests {
|
||||
short_description: None,
|
||||
icon_small: None,
|
||||
icon_large: None,
|
||||
icon_small_url: None,
|
||||
icon_large_url: None,
|
||||
brand_color: None,
|
||||
default_prompt: None,
|
||||
}),
|
||||
|
||||
Reference in New Issue
Block a user