Also accept null as description, not just missing (#5589)

This commit is contained in:
Jack Amadeo
2025-11-05 22:33:38 -05:00
committed by GitHub
parent 9086438fca
commit f7e2bcd98a
+83 -4
View File
@@ -11,6 +11,7 @@ use once_cell::sync::Lazy;
use rmcp::model::Tool; use rmcp::model::Tool;
use rmcp::service::ClientInitializeError; use rmcp::service::ClientInitializeError;
use rmcp::ServiceError as ClientError; use rmcp::ServiceError as ClientError;
use serde::Deserializer;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use thiserror::Error; use thiserror::Error;
use tracing::warn; use tracing::warn;
@@ -216,6 +217,7 @@ pub enum ExtensionConfig {
/// The name used to identify this extension /// The name used to identify this extension
name: String, name: String,
#[serde(default)] #[serde(default)]
#[serde(deserialize_with = "deserialize_null_with_default")]
#[schema(required)] #[schema(required)]
description: String, description: String,
uri: String, uri: String,
@@ -237,6 +239,7 @@ pub enum ExtensionConfig {
/// The name used to identify this extension /// The name used to identify this extension
name: String, name: String,
#[serde(default)] #[serde(default)]
#[serde(deserialize_with = "deserialize_null_with_default")]
#[schema(required)] #[schema(required)]
description: String, description: String,
cmd: String, cmd: String,
@@ -257,6 +260,7 @@ pub enum ExtensionConfig {
/// The name used to identify this extension /// The name used to identify this extension
name: String, name: String,
#[serde(default)] #[serde(default)]
#[serde(deserialize_with = "deserialize_null_with_default")]
#[schema(required)] #[schema(required)]
description: String, description: String,
display_name: Option<String>, // needed for the UI display_name: Option<String>, // needed for the UI
@@ -271,7 +275,7 @@ pub enum ExtensionConfig {
Platform { Platform {
/// The name used to identify this extension /// The name used to identify this extension
name: String, name: String,
#[serde(default)] #[serde(deserialize_with = "deserialize_null_with_default")]
#[schema(required)] #[schema(required)]
description: String, description: String,
#[serde(default)] #[serde(default)]
@@ -284,7 +288,7 @@ pub enum ExtensionConfig {
StreamableHttp { StreamableHttp {
/// The name used to identify this extension /// The name used to identify this extension
name: String, name: String,
#[serde(default)] #[serde(deserialize_with = "deserialize_null_with_default")]
#[schema(required)] #[schema(required)]
description: String, description: String,
uri: String, uri: String,
@@ -307,7 +311,7 @@ pub enum ExtensionConfig {
Frontend { Frontend {
/// The name used to identify this extension /// The name used to identify this extension
name: String, name: String,
#[serde(default)] #[serde(deserialize_with = "deserialize_null_with_default")]
#[schema(required)] #[schema(required)]
description: String, description: String,
/// The tools provided by the frontend /// The tools provided by the frontend
@@ -324,7 +328,7 @@ pub enum ExtensionConfig {
InlinePython { InlinePython {
/// The name used to identify this extension /// The name used to identify this extension
name: String, name: String,
#[serde(default)] #[serde(deserialize_with = "deserialize_null_with_default")]
#[schema(required)] #[schema(required)]
description: String, description: String,
/// The Python code to execute /// The Python code to execute
@@ -544,6 +548,15 @@ impl ExtensionInfo {
} }
} }
fn deserialize_null_with_default<'de, D, T>(deserializer: D) -> Result<T, D::Error>
where
T: Default + Deserialize<'de>,
D: Deserializer<'de>,
{
let opt = Option::deserialize(deserializer)?;
Ok(opt.unwrap_or_default())
}
/// Information about the tool used for building prompts /// Information about the tool used for building prompts
#[derive(Clone, Debug, Serialize, ToSchema)] #[derive(Clone, Debug, Serialize, ToSchema)]
pub struct ToolInfo { pub struct ToolInfo {
@@ -568,3 +581,69 @@ impl ToolInfo {
} }
} }
} }
#[cfg(test)]
mod tests {
use crate::agents::*;
#[test]
fn test_deserialize_missing_description() {
let config: ExtensionConfig = serde_yaml::from_str(
"enabled: true
type: builtin
name: developer
display_name: Developer
timeout: 300
bundled: true
available_tools: []",
)
.unwrap();
if let ExtensionConfig::Builtin { description, .. } = config {
assert_eq!(description, "")
} else {
panic!("unexpected result of deserialization: {}", config)
}
}
#[test]
fn test_deserialize_null_description() {
let config: ExtensionConfig = serde_yaml::from_str(
"enabled: true
type: builtin
name: developer
display_name: Developer
description: null
timeout: 300
bundled: true
available_tools: []
",
)
.unwrap();
if let ExtensionConfig::Builtin { description, .. } = config {
assert_eq!(description, "")
} else {
panic!("unexpected result of deserialization: {}", config)
}
}
#[test]
fn test_deserialize_normal_description() {
let config: ExtensionConfig = serde_yaml::from_str(
"enabled: true
type: builtin
name: developer
display_name: Developer
description: description goes here
timeout: 300
bundled: true
available_tools: []
",
)
.unwrap();
if let ExtensionConfig::Builtin { description, .. } = config {
assert_eq!(description, "description goes here")
} else {
panic!("unexpected result of deserialization: {}", config)
}
}
}