mirror of
https://github.com/block/goose.git
synced 2026-07-09 16:09:22 +00:00
feat (acp): exposed available tools in acp schema (#10097)
This commit is contained in:
parent
d43c692127
commit
2084c43476
15 changed files with 543 additions and 40 deletions
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -17,6 +17,10 @@ function headersToRecord(headers: { name: string; value: string }[] = []) {
|
|||
return Object.fromEntries(headers.map(({ name, value }) => [name, value]));
|
||||
}
|
||||
|
||||
function availableToolsOrUndefined(availableTools?: string[] | null): string[] | undefined {
|
||||
return availableTools?.length ? availableTools : undefined;
|
||||
}
|
||||
|
||||
export function gooseExtensionToExtensionConfig(extension: GooseExtension): ExtensionConfig | null {
|
||||
switch (extension.type) {
|
||||
case 'builtin':
|
||||
|
|
@ -24,6 +28,7 @@ export function gooseExtensionToExtensionConfig(extension: GooseExtension): Exte
|
|||
return {
|
||||
...extension,
|
||||
description: extension.description ?? '',
|
||||
available_tools: availableToolsOrUndefined(extension.available_tools),
|
||||
};
|
||||
case 'mcp': {
|
||||
const server = extension.server;
|
||||
|
|
@ -37,6 +42,7 @@ export function gooseExtensionToExtensionConfig(extension: GooseExtension): Exte
|
|||
env_keys: extension.envKeys ?? [],
|
||||
timeout: extension.timeout,
|
||||
bundled: extension.bundled,
|
||||
available_tools: availableToolsOrUndefined(extension.available_tools),
|
||||
};
|
||||
}
|
||||
if ('url' in server) {
|
||||
|
|
@ -50,6 +56,7 @@ export function gooseExtensionToExtensionConfig(extension: GooseExtension): Exte
|
|||
timeout: extension.timeout,
|
||||
socket: extension.socket,
|
||||
bundled: extension.bundled,
|
||||
available_tools: availableToolsOrUndefined(extension.available_tools),
|
||||
};
|
||||
}
|
||||
return null;
|
||||
|
|
@ -94,6 +101,7 @@ export function extensionConfigToGooseExtension(config: ExtensionConfig): GooseE
|
|||
display_name: config.display_name,
|
||||
timeout: config.timeout,
|
||||
bundled: config.bundled,
|
||||
available_tools: availableToolsOrUndefined(config.available_tools),
|
||||
};
|
||||
case 'platform':
|
||||
return {
|
||||
|
|
@ -102,6 +110,7 @@ export function extensionConfigToGooseExtension(config: ExtensionConfig): GooseE
|
|||
description: config.description,
|
||||
display_name: config.display_name,
|
||||
bundled: config.bundled,
|
||||
available_tools: availableToolsOrUndefined(config.available_tools),
|
||||
};
|
||||
case 'stdio':
|
||||
return {
|
||||
|
|
@ -111,6 +120,7 @@ export function extensionConfigToGooseExtension(config: ExtensionConfig): GooseE
|
|||
description: config.description,
|
||||
timeout: config.timeout,
|
||||
bundled: config.bundled,
|
||||
available_tools: availableToolsOrUndefined(config.available_tools),
|
||||
};
|
||||
case 'streamable_http':
|
||||
return {
|
||||
|
|
@ -126,6 +136,7 @@ export function extensionConfigToGooseExtension(config: ExtensionConfig): GooseE
|
|||
timeout: config.timeout,
|
||||
socket: config.socket,
|
||||
bundled: config.bundled,
|
||||
available_tools: availableToolsOrUndefined(config.available_tools),
|
||||
};
|
||||
case 'sse':
|
||||
case 'frontend':
|
||||
|
|
|
|||
|
|
@ -259,13 +259,12 @@ export default function CreateEditRecipeModal({
|
|||
: undefined;
|
||||
|
||||
const cleanedExtensions = extensions?.map(
|
||||
(
|
||||
extension: RecipeExtension & {
|
||||
enabled?: boolean;
|
||||
available_tools?: unknown;
|
||||
(extension: RecipeExtension & { enabled?: boolean }) => {
|
||||
const { enabled: _enabled, ...rest } = extension;
|
||||
if (rest.available_tools == null || rest.available_tools.length === 0) {
|
||||
const { available_tools: _availableTools, ...withoutAvailableTools } = rest;
|
||||
return withoutAvailableTools;
|
||||
}
|
||||
) => {
|
||||
const { enabled: _enabled, available_tools: _availableTools, ...rest } = extension;
|
||||
return rest;
|
||||
}
|
||||
) as RecipeExtension[] | undefined;
|
||||
|
|
|
|||
|
|
@ -38,6 +38,12 @@ type DisplayRecipeExtension = RecipeExtension & {
|
|||
enabled?: boolean;
|
||||
};
|
||||
|
||||
function availableToolsProps(availableTools?: string[] | null) {
|
||||
return availableTools && availableTools.length > 0
|
||||
? { available_tools: availableTools }
|
||||
: undefined;
|
||||
}
|
||||
|
||||
function toRecipeExtension(
|
||||
extension: FixedExtensionEntry | DisplayRecipeExtension
|
||||
): DisplayRecipeExtension | null {
|
||||
|
|
@ -45,21 +51,74 @@ function toRecipeExtension(
|
|||
|
||||
switch (extension.type) {
|
||||
case 'builtin': {
|
||||
const { name, description, display_name, timeout, bundled, type } = extension;
|
||||
return { name, description, display_name, timeout, bundled, type, enabled };
|
||||
const { name, description, display_name, timeout, bundled, available_tools, type } =
|
||||
extension;
|
||||
return {
|
||||
name,
|
||||
description,
|
||||
display_name,
|
||||
timeout,
|
||||
bundled,
|
||||
...availableToolsProps(available_tools),
|
||||
type,
|
||||
enabled,
|
||||
};
|
||||
}
|
||||
case 'platform': {
|
||||
const { name, description, display_name, bundled, type } = extension;
|
||||
return { name, description, display_name, bundled, type, enabled };
|
||||
const { name, description, display_name, bundled, available_tools, type } = extension;
|
||||
return {
|
||||
name,
|
||||
description,
|
||||
display_name,
|
||||
bundled,
|
||||
...availableToolsProps(available_tools),
|
||||
type,
|
||||
enabled,
|
||||
};
|
||||
}
|
||||
case 'stdio': {
|
||||
const { name, description, cmd, args, envs, env_keys, timeout, cwd, bundled, type } =
|
||||
extension;
|
||||
return { name, description, cmd, args, envs, env_keys, timeout, cwd, bundled, type, enabled };
|
||||
const {
|
||||
name,
|
||||
description,
|
||||
cmd,
|
||||
args,
|
||||
envs,
|
||||
env_keys,
|
||||
timeout,
|
||||
cwd,
|
||||
bundled,
|
||||
available_tools,
|
||||
type,
|
||||
} = extension;
|
||||
return {
|
||||
name,
|
||||
description,
|
||||
cmd,
|
||||
args,
|
||||
envs,
|
||||
env_keys,
|
||||
timeout,
|
||||
cwd,
|
||||
bundled,
|
||||
...availableToolsProps(available_tools),
|
||||
type,
|
||||
enabled,
|
||||
};
|
||||
}
|
||||
case 'streamable_http': {
|
||||
const { name, description, uri, envs, env_keys, headers, timeout, socket, bundled, type } =
|
||||
extension;
|
||||
const {
|
||||
name,
|
||||
description,
|
||||
uri,
|
||||
envs,
|
||||
env_keys,
|
||||
headers,
|
||||
timeout,
|
||||
socket,
|
||||
bundled,
|
||||
available_tools,
|
||||
type,
|
||||
} = extension;
|
||||
return {
|
||||
name,
|
||||
description,
|
||||
|
|
@ -70,6 +129,7 @@ function toRecipeExtension(
|
|||
timeout,
|
||||
socket,
|
||||
bundled,
|
||||
...availableToolsProps(available_tools),
|
||||
type,
|
||||
enabled,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,54 @@
|
|||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { render, type RenderOptions, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
import { RecipeExtensionSelector } from '../RecipeExtensionSelector';
|
||||
import { IntlTestWrapper } from '../../../../i18n/test-utils';
|
||||
import type { FixedExtensionEntry } from '../../../ConfigContext';
|
||||
|
||||
const configContextMock = vi.hoisted(() => ({
|
||||
extensionsList: [] as FixedExtensionEntry[],
|
||||
}));
|
||||
|
||||
vi.mock('../../../ConfigContext', () => ({
|
||||
useConfig: () => ({
|
||||
extensionsList: configContextMock.extensionsList,
|
||||
}),
|
||||
}));
|
||||
|
||||
const renderWithIntl = (ui: React.ReactElement, options?: RenderOptions) =>
|
||||
render(ui, { wrapper: IntlTestWrapper, ...options });
|
||||
|
||||
describe('RecipeExtensionSelector', () => {
|
||||
beforeEach(() => {
|
||||
configContextMock.extensionsList = [];
|
||||
});
|
||||
|
||||
it('preserves non-empty available tools when selecting a configured extension', async () => {
|
||||
const user = userEvent.setup();
|
||||
const onExtensionsChange = vi.fn();
|
||||
configContextMock.extensionsList = [
|
||||
{
|
||||
type: 'builtin',
|
||||
name: 'developer',
|
||||
description: 'Developer tools',
|
||||
enabled: true,
|
||||
available_tools: ['shell', 'read_file'],
|
||||
},
|
||||
];
|
||||
|
||||
renderWithIntl(
|
||||
<RecipeExtensionSelector selectedExtensions={[]} onExtensionsChange={onExtensionsChange} />
|
||||
);
|
||||
|
||||
await user.click(screen.getByText('Developer'));
|
||||
|
||||
expect(onExtensionsChange).toHaveBeenCalledWith([
|
||||
expect.objectContaining({
|
||||
type: 'builtin',
|
||||
name: 'developer',
|
||||
available_tools: ['shell', 'read_file'],
|
||||
}),
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
|
@ -157,6 +157,34 @@ describe('Extension Utils', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('should preserve available tools metadata', () => {
|
||||
const extension: FixedExtensionEntry = {
|
||||
type: 'builtin',
|
||||
name: 'developer',
|
||||
description: 'developer',
|
||||
enabled: true,
|
||||
available_tools: ['shell', 'read_file'],
|
||||
};
|
||||
|
||||
const formData = extensionToFormData(extension);
|
||||
|
||||
expect(formData.available_tools).toEqual(['shell', 'read_file']);
|
||||
});
|
||||
|
||||
it('should omit empty available tools metadata', () => {
|
||||
const extension: FixedExtensionEntry = {
|
||||
type: 'builtin',
|
||||
name: 'developer',
|
||||
description: 'developer',
|
||||
enabled: true,
|
||||
available_tools: [],
|
||||
};
|
||||
|
||||
const formData = extensionToFormData(extension);
|
||||
|
||||
expect(formData).not.toHaveProperty('available_tools');
|
||||
});
|
||||
|
||||
it('should not escape @ in command args', () => {
|
||||
const extension: FixedExtensionEntry = {
|
||||
type: 'stdio',
|
||||
|
|
@ -320,6 +348,73 @@ describe('Extension Utils', () => {
|
|||
timeout: 300,
|
||||
});
|
||||
});
|
||||
|
||||
it('should create sse extension config', () => {
|
||||
const formData = {
|
||||
name: 'test-sse',
|
||||
description: 'Test SSE extension',
|
||||
type: 'sse' as const,
|
||||
cmd: '',
|
||||
endpoint: 'http://api.example.com/sse',
|
||||
enabled: true,
|
||||
timeout: 300,
|
||||
envVars: [],
|
||||
headers: [],
|
||||
};
|
||||
|
||||
const config = createExtensionConfig(formData);
|
||||
|
||||
expect(config).toEqual({
|
||||
type: 'sse',
|
||||
name: 'test-sse',
|
||||
description: 'Test SSE extension',
|
||||
uri: 'http://api.example.com/sse',
|
||||
});
|
||||
});
|
||||
|
||||
it('should preserve available tools metadata', () => {
|
||||
const formData = {
|
||||
name: 'developer',
|
||||
description: 'developer',
|
||||
type: 'builtin' as const,
|
||||
cmd: '',
|
||||
endpoint: '',
|
||||
enabled: true,
|
||||
timeout: 300,
|
||||
envVars: [],
|
||||
headers: [],
|
||||
available_tools: ['shell', 'read_file'],
|
||||
};
|
||||
|
||||
const config = createExtensionConfig(formData);
|
||||
|
||||
expect(config).toEqual({
|
||||
type: 'builtin',
|
||||
name: 'developer',
|
||||
description: 'developer',
|
||||
timeout: 300,
|
||||
available_tools: ['shell', 'read_file'],
|
||||
});
|
||||
});
|
||||
|
||||
it('should omit empty available tools metadata', () => {
|
||||
const formData = {
|
||||
name: 'developer',
|
||||
description: 'developer',
|
||||
type: 'builtin' as const,
|
||||
cmd: '',
|
||||
endpoint: '',
|
||||
enabled: true,
|
||||
timeout: 300,
|
||||
envVars: [],
|
||||
headers: [],
|
||||
available_tools: [],
|
||||
};
|
||||
|
||||
const config = createExtensionConfig(formData);
|
||||
|
||||
expect(config).not.toHaveProperty('available_tools');
|
||||
});
|
||||
});
|
||||
|
||||
describe('splitCmdAndArgs', () => {
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ export interface ExtensionFormData {
|
|||
isEdited?: boolean;
|
||||
}[];
|
||||
installation_notes?: string;
|
||||
available_tools?: string[];
|
||||
}
|
||||
|
||||
export function getDefaultFormData(): ExtensionFormData {
|
||||
|
|
@ -95,6 +96,11 @@ export function extensionToFormData(extension: FixedExtensionEntry): ExtensionFo
|
|||
);
|
||||
}
|
||||
|
||||
const availableTools =
|
||||
'available_tools' in extension
|
||||
? availableToolsOrUndefined(extension.available_tools)
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
name: extension.name || '',
|
||||
description: extension.description || '',
|
||||
|
|
@ -116,9 +122,19 @@ export function extensionToFormData(extension: FixedExtensionEntry): ExtensionFo
|
|||
installation_notes: (extension as Record<string, unknown>)['installation_notes'] as
|
||||
| string
|
||||
| undefined,
|
||||
...(availableTools ? { available_tools: availableTools } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
function availableToolsOrUndefined(availableTools?: string[] | null): string[] | undefined {
|
||||
return availableTools && availableTools.length > 0 ? availableTools : undefined;
|
||||
}
|
||||
|
||||
function availableToolsConfig(availableTools?: string[] | null) {
|
||||
const normalized = availableToolsOrUndefined(availableTools);
|
||||
return normalized ? { available_tools: normalized } : undefined;
|
||||
}
|
||||
|
||||
export function createExtensionConfig(formData: ExtensionFormData): ExtensionConfig {
|
||||
// Extract just the keys from env vars
|
||||
const env_keys = formData.envVars.map(({ key }) => key).filter((key) => key.length > 0);
|
||||
|
|
@ -135,6 +151,7 @@ export function createExtensionConfig(formData: ExtensionFormData): ExtensionCon
|
|||
args: args,
|
||||
timeout: formData.timeout,
|
||||
...(env_keys.length > 0 ? { env_keys } : {}),
|
||||
...availableToolsConfig(formData.available_tools),
|
||||
};
|
||||
} else if (formData.type === 'streamable_http') {
|
||||
// Extract headers
|
||||
|
|
@ -156,14 +173,22 @@ export function createExtensionConfig(formData: ExtensionFormData): ExtensionCon
|
|||
uri: formData.endpoint || '',
|
||||
...(env_keys.length > 0 ? { env_keys } : {}),
|
||||
headers,
|
||||
...availableToolsConfig(formData.available_tools),
|
||||
};
|
||||
} else {
|
||||
// For other types
|
||||
} else if (formData.type === 'builtin') {
|
||||
return {
|
||||
type: formData.type,
|
||||
name: formData.name,
|
||||
description: formData.description,
|
||||
timeout: formData.timeout,
|
||||
...availableToolsConfig(formData.available_tools),
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
type: formData.type,
|
||||
name: formData.name,
|
||||
description: formData.description,
|
||||
uri: formData.endpoint || '',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ describe('Recipe Validation', () => {
|
|||
expect(schemaJson).not.toContain('sse');
|
||||
expect(schemaJson).not.toContain('frontend');
|
||||
expect(schemaJson).not.toContain('inline_python');
|
||||
expect(schemaJson).not.toContain('available_tools');
|
||||
expect(schemaJson).toContain('available_tools');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -15,12 +15,20 @@ export type GooseExtension = {
|
|||
display_name?: string | null;
|
||||
timeout?: number | null;
|
||||
bundled?: boolean | null;
|
||||
/**
|
||||
* Tool allowlist for this extension. Omit this field to allow all tools.
|
||||
*/
|
||||
available_tools?: Array<string> | null;
|
||||
type: 'builtin';
|
||||
} | {
|
||||
name: string;
|
||||
description?: string | null;
|
||||
display_name?: string | null;
|
||||
bundled?: boolean | null;
|
||||
/**
|
||||
* Tool allowlist for this extension. Omit this field to allow all tools.
|
||||
*/
|
||||
available_tools?: Array<string> | null;
|
||||
type: 'platform';
|
||||
} | {
|
||||
server: McpServer;
|
||||
|
|
@ -29,6 +37,10 @@ export type GooseExtension = {
|
|||
timeout?: number | null;
|
||||
socket?: string | null;
|
||||
bundled?: boolean | null;
|
||||
/**
|
||||
* Tool allowlist for this extension. Omit this field to allow all tools.
|
||||
*/
|
||||
available_tools?: Array<string> | null;
|
||||
type: 'mcp';
|
||||
};
|
||||
|
||||
|
|
@ -1527,12 +1539,20 @@ export type RecipeExtensionDto = {
|
|||
display_name?: string | null;
|
||||
timeout?: number | null;
|
||||
bundled?: boolean | null;
|
||||
/**
|
||||
* Tool allowlist for this extension. Omit this field to allow all tools.
|
||||
*/
|
||||
available_tools?: Array<string> | null;
|
||||
type: 'builtin';
|
||||
} | {
|
||||
name: string;
|
||||
description?: string | null;
|
||||
display_name?: string | null;
|
||||
bundled?: boolean | null;
|
||||
/**
|
||||
* Tool allowlist for this extension. Omit this field to allow all tools.
|
||||
*/
|
||||
available_tools?: Array<string> | null;
|
||||
type: 'platform';
|
||||
} | {
|
||||
name: string;
|
||||
|
|
@ -1546,6 +1566,10 @@ export type RecipeExtensionDto = {
|
|||
timeout?: number | null;
|
||||
cwd?: string | null;
|
||||
bundled?: boolean | null;
|
||||
/**
|
||||
* Tool allowlist for this extension. Omit this field to allow all tools.
|
||||
*/
|
||||
available_tools?: Array<string> | null;
|
||||
type: 'stdio';
|
||||
} | {
|
||||
name: string;
|
||||
|
|
@ -1561,6 +1585,10 @@ export type RecipeExtensionDto = {
|
|||
timeout?: number | null;
|
||||
socket?: string | null;
|
||||
bundled?: boolean | null;
|
||||
/**
|
||||
* Tool allowlist for this extension. Omit this field to allow all tools.
|
||||
*/
|
||||
available_tools?: Array<string> | null;
|
||||
type: 'streamable_http';
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -134,6 +134,10 @@ export const zGooseExtension = z.union([
|
|||
z.boolean(),
|
||||
z.null()
|
||||
]).optional(),
|
||||
available_tools: z.union([
|
||||
z.array(z.string()),
|
||||
z.null()
|
||||
]).optional(),
|
||||
type: z.literal('builtin')
|
||||
}),
|
||||
z.object({
|
||||
|
|
@ -150,6 +154,10 @@ export const zGooseExtension = z.union([
|
|||
z.boolean(),
|
||||
z.null()
|
||||
]).optional(),
|
||||
available_tools: z.union([
|
||||
z.array(z.string()),
|
||||
z.null()
|
||||
]).optional(),
|
||||
type: z.literal('platform')
|
||||
}),
|
||||
z.object({
|
||||
|
|
@ -171,6 +179,10 @@ export const zGooseExtension = z.union([
|
|||
z.boolean(),
|
||||
z.null()
|
||||
]).optional(),
|
||||
available_tools: z.union([
|
||||
z.array(z.string()),
|
||||
z.null()
|
||||
]).optional(),
|
||||
type: z.literal('mcp')
|
||||
})
|
||||
]);
|
||||
|
|
@ -1464,6 +1476,10 @@ export const zRecipeExtensionDto = z.union([
|
|||
z.boolean(),
|
||||
z.null()
|
||||
]).optional(),
|
||||
available_tools: z.union([
|
||||
z.array(z.string()),
|
||||
z.null()
|
||||
]).optional(),
|
||||
type: z.literal('builtin')
|
||||
}),
|
||||
z.object({
|
||||
|
|
@ -1480,6 +1496,10 @@ export const zRecipeExtensionDto = z.union([
|
|||
z.boolean(),
|
||||
z.null()
|
||||
]).optional(),
|
||||
available_tools: z.union([
|
||||
z.array(z.string()),
|
||||
z.null()
|
||||
]).optional(),
|
||||
type: z.literal('platform')
|
||||
}),
|
||||
z.object({
|
||||
|
|
@ -1504,6 +1524,10 @@ export const zRecipeExtensionDto = z.union([
|
|||
z.boolean(),
|
||||
z.null()
|
||||
]).optional(),
|
||||
available_tools: z.union([
|
||||
z.array(z.string()),
|
||||
z.null()
|
||||
]).optional(),
|
||||
type: z.literal('stdio')
|
||||
}),
|
||||
z.object({
|
||||
|
|
@ -1528,6 +1552,10 @@ export const zRecipeExtensionDto = z.union([
|
|||
z.boolean(),
|
||||
z.null()
|
||||
]).optional(),
|
||||
available_tools: z.union([
|
||||
z.array(z.string()),
|
||||
z.null()
|
||||
]).optional(),
|
||||
type: z.literal('streamable_http')
|
||||
})
|
||||
]);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue