use std::collections::{HashMap, HashSet};
use std::path::Path;
use yaml_rust2::parser::{Event, Parser};
use yaml_rust2::scanner::ScanError;
use crate::capability::{
CAPABILITY_KEY, RESERVED_PREFIX, is_valid_token_char, parse_capability_value,
};
use crate::{CapabilitySet, ToolsetFormatError};
const MAX_FILE_BYTES: u64 = 256 * 1024;
const MAX_DEPTH: usize = 8;
const NAME_MAX_LEN: usize = 64;
const DESC_MAX_LEN: usize = 1024;
const COMPAT_MAX_LEN: usize = 500;
#[non_exhaustive]
#[derive(Clone, Debug)]
pub struct Toolset {
pub name: String,
pub description: String,
pub license: Option<String>,
pub compatibility: Option<String>,
pub metadata: HashMap<String, String>,
pub allowed_tools: Vec<String>,
pub capabilities: CapabilitySet,
pub instructions: String,
}
pub fn parse_toolset(dir: &Path) -> Result<Toolset, ToolsetFormatError> {
let dir_name = dir
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("")
.to_owned();
let toolset_md_path = dir.join("TOOLSET.md");
let raw_bytes = read_size_capped(&toolset_md_path)?;
let content = std::str::from_utf8(&raw_bytes).map_err(|_| ToolsetFormatError::NotUtf8)?;
let (frontmatter_yaml, body) = split_frontmatter(content)?;
let fm = parse_frontmatter(frontmatter_yaml)?;
validate_name(&fm.name, &dir_name)?;
validate_description(&fm.description)?;
if let Some(ref compat) = fm.compatibility {
validate_compatibility(compat)?;
}
let capabilities = extract_capabilities(&fm.metadata)?;
Ok(Toolset {
name: fm.name.unwrap_or_default(),
description: fm.description.unwrap_or_default(),
license: fm.license,
compatibility: fm.compatibility,
metadata: fm.metadata,
allowed_tools: fm.allowed_tools,
capabilities,
instructions: body.to_owned(),
})
}
#[derive(Debug, Default)]
struct Frontmatter {
name: Option<String>,
description: Option<String>,
license: Option<String>,
compatibility: Option<String>,
metadata: HashMap<String, String>,
allowed_tools: Vec<String>,
}
fn read_size_capped(path: &Path) -> Result<Vec<u8>, ToolsetFormatError> {
use std::io::Read;
let file = std::fs::File::open(path).map_err(|e| ToolsetFormatError::Io {
detail: e.to_string(),
})?;
let metadata = file.metadata().map_err(|e| ToolsetFormatError::Io {
detail: e.to_string(),
})?;
let file_size = metadata.len();
if file_size > MAX_FILE_BYTES {
return Err(ToolsetFormatError::ToolsetFileTooLarge {
size: file_size,
cap: MAX_FILE_BYTES,
});
}
let limit = usize::try_from(MAX_FILE_BYTES).unwrap_or(usize::MAX) + 1;
let mut buf = Vec::with_capacity(usize::try_from(file_size).unwrap_or(0).min(limit));
file.take(MAX_FILE_BYTES + 1)
.read_to_end(&mut buf)
.map_err(|e| ToolsetFormatError::Io {
detail: e.to_string(),
})?;
if buf.len() > usize::try_from(MAX_FILE_BYTES).unwrap_or(usize::MAX) {
return Err(ToolsetFormatError::ToolsetFileTooLarge {
size: u64::try_from(buf.len()).unwrap_or(u64::MAX),
cap: MAX_FILE_BYTES,
});
}
Ok(buf)
}
fn split_frontmatter(content: &str) -> Result<(&str, &str), ToolsetFormatError> {
let after_open = content
.strip_prefix("---\n")
.or_else(|| content.strip_prefix("---\r\n"))
.ok_or(ToolsetFormatError::MissingFrontmatter)?;
if let Some(close_pos) = find_closing_fence(after_open) {
let frontmatter = &after_open[..close_pos];
let rest = &after_open[close_pos..];
let body = rest
.strip_prefix("---\n")
.or_else(|| rest.strip_prefix("---\r\n"))
.or_else(|| rest.strip_prefix("---"))
.unwrap_or(rest);
Ok((frontmatter, body))
} else {
Ok((after_open, ""))
}
}
fn find_closing_fence(s: &str) -> Option<usize> {
let mut offset = 0;
for line in s.lines() {
let line_start = offset;
let line_bytes = line.len();
if line == "---" {
return Some(line_start);
}
offset += line_bytes;
if s.as_bytes().get(offset) == Some(&b'\r') {
offset += 1; }
if s.as_bytes().get(offset) == Some(&b'\n') {
offset += 1; }
}
None
}
fn parse_frontmatter(yaml_str: &str) -> Result<Frontmatter, ToolsetFormatError> {
let mut receiver = FrontmatterReceiver::new();
let mut parser = Parser::new_from_str(yaml_str);
loop {
let (ev, _) = parser.next_token().map_err(|e: ScanError| {
ToolsetFormatError::MalformedFrontmatter {
detail: e.to_string(),
}
})?;
let is_stream_end = ev == Event::StreamEnd;
receiver.process_event(ev);
if let Some(err) = receiver.error {
return Err(err);
}
if is_stream_end {
break;
}
}
Ok(receiver.frontmatter)
}
#[derive(Debug)]
enum ReceiverState {
Init,
TopLevelMapping { current_key: Option<String> },
MetadataMapping { current_key: Option<String> },
SkippingUnknown {
return_depth: usize,
},
Done,
}
struct FrontmatterReceiver {
frontmatter: Frontmatter,
state: ReceiverState,
depth: usize,
top_level_seen_keys: HashSet<String>,
metadata_seen_keys: HashSet<String>,
error: Option<ToolsetFormatError>,
}
impl FrontmatterReceiver {
fn new() -> Self {
Self {
frontmatter: Frontmatter::default(),
state: ReceiverState::Init,
depth: 0,
top_level_seen_keys: HashSet::new(),
metadata_seen_keys: HashSet::new(),
error: None,
}
}
fn set_error(&mut self, err: ToolsetFormatError) {
if self.error.is_none() {
self.error = Some(err);
}
self.state = ReceiverState::Done;
}
#[allow(
clippy::too_many_lines,
reason = "single large match over YAML event types"
)]
fn process_event(&mut self, ev: Event) {
if self.error.is_some() {
return;
}
match &ev {
Event::Alias(_) => {
self.set_error(ToolsetFormatError::YamlAnchorsForbidden);
return;
}
Event::MappingStart(anchor_id, _) | Event::SequenceStart(anchor_id, _) => {
if *anchor_id != 0 {
self.set_error(ToolsetFormatError::YamlAnchorsForbidden);
return;
}
self.depth += 1;
if self.depth > MAX_DEPTH {
self.set_error(ToolsetFormatError::FrontmatterTooDeep);
return;
}
}
Event::MappingEnd | Event::SequenceEnd => {
self.depth = self.depth.saturating_sub(1);
}
Event::Scalar(_, _, anchor_id, _) if *anchor_id != 0 => {
self.set_error(ToolsetFormatError::YamlAnchorsForbidden);
return;
}
_ => {}
}
match &self.state {
ReceiverState::Done => {}
ReceiverState::SkippingUnknown { return_depth } => {
let return_depth = *return_depth;
match ev {
Event::MappingEnd | Event::SequenceEnd if self.depth == return_depth => {
self.state = ReceiverState::TopLevelMapping { current_key: None };
}
_ => {}
}
}
ReceiverState::Init => match ev {
Event::StreamStart
| Event::DocumentStart
| Event::DocumentEnd
| Event::StreamEnd => {
}
Event::MappingStart(_, _) => {
self.state = ReceiverState::TopLevelMapping { current_key: None };
}
_ => {
self.set_error(ToolsetFormatError::MalformedFrontmatter {
detail: "frontmatter must be a YAML mapping".to_owned(),
});
}
},
ReceiverState::TopLevelMapping { current_key } => {
match ev {
Event::MappingEnd => {
self.state = ReceiverState::Done;
}
Event::Scalar(value, _, _, _) if current_key.is_none() => {
let key = value;
if !self.top_level_seen_keys.insert(key.clone()) {
self.set_error(ToolsetFormatError::DuplicateKey { key });
return;
}
self.state = ReceiverState::TopLevelMapping {
current_key: Some(key),
};
}
Event::Scalar(value, _, _, _) => {
let key = match current_key.as_deref() {
Some(k) => k,
None => {
self.set_error(ToolsetFormatError::MalformedFrontmatter {
detail: "unexpected scalar value without a key".to_owned(),
});
return;
}
};
let val = value;
match key {
"name" => self.frontmatter.name = Some(val),
"description" => self.frontmatter.description = Some(val),
"license" => self.frontmatter.license = Some(val),
"compatibility" => self.frontmatter.compatibility = Some(val),
"allowed-tools" => {
self.frontmatter.allowed_tools =
val.split_ascii_whitespace().map(str::to_owned).collect();
}
_other => {}
}
self.state = ReceiverState::TopLevelMapping { current_key: None };
}
Event::MappingStart(_, _) if current_key.as_deref() == Some("metadata") => {
self.state = ReceiverState::MetadataMapping { current_key: None };
}
Event::MappingStart(_, _) | Event::SequenceStart(_, _) => {
self.state = ReceiverState::SkippingUnknown {
return_depth: self.depth - 1,
};
}
_ => {
}
}
}
ReceiverState::MetadataMapping { current_key } => {
match ev {
Event::MappingEnd => {
self.state = ReceiverState::TopLevelMapping { current_key: None };
}
Event::Scalar(value, _, _, _) if current_key.is_none() => {
let key = value;
if !self.metadata_seen_keys.insert(key.clone()) {
self.set_error(ToolsetFormatError::DuplicateKey { key });
return;
}
if key.starts_with(RESERVED_PREFIX) && key != CAPABILITY_KEY {
self.set_error(ToolsetFormatError::ReservedMetadataKey { key });
return;
}
self.state = ReceiverState::MetadataMapping {
current_key: Some(key),
};
}
Event::Scalar(value, _, _, _) => {
let key = match current_key.as_ref() {
Some(k) => k.clone(),
None => {
self.set_error(ToolsetFormatError::MalformedFrontmatter {
detail: "unexpected metadata value scalar without a key"
.to_owned(),
});
return;
}
};
self.frontmatter.metadata.insert(key, value);
self.state = ReceiverState::MetadataMapping { current_key: None };
}
Event::MappingStart(_, _) | Event::SequenceStart(_, _) => {
let key = current_key.as_deref().unwrap_or("");
let detail = if key == CAPABILITY_KEY {
"stellar-agent-capabilities value must be a string, not a mapping or sequence".to_owned()
} else {
format!("metadata value for key '{key}' must be a string")
};
if key == CAPABILITY_KEY {
self.set_error(ToolsetFormatError::CapabilityManifestMalformed {
detail,
});
} else {
self.set_error(ToolsetFormatError::MalformedFrontmatter { detail });
}
}
_ => {}
}
}
}
}
}
fn validate_name(name: &Option<String>, dir_name: &str) -> Result<(), ToolsetFormatError> {
let n = name.as_deref().ok_or(ToolsetFormatError::MissingName)?;
if n.is_empty() {
return Err(ToolsetFormatError::NameEmpty);
}
if n.chars().count() > NAME_MAX_LEN {
return Err(ToolsetFormatError::NameTooLong);
}
if !n.chars().all(is_valid_token_char) {
return Err(ToolsetFormatError::NameInvalidChar);
}
if n.starts_with('-') || n.ends_with('-') {
return Err(ToolsetFormatError::NameLeadingTrailingHyphen);
}
if n.contains("--") {
return Err(ToolsetFormatError::NameConsecutiveHyphens);
}
if n != dir_name {
return Err(ToolsetFormatError::NameDirMismatch {
name: n.to_owned(),
dir: dir_name.to_owned(),
});
}
Ok(())
}
fn validate_description(desc: &Option<String>) -> Result<(), ToolsetFormatError> {
let d = desc
.as_deref()
.ok_or(ToolsetFormatError::MissingDescription)?;
if d.trim().is_empty() {
return Err(ToolsetFormatError::DescriptionEmpty);
}
if d.chars().count() > DESC_MAX_LEN {
return Err(ToolsetFormatError::DescriptionTooLong);
}
Ok(())
}
fn validate_compatibility(compat: &str) -> Result<(), ToolsetFormatError> {
if compat.chars().count() > COMPAT_MAX_LEN {
return Err(ToolsetFormatError::CompatibilityTooLong);
}
Ok(())
}
fn extract_capabilities(
metadata: &HashMap<String, String>,
) -> Result<CapabilitySet, ToolsetFormatError> {
match metadata.get(CAPABILITY_KEY) {
None => Ok(CapabilitySet::empty()),
Some(value) => parse_capability_value(value),
}
}
#[cfg(test)]
mod tests {
#![allow(
clippy::unwrap_used,
clippy::expect_used,
reason = "test-only; panics acceptable in unit tests"
)]
use super::*;
#[test]
fn split_minimal_frontmatter() {
let content = "---\nname: foo\n---\nbody";
let (fm, body) = split_frontmatter(content).unwrap();
assert!(fm.contains("name: foo"), "fm={fm:?}");
assert_eq!(body, "body");
}
#[test]
fn split_no_closing_fence_body_empty() {
let content = "---\nname: foo\n";
let (fm, body) = split_frontmatter(content).unwrap();
assert!(fm.contains("name: foo"));
assert_eq!(body, "");
}
#[test]
fn split_missing_fence_error() {
let err = split_frontmatter("no fence here").unwrap_err();
assert!(matches!(err, ToolsetFormatError::MissingFrontmatter));
}
#[test]
fn name_valid_simple() {
validate_name(&Some("my-toolset".to_owned()), "my-toolset").unwrap();
}
#[test]
fn name_missing() {
let err = validate_name(&None, "foo").unwrap_err();
assert!(matches!(err, ToolsetFormatError::MissingName));
}
#[test]
fn name_empty() {
let err = validate_name(&Some(String::new()), "").unwrap_err();
assert!(matches!(err, ToolsetFormatError::NameEmpty));
}
#[test]
fn name_too_long() {
let long = "a".repeat(65);
let err = validate_name(&Some(long.clone()), &long).unwrap_err();
assert!(matches!(err, ToolsetFormatError::NameTooLong));
}
#[test]
fn name_uppercase_refused() {
let err = validate_name(&Some("MyToolset".to_owned()), "MyToolset").unwrap_err();
assert!(matches!(err, ToolsetFormatError::NameInvalidChar));
}
#[test]
fn name_leading_hyphen_refused() {
let err = validate_name(&Some("-toolset".to_owned()), "-toolset").unwrap_err();
assert!(matches!(err, ToolsetFormatError::NameLeadingTrailingHyphen));
}
#[test]
fn name_trailing_hyphen_refused() {
let err = validate_name(&Some("toolset-".to_owned()), "toolset-").unwrap_err();
assert!(matches!(err, ToolsetFormatError::NameLeadingTrailingHyphen));
}
#[test]
fn name_consecutive_hyphens_refused() {
let err = validate_name(&Some("my--toolset".to_owned()), "my--toolset").unwrap_err();
assert!(matches!(err, ToolsetFormatError::NameConsecutiveHyphens));
}
#[test]
fn name_dir_mismatch_refused() {
let err = validate_name(&Some("my-toolset".to_owned()), "other-toolset").unwrap_err();
assert!(matches!(err, ToolsetFormatError::NameDirMismatch { .. }));
}
#[test]
fn name_unicode_homoglyph_dir_refused() {
let cyrillic_dir = "my-sk\u{0456}ll"; let err = validate_name(&Some("my-toolset".to_owned()), cyrillic_dir).unwrap_err();
assert!(matches!(err, ToolsetFormatError::NameDirMismatch { .. }));
}
#[test]
fn description_valid() {
validate_description(&Some("A useful toolset.".to_owned())).unwrap();
}
#[test]
fn description_missing() {
let err = validate_description(&None).unwrap_err();
assert!(matches!(err, ToolsetFormatError::MissingDescription));
}
#[test]
fn description_empty() {
let err = validate_description(&Some(String::new())).unwrap_err();
assert!(matches!(err, ToolsetFormatError::DescriptionEmpty));
}
#[test]
fn description_whitespace_only() {
let err = validate_description(&Some(" \t\n ".to_owned())).unwrap_err();
assert!(matches!(err, ToolsetFormatError::DescriptionEmpty));
}
#[test]
fn description_too_long() {
let long = "a".repeat(1025);
let err = validate_description(&Some(long)).unwrap_err();
assert!(matches!(err, ToolsetFormatError::DescriptionTooLong));
}
#[test]
fn compatibility_valid() {
validate_compatibility("Requires Python 3.14+").unwrap();
}
#[test]
fn compatibility_too_long() {
let long = "x".repeat(501);
let err = validate_compatibility(&long).unwrap_err();
assert!(matches!(err, ToolsetFormatError::CompatibilityTooLong));
}
#[test]
fn alias_bomb_refused_pre_expansion() {
let yaml = "a: &a []\nb: *a\n";
let err = parse_frontmatter(yaml).unwrap_err();
assert!(
matches!(err, ToolsetFormatError::YamlAnchorsForbidden),
"expected YamlAnchorsForbidden, got {err:?}"
);
}
#[test]
fn deep_nesting_refused() {
let yaml = "a:\n b:\n c:\n d:\n e:\n f:\n g:\n h:\n i: deep\n";
let err = parse_frontmatter(yaml).unwrap_err();
assert!(
matches!(err, ToolsetFormatError::FrontmatterTooDeep),
"expected FrontmatterTooDeep, got {err:?}"
);
}
#[test]
fn duplicate_top_level_key_refused() {
let yaml = "name: foo\ndescription: bar\nname: baz\n";
let err = parse_frontmatter(yaml).unwrap_err();
assert!(
matches!(err, ToolsetFormatError::DuplicateKey { .. }),
"expected DuplicateKey, got {err:?}"
);
}
#[test]
fn duplicate_metadata_key_refused() {
let yaml = "name: foo\ndescription: bar\nmetadata:\n author: a\n author: b\n";
let err = parse_frontmatter(yaml).unwrap_err();
assert!(
matches!(err, ToolsetFormatError::DuplicateKey { .. }),
"expected DuplicateKey, got {err:?}"
);
}
#[test]
fn capability_non_string_refused() {
let yaml =
"name: foo\ndescription: bar\nmetadata:\n stellar-agent-capabilities:\n - item\n";
let err = parse_frontmatter(yaml).unwrap_err();
assert!(
matches!(err, ToolsetFormatError::CapabilityManifestMalformed { .. }),
"expected CapabilityManifestMalformed, got {err:?}"
);
}
#[test]
fn reserved_metadata_key_refused() {
let yaml = "name: foo\ndescription: bar\nmetadata:\n stellar-agent-policy: x\n";
let err = parse_frontmatter(yaml).unwrap_err();
assert!(
matches!(err, ToolsetFormatError::ReservedMetadataKey { .. }),
"expected ReservedMetadataKey, got {err:?}"
);
}
#[test]
fn recognised_capability_key_not_reserved() {
let yaml =
"name: foo\ndescription: bar\nmetadata:\n stellar-agent-capabilities: read-balance\n";
parse_frontmatter(yaml).unwrap();
}
const SKIP_STATE_YAML: &str = "name: test-toolset\ndescription: A test.\nextended-info:\n name: inner\nmetadata:\n stellar-agent-capabilities: read-balance\n";
#[test]
fn skip_state_unknown_nested_map_before_metadata() {
let fm = parse_frontmatter(SKIP_STATE_YAML).unwrap();
assert!(
fm.metadata.contains_key("stellar-agent-capabilities"),
"metadata must be populated after unknown nested map: {fm:?}"
);
}
#[test]
fn skip_state_inner_key_not_inserted_into_top_level_seen() {
let result = parse_frontmatter(SKIP_STATE_YAML);
assert!(
!matches!(result, Err(ToolsetFormatError::DuplicateKey { .. })),
"inner key must not produce false DuplicateKey: {result:?}"
);
}
#[test]
fn skip_state_alias_inside_skipped_subtree_still_refused() {
let yaml = "name: test-toolset\nextended-info:\n x: &a val\n y: *a\n";
let err = parse_frontmatter(yaml).unwrap_err();
assert!(
matches!(err, ToolsetFormatError::YamlAnchorsForbidden),
"expected YamlAnchorsForbidden inside skipped subtree, got {err:?}"
);
}
#[test]
fn no_panic_truncated_input() {
let yaml = "name: foo\ndescription: \"\n";
let result = parse_frontmatter(yaml);
let _ = result;
}
#[test]
fn no_panic_garbage_bytes_via_split() {
let content = "---\n\x00\x01\x02\x03\n---\n";
let result = split_frontmatter(content);
let _ = result;
}
#[test]
fn no_panic_empty_frontmatter() {
let result = parse_frontmatter("");
let _ = result;
}
#[test]
fn block_sequence_compact_deep_refused_no_overflow() {
let levels = 10_000_usize;
let mut yaml = String::with_capacity(levels * 2 + 4);
for _ in 0..levels {
yaml.push_str("- ");
}
yaml.push('z');
let err = parse_frontmatter(&yaml).unwrap_err();
assert!(
matches!(
err,
ToolsetFormatError::FrontmatterTooDeep
| ToolsetFormatError::MalformedFrontmatter { .. }
),
"expected FrontmatterTooDeep or MalformedFrontmatter for compact block sequence, \
got {err:?}"
);
}
#[test]
fn block_sequence_deep_refused_via_parse_toolset_no_overflow() {
use std::io::Write;
let tmp = tempfile::TempDir::new().unwrap();
let toolset_dir = tmp.path().join("test-toolset");
std::fs::create_dir_all(&toolset_dir).unwrap();
let mut f = std::fs::File::create(toolset_dir.join("TOOLSET.md")).unwrap();
let levels = 10_000_usize;
let mut chain = String::with_capacity(levels * 2 + 4);
for _ in 0..levels {
chain.push_str("- ");
}
chain.push('z');
write!(f, "---\n{chain}\n---\n").unwrap();
let result = parse_toolset(&toolset_dir);
assert!(
result.is_err(),
"deeply nested compact block sequence must return Err, got Ok"
);
}
#[test]
fn block_mapping_compact_deep_refused_no_overflow() {
let levels = 10_000_usize;
let mut yaml = String::with_capacity(levels * 4);
for i in 0..levels {
let indent = " ".repeat(i);
yaml.push_str(&format!("{indent}k:\n"));
}
yaml.push_str(&format!("{} v", " ".repeat(levels)));
let result = parse_frontmatter(&yaml);
assert!(
result.is_err(),
"deeply nested block mapping chain must return Err, got Ok"
);
}
#[test]
fn flow_sequence_deep_refused_no_overflow() {
let deep: String = "[".repeat(60_000);
let result = parse_frontmatter(&deep);
assert!(
result.is_err(),
"deeply nested flow sequence must return Err, got Ok"
);
}
#[test]
fn flow_mapping_deep_refused_no_overflow() {
let levels = 20_000_usize;
let deep: String = "{a:".repeat(levels);
let result = parse_frontmatter(&deep);
assert!(
result.is_err(),
"deeply nested flow mapping must return Err, got Ok"
);
}
}