feat (acp): exposed available tools in acp schema (#10097)
This commit is contained in:
@@ -331,6 +331,9 @@ pub enum GooseExtension {
|
||||
timeout: Option<u64>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
bundled: Option<bool>,
|
||||
/// Tool allowlist for this extension. Omit this field to allow all tools.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
available_tools: Option<Vec<String>>,
|
||||
},
|
||||
Platform {
|
||||
name: String,
|
||||
@@ -340,6 +343,9 @@ pub enum GooseExtension {
|
||||
display_name: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
bundled: Option<bool>,
|
||||
/// Tool allowlist for this extension. Omit this field to allow all tools.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
available_tools: Option<Vec<String>>,
|
||||
},
|
||||
Mcp {
|
||||
server: McpServer,
|
||||
@@ -353,6 +359,9 @@ pub enum GooseExtension {
|
||||
socket: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
bundled: Option<bool>,
|
||||
/// Tool allowlist for this extension. Omit this field to allow all tools.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
available_tools: Option<Vec<String>>,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -364,6 +373,7 @@ impl Default for GooseExtension {
|
||||
display_name: None,
|
||||
timeout: None,
|
||||
bundled: None,
|
||||
available_tools: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,6 +171,9 @@ pub enum RecipeExtensionDto {
|
||||
timeout: Option<u64>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
bundled: Option<bool>,
|
||||
/// Tool allowlist for this extension. Omit this field to allow all tools.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
available_tools: Option<Vec<String>>,
|
||||
},
|
||||
Platform {
|
||||
name: String,
|
||||
@@ -180,6 +183,9 @@ pub enum RecipeExtensionDto {
|
||||
display_name: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
bundled: Option<bool>,
|
||||
/// Tool allowlist for this extension. Omit this field to allow all tools.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
available_tools: Option<Vec<String>>,
|
||||
},
|
||||
Stdio {
|
||||
name: String,
|
||||
@@ -198,6 +204,9 @@ pub enum RecipeExtensionDto {
|
||||
cwd: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
bundled: Option<bool>,
|
||||
/// Tool allowlist for this extension. Omit this field to allow all tools.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
available_tools: Option<Vec<String>>,
|
||||
},
|
||||
StreamableHttp {
|
||||
name: String,
|
||||
@@ -216,6 +225,9 @@ pub enum RecipeExtensionDto {
|
||||
socket: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
bundled: Option<bool>,
|
||||
/// Tool allowlist for this extension. Omit this field to allow all tools.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
available_tools: Option<Vec<String>>,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -227,6 +239,7 @@ impl Default for RecipeExtensionDto {
|
||||
display_name: None,
|
||||
timeout: None,
|
||||
bundled: None,
|
||||
available_tools: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +53,16 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"available_tools": {
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
],
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Tool allowlist for this extension. Omit this field to allow all tools."
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"const": "builtin"
|
||||
@@ -87,6 +97,16 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"available_tools": {
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
],
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Tool allowlist for this extension. Omit this field to allow all tools."
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"const": "platform"
|
||||
@@ -134,6 +154,16 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"available_tools": {
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
],
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Tool allowlist for this extension. Omit this field to allow all tools."
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"const": "mcp"
|
||||
@@ -3630,6 +3660,16 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"available_tools": {
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
],
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Tool allowlist for this extension. Omit this field to allow all tools."
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"const": "builtin"
|
||||
@@ -3664,6 +3704,16 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"available_tools": {
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
],
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Tool allowlist for this extension. Omit this field to allow all tools."
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"const": "platform"
|
||||
@@ -3726,6 +3776,16 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"available_tools": {
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
],
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Tool allowlist for this extension. Omit this field to allow all tools."
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"const": "stdio"
|
||||
@@ -3789,6 +3849,16 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"available_tools": {
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
],
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Tool allowlist for this extension. Omit this field to allow all tools."
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"const": "streamable_http"
|
||||
|
||||
@@ -145,25 +145,27 @@ fn config_to_goose_extension(
|
||||
display_name,
|
||||
timeout,
|
||||
bundled,
|
||||
..
|
||||
available_tools,
|
||||
} => GooseExtension::Builtin {
|
||||
name: name.clone(),
|
||||
description: empty_string_to_none(description),
|
||||
display_name: display_name.clone(),
|
||||
timeout: *timeout,
|
||||
bundled: *bundled,
|
||||
available_tools: available_tools_to_wire(available_tools),
|
||||
},
|
||||
ExtensionConfig::Platform {
|
||||
name,
|
||||
description,
|
||||
display_name,
|
||||
bundled,
|
||||
..
|
||||
available_tools,
|
||||
} => GooseExtension::Platform {
|
||||
name: name.clone(),
|
||||
description: empty_string_to_none(description),
|
||||
display_name: display_name.clone(),
|
||||
bundled: *bundled,
|
||||
available_tools: available_tools_to_wire(available_tools),
|
||||
},
|
||||
ExtensionConfig::Stdio {
|
||||
name,
|
||||
@@ -173,6 +175,7 @@ fn config_to_goose_extension(
|
||||
env_keys,
|
||||
timeout,
|
||||
bundled,
|
||||
available_tools,
|
||||
..
|
||||
} => GooseExtension::Mcp {
|
||||
server: McpServer::Stdio(McpServerStdio::new(name, cmd).args(args.clone())),
|
||||
@@ -181,6 +184,7 @@ fn config_to_goose_extension(
|
||||
timeout: *timeout,
|
||||
socket: None,
|
||||
bundled: *bundled,
|
||||
available_tools: available_tools_to_wire(available_tools),
|
||||
},
|
||||
ExtensionConfig::StreamableHttp {
|
||||
name,
|
||||
@@ -191,6 +195,7 @@ fn config_to_goose_extension(
|
||||
timeout,
|
||||
socket,
|
||||
bundled,
|
||||
available_tools,
|
||||
..
|
||||
} => {
|
||||
let headers = headers
|
||||
@@ -204,6 +209,7 @@ fn config_to_goose_extension(
|
||||
timeout: *timeout,
|
||||
socket: socket.clone(),
|
||||
bundled: *bundled,
|
||||
available_tools: available_tools_to_wire(available_tools),
|
||||
}
|
||||
}
|
||||
ExtensionConfig::Frontend { .. }
|
||||
@@ -229,25 +235,27 @@ fn goose_extension_to_config(
|
||||
display_name,
|
||||
timeout,
|
||||
bundled,
|
||||
available_tools,
|
||||
} => ExtensionConfig::Builtin {
|
||||
name,
|
||||
description: description.unwrap_or_default(),
|
||||
display_name,
|
||||
timeout,
|
||||
bundled,
|
||||
available_tools: Vec::new(),
|
||||
available_tools: available_tools.unwrap_or_default(),
|
||||
},
|
||||
GooseExtension::Platform {
|
||||
name,
|
||||
description,
|
||||
display_name,
|
||||
bundled,
|
||||
available_tools,
|
||||
} => ExtensionConfig::Platform {
|
||||
name,
|
||||
description: description.unwrap_or_default(),
|
||||
display_name,
|
||||
bundled,
|
||||
available_tools: Vec::new(),
|
||||
available_tools: available_tools.unwrap_or_default(),
|
||||
},
|
||||
GooseExtension::Mcp {
|
||||
server,
|
||||
@@ -256,6 +264,7 @@ fn goose_extension_to_config(
|
||||
timeout,
|
||||
socket,
|
||||
bundled,
|
||||
available_tools,
|
||||
} => match server {
|
||||
McpServer::Stdio(stdio) => {
|
||||
if socket.is_some() {
|
||||
@@ -279,7 +288,7 @@ fn goose_extension_to_config(
|
||||
timeout,
|
||||
cwd: None,
|
||||
bundled,
|
||||
available_tools: Vec::new(),
|
||||
available_tools: available_tools.unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
McpServer::Http(http) => ExtensionConfig::StreamableHttp {
|
||||
@@ -296,7 +305,7 @@ fn goose_extension_to_config(
|
||||
timeout,
|
||||
socket,
|
||||
bundled,
|
||||
available_tools: Vec::new(),
|
||||
available_tools: available_tools.unwrap_or_default(),
|
||||
},
|
||||
McpServer::Sse(_) => {
|
||||
return Err(agent_client_protocol::Error::invalid_params()
|
||||
@@ -358,6 +367,14 @@ fn empty_string_to_none(value: &str) -> Option<String> {
|
||||
}
|
||||
}
|
||||
|
||||
fn available_tools_to_wire(available_tools: &[String]) -> Option<Vec<String>> {
|
||||
if available_tools.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(available_tools.to_vec())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -386,6 +403,7 @@ mod tests {
|
||||
display_name,
|
||||
timeout,
|
||||
bundled,
|
||||
available_tools,
|
||||
} = extension
|
||||
else {
|
||||
panic!("expected builtin extension");
|
||||
@@ -396,6 +414,7 @@ mod tests {
|
||||
assert_eq!(display_name.as_deref(), Some("Developer"));
|
||||
assert_eq!(timeout, Some(30));
|
||||
assert_eq!(bundled, Some(true));
|
||||
assert_eq!(available_tools, Some(vec!["shell".to_string()]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -417,6 +436,7 @@ mod tests {
|
||||
description,
|
||||
display_name,
|
||||
bundled,
|
||||
available_tools,
|
||||
} = extension
|
||||
else {
|
||||
panic!("expected platform extension");
|
||||
@@ -426,6 +446,7 @@ mod tests {
|
||||
assert_eq!(description.as_deref(), Some("Todo tools"));
|
||||
assert_eq!(display_name.as_deref(), Some("Todo"));
|
||||
assert_eq!(bundled, Some(true));
|
||||
assert_eq!(available_tools, Some(vec!["write_todos".to_string()]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -443,7 +464,7 @@ mod tests {
|
||||
timeout: Some(42),
|
||||
cwd: None,
|
||||
bundled: None,
|
||||
available_tools: vec![],
|
||||
available_tools: vec!["run".to_string()],
|
||||
};
|
||||
|
||||
let extension = config_to_goose_extension(&config)
|
||||
@@ -457,6 +478,7 @@ mod tests {
|
||||
timeout,
|
||||
socket,
|
||||
bundled,
|
||||
available_tools,
|
||||
} = extension
|
||||
else {
|
||||
panic!("expected mcp extension");
|
||||
@@ -467,6 +489,7 @@ mod tests {
|
||||
assert_eq!(timeout, Some(42));
|
||||
assert_eq!(socket, None);
|
||||
assert_eq!(bundled, None);
|
||||
assert_eq!(available_tools, Some(vec!["run".to_string()]));
|
||||
|
||||
let McpServer::Stdio(stdio) = server else {
|
||||
panic!("expected stdio server");
|
||||
@@ -496,7 +519,7 @@ mod tests {
|
||||
timeout: Some(99),
|
||||
socket: Some("@egress.sock".to_string()),
|
||||
bundled: None,
|
||||
available_tools: vec![],
|
||||
available_tools: vec!["fetch".to_string()],
|
||||
};
|
||||
|
||||
let extension = config_to_goose_extension(&config)
|
||||
@@ -510,6 +533,7 @@ mod tests {
|
||||
timeout,
|
||||
socket,
|
||||
bundled,
|
||||
available_tools,
|
||||
} = extension
|
||||
else {
|
||||
panic!("expected mcp extension");
|
||||
@@ -520,6 +544,7 @@ mod tests {
|
||||
assert_eq!(timeout, Some(99));
|
||||
assert_eq!(socket.as_deref(), Some("@egress.sock"));
|
||||
assert_eq!(bundled, None);
|
||||
assert_eq!(available_tools, Some(vec!["fetch".to_string()]));
|
||||
|
||||
let McpServer::Http(http) = server else {
|
||||
panic!("expected http server");
|
||||
@@ -602,6 +627,7 @@ mod tests {
|
||||
timeout: Some(42),
|
||||
socket: None,
|
||||
bundled: Some(true),
|
||||
available_tools: Some(vec!["run".to_string()]),
|
||||
};
|
||||
|
||||
let conversion = goose_extension_to_config(extension).expect("conversion should succeed");
|
||||
@@ -634,7 +660,7 @@ mod tests {
|
||||
assert_eq!(env_keys, vec!["SECRET_TOKEN"]);
|
||||
assert_eq!(timeout, Some(42));
|
||||
assert_eq!(bundled, Some(true));
|
||||
assert!(available_tools.is_empty());
|
||||
assert_eq!(available_tools, vec!["run"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -652,6 +678,7 @@ mod tests {
|
||||
timeout: Some(42),
|
||||
socket: None,
|
||||
bundled: Some(true),
|
||||
available_tools: None,
|
||||
};
|
||||
|
||||
let conversion = goose_extension_to_config(extension).expect("conversion should succeed");
|
||||
@@ -694,6 +721,7 @@ mod tests {
|
||||
timeout: Some(99),
|
||||
socket: Some("@egress.sock".to_string()),
|
||||
bundled: Some(true),
|
||||
available_tools: Some(vec!["fetch".to_string()]),
|
||||
};
|
||||
|
||||
let conversion = goose_extension_to_config(extension).expect("conversion should succeed");
|
||||
@@ -733,7 +761,7 @@ mod tests {
|
||||
assert_eq!(timeout, Some(99));
|
||||
assert_eq!(socket.as_deref(), Some("@egress.sock"));
|
||||
assert_eq!(bundled, Some(true));
|
||||
assert!(available_tools.is_empty());
|
||||
assert_eq!(available_tools, vec!["fetch"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -744,6 +772,7 @@ mod tests {
|
||||
display_name: Some("Developer".to_string()),
|
||||
timeout: Some(30),
|
||||
bundled: Some(true),
|
||||
available_tools: Some(vec!["shell".to_string()]),
|
||||
};
|
||||
|
||||
let conversion = goose_extension_to_config(builtin).expect("conversion should succeed");
|
||||
@@ -766,7 +795,7 @@ mod tests {
|
||||
assert_eq!(display_name.as_deref(), Some("Developer"));
|
||||
assert_eq!(timeout, Some(30));
|
||||
assert_eq!(bundled, Some(true));
|
||||
assert!(available_tools.is_empty());
|
||||
assert_eq!(available_tools, vec!["shell"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -776,6 +805,7 @@ mod tests {
|
||||
description: Some("Todo tools".to_string()),
|
||||
display_name: Some("Todo".to_string()),
|
||||
bundled: Some(true),
|
||||
available_tools: Some(vec!["write_todos".to_string()]),
|
||||
};
|
||||
|
||||
let conversion = goose_extension_to_config(platform).expect("conversion should succeed");
|
||||
@@ -796,7 +826,7 @@ mod tests {
|
||||
assert_eq!(description, "Todo tools");
|
||||
assert_eq!(display_name.as_deref(), Some("Todo"));
|
||||
assert_eq!(bundled, Some(true));
|
||||
assert!(available_tools.is_empty());
|
||||
assert_eq!(available_tools, vec!["write_todos"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -808,6 +838,7 @@ mod tests {
|
||||
timeout: None,
|
||||
socket: None,
|
||||
bundled: None,
|
||||
available_tools: None,
|
||||
};
|
||||
|
||||
assert!(goose_extension_to_config(extension).is_err());
|
||||
|
||||
@@ -290,25 +290,27 @@ impl TryFrom<RecipeExtensionDto> for ExtensionConfig {
|
||||
display_name,
|
||||
timeout,
|
||||
bundled,
|
||||
available_tools,
|
||||
} => Self::Builtin {
|
||||
name,
|
||||
description: description.unwrap_or_default(),
|
||||
display_name,
|
||||
timeout,
|
||||
bundled,
|
||||
available_tools: Vec::new(),
|
||||
available_tools: available_tools.unwrap_or_default(),
|
||||
},
|
||||
RecipeExtensionDto::Platform {
|
||||
name,
|
||||
description,
|
||||
display_name,
|
||||
bundled,
|
||||
available_tools,
|
||||
} => Self::Platform {
|
||||
name,
|
||||
description: description.unwrap_or_default(),
|
||||
display_name,
|
||||
bundled,
|
||||
available_tools: Vec::new(),
|
||||
available_tools: available_tools.unwrap_or_default(),
|
||||
},
|
||||
RecipeExtensionDto::Stdio {
|
||||
name,
|
||||
@@ -320,6 +322,7 @@ impl TryFrom<RecipeExtensionDto> for ExtensionConfig {
|
||||
timeout,
|
||||
cwd,
|
||||
bundled,
|
||||
available_tools,
|
||||
} => Self::Stdio {
|
||||
name,
|
||||
description: description.unwrap_or_default(),
|
||||
@@ -330,7 +333,7 @@ impl TryFrom<RecipeExtensionDto> for ExtensionConfig {
|
||||
timeout,
|
||||
cwd,
|
||||
bundled,
|
||||
available_tools: Vec::new(),
|
||||
available_tools: available_tools.unwrap_or_default(),
|
||||
},
|
||||
RecipeExtensionDto::StreamableHttp {
|
||||
name,
|
||||
@@ -342,6 +345,7 @@ impl TryFrom<RecipeExtensionDto> for ExtensionConfig {
|
||||
timeout,
|
||||
socket,
|
||||
bundled,
|
||||
available_tools,
|
||||
} => Self::StreamableHttp {
|
||||
name,
|
||||
description: description.unwrap_or_default(),
|
||||
@@ -352,7 +356,7 @@ impl TryFrom<RecipeExtensionDto> for ExtensionConfig {
|
||||
timeout,
|
||||
socket,
|
||||
bundled,
|
||||
available_tools: Vec::new(),
|
||||
available_tools: available_tools.unwrap_or_default(),
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -369,25 +373,27 @@ impl TryFrom<ExtensionConfig> for RecipeExtensionDto {
|
||||
display_name,
|
||||
timeout,
|
||||
bundled,
|
||||
..
|
||||
available_tools,
|
||||
} => Self::Builtin {
|
||||
name,
|
||||
description: Some(description),
|
||||
display_name,
|
||||
timeout,
|
||||
bundled,
|
||||
available_tools: available_tools_to_wire(available_tools),
|
||||
},
|
||||
ExtensionConfig::Platform {
|
||||
name,
|
||||
description,
|
||||
display_name,
|
||||
bundled,
|
||||
..
|
||||
available_tools,
|
||||
} => Self::Platform {
|
||||
name,
|
||||
description: Some(description),
|
||||
display_name,
|
||||
bundled,
|
||||
available_tools: available_tools_to_wire(available_tools),
|
||||
},
|
||||
ExtensionConfig::Stdio {
|
||||
name,
|
||||
@@ -399,7 +405,7 @@ impl TryFrom<ExtensionConfig> for RecipeExtensionDto {
|
||||
timeout,
|
||||
cwd,
|
||||
bundled,
|
||||
..
|
||||
available_tools,
|
||||
} => Self::Stdio {
|
||||
name,
|
||||
description: Some(description),
|
||||
@@ -410,6 +416,7 @@ impl TryFrom<ExtensionConfig> for RecipeExtensionDto {
|
||||
timeout,
|
||||
cwd,
|
||||
bundled,
|
||||
available_tools: available_tools_to_wire(available_tools),
|
||||
},
|
||||
ExtensionConfig::StreamableHttp {
|
||||
name,
|
||||
@@ -421,7 +428,7 @@ impl TryFrom<ExtensionConfig> for RecipeExtensionDto {
|
||||
timeout,
|
||||
socket,
|
||||
bundled,
|
||||
..
|
||||
available_tools,
|
||||
} => Self::StreamableHttp {
|
||||
name,
|
||||
description: Some(description),
|
||||
@@ -432,6 +439,7 @@ impl TryFrom<ExtensionConfig> for RecipeExtensionDto {
|
||||
timeout,
|
||||
socket,
|
||||
bundled,
|
||||
available_tools: available_tools_to_wire(available_tools),
|
||||
},
|
||||
ExtensionConfig::Sse { .. } => bail_unsupported_extension("sse")?,
|
||||
ExtensionConfig::Frontend { .. } => bail_unsupported_extension("frontend")?,
|
||||
@@ -444,6 +452,14 @@ fn bail_unsupported_extension(extension_type: &str) -> Result<RecipeExtensionDto
|
||||
bail!("recipe extension type `{extension_type}` is not supported by RecipeDto")
|
||||
}
|
||||
|
||||
fn available_tools_to_wire(available_tools: Vec<String>) -> Option<Vec<String>> {
|
||||
if available_tools.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(available_tools)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn recipe_manifest_to_list_entry_dto(
|
||||
id: String,
|
||||
recipe: Recipe,
|
||||
@@ -484,6 +500,7 @@ mod tests {
|
||||
display_name: Some("Developer".to_string()),
|
||||
timeout: Some(300),
|
||||
bundled: Some(true),
|
||||
available_tools: Some(vec!["shell".to_string()]),
|
||||
},
|
||||
RecipeExtensionDto::Stdio {
|
||||
name: "local".to_string(),
|
||||
@@ -495,6 +512,7 @@ mod tests {
|
||||
timeout: Some(60),
|
||||
cwd: Some("/tmp".to_string()),
|
||||
bundled: None,
|
||||
available_tools: Some(vec!["run".to_string()]),
|
||||
},
|
||||
RecipeExtensionDto::StreamableHttp {
|
||||
name: "remote".to_string(),
|
||||
@@ -506,6 +524,7 @@ mod tests {
|
||||
timeout: Some(30),
|
||||
socket: None,
|
||||
bundled: Some(false),
|
||||
available_tools: Some(vec!["fetch".to_string()]),
|
||||
},
|
||||
]),
|
||||
settings: Some(RecipeSettingsDto {
|
||||
@@ -558,15 +577,33 @@ mod tests {
|
||||
assert_eq!(recipe.version, "1.0.0");
|
||||
assert_eq!(recipe.title, "Test Recipe");
|
||||
assert_eq!(recipe.extensions.as_ref().unwrap().len(), 3);
|
||||
match &recipe.extensions.as_ref().unwrap()[0] {
|
||||
ExtensionConfig::Builtin {
|
||||
available_tools, ..
|
||||
} => {
|
||||
assert_eq!(available_tools, &vec!["shell".to_string()]);
|
||||
}
|
||||
extension => panic!("expected builtin extension, got {extension:?}"),
|
||||
}
|
||||
match &recipe.extensions.as_ref().unwrap()[1] {
|
||||
ExtensionConfig::Stdio { envs, .. } => {
|
||||
ExtensionConfig::Stdio {
|
||||
envs,
|
||||
available_tools,
|
||||
..
|
||||
} => {
|
||||
assert_eq!(envs.get_env()["LOCAL_MODE"], "true");
|
||||
assert_eq!(available_tools, &vec!["run".to_string()]);
|
||||
}
|
||||
extension => panic!("expected stdio extension, got {extension:?}"),
|
||||
}
|
||||
match &recipe.extensions.as_ref().unwrap()[2] {
|
||||
ExtensionConfig::StreamableHttp { envs, .. } => {
|
||||
ExtensionConfig::StreamableHttp {
|
||||
envs,
|
||||
available_tools,
|
||||
..
|
||||
} => {
|
||||
assert_eq!(envs.get_env()["REMOTE_MODE"], "true");
|
||||
assert_eq!(available_tools, &vec!["fetch".to_string()]);
|
||||
}
|
||||
extension => panic!("expected streamable_http extension, got {extension:?}"),
|
||||
}
|
||||
@@ -582,8 +619,11 @@ mod tests {
|
||||
assert!(serialized.get("subRecipes").is_none());
|
||||
assert_eq!(serialized["parameters"][0]["input_type"], json!("select"));
|
||||
assert_eq!(serialized["retry"]["checks"][0]["type"], json!("shell"));
|
||||
assert_eq!(serialized["extensions"][0]["available_tools"][0], "shell");
|
||||
assert_eq!(serialized["extensions"][1]["envs"]["LOCAL_MODE"], "true");
|
||||
assert_eq!(serialized["extensions"][1]["available_tools"][0], "run");
|
||||
assert_eq!(serialized["extensions"][2]["envs"]["REMOTE_MODE"], "true");
|
||||
assert_eq!(serialized["extensions"][2]["available_tools"][0], "fetch");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -195,6 +195,7 @@ pub enum ExtensionConfig {
|
||||
#[serde(default)]
|
||||
bundled: Option<bool>,
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||
available_tools: Vec<String>,
|
||||
},
|
||||
/// Built-in extension that is part of the bundled goose MCP server
|
||||
@@ -211,6 +212,7 @@ pub enum ExtensionConfig {
|
||||
#[serde(default)]
|
||||
bundled: Option<bool>,
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||
available_tools: Vec<String>,
|
||||
},
|
||||
/// Platform extensions that have direct access to the agent etc and run in the agent process
|
||||
@@ -226,6 +228,7 @@ pub enum ExtensionConfig {
|
||||
#[serde(default)]
|
||||
bundled: Option<bool>,
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||
available_tools: Vec<String>,
|
||||
},
|
||||
/// Streamable HTTP client with a URI endpoint using MCP Streamable HTTP specification
|
||||
@@ -256,6 +259,7 @@ pub enum ExtensionConfig {
|
||||
#[serde(default)]
|
||||
bundled: Option<bool>,
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||
available_tools: Vec<String>,
|
||||
},
|
||||
/// Frontend-provided tools that will be called through the frontend
|
||||
@@ -274,6 +278,7 @@ pub enum ExtensionConfig {
|
||||
#[serde(default)]
|
||||
bundled: Option<bool>,
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||
available_tools: Vec<String>,
|
||||
},
|
||||
/// Inline Python code that will be executed using uvx
|
||||
@@ -293,6 +298,7 @@ pub enum ExtensionConfig {
|
||||
#[serde(default)]
|
||||
dependencies: Option<Vec<String>>,
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||
available_tools: Vec<String>,
|
||||
},
|
||||
}
|
||||
@@ -685,6 +691,39 @@ available_tools: []
|
||||
assert_eq!(map.get("SAFE_VAR"), Some(&"ok".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialization_omits_empty_available_tools() {
|
||||
let config = ExtensionConfig::Builtin {
|
||||
name: "developer".into(),
|
||||
description: "dev".into(),
|
||||
display_name: Some("Developer".into()),
|
||||
timeout: Some(300),
|
||||
bundled: Some(true),
|
||||
available_tools: vec![],
|
||||
};
|
||||
|
||||
let yaml = serde_yaml::to_string(&config).unwrap();
|
||||
|
||||
assert!(!yaml.contains("available_tools"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialization_preserves_available_tools() {
|
||||
let config = ExtensionConfig::Builtin {
|
||||
name: "developer".into(),
|
||||
description: "dev".into(),
|
||||
display_name: Some("Developer".into()),
|
||||
timeout: Some(300),
|
||||
bundled: Some(true),
|
||||
available_tools: vec!["shell".to_string()],
|
||||
};
|
||||
|
||||
let yaml = serde_yaml::to_string(&config).unwrap();
|
||||
|
||||
assert!(yaml.contains("available_tools"));
|
||||
assert!(yaml.contains("- shell"));
|
||||
}
|
||||
|
||||
#[test_case(
|
||||
ExtensionConfig::Builtin {
|
||||
name: "developer".into(),
|
||||
|
||||
Reference in New Issue
Block a user