diff --git a/clients/dotnet/src/AgentHostProtocol.Abstractions/Generated/Actions.generated.cs b/clients/dotnet/src/AgentHostProtocol.Abstractions/Generated/Actions.generated.cs
index f0617a73c..9426d52c4 100644
--- a/clients/dotnet/src/AgentHostProtocol.Abstractions/Generated/Actions.generated.cs
+++ b/clients/dotnet/src/AgentHostProtocol.Abstractions/Generated/Actions.generated.cs
@@ -2396,7 +2396,9 @@ public sealed record AnnotationsUpdatedAction
///
/// This side-effect request leaves optimistic catalogue state unchanged. The
/// host validates trigger ids and configuration, normalizes event-trigger
-/// titles and descriptions, persists the definition, then publishes the
+/// titles and descriptions, captures any
+/// {@link AutomationSessionTemplate.customizations | session customizations}
+/// from the dispatching client, persists the definition, then publishes the
/// authoritative result with {@link AutomationSetAction | `automation/set`}.
/// Rejections leave the catalogue unchanged.
public sealed record AutomationCreateRequestedAction
diff --git a/clients/dotnet/src/AgentHostProtocol.Abstractions/Generated/Commands.generated.cs b/clients/dotnet/src/AgentHostProtocol.Abstractions/Generated/Commands.generated.cs
index 0ef02d8be..72a9e4361 100644
--- a/clients/dotnet/src/AgentHostProtocol.Abstractions/Generated/Commands.generated.cs
+++ b/clients/dotnet/src/AgentHostProtocol.Abstractions/Generated/Commands.generated.cs
@@ -300,6 +300,11 @@ public sealed record AutomationCapabilities
/// implementation-defined.
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public long? RunHistoryLimit { get; init; }
+
+ /// Present when {@link AutomationSessionTemplate.customizations} may contain
+ /// client plugins for the host to capture.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ public AutomationCustomizationsCapability? Customizations { get; init; }
}
/// Presence capability for {@link AutomationCreateRequestedAction |
@@ -333,6 +338,15 @@ public sealed record AutomationRunCancellationCapability
{
}
+/// Presence capability for
+/// {@link AutomationSessionTemplate.customizations | automation customizations}.
+///
+/// The empty object means "supported"; fields are reserved for future
+/// capture options and limits.
+public sealed record AutomationCustomizationsCapability
+{
+}
+
/// Re-establishes a dropped connection. The server replays missed actions or
/// provides fresh snapshots.
public sealed record ReconnectParams
diff --git a/clients/dotnet/src/AgentHostProtocol.Abstractions/Generated/JsonSerializerContext.generated.cs b/clients/dotnet/src/AgentHostProtocol.Abstractions/Generated/JsonSerializerContext.generated.cs
index 0640c712e..4b0b296cc 100644
--- a/clients/dotnet/src/AgentHostProtocol.Abstractions/Generated/JsonSerializerContext.generated.cs
+++ b/clients/dotnet/src/AgentHostProtocol.Abstractions/Generated/JsonSerializerContext.generated.cs
@@ -36,6 +36,7 @@ namespace Microsoft.AgentHostProtocol;
[JsonSerializable(typeof(AutomationCompletedRunLifecycle))]
[JsonSerializable(typeof(AutomationCreateCapability))]
[JsonSerializable(typeof(AutomationCreateRequestedAction))]
+[JsonSerializable(typeof(AutomationCustomizationsCapability))]
[JsonSerializable(typeof(AutomationDefinition))]
[JsonSerializable(typeof(AutomationDefinitionPatch))]
[JsonSerializable(typeof(AutomationEntry))]
diff --git a/clients/dotnet/src/AgentHostProtocol.Abstractions/Generated/State.generated.cs b/clients/dotnet/src/AgentHostProtocol.Abstractions/Generated/State.generated.cs
index 9590319ea..8b14d7302 100644
--- a/clients/dotnet/src/AgentHostProtocol.Abstractions/Generated/State.generated.cs
+++ b/clients/dotnet/src/AgentHostProtocol.Abstractions/Generated/State.generated.cs
@@ -5228,6 +5228,32 @@ public sealed record AutomationSessionTemplate
/// {@link ResolveSessionConfigResult.values}.
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public Dictionary? Config { get; init; }
+
+ /// Client plugins to make available in every run session, in the same
+ /// published shape as
+ /// {@link SessionActiveClient.customizations | `activeClients[].customizations`}.
+ /// Entries are keyed by `id`.
+ ///
+ /// Runs usually start when no client is connected, so the host does not
+ /// resolve these URIs at run time. Instead, when it accepts a
+ /// {@link AutomationCreateRequestedAction | `automation/createRequested`} or
+ /// {@link AutomationUpdateRequestedAction | `automation/updateRequested`}
+ /// that adds an entry or changes an entry's `uri` or `nonce`, the host
+ /// captures a host-owned copy of the plugin. For client-served URIs such as
+ /// `virtual://…`, it reads the contents from the dispatching client with
+ /// server→client `resource*` requests. If a capture fails, the host rejects
+ /// the whole action. Entries whose `id`, `uri`, and `nonce` are unchanged keep
+ /// their existing copy, so any client can re-submit a template it received
+ /// without being able to serve the plugin itself. The resulting copies are
+ /// reported in {@link AutomationEntry.customizations}.
+ ///
+ /// The host MAY share one stored copy between entries with equal `uri` and
+ /// `nonce`, including across automations; this is not observable to clients.
+ ///
+ /// Clients MUST NOT set this field unless the host advertises
+ /// {@link AutomationCapabilities.customizations}.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ public List? Customizations { get; init; }
}
/// Durable, client-editable definition of an automation.
@@ -5277,7 +5303,9 @@ public sealed record AutomationDefinitionPatch
public Message? Message { get; init; }
/// Replacement {@link AutomationDefinition.session}. The host revalidates
- /// affected event triggers when their discovery context changes.
+ /// affected event triggers when their discovery context changes, and
+ /// captures {@link AutomationSessionTemplate.customizations} entries that
+ /// are new or whose `uri` or `nonce` changed.
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public AutomationSessionTemplate? Session { get; init; }
@@ -5325,6 +5353,22 @@ public sealed class AutomationEntry
/// Operations currently permitted for this automation.
public required List Operations { get; set; }
+ /// Host-owned copies of the plugins in
+ /// {@link AutomationSessionTemplate.customizations}, one per template entry
+ /// with the same `id`. Absent when the template has no customizations.
+ ///
+ /// Each copy's `uri` identifies the captured contents, which clients can
+ /// browse with `resourceRead`. `children` and `load` report what the host
+ /// found in that copy, independent of whether the originating client is
+ /// connected. `clientId` is absent because the copy no longer depends on a
+ /// client.
+ ///
+ /// Every run session receives these plugins in
+ /// {@link SessionState.customizations}, with the enablement from the
+ /// matching template entry.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ public List? Customizations { get; set; }
+
/// Creation timestamp in ISO 8601 format.
public required string CreatedAt { get; set; }
diff --git a/clients/go/ahptypes/actions.generated.go b/clients/go/ahptypes/actions.generated.go
index ea5e6a569..163db0ca9 100644
--- a/clients/go/ahptypes/actions.generated.go
+++ b/clients/go/ahptypes/actions.generated.go
@@ -1573,7 +1573,9 @@ type ResourceWatchChangedAction struct {
//
// This side-effect request leaves optimistic catalogue state unchanged. The
// host validates trigger ids and configuration, normalizes event-trigger
-// titles and descriptions, persists the definition, then publishes the
+// titles and descriptions, captures any
+// {@link AutomationSessionTemplate.customizations | session customizations}
+// from the dispatching client, persists the definition, then publishes the
// authoritative result with {@link AutomationSetAction | `automation/set`}.
// Rejections leave the catalogue unchanged.
type AutomationCreateRequestedAction struct {
diff --git a/clients/go/ahptypes/commands.generated.go b/clients/go/ahptypes/commands.generated.go
index 9ef81b6f8..4b695fc1e 100644
--- a/clients/go/ahptypes/commands.generated.go
+++ b/clients/go/ahptypes/commands.generated.go
@@ -223,6 +223,9 @@ type AutomationCapabilities struct {
// runs are not counted toward the limit. Absence means the retention limit is
// implementation-defined.
RunHistoryLimit *int64 `json:"runHistoryLimit,omitempty"`
+ // Present when {@link AutomationSessionTemplate.customizations} may contain
+ // client plugins for the host to capture.
+ Customizations *AutomationCustomizationsCapability `json:"customizations,omitempty"`
}
// Presence capability for {@link AutomationCreateRequestedAction |
@@ -252,6 +255,14 @@ type AutomationScheduleCapabilities struct {
type AutomationRunCancellationCapability struct {
}
+// Presence capability for
+// {@link AutomationSessionTemplate.customizations | automation customizations}.
+//
+// The empty object means "supported"; fields are reserved for future
+// capture options and limits.
+type AutomationCustomizationsCapability struct {
+}
+
// Identifies a protocol implementation — the software (and build) on one end
// of the connection, as distinct from the {@link AgentInfo | agent persona} it
// hosts. Carried as {@link InitializeParams.clientInfo | `clientInfo`} on the
diff --git a/clients/go/ahptypes/state.generated.go b/clients/go/ahptypes/state.generated.go
index 4c002cade..a4f280886 100644
--- a/clients/go/ahptypes/state.generated.go
+++ b/clients/go/ahptypes/state.generated.go
@@ -3939,6 +3939,30 @@ type AutomationSessionTemplate struct {
// {@link CreateSessionParams.config}, normally obtained from
// {@link ResolveSessionConfigResult.values}.
Config map[string]json.RawMessage `json:"config,omitempty"`
+ // Client plugins to make available in every run session, in the same
+ // published shape as
+ // {@link SessionActiveClient.customizations | `activeClients[].customizations`}.
+ // Entries are keyed by `id`.
+ //
+ // Runs usually start when no client is connected, so the host does not
+ // resolve these URIs at run time. Instead, when it accepts a
+ // {@link AutomationCreateRequestedAction | `automation/createRequested`} or
+ // {@link AutomationUpdateRequestedAction | `automation/updateRequested`}
+ // that adds an entry or changes an entry's `uri` or `nonce`, the host
+ // captures a host-owned copy of the plugin. For client-served URIs such as
+ // `virtual://…`, it reads the contents from the dispatching client with
+ // server→client `resource*` requests. If a capture fails, the host rejects
+ // the whole action. Entries whose `id`, `uri`, and `nonce` are unchanged keep
+ // their existing copy, so any client can re-submit a template it received
+ // without being able to serve the plugin itself. The resulting copies are
+ // reported in {@link AutomationEntry.customizations}.
+ //
+ // The host MAY share one stored copy between entries with equal `uri` and
+ // `nonce`, including across automations; this is not observable to clients.
+ //
+ // Clients MUST NOT set this field unless the host advertises
+ // {@link AutomationCapabilities.customizations}.
+ Customizations []ClientPluginCustomization `json:"customizations,omitempty"`
}
// Durable, client-editable definition of an automation.
@@ -3975,7 +3999,9 @@ type AutomationDefinitionPatch struct {
// Replacement {@link AutomationDefinition.message}.
Message *Message `json:"message,omitempty"`
// Replacement {@link AutomationDefinition.session}. The host revalidates
- // affected event triggers when their discovery context changes.
+ // affected event triggers when their discovery context changes, and
+ // captures {@link AutomationSessionTemplate.customizations} entries that
+ // are new or whose `uri` or `nonce` changed.
Session *AutomationSessionTemplate `json:"session,omitempty"`
// Replacement {@link AutomationDefinition.enabled}.
Enabled *bool `json:"enabled,omitempty"`
@@ -4006,6 +4032,20 @@ type AutomationEntry struct {
RunsNextCursor *string `json:"runsNextCursor,omitempty"`
// Operations currently permitted for this automation.
Operations []AutomationOperation `json:"operations"`
+ // Host-owned copies of the plugins in
+ // {@link AutomationSessionTemplate.customizations}, one per template entry
+ // with the same `id`. Absent when the template has no customizations.
+ //
+ // Each copy's `uri` identifies the captured contents, which clients can
+ // browse with `resourceRead`. `children` and `load` report what the host
+ // found in that copy, independent of whether the originating client is
+ // connected. `clientId` is absent because the copy no longer depends on a
+ // client.
+ //
+ // Every run session receives these plugins in
+ // {@link SessionState.customizations}, with the enablement from the
+ // matching template entry.
+ Customizations []PluginCustomization `json:"customizations,omitempty"`
// Creation timestamp in ISO 8601 format.
CreatedAt string `json:"createdAt"`
// Last definition modification timestamp in ISO 8601 format.
diff --git a/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Commands.generated.kt b/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Commands.generated.kt
index 01fb0e15e..814c54e70 100644
--- a/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Commands.generated.kt
+++ b/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/Commands.generated.kt
@@ -431,7 +431,12 @@ data class AutomationCapabilities(
* runs are not counted toward the limit. Absence means the retention limit is
* implementation-defined.
*/
- val runHistoryLimit: Long? = null
+ val runHistoryLimit: Long? = null,
+ /**
+ * Present when {@link AutomationSessionTemplate.customizations} may contain
+ * client plugins for the host to capture.
+ */
+ val customizations: AutomationCustomizationsCapability? = null
)
@Serializable
@@ -450,6 +455,9 @@ data class AutomationScheduleCapabilities(
@Serializable
class AutomationRunCancellationCapability
+@Serializable
+class AutomationCustomizationsCapability
+
@Serializable
data class Implementation(
/**
diff --git a/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/State.generated.kt b/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/State.generated.kt
index 6b4d69ced..7fb225c0b 100644
--- a/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/State.generated.kt
+++ b/clients/kotlin/src/main/kotlin/com/microsoft/agenthostprotocol/generated/State.generated.kt
@@ -5385,7 +5385,33 @@ data class AutomationSessionTemplate(
* {@link CreateSessionParams.config}, normally obtained from
* {@link ResolveSessionConfigResult.values}.
*/
- val config: Map? = null
+ val config: Map? = null,
+ /**
+ * Client plugins to make available in every run session, in the same
+ * published shape as
+ * {@link SessionActiveClient.customizations | `activeClients[].customizations`}.
+ * Entries are keyed by `id`.
+ *
+ * Runs usually start when no client is connected, so the host does not
+ * resolve these URIs at run time. Instead, when it accepts a
+ * {@link AutomationCreateRequestedAction | `automation/createRequested`} or
+ * {@link AutomationUpdateRequestedAction | `automation/updateRequested`}
+ * that adds an entry or changes an entry's `uri` or `nonce`, the host
+ * captures a host-owned copy of the plugin. For client-served URIs such as
+ * `virtual://…`, it reads the contents from the dispatching client with
+ * server→client `resource*` requests. If a capture fails, the host rejects
+ * the whole action. Entries whose `id`, `uri`, and `nonce` are unchanged keep
+ * their existing copy, so any client can re-submit a template it received
+ * without being able to serve the plugin itself. The resulting copies are
+ * reported in {@link AutomationEntry.customizations}.
+ *
+ * The host MAY share one stored copy between entries with equal `uri` and
+ * `nonce`, including across automations; this is not observable to clients.
+ *
+ * Clients MUST NOT set this field unless the host advertises
+ * {@link AutomationCapabilities.customizations}.
+ */
+ val customizations: List? = null
)
@Serializable
@@ -5432,7 +5458,9 @@ data class AutomationDefinitionPatch(
val message: Message? = null,
/**
* Replacement {@link AutomationDefinition.session}. The host revalidates
- * affected event triggers when their discovery context changes.
+ * affected event triggers when their discovery context changes, and
+ * captures {@link AutomationSessionTemplate.customizations} entries that
+ * are new or whose `uri` or `nonce` changed.
*/
val session: AutomationSessionTemplate? = null,
/**
@@ -5479,6 +5507,22 @@ data class AutomationEntry(
* Operations currently permitted for this automation.
*/
val operations: List,
+ /**
+ * Host-owned copies of the plugins in
+ * {@link AutomationSessionTemplate.customizations}, one per template entry
+ * with the same `id`. Absent when the template has no customizations.
+ *
+ * Each copy's `uri` identifies the captured contents, which clients can
+ * browse with `resourceRead`. `children` and `load` report what the host
+ * found in that copy, independent of whether the originating client is
+ * connected. `clientId` is absent because the copy no longer depends on a
+ * client.
+ *
+ * Every run session receives these plugins in
+ * {@link SessionState.customizations}, with the enablement from the
+ * matching template entry.
+ */
+ val customizations: List? = null,
/**
* Creation timestamp in ISO 8601 format.
*/
diff --git a/clients/rust/crates/ahp-types/src/actions.rs b/clients/rust/crates/ahp-types/src/actions.rs
index 0821be697..b4f7703c4 100644
--- a/clients/rust/crates/ahp-types/src/actions.rs
+++ b/clients/rust/crates/ahp-types/src/actions.rs
@@ -2066,7 +2066,9 @@ pub struct ResourceWatchChangedAction {
///
/// This side-effect request leaves optimistic catalogue state unchanged. The
/// host validates trigger ids and configuration, normalizes event-trigger
-/// titles and descriptions, persists the definition, then publishes the
+/// titles and descriptions, captures any
+/// {@link AutomationSessionTemplate.customizations | session customizations}
+/// from the dispatching client, persists the definition, then publishes the
/// authoritative result with {@link AutomationSetAction | `automation/set`}.
/// Rejections leave the catalogue unchanged.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
diff --git a/clients/rust/crates/ahp-types/src/commands.rs b/clients/rust/crates/ahp-types/src/commands.rs
index e3dfe255e..e2af670a7 100644
--- a/clients/rust/crates/ahp-types/src/commands.rs
+++ b/clients/rust/crates/ahp-types/src/commands.rs
@@ -344,6 +344,10 @@ pub struct AutomationCapabilities {
/// implementation-defined.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub run_history_limit: Option,
+ /// Present when {@link AutomationSessionTemplate.customizations} may contain
+ /// client plugins for the host to capture.
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ pub customizations: Option,
}
/// Presence capability for {@link AutomationCreateRequestedAction |
@@ -378,6 +382,15 @@ pub struct AutomationScheduleCapabilities {
#[serde(rename_all = "camelCase")]
pub struct AutomationRunCancellationCapability {}
+/// Presence capability for
+/// {@link AutomationSessionTemplate.customizations | automation customizations}.
+///
+/// The empty object means "supported"; fields are reserved for future
+/// capture options and limits.
+#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct AutomationCustomizationsCapability {}
+
/// Identifies a protocol implementation — the software (and build) on one end
/// of the connection, as distinct from the {@link AgentInfo | agent persona} it
/// hosts. Carried as {@link InitializeParams.clientInfo | `clientInfo`} on the
diff --git a/clients/rust/crates/ahp-types/src/state.rs b/clients/rust/crates/ahp-types/src/state.rs
index 3af369953..0c891c782 100644
--- a/clients/rust/crates/ahp-types/src/state.rs
+++ b/clients/rust/crates/ahp-types/src/state.rs
@@ -3979,6 +3979,7 @@ pub struct ClientPluginCustomization {
/// nothing.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub children: Option>,
+ pub r#type: CustomizationType,
/// Explicit enablement decisions. See {@link McpServerCustomization.enablement}.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub enablement: Option>,
@@ -5465,6 +5466,31 @@ pub struct AutomationSessionTemplate {
/// {@link ResolveSessionConfigResult.values}.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub config: Option,
+ /// Client plugins to make available in every run session, in the same
+ /// published shape as
+ /// {@link SessionActiveClient.customizations | `activeClients[].customizations`}.
+ /// Entries are keyed by `id`.
+ ///
+ /// Runs usually start when no client is connected, so the host does not
+ /// resolve these URIs at run time. Instead, when it accepts a
+ /// {@link AutomationCreateRequestedAction | `automation/createRequested`} or
+ /// {@link AutomationUpdateRequestedAction | `automation/updateRequested`}
+ /// that adds an entry or changes an entry's `uri` or `nonce`, the host
+ /// captures a host-owned copy of the plugin. For client-served URIs such as
+ /// `virtual://…`, it reads the contents from the dispatching client with
+ /// server→client `resource*` requests. If a capture fails, the host rejects
+ /// the whole action. Entries whose `id`, `uri`, and `nonce` are unchanged keep
+ /// their existing copy, so any client can re-submit a template it received
+ /// without being able to serve the plugin itself. The resulting copies are
+ /// reported in {@link AutomationEntry.customizations}.
+ ///
+ /// The host MAY share one stored copy between entries with equal `uri` and
+ /// `nonce`, including across automations; this is not observable to clients.
+ ///
+ /// Clients MUST NOT set this field unless the host advertises
+ /// {@link AutomationCapabilities.customizations}.
+ #[serde(default, skip_serializing_if = "Option::is_none")]
+ pub customizations: Option>,
}
/// Durable, client-editable definition of an automation.
@@ -5508,7 +5534,9 @@ pub struct AutomationDefinitionPatch {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub message: Option,
/// Replacement {@link AutomationDefinition.session}. The host revalidates
- /// affected event triggers when their discovery context changes.
+ /// affected event triggers when their discovery context changes, and
+ /// captures {@link AutomationSessionTemplate.customizations} entries that
+ /// are new or whose `uri` or `nonce` changed.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub session: Option,
/// Replacement {@link AutomationDefinition.enabled}.
@@ -5547,6 +5575,26 @@ pub struct AutomationEntry {
pub runs_next_cursor: Option,
/// Operations currently permitted for this automation.
pub operations: Vec,
+ /// Host-owned copies of the plugins in
+ /// {@link AutomationSessionTemplate.customizations}, one per template entry
+ /// with the same `id`. Absent when the template has no customizations.
+ ///
+ /// Each copy's `uri` identifies the captured contents, which clients can
+ /// browse with `resourceRead`. `children` and `load` report what the host
+ /// found in that copy, independent of whether the originating client is
+ /// connected. `clientId` is absent because the copy no longer depends on a
+ /// client.
+ ///
+ /// Every run session receives these plugins in
+ /// {@link SessionState.customizations}, with the enablement from the
+ /// matching template entry.
+ #[serde(
+ default,
+ skip_serializing_if = "Option::is_none",
+ serialize_with = "serialize_plugin_customizations",
+ deserialize_with = "deserialize_plugin_customizations"
+ )]
+ pub customizations: Option>,
/// Creation timestamp in ISO 8601 format.
pub created_at: String,
/// Last definition modification timestamp in ISO 8601 format.
@@ -5556,6 +5604,56 @@ pub struct AutomationEntry {
pub meta: Option,
}
+fn serialize_plugin_customizations(
+ value: &Option>,
+ serializer: S,
+) -> Result
+where
+ S: serde::Serializer,
+{
+ let Some(items) = value else {
+ return serializer.serialize_none();
+ };
+ let mut out = Vec::with_capacity(items.len());
+ for item in items {
+ let mut raw = serde_json::to_value(item).map_err(serde::ser::Error::custom)?;
+ let serde_json::Value::Object(object) = &mut raw else {
+ return Err(serde::ser::Error::custom(
+ "plugin customization must serialize to an object",
+ ));
+ };
+ object.insert(
+ "type".to_owned(),
+ serde_json::Value::String("plugin".to_owned()),
+ );
+ out.push(raw);
+ }
+ serde::Serialize::serialize(&out, serializer)
+}
+
+fn deserialize_plugin_customizations<'de, D>(
+ deserializer: D,
+) -> Result