support plugin/list. (#13540)

Introduce a plugin/list which reads from local marketplace.json.
Also update the signature for plugin/install.
This commit is contained in:
xl-openai
2026-03-05 21:58:50 -05:00
committed by GitHub
Unverified
parent 56420da857
commit 520ed724d2
25 changed files with 1645 additions and 145 deletions
@@ -953,25 +953,34 @@
},
"PluginInstallParams": {
"properties": {
"cwd": {
"type": [
"string",
"null"
]
},
"marketplaceName": {
"type": "string"
"marketplacePath": {
"$ref": "#/definitions/AbsolutePathBuf"
},
"pluginName": {
"type": "string"
}
},
"required": [
"marketplaceName",
"marketplacePath",
"pluginName"
],
"type": "object"
},
"PluginListParams": {
"properties": {
"cwds": {
"description": "Optional working directories used to discover repo marketplaces. When omitted, only home-scoped marketplaces are considered.",
"items": {
"$ref": "#/definitions/AbsolutePathBuf"
},
"type": [
"array",
"null"
]
}
},
"type": "object"
},
"ProductSurface": {
"enum": [
"chatgpt",
@@ -3264,6 +3273,30 @@
"title": "Skills/listRequest",
"type": "object"
},
{
"properties": {
"id": {
"$ref": "#/definitions/RequestId"
},
"method": {
"enum": [
"plugin/list"
],
"title": "Plugin/listRequestMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/PluginListParams"
}
},
"required": [
"id",
"method",
"params"
],
"title": "Plugin/listRequest",
"type": "object"
},
{
"properties": {
"id": {
@@ -716,6 +716,30 @@
"title": "Skills/listRequest",
"type": "object"
},
{
"properties": {
"id": {
"$ref": "#/definitions/v2/RequestId"
},
"method": {
"enum": [
"plugin/list"
],
"title": "Plugin/listRequestMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/v2/PluginListParams"
}
},
"required": [
"id",
"method",
"params"
],
"title": "Plugin/listRequest",
"type": "object"
},
{
"properties": {
"id": {
@@ -11174,21 +11198,15 @@
"PluginInstallParams": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"cwd": {
"type": [
"string",
"null"
]
},
"marketplaceName": {
"type": "string"
"marketplacePath": {
"$ref": "#/definitions/v2/AbsolutePathBuf"
},
"pluginName": {
"type": "string"
}
},
"required": [
"marketplaceName",
"marketplacePath",
"pluginName"
],
"title": "PluginInstallParams",
@@ -11199,6 +11217,104 @@
"title": "PluginInstallResponse",
"type": "object"
},
"PluginListParams": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"cwds": {
"description": "Optional working directories used to discover repo marketplaces. When omitted, only home-scoped marketplaces are considered.",
"items": {
"$ref": "#/definitions/v2/AbsolutePathBuf"
},
"type": [
"array",
"null"
]
}
},
"title": "PluginListParams",
"type": "object"
},
"PluginListResponse": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"marketplaces": {
"items": {
"$ref": "#/definitions/v2/PluginMarketplaceEntry"
},
"type": "array"
}
},
"required": [
"marketplaces"
],
"title": "PluginListResponse",
"type": "object"
},
"PluginMarketplaceEntry": {
"properties": {
"name": {
"type": "string"
},
"path": {
"type": "string"
},
"plugins": {
"items": {
"$ref": "#/definitions/v2/PluginSummary"
},
"type": "array"
}
},
"required": [
"name",
"path",
"plugins"
],
"type": "object"
},
"PluginSource": {
"oneOf": [
{
"properties": {
"path": {
"type": "string"
},
"type": {
"enum": [
"local"
],
"title": "LocalPluginSourceType",
"type": "string"
}
},
"required": [
"path",
"type"
],
"title": "LocalPluginSource",
"type": "object"
}
]
},
"PluginSummary": {
"properties": {
"enabled": {
"type": "boolean"
},
"name": {
"type": "string"
},
"source": {
"$ref": "#/definitions/v2/PluginSource"
}
},
"required": [
"enabled",
"name",
"source"
],
"type": "object"
},
"ProductSurface": {
"enum": [
"chatgpt",
@@ -1211,6 +1211,30 @@
"title": "Skills/listRequest",
"type": "object"
},
{
"properties": {
"id": {
"$ref": "#/definitions/RequestId"
},
"method": {
"enum": [
"plugin/list"
],
"title": "Plugin/listRequestMethod",
"type": "string"
},
"params": {
"$ref": "#/definitions/PluginListParams"
}
},
"required": [
"id",
"method",
"params"
],
"title": "Plugin/listRequest",
"type": "object"
},
{
"properties": {
"id": {
@@ -8371,21 +8395,15 @@
"PluginInstallParams": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"cwd": {
"type": [
"string",
"null"
]
},
"marketplaceName": {
"type": "string"
"marketplacePath": {
"$ref": "#/definitions/AbsolutePathBuf"
},
"pluginName": {
"type": "string"
}
},
"required": [
"marketplaceName",
"marketplacePath",
"pluginName"
],
"title": "PluginInstallParams",
@@ -8396,6 +8414,104 @@
"title": "PluginInstallResponse",
"type": "object"
},
"PluginListParams": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"cwds": {
"description": "Optional working directories used to discover repo marketplaces. When omitted, only home-scoped marketplaces are considered.",
"items": {
"$ref": "#/definitions/AbsolutePathBuf"
},
"type": [
"array",
"null"
]
}
},
"title": "PluginListParams",
"type": "object"
},
"PluginListResponse": {
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"marketplaces": {
"items": {
"$ref": "#/definitions/PluginMarketplaceEntry"
},
"type": "array"
}
},
"required": [
"marketplaces"
],
"title": "PluginListResponse",
"type": "object"
},
"PluginMarketplaceEntry": {
"properties": {
"name": {
"type": "string"
},
"path": {
"type": "string"
},
"plugins": {
"items": {
"$ref": "#/definitions/PluginSummary"
},
"type": "array"
}
},
"required": [
"name",
"path",
"plugins"
],
"type": "object"
},
"PluginSource": {
"oneOf": [
{
"properties": {
"path": {
"type": "string"
},
"type": {
"enum": [
"local"
],
"title": "LocalPluginSourceType",
"type": "string"
}
},
"required": [
"path",
"type"
],
"title": "LocalPluginSource",
"type": "object"
}
]
},
"PluginSummary": {
"properties": {
"enabled": {
"type": "boolean"
},
"name": {
"type": "string"
},
"source": {
"$ref": "#/definitions/PluginSource"
}
},
"required": [
"enabled",
"name",
"source"
],
"type": "object"
},
"ProductSurface": {
"enum": [
"chatgpt",
@@ -1,21 +1,21 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"properties": {
"cwd": {
"type": [
"string",
"null"
]
},
"marketplaceName": {
"definitions": {
"AbsolutePathBuf": {
"description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.",
"type": "string"
}
},
"properties": {
"marketplacePath": {
"$ref": "#/definitions/AbsolutePathBuf"
},
"pluginName": {
"type": "string"
}
},
"required": [
"marketplaceName",
"marketplacePath",
"pluginName"
],
"title": "PluginInstallParams",
@@ -0,0 +1,23 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"AbsolutePathBuf": {
"description": "A path that is guaranteed to be absolute and normalized (though it is not guaranteed to be canonicalized or exist on the filesystem).\n\nIMPORTANT: When deserializing an `AbsolutePathBuf`, a base path must be set using [AbsolutePathBufGuard::new]. If no base path is set, the deserialization will fail unless the path being deserialized is already absolute.",
"type": "string"
}
},
"properties": {
"cwds": {
"description": "Optional working directories used to discover repo marketplaces. When omitted, only home-scoped marketplaces are considered.",
"items": {
"$ref": "#/definitions/AbsolutePathBuf"
},
"type": [
"array",
"null"
]
}
},
"title": "PluginListParams",
"type": "object"
}
@@ -0,0 +1,83 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"PluginMarketplaceEntry": {
"properties": {
"name": {
"type": "string"
},
"path": {
"type": "string"
},
"plugins": {
"items": {
"$ref": "#/definitions/PluginSummary"
},
"type": "array"
}
},
"required": [
"name",
"path",
"plugins"
],
"type": "object"
},
"PluginSource": {
"oneOf": [
{
"properties": {
"path": {
"type": "string"
},
"type": {
"enum": [
"local"
],
"title": "LocalPluginSourceType",
"type": "string"
}
},
"required": [
"path",
"type"
],
"title": "LocalPluginSource",
"type": "object"
}
]
},
"PluginSummary": {
"properties": {
"enabled": {
"type": "boolean"
},
"name": {
"type": "string"
},
"source": {
"$ref": "#/definitions/PluginSource"
}
},
"required": [
"enabled",
"name",
"source"
],
"type": "object"
}
},
"properties": {
"marketplaces": {
"items": {
"$ref": "#/definitions/PluginMarketplaceEntry"
},
"type": "array"
}
},
"required": [
"marketplaces"
],
"title": "PluginListResponse",
"type": "object"
}
@@ -23,6 +23,7 @@ import type { LoginAccountParams } from "./v2/LoginAccountParams";
import type { McpServerOauthLoginParams } from "./v2/McpServerOauthLoginParams";
import type { ModelListParams } from "./v2/ModelListParams";
import type { PluginInstallParams } from "./v2/PluginInstallParams";
import type { PluginListParams } from "./v2/PluginListParams";
import type { ReviewStartParams } from "./v2/ReviewStartParams";
import type { SkillsConfigWriteParams } from "./v2/SkillsConfigWriteParams";
import type { SkillsListParams } from "./v2/SkillsListParams";
@@ -49,4 +50,4 @@ import type { WindowsSandboxSetupStartParams } from "./v2/WindowsSandboxSetupSta
/**
* Request from the client to the server.
*/
export type ClientRequest ={ "method": "initialize", id: RequestId, params: InitializeParams, } | { "method": "thread/start", id: RequestId, params: ThreadStartParams, } | { "method": "thread/resume", id: RequestId, params: ThreadResumeParams, } | { "method": "thread/fork", id: RequestId, params: ThreadForkParams, } | { "method": "thread/archive", id: RequestId, params: ThreadArchiveParams, } | { "method": "thread/unsubscribe", id: RequestId, params: ThreadUnsubscribeParams, } | { "method": "thread/name/set", id: RequestId, params: ThreadSetNameParams, } | { "method": "thread/metadata/update", id: RequestId, params: ThreadMetadataUpdateParams, } | { "method": "thread/unarchive", id: RequestId, params: ThreadUnarchiveParams, } | { "method": "thread/compact/start", id: RequestId, params: ThreadCompactStartParams, } | { "method": "thread/rollback", id: RequestId, params: ThreadRollbackParams, } | { "method": "thread/list", id: RequestId, params: ThreadListParams, } | { "method": "thread/loaded/list", id: RequestId, params: ThreadLoadedListParams, } | { "method": "thread/read", id: RequestId, params: ThreadReadParams, } | { "method": "skills/list", id: RequestId, params: SkillsListParams, } | { "method": "skills/remote/list", id: RequestId, params: SkillsRemoteReadParams, } | { "method": "skills/remote/export", id: RequestId, params: SkillsRemoteWriteParams, } | { "method": "app/list", id: RequestId, params: AppsListParams, } | { "method": "skills/config/write", id: RequestId, params: SkillsConfigWriteParams, } | { "method": "plugin/install", id: RequestId, params: PluginInstallParams, } | { "method": "turn/start", id: RequestId, params: TurnStartParams, } | { "method": "turn/steer", id: RequestId, params: TurnSteerParams, } | { "method": "turn/interrupt", id: RequestId, params: TurnInterruptParams, } | { "method": "review/start", id: RequestId, params: ReviewStartParams, } | { "method": "model/list", id: RequestId, params: ModelListParams, } | { "method": "experimentalFeature/list", id: RequestId, params: ExperimentalFeatureListParams, } | { "method": "mcpServer/oauth/login", id: RequestId, params: McpServerOauthLoginParams, } | { "method": "config/mcpServer/reload", id: RequestId, params: undefined, } | { "method": "mcpServerStatus/list", id: RequestId, params: ListMcpServerStatusParams, } | { "method": "windowsSandbox/setupStart", id: RequestId, params: WindowsSandboxSetupStartParams, } | { "method": "account/login/start", id: RequestId, params: LoginAccountParams, } | { "method": "account/login/cancel", id: RequestId, params: CancelLoginAccountParams, } | { "method": "account/logout", id: RequestId, params: undefined, } | { "method": "account/rateLimits/read", id: RequestId, params: undefined, } | { "method": "feedback/upload", id: RequestId, params: FeedbackUploadParams, } | { "method": "command/exec", id: RequestId, params: CommandExecParams, } | { "method": "config/read", id: RequestId, params: ConfigReadParams, } | { "method": "externalAgentConfig/detect", id: RequestId, params: ExternalAgentConfigDetectParams, } | { "method": "externalAgentConfig/import", id: RequestId, params: ExternalAgentConfigImportParams, } | { "method": "config/value/write", id: RequestId, params: ConfigValueWriteParams, } | { "method": "config/batchWrite", id: RequestId, params: ConfigBatchWriteParams, } | { "method": "configRequirements/read", id: RequestId, params: undefined, } | { "method": "account/read", id: RequestId, params: GetAccountParams, } | { "method": "getConversationSummary", id: RequestId, params: GetConversationSummaryParams, } | { "method": "gitDiffToRemote", id: RequestId, params: GitDiffToRemoteParams, } | { "method": "getAuthStatus", id: RequestId, params: GetAuthStatusParams, } | { "method": "fuzzyFileSearch", id: RequestId, params: FuzzyFileSearchParams, };
export type ClientRequest ={ "method": "initialize", id: RequestId, params: InitializeParams, } | { "method": "thread/start", id: RequestId, params: ThreadStartParams, } | { "method": "thread/resume", id: RequestId, params: ThreadResumeParams, } | { "method": "thread/fork", id: RequestId, params: ThreadForkParams, } | { "method": "thread/archive", id: RequestId, params: ThreadArchiveParams, } | { "method": "thread/unsubscribe", id: RequestId, params: ThreadUnsubscribeParams, } | { "method": "thread/name/set", id: RequestId, params: ThreadSetNameParams, } | { "method": "thread/metadata/update", id: RequestId, params: ThreadMetadataUpdateParams, } | { "method": "thread/unarchive", id: RequestId, params: ThreadUnarchiveParams, } | { "method": "thread/compact/start", id: RequestId, params: ThreadCompactStartParams, } | { "method": "thread/rollback", id: RequestId, params: ThreadRollbackParams, } | { "method": "thread/list", id: RequestId, params: ThreadListParams, } | { "method": "thread/loaded/list", id: RequestId, params: ThreadLoadedListParams, } | { "method": "thread/read", id: RequestId, params: ThreadReadParams, } | { "method": "skills/list", id: RequestId, params: SkillsListParams, } | { "method": "plugin/list", id: RequestId, params: PluginListParams, } | { "method": "skills/remote/list", id: RequestId, params: SkillsRemoteReadParams, } | { "method": "skills/remote/export", id: RequestId, params: SkillsRemoteWriteParams, } | { "method": "app/list", id: RequestId, params: AppsListParams, } | { "method": "skills/config/write", id: RequestId, params: SkillsConfigWriteParams, } | { "method": "plugin/install", id: RequestId, params: PluginInstallParams, } | { "method": "turn/start", id: RequestId, params: TurnStartParams, } | { "method": "turn/steer", id: RequestId, params: TurnSteerParams, } | { "method": "turn/interrupt", id: RequestId, params: TurnInterruptParams, } | { "method": "review/start", id: RequestId, params: ReviewStartParams, } | { "method": "model/list", id: RequestId, params: ModelListParams, } | { "method": "experimentalFeature/list", id: RequestId, params: ExperimentalFeatureListParams, } | { "method": "mcpServer/oauth/login", id: RequestId, params: McpServerOauthLoginParams, } | { "method": "config/mcpServer/reload", id: RequestId, params: undefined, } | { "method": "mcpServerStatus/list", id: RequestId, params: ListMcpServerStatusParams, } | { "method": "windowsSandbox/setupStart", id: RequestId, params: WindowsSandboxSetupStartParams, } | { "method": "account/login/start", id: RequestId, params: LoginAccountParams, } | { "method": "account/login/cancel", id: RequestId, params: CancelLoginAccountParams, } | { "method": "account/logout", id: RequestId, params: undefined, } | { "method": "account/rateLimits/read", id: RequestId, params: undefined, } | { "method": "feedback/upload", id: RequestId, params: FeedbackUploadParams, } | { "method": "command/exec", id: RequestId, params: CommandExecParams, } | { "method": "config/read", id: RequestId, params: ConfigReadParams, } | { "method": "externalAgentConfig/detect", id: RequestId, params: ExternalAgentConfigDetectParams, } | { "method": "externalAgentConfig/import", id: RequestId, params: ExternalAgentConfigImportParams, } | { "method": "config/value/write", id: RequestId, params: ConfigValueWriteParams, } | { "method": "config/batchWrite", id: RequestId, params: ConfigBatchWriteParams, } | { "method": "configRequirements/read", id: RequestId, params: undefined, } | { "method": "account/read", id: RequestId, params: GetAccountParams, } | { "method": "getConversationSummary", id: RequestId, params: GetConversationSummaryParams, } | { "method": "gitDiffToRemote", id: RequestId, params: GitDiffToRemoteParams, } | { "method": "getAuthStatus", id: RequestId, params: GetAuthStatusParams, } | { "method": "fuzzyFileSearch", id: RequestId, params: FuzzyFileSearchParams, };
@@ -1,5 +1,6 @@
// 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.
import type { AbsolutePathBuf } from "../AbsolutePathBuf";
export type PluginInstallParams = { marketplaceName: string, pluginName: string, cwd?: string | null, };
export type PluginInstallParams = { marketplacePath: AbsolutePathBuf, pluginName: string, };
@@ -0,0 +1,11 @@
// 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.
import type { AbsolutePathBuf } from "../AbsolutePathBuf";
export type PluginListParams = {
/**
* Optional working directories used to discover repo marketplaces. When omitted,
* only home-scoped marketplaces are considered.
*/
cwds?: Array<AbsolutePathBuf> | null, };
@@ -0,0 +1,6 @@
// 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.
import type { PluginMarketplaceEntry } from "./PluginMarketplaceEntry";
export type PluginListResponse = { marketplaces: Array<PluginMarketplaceEntry>, };
@@ -0,0 +1,6 @@
// 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.
import type { PluginSummary } from "./PluginSummary";
export type PluginMarketplaceEntry = { name: string, path: string, plugins: Array<PluginSummary>, };
@@ -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 PluginSource = { "type": "local", path: string, };
@@ -0,0 +1,6 @@
// 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.
import type { PluginSource } from "./PluginSource";
export type PluginSummary = { name: string, source: PluginSource, enabled: boolean, };
@@ -129,6 +129,11 @@ export type { PatchChangeKind } from "./PatchChangeKind";
export type { PlanDeltaNotification } from "./PlanDeltaNotification";
export type { PluginInstallParams } from "./PluginInstallParams";
export type { PluginInstallResponse } from "./PluginInstallResponse";
export type { PluginListParams } from "./PluginListParams";
export type { PluginListResponse } from "./PluginListResponse";
export type { PluginMarketplaceEntry } from "./PluginMarketplaceEntry";
export type { PluginSource } from "./PluginSource";
export type { PluginSummary } from "./PluginSummary";
export type { ProductSurface } from "./ProductSurface";
export type { ProfileV2 } from "./ProfileV2";
export type { RateLimitSnapshot } from "./RateLimitSnapshot";
@@ -248,6 +248,10 @@ client_request_definitions! {
params: v2::SkillsListParams,
response: v2::SkillsListResponse,
},
PluginList => "plugin/list" {
params: v2::PluginListParams,
response: v2::PluginListResponse,
},
SkillsRemoteList => "skills/remote/list" {
params: v2::SkillsRemoteReadParams,
response: v2::SkillsRemoteReadResponse,
@@ -2371,6 +2371,23 @@ pub struct SkillsListResponse {
pub data: Vec<SkillsListEntry>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct PluginListParams {
/// Optional working directories used to discover repo marketplaces. When omitted,
/// only home-scoped marketplaces are considered.
#[ts(optional = nullable)]
pub cwds: Option<Vec<AbsolutePathBuf>>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct PluginListResponse {
pub marketplaces: Vec<PluginMarketplaceEntry>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
@@ -2534,6 +2551,34 @@ pub struct SkillsListEntry {
pub errors: Vec<SkillErrorInfo>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct PluginMarketplaceEntry {
pub name: String,
pub path: PathBuf,
pub plugins: Vec<PluginSummary>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct PluginSummary {
pub name: String,
pub source: PluginSource,
pub enabled: bool,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(tag = "type", rename_all = "camelCase")]
#[ts(tag = "type")]
#[ts(export_to = "v2/")]
pub enum PluginSource {
#[serde(rename_all = "camelCase")]
#[ts(rename_all = "camelCase")]
Local { path: PathBuf },
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
@@ -2553,10 +2598,8 @@ pub struct SkillsConfigWriteResponse {
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct PluginInstallParams {
pub marketplace_name: String,
pub marketplace_path: AbsolutePathBuf,
pub plugin_name: String,
#[ts(optional = nullable)]
pub cwd: Option<PathBuf>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]