Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions agent/skills/frontmatter_source_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,33 @@ func TestValidateCompatibility_Unset_ReturnsNil(t *testing.T) {
}
}

func TestExportedFieldValidators(t *testing.T) {
for _, name := range []string{"my-skill", "a", "a1"} {
if err := skills.ValidateName(name); err != nil {
t.Errorf("ValidateName(%q) = %v", name, err)
}
}
for _, name := range []string{"", " ", "-a", "a-", "a--b", "Abc", strings.Repeat("a", 65)} {
if err := skills.ValidateName(name); err == nil {
t.Errorf("ValidateName(%q) succeeded", name)
}
}
if err := skills.ValidateDescription("ok"); err != nil {
t.Errorf("ValidateDescription: %v", err)
}
for _, d := range []string{"", " ", strings.Repeat("x", 1025)} {
if err := skills.ValidateDescription(d); err == nil {
t.Errorf("ValidateDescription(len %d) succeeded", len(d))
}
}
if err := skills.ValidateCompatibility(""); err != nil {
t.Errorf("ValidateCompatibility empty: %v", err)
}
if err := skills.ValidateCompatibility(strings.Repeat("x", 501)); err == nil {
t.Error("ValidateCompatibility accepted 501 characters")
}
}

func TestFrontmatter_UnicodeLengthLimits(t *testing.T) {
for _, tc := range []struct {
name string
Expand Down
22 changes: 13 additions & 9 deletions agent/skills/skills.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,13 @@ type Frontmatter struct {
// Validate validates the frontmatter according to the Agent Skills specification.
// Description and compatibility limits count UTF-16 code units.
func (f Frontmatter) Validate() error {
if err := validateName(f.Name); err != nil {
if err := ValidateName(f.Name); err != nil {
return err
}
if err := validateDescription(f.Description); err != nil {
if err := ValidateDescription(f.Description); err != nil {
return err
}
if err := validateCompatibility(f.Compatibility); err != nil {
if err := ValidateCompatibility(f.Compatibility); err != nil {
return err
}
return nil
Expand Down Expand Up @@ -122,8 +122,10 @@ type Script struct {
// The runner is responsible for decoding and validating its argument format.
type ScriptRunner func(context.Context, *Skill, *Script, json.RawMessage) (any, error)

// validateName validates a skill name.
func validateName(name string) error {
// ValidateName validates a skill name. It must be non-blank, at most 64
// characters, and use only lowercase letters, digits, and single interior
// hyphens.
func ValidateName(name string) error {
if strings.TrimSpace(name) == "" {
return fmt.Errorf("skill name is required")
}
Expand Down Expand Up @@ -157,8 +159,9 @@ func isValidSkillName(name string) bool {
return !previousHyphen
}

// validateDescription validates a skill description.
func validateDescription(description string) error {
// ValidateDescription validates a skill description. It must be non-blank and
// at most 1024 UTF-16 code units.
func ValidateDescription(description string) error {
if strings.TrimSpace(description) == "" {
return fmt.Errorf("skill description is required")
}
Expand All @@ -168,8 +171,9 @@ func validateDescription(description string) error {
return nil
}

// validateCompatibility validates an optional compatibility value.
func validateCompatibility(compatibility string) error {
// ValidateCompatibility validates an optional compatibility value. An empty
// value is valid; otherwise it must be at most 500 UTF-16 code units.
func ValidateCompatibility(compatibility string) error {
if utf16Length(compatibility) > maxCompatibilityLength {
return fmt.Errorf("skill compatibility must be %d characters or fewer", maxCompatibilityLength)
}
Expand Down
Loading