Files
clawgo/pkg/tools/tool_allowlist_groups.go

182 lines
4.3 KiB
Go

package tools
import (
"sort"
"strings"
)
type ToolAllowlistGroup struct {
Name string `json:"name"`
Description string `json:"description"`
Aliases []string `json:"aliases,omitempty"`
Tools []string `json:"tools"`
}
var defaultToolAllowlistGroups = []ToolAllowlistGroup{
{
Name: "files_read",
Description: "Read-only workspace file tools",
Aliases: []string{"file_read", "readonly_files"},
Tools: []string{"read_file", "list_dir", "repo_map", "read"},
},
{
Name: "files_write",
Description: "Workspace file modification tools",
Aliases: []string{"file_write"},
Tools: []string{"write_file", "edit_file", "write", "edit"},
},
{
Name: "memory_read",
Description: "Read-only memory tools",
Aliases: []string{"mem_read"},
Tools: []string{"memory_search", "memory_get"},
},
{
Name: "memory_write",
Description: "Memory write tools",
Aliases: []string{"mem_write"},
Tools: []string{"memory_write"},
},
{
Name: "memory_all",
Description: "All memory tools",
Aliases: []string{"memory"},
Tools: []string{"memory_search", "memory_get", "memory_write"},
},
{
Name: "subagents",
Description: "Subagent management tools",
Aliases: []string{"subagent", "agent_runtime"},
Tools: []string{"spawn", "subagents", "subagent_profile"},
},
{
Name: "skills",
Description: "Skill script execution tools",
Aliases: []string{"skill", "skill_scripts"},
Tools: []string{"skill_exec"},
},
}
func ToolAllowlistGroups() []ToolAllowlistGroup {
out := make([]ToolAllowlistGroup, 0, len(defaultToolAllowlistGroups))
for _, g := range defaultToolAllowlistGroups {
item := ToolAllowlistGroup{
Name: strings.ToLower(strings.TrimSpace(g.Name)),
Description: strings.TrimSpace(g.Description),
Aliases: normalizeAllowlistTokenList(g.Aliases),
Tools: normalizeAllowlistTokenList(g.Tools),
}
if item.Name == "" {
continue
}
out = append(out, item)
}
sort.Slice(out, func(i, j int) bool { return out[i].Name < out[j].Name })
return out
}
func ExpandToolAllowlistEntries(entries []string) []string {
if len(entries) == 0 {
return nil
}
groups := ToolAllowlistGroups()
resolved := make(map[string][]string, len(groups))
for _, g := range groups {
if g.Name != "" {
resolved[g.Name] = g.Tools
}
for _, alias := range g.Aliases {
resolved[alias] = g.Tools
}
}
out := map[string]struct{}{}
for _, raw := range entries {
token := normalizeAllowlistToken(raw)
if token == "" {
continue
}
if token == "*" || token == "all" {
out[token] = struct{}{}
continue
}
if groupName, isGroupToken := parseAllowlistGroupToken(token); isGroupToken {
if members, ok := resolved[groupName]; ok {
for _, name := range members {
out[name] = struct{}{}
}
continue
}
// Keep unknown group token as-is to preserve user intent and avoid silent mutation.
out[token] = struct{}{}
continue
}
if members, ok := resolved[token]; ok {
for _, name := range members {
out[name] = struct{}{}
}
continue
}
out[token] = struct{}{}
}
if len(out) == 0 {
return nil
}
result := make([]string, 0, len(out))
for name := range out {
result = append(result, name)
}
sort.Strings(result)
return result
}
func parseAllowlistGroupToken(token string) (string, bool) {
token = normalizeAllowlistToken(token)
if token == "" {
return "", false
}
if strings.HasPrefix(token, "group:") {
v := normalizeAllowlistToken(strings.TrimPrefix(token, "group:"))
if v != "" {
return v, true
}
return "", false
}
if strings.HasPrefix(token, "@") {
v := normalizeAllowlistToken(strings.TrimPrefix(token, "@"))
if v != "" {
return v, true
}
return "", false
}
return "", false
}
func normalizeAllowlistTokenList(in []string) []string {
if len(in) == 0 {
return nil
}
seen := map[string]struct{}{}
out := make([]string, 0, len(in))
for _, item := range in {
v := normalizeAllowlistToken(item)
if v == "" {
continue
}
if _, ok := seen[v]; ok {
continue
}
seen[v] = struct{}{}
out = append(out, v)
}
sort.Strings(out)
return out
}
func normalizeAllowlistToken(in string) string {
return strings.ToLower(strings.TrimSpace(in))
}