Update registerSharedTools to use new WebSearchToolOptions API and add hardware tools (I2C, SPI) from upstream. Accept upstream's new web tools config test.
328 lines
8.6 KiB
Go
328 lines
8.6 KiB
Go
package config
|
|
|
|
import (
|
|
"encoding/json"
|
|
"testing"
|
|
)
|
|
|
|
func TestAgentModelConfig_UnmarshalString(t *testing.T) {
|
|
var m AgentModelConfig
|
|
if err := json.Unmarshal([]byte(`"gpt-4"`), &m); err != nil {
|
|
t.Fatalf("unmarshal string: %v", err)
|
|
}
|
|
if m.Primary != "gpt-4" {
|
|
t.Errorf("Primary = %q, want 'gpt-4'", m.Primary)
|
|
}
|
|
if m.Fallbacks != nil {
|
|
t.Errorf("Fallbacks = %v, want nil", m.Fallbacks)
|
|
}
|
|
}
|
|
|
|
func TestAgentModelConfig_UnmarshalObject(t *testing.T) {
|
|
var m AgentModelConfig
|
|
data := `{"primary": "claude-opus", "fallbacks": ["gpt-4o-mini", "haiku"]}`
|
|
if err := json.Unmarshal([]byte(data), &m); err != nil {
|
|
t.Fatalf("unmarshal object: %v", err)
|
|
}
|
|
if m.Primary != "claude-opus" {
|
|
t.Errorf("Primary = %q, want 'claude-opus'", m.Primary)
|
|
}
|
|
if len(m.Fallbacks) != 2 {
|
|
t.Fatalf("Fallbacks len = %d, want 2", len(m.Fallbacks))
|
|
}
|
|
if m.Fallbacks[0] != "gpt-4o-mini" || m.Fallbacks[1] != "haiku" {
|
|
t.Errorf("Fallbacks = %v", m.Fallbacks)
|
|
}
|
|
}
|
|
|
|
func TestAgentModelConfig_MarshalString(t *testing.T) {
|
|
m := AgentModelConfig{Primary: "gpt-4"}
|
|
data, err := json.Marshal(m)
|
|
if err != nil {
|
|
t.Fatalf("marshal: %v", err)
|
|
}
|
|
if string(data) != `"gpt-4"` {
|
|
t.Errorf("marshal = %s, want '\"gpt-4\"'", string(data))
|
|
}
|
|
}
|
|
|
|
func TestAgentModelConfig_MarshalObject(t *testing.T) {
|
|
m := AgentModelConfig{Primary: "claude-opus", Fallbacks: []string{"haiku"}}
|
|
data, err := json.Marshal(m)
|
|
if err != nil {
|
|
t.Fatalf("marshal: %v", err)
|
|
}
|
|
var result map[string]interface{}
|
|
json.Unmarshal(data, &result)
|
|
if result["primary"] != "claude-opus" {
|
|
t.Errorf("primary = %v", result["primary"])
|
|
}
|
|
}
|
|
|
|
func TestAgentConfig_FullParse(t *testing.T) {
|
|
jsonData := `{
|
|
"agents": {
|
|
"defaults": {
|
|
"workspace": "~/.picoclaw/workspace",
|
|
"model": "glm-4.7",
|
|
"max_tokens": 8192,
|
|
"max_tool_iterations": 20
|
|
},
|
|
"list": [
|
|
{
|
|
"id": "sales",
|
|
"default": true,
|
|
"name": "Sales Bot",
|
|
"model": "gpt-4"
|
|
},
|
|
{
|
|
"id": "support",
|
|
"name": "Support Bot",
|
|
"model": {
|
|
"primary": "claude-opus",
|
|
"fallbacks": ["haiku"]
|
|
},
|
|
"subagents": {
|
|
"allow_agents": ["sales"]
|
|
}
|
|
}
|
|
]
|
|
},
|
|
"bindings": [
|
|
{
|
|
"agent_id": "support",
|
|
"match": {
|
|
"channel": "telegram",
|
|
"account_id": "*",
|
|
"peer": {"kind": "direct", "id": "user123"}
|
|
}
|
|
}
|
|
],
|
|
"session": {
|
|
"dm_scope": "per-peer",
|
|
"identity_links": {
|
|
"john": ["telegram:123", "discord:john#1234"]
|
|
}
|
|
}
|
|
}`
|
|
|
|
cfg := DefaultConfig()
|
|
if err := json.Unmarshal([]byte(jsonData), cfg); err != nil {
|
|
t.Fatalf("unmarshal: %v", err)
|
|
}
|
|
|
|
if len(cfg.Agents.List) != 2 {
|
|
t.Fatalf("agents.list len = %d, want 2", len(cfg.Agents.List))
|
|
}
|
|
|
|
sales := cfg.Agents.List[0]
|
|
if sales.ID != "sales" || !sales.Default || sales.Name != "Sales Bot" {
|
|
t.Errorf("sales = %+v", sales)
|
|
}
|
|
if sales.Model == nil || sales.Model.Primary != "gpt-4" {
|
|
t.Errorf("sales.Model = %+v", sales.Model)
|
|
}
|
|
|
|
support := cfg.Agents.List[1]
|
|
if support.ID != "support" || support.Name != "Support Bot" {
|
|
t.Errorf("support = %+v", support)
|
|
}
|
|
if support.Model == nil || support.Model.Primary != "claude-opus" {
|
|
t.Errorf("support.Model = %+v", support.Model)
|
|
}
|
|
if len(support.Model.Fallbacks) != 1 || support.Model.Fallbacks[0] != "haiku" {
|
|
t.Errorf("support.Model.Fallbacks = %v", support.Model.Fallbacks)
|
|
}
|
|
if support.Subagents == nil || len(support.Subagents.AllowAgents) != 1 {
|
|
t.Errorf("support.Subagents = %+v", support.Subagents)
|
|
}
|
|
|
|
if len(cfg.Bindings) != 1 {
|
|
t.Fatalf("bindings len = %d, want 1", len(cfg.Bindings))
|
|
}
|
|
binding := cfg.Bindings[0]
|
|
if binding.AgentID != "support" || binding.Match.Channel != "telegram" {
|
|
t.Errorf("binding = %+v", binding)
|
|
}
|
|
if binding.Match.Peer == nil || binding.Match.Peer.Kind != "direct" || binding.Match.Peer.ID != "user123" {
|
|
t.Errorf("binding.Match.Peer = %+v", binding.Match.Peer)
|
|
}
|
|
|
|
if cfg.Session.DMScope != "per-peer" {
|
|
t.Errorf("Session.DMScope = %q", cfg.Session.DMScope)
|
|
}
|
|
if len(cfg.Session.IdentityLinks) != 1 {
|
|
t.Errorf("Session.IdentityLinks = %v", cfg.Session.IdentityLinks)
|
|
}
|
|
links := cfg.Session.IdentityLinks["john"]
|
|
if len(links) != 2 {
|
|
t.Errorf("john links = %v", links)
|
|
}
|
|
}
|
|
|
|
func TestConfig_BackwardCompat_NoAgentsList(t *testing.T) {
|
|
jsonData := `{
|
|
"agents": {
|
|
"defaults": {
|
|
"workspace": "~/.picoclaw/workspace",
|
|
"model": "glm-4.7",
|
|
"max_tokens": 8192,
|
|
"max_tool_iterations": 20
|
|
}
|
|
}
|
|
}`
|
|
|
|
cfg := DefaultConfig()
|
|
if err := json.Unmarshal([]byte(jsonData), cfg); err != nil {
|
|
t.Fatalf("unmarshal: %v", err)
|
|
}
|
|
|
|
if len(cfg.Agents.List) != 0 {
|
|
t.Errorf("agents.list should be empty for backward compat, got %d", len(cfg.Agents.List))
|
|
}
|
|
if len(cfg.Bindings) != 0 {
|
|
t.Errorf("bindings should be empty, got %d", len(cfg.Bindings))
|
|
}
|
|
}
|
|
|
|
// TestDefaultConfig_HeartbeatEnabled verifies heartbeat is enabled by default
|
|
func TestDefaultConfig_HeartbeatEnabled(t *testing.T) {
|
|
cfg := DefaultConfig()
|
|
|
|
if !cfg.Heartbeat.Enabled {
|
|
t.Error("Heartbeat should be enabled by default")
|
|
}
|
|
}
|
|
|
|
// TestDefaultConfig_WorkspacePath verifies workspace path is correctly set
|
|
func TestDefaultConfig_WorkspacePath(t *testing.T) {
|
|
cfg := DefaultConfig()
|
|
|
|
if cfg.Agents.Defaults.Workspace == "" {
|
|
t.Error("Workspace should not be empty")
|
|
}
|
|
}
|
|
|
|
// TestDefaultConfig_Model verifies model is set
|
|
func TestDefaultConfig_Model(t *testing.T) {
|
|
cfg := DefaultConfig()
|
|
|
|
if cfg.Agents.Defaults.Model == "" {
|
|
t.Error("Model should not be empty")
|
|
}
|
|
}
|
|
|
|
// TestDefaultConfig_MaxTokens verifies max tokens has default value
|
|
func TestDefaultConfig_MaxTokens(t *testing.T) {
|
|
cfg := DefaultConfig()
|
|
|
|
if cfg.Agents.Defaults.MaxTokens == 0 {
|
|
t.Error("MaxTokens should not be zero")
|
|
}
|
|
}
|
|
|
|
// TestDefaultConfig_MaxToolIterations verifies max tool iterations has default value
|
|
func TestDefaultConfig_MaxToolIterations(t *testing.T) {
|
|
cfg := DefaultConfig()
|
|
|
|
if cfg.Agents.Defaults.MaxToolIterations == 0 {
|
|
t.Error("MaxToolIterations should not be zero")
|
|
}
|
|
}
|
|
|
|
// TestDefaultConfig_Temperature verifies temperature has default value
|
|
func TestDefaultConfig_Temperature(t *testing.T) {
|
|
cfg := DefaultConfig()
|
|
|
|
if cfg.Agents.Defaults.Temperature == 0 {
|
|
t.Error("Temperature should not be zero")
|
|
}
|
|
}
|
|
|
|
// TestDefaultConfig_Gateway verifies gateway defaults
|
|
func TestDefaultConfig_Gateway(t *testing.T) {
|
|
cfg := DefaultConfig()
|
|
|
|
if cfg.Gateway.Host != "0.0.0.0" {
|
|
t.Error("Gateway host should have default value")
|
|
}
|
|
if cfg.Gateway.Port == 0 {
|
|
t.Error("Gateway port should have default value")
|
|
}
|
|
}
|
|
|
|
// TestDefaultConfig_Providers verifies provider structure
|
|
func TestDefaultConfig_Providers(t *testing.T) {
|
|
cfg := DefaultConfig()
|
|
|
|
if cfg.Providers.Anthropic.APIKey != "" {
|
|
t.Error("Anthropic API key should be empty by default")
|
|
}
|
|
if cfg.Providers.OpenAI.APIKey != "" {
|
|
t.Error("OpenAI API key should be empty by default")
|
|
}
|
|
if cfg.Providers.OpenRouter.APIKey != "" {
|
|
t.Error("OpenRouter API key should be empty by default")
|
|
}
|
|
}
|
|
|
|
// TestDefaultConfig_Channels verifies channels are disabled by default
|
|
func TestDefaultConfig_Channels(t *testing.T) {
|
|
cfg := DefaultConfig()
|
|
|
|
if cfg.Channels.Telegram.Enabled {
|
|
t.Error("Telegram should be disabled by default")
|
|
}
|
|
if cfg.Channels.Discord.Enabled {
|
|
t.Error("Discord should be disabled by default")
|
|
}
|
|
if cfg.Channels.Slack.Enabled {
|
|
t.Error("Slack should be disabled by default")
|
|
}
|
|
}
|
|
|
|
// TestDefaultConfig_WebTools verifies web tools config
|
|
func TestDefaultConfig_WebTools(t *testing.T) {
|
|
cfg := DefaultConfig()
|
|
|
|
// Verify web tools defaults
|
|
if cfg.Tools.Web.Brave.MaxResults != 5 {
|
|
t.Error("Expected Brave MaxResults 5, got ", cfg.Tools.Web.Brave.MaxResults)
|
|
}
|
|
if cfg.Tools.Web.Brave.APIKey != "" {
|
|
t.Error("Brave API key should be empty by default")
|
|
}
|
|
if cfg.Tools.Web.DuckDuckGo.MaxResults != 5 {
|
|
t.Error("Expected DuckDuckGo MaxResults 5, got ", cfg.Tools.Web.DuckDuckGo.MaxResults)
|
|
}
|
|
}
|
|
|
|
// TestConfig_Complete verifies all config fields are set
|
|
func TestConfig_Complete(t *testing.T) {
|
|
cfg := DefaultConfig()
|
|
|
|
if cfg.Agents.Defaults.Workspace == "" {
|
|
t.Error("Workspace should not be empty")
|
|
}
|
|
if cfg.Agents.Defaults.Model == "" {
|
|
t.Error("Model should not be empty")
|
|
}
|
|
if cfg.Agents.Defaults.Temperature == 0 {
|
|
t.Error("Temperature should have default value")
|
|
}
|
|
if cfg.Agents.Defaults.MaxTokens == 0 {
|
|
t.Error("MaxTokens should not be zero")
|
|
}
|
|
if cfg.Agents.Defaults.MaxToolIterations == 0 {
|
|
t.Error("MaxToolIterations should not be zero")
|
|
}
|
|
if cfg.Gateway.Host != "0.0.0.0" {
|
|
t.Error("Gateway host should have default value")
|
|
}
|
|
if cfg.Gateway.Port == 0 {
|
|
t.Error("Gateway port should have default value")
|
|
}
|
|
if !cfg.Heartbeat.Enabled {
|
|
t.Error("Heartbeat should be enabled by default")
|
|
}
|
|
}
|