251 lines
5.8 KiB
Go
251 lines
5.8 KiB
Go
package agent
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"github.com/sipeed/picoclaw/pkg/config"
|
|
agenttools "github.com/sipeed/picoclaw/pkg/tools"
|
|
)
|
|
|
|
type allowlistTestTool struct {
|
|
name string
|
|
}
|
|
|
|
func (t *allowlistTestTool) Name() string { return t.name }
|
|
|
|
func (t *allowlistTestTool) Description() string { return "test tool" }
|
|
|
|
func (t *allowlistTestTool) Parameters() map[string]any {
|
|
return map[string]any{"type": "object"}
|
|
}
|
|
|
|
func (t *allowlistTestTool) Execute(
|
|
_ context.Context,
|
|
_ map[string]any,
|
|
) *agenttools.ToolResult {
|
|
return agenttools.NewToolResult("ok")
|
|
}
|
|
|
|
func TestUnknownAgentToolNames(t *testing.T) {
|
|
workspace := setupWorkspace(t, map[string]string{
|
|
"AGENT.md": `---
|
|
tools: [read_file, web_serach, mcp_github_search]
|
|
---
|
|
# Agent
|
|
`,
|
|
})
|
|
defer cleanupWorkspace(t, workspace)
|
|
|
|
registry := agenttools.NewToolRegistry()
|
|
registry.Register(&allowlistTestTool{name: "read_file"})
|
|
registry.Register(&allowlistTestTool{name: "web_search"})
|
|
|
|
unknown := unknownAgentToolNames(registry, loadAgentDefinition(workspace))
|
|
if len(unknown) != 1 || unknown[0] != "web_serach" {
|
|
t.Fatalf("unknownAgentToolNames() = %v, want [web_serach]", unknown)
|
|
}
|
|
}
|
|
|
|
func TestUnknownAgentToolNamesUsesRegisteredRuntimeTools(t *testing.T) {
|
|
workspace := setupWorkspace(t, map[string]string{
|
|
"AGENT.md": `---
|
|
tools: [serial, reaction, send_tts, load_image, delegate, made_up]
|
|
---
|
|
# Agent
|
|
`,
|
|
})
|
|
defer cleanupWorkspace(t, workspace)
|
|
|
|
registry := agenttools.NewToolRegistry()
|
|
for _, name := range []string{"serial", "reaction", "send_tts", "load_image", "delegate"} {
|
|
registry.Register(&allowlistTestTool{name: name})
|
|
}
|
|
|
|
unknown := unknownAgentToolNames(registry, loadAgentDefinition(workspace))
|
|
if len(unknown) != 1 || unknown[0] != "made_up" {
|
|
t.Fatalf("unknownAgentToolNames() = %v, want [made_up]", unknown)
|
|
}
|
|
}
|
|
|
|
func TestResolveAgentToolPolicyDistinguishesMissingAndEmptyToolsField(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
agentMD string
|
|
wantNil bool
|
|
wantEmpty bool
|
|
}{
|
|
{
|
|
name: "missing tools field allows all tools",
|
|
agentMD: `---
|
|
name: pico
|
|
---
|
|
# Agent
|
|
`,
|
|
wantNil: true,
|
|
},
|
|
{
|
|
name: "explicit empty tools list blocks all tools",
|
|
agentMD: `---
|
|
tools: []
|
|
---
|
|
# Agent
|
|
`,
|
|
wantEmpty: true,
|
|
},
|
|
{
|
|
name: "blank tools field blocks all tools",
|
|
agentMD: `---
|
|
tools:
|
|
---
|
|
# Agent
|
|
`,
|
|
wantEmpty: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
workspace := setupWorkspace(t, map[string]string{
|
|
"AGENT.md": tt.agentMD,
|
|
})
|
|
defer cleanupWorkspace(t, workspace)
|
|
|
|
policy := resolveAgentToolPolicy(loadAgentDefinition(workspace))
|
|
|
|
if tt.wantNil {
|
|
if policy != nil {
|
|
t.Fatalf("resolveAgentToolPolicy() = %v, want nil", policy)
|
|
}
|
|
return
|
|
}
|
|
|
|
if policy == nil {
|
|
t.Fatal("resolveAgentToolPolicy() = nil, want explicit empty allowlist")
|
|
}
|
|
if len(policy.Allow) != 0 {
|
|
t.Fatalf("resolveAgentToolPolicy() = %+v, want empty allowlist", policy)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestResolveAgentToolPolicy_ObjectAllowDeny(t *testing.T) {
|
|
workspace := setupWorkspace(t, map[string]string{
|
|
"AGENT.md": `---
|
|
tools:
|
|
allow:
|
|
- mcp_*
|
|
- web_fetch
|
|
deny:
|
|
- mcp_gpt_researcher_*
|
|
---
|
|
# Agent
|
|
`,
|
|
})
|
|
defer cleanupWorkspace(t, workspace)
|
|
|
|
policy := resolveAgentToolPolicy(loadAgentDefinition(workspace))
|
|
if policy == nil {
|
|
t.Fatal("resolveAgentToolPolicy() = nil, want policy")
|
|
}
|
|
if got, want := policy.Allow, []string{"mcp_*", "web_fetch"}; len(got) != len(want) ||
|
|
got[0] != want[0] || got[1] != want[1] {
|
|
t.Fatalf("allow = %v, want %v", got, want)
|
|
}
|
|
if got, want := policy.Deny, []string{"mcp_gpt_researcher_*"}; len(got) != len(want) || got[0] != want[0] {
|
|
t.Fatalf("deny = %v, want %v", got, want)
|
|
}
|
|
}
|
|
|
|
func TestUnknownAgentMCPServerNames(t *testing.T) {
|
|
workspace := setupWorkspace(t, map[string]string{
|
|
"AGENT.md": `---
|
|
mcpServers: [github, githb]
|
|
---
|
|
# Agent
|
|
`,
|
|
})
|
|
defer cleanupWorkspace(t, workspace)
|
|
|
|
cfg := &config.Config{
|
|
Tools: config.ToolsConfig{
|
|
MCP: config.MCPConfig{
|
|
Servers: map[string]config.MCPServerConfig{
|
|
"github": {Enabled: true},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
unknown := unknownAgentMCPServerNames(cfg, loadAgentDefinition(workspace))
|
|
if len(unknown) != 1 || unknown[0] != "githb" {
|
|
t.Fatalf("unknownAgentMCPServerNames() = %v, want [githb]", unknown)
|
|
}
|
|
}
|
|
|
|
func TestUnknownAgentMCPServerNamesMatchesConfigCaseInsensitively(t *testing.T) {
|
|
workspace := setupWorkspace(t, map[string]string{
|
|
"AGENT.md": `---
|
|
mcpServers: [github, FileSystem, slak]
|
|
---
|
|
# Agent
|
|
`,
|
|
})
|
|
defer cleanupWorkspace(t, workspace)
|
|
|
|
cfg := &config.Config{
|
|
Tools: config.ToolsConfig{
|
|
MCP: config.MCPConfig{
|
|
Servers: map[string]config.MCPServerConfig{
|
|
"GitHub": {Enabled: true},
|
|
"filesystem": {Enabled: true},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
unknown := unknownAgentMCPServerNames(cfg, loadAgentDefinition(workspace))
|
|
if len(unknown) != 1 || unknown[0] != "slak" {
|
|
t.Fatalf("unknownAgentMCPServerNames() = %v, want [slak]", unknown)
|
|
}
|
|
}
|
|
|
|
func TestUnknownDeclarationsIgnoreGlobPatterns(t *testing.T) {
|
|
workspace := setupWorkspace(t, map[string]string{
|
|
"AGENT.md": `---
|
|
tools:
|
|
allow:
|
|
- mcp_*
|
|
- web_*
|
|
- read_file
|
|
mcpServers:
|
|
allow:
|
|
- git*
|
|
- filesystem
|
|
---
|
|
# Agent
|
|
`,
|
|
})
|
|
defer cleanupWorkspace(t, workspace)
|
|
|
|
registry := agenttools.NewToolRegistry()
|
|
registry.Register(&allowlistTestTool{name: "read_file"})
|
|
cfg := &config.Config{
|
|
Tools: config.ToolsConfig{
|
|
MCP: config.MCPConfig{
|
|
Servers: map[string]config.MCPServerConfig{
|
|
"github": {Enabled: true},
|
|
"filesystem": {Enabled: true},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
if unknown := unknownAgentToolNames(registry, loadAgentDefinition(workspace)); len(unknown) != 0 {
|
|
t.Fatalf("unknownAgentToolNames() = %v, want none", unknown)
|
|
}
|
|
if unknown := unknownAgentMCPServerNames(cfg, loadAgentDefinition(workspace)); len(unknown) != 0 {
|
|
t.Fatalf("unknownAgentMCPServerNames() = %v, want none", unknown)
|
|
}
|
|
}
|