Files
viettranx 1f560e15d7 feat(gateway): config.defaults RPC exposing SSoT pruning values
Adds config.defaults WS method so UI can render pruning-knob placeholders
that mirror the server's internal fallback values. Exports
agent.DefaultPruningValues() as the single source of truth used by both
the resolver and this RPC — keeps UI in sync without a parallel table.
2026-04-16 14:17:47 +07:00

154 lines
5.0 KiB
Go

package methods
import (
"encoding/json"
"testing"
"github.com/nextlevelbuilder/goclaw/internal/agent"
"github.com/nextlevelbuilder/goclaw/internal/config"
"github.com/nextlevelbuilder/goclaw/internal/tools"
)
func TestBuildConfigDefaults_NilCfg(t *testing.T) {
resp := buildConfigDefaults(nil)
p := agent.DefaultPruningValues()
if resp.Agents.ContextPruning.KeepLastAssistants != p.KeepLastAssistants {
t.Errorf("keepLastAssistants = %d, want %d",
resp.Agents.ContextPruning.KeepLastAssistants, p.KeepLastAssistants)
}
if resp.Agents.ContextPruning.SoftTrim.MaxChars != p.SoftTrimMaxChars {
t.Errorf("softTrim.maxChars = %d, want %d",
resp.Agents.ContextPruning.SoftTrim.MaxChars, p.SoftTrimMaxChars)
}
if resp.Agents.ContextPruning.TTL != p.TTL {
t.Errorf("ttl = %q, want %q", resp.Agents.ContextPruning.TTL, p.TTL)
}
s := tools.DefaultSubagentConfig()
if resp.Agents.Subagents.MaxConcurrent != s.MaxConcurrent {
t.Errorf("subagents.maxConcurrent = %d, want %d",
resp.Agents.Subagents.MaxConcurrent, s.MaxConcurrent)
}
if resp.Agents.Subagents.MaxRetries != s.MaxRetries {
t.Errorf("subagents.maxRetries = %d, want %d",
resp.Agents.Subagents.MaxRetries, s.MaxRetries)
}
if resp.Agents.Subagents.ArchiveAfterMinutes != s.ArchiveAfterMinutes {
t.Errorf("subagents.archiveAfterMinutes = %d, want %d",
resp.Agents.Subagents.ArchiveAfterMinutes, s.ArchiveAfterMinutes)
}
}
func TestBuildConfigDefaults_EmptyCfg(t *testing.T) {
cfg := &config.Config{}
resp := buildConfigDefaults(cfg)
// Empty cfg (no overlay) should match bare defaults.
p := agent.DefaultPruningValues()
if resp.Agents.ContextPruning.SoftTrimRatio != p.SoftTrimRatio {
t.Errorf("softTrimRatio = %v, want %v",
resp.Agents.ContextPruning.SoftTrimRatio, p.SoftTrimRatio)
}
}
func TestBuildConfigDefaults_PartialOverlay(t *testing.T) {
enabledFalse := false
cfg := &config.Config{}
cfg.Agents.Defaults.ContextPruning = &config.ContextPruningConfig{
KeepLastAssistants: 7,
SoftTrim: &config.ContextPruningSoftTrim{
MaxChars: 9999,
},
HardClear: &config.ContextPruningHardClear{
Enabled: &enabledFalse,
Placeholder: "custom",
},
TTL: "10m",
}
cfg.Agents.Defaults.Subagents = &config.SubagentsConfig{
MaxConcurrent: 16,
MaxRetries: 5,
}
resp := buildConfigDefaults(cfg)
if got := resp.Agents.ContextPruning.KeepLastAssistants; got != 7 {
t.Errorf("keepLastAssistants = %d, want 7", got)
}
if got := resp.Agents.ContextPruning.SoftTrim.MaxChars; got != 9999 {
t.Errorf("softTrim.maxChars = %d, want 9999", got)
}
// HeadChars not overlaid — should keep default
p := agent.DefaultPruningValues()
if got := resp.Agents.ContextPruning.SoftTrim.HeadChars; got != p.SoftTrimHeadChars {
t.Errorf("softTrim.headChars = %d, want default %d", got, p.SoftTrimHeadChars)
}
if got := resp.Agents.ContextPruning.HardClear.Enabled; got != false {
t.Errorf("hardClear.enabled = %v, want false", got)
}
if got := resp.Agents.ContextPruning.HardClear.Placeholder; got != "custom" {
t.Errorf("hardClear.placeholder = %q, want %q", got, "custom")
}
if got := resp.Agents.ContextPruning.TTL; got != "10m" {
t.Errorf("ttl = %q, want 10m", got)
}
if got := resp.Agents.Subagents.MaxConcurrent; got != 16 {
t.Errorf("subagents.maxConcurrent = %d, want 16", got)
}
if got := resp.Agents.Subagents.MaxRetries; got != 5 {
t.Errorf("subagents.maxRetries = %d, want 5", got)
}
// ArchiveAfterMinutes not overlaid — should keep default
s := tools.DefaultSubagentConfig()
if got := resp.Agents.Subagents.ArchiveAfterMinutes; got != s.ArchiveAfterMinutes {
t.Errorf("subagents.archiveAfterMinutes = %d, want default %d", got, s.ArchiveAfterMinutes)
}
}
func TestBuildConfigDefaults_JSONShape(t *testing.T) {
// JSON roundtrip: UI expects nested keys softTrim.maxChars, hardClear.enabled, etc.
resp := buildConfigDefaults(nil)
b, err := json.Marshal(resp)
if err != nil {
t.Fatalf("marshal: %v", err)
}
var parsed map[string]any
if err := json.Unmarshal(b, &parsed); err != nil {
t.Fatalf("unmarshal: %v", err)
}
agentsMap, _ := parsed["agents"].(map[string]any)
if agentsMap == nil {
t.Fatal("missing agents key")
}
pruning, _ := agentsMap["contextPruning"].(map[string]any)
if pruning == nil {
t.Fatal("missing agents.contextPruning")
}
softTrim, _ := pruning["softTrim"].(map[string]any)
if softTrim == nil {
t.Fatal("missing agents.contextPruning.softTrim")
}
if _, ok := softTrim["maxChars"]; !ok {
t.Error("missing softTrim.maxChars")
}
hardClear, _ := pruning["hardClear"].(map[string]any)
if hardClear == nil {
t.Fatal("missing agents.contextPruning.hardClear")
}
if _, ok := hardClear["enabled"]; !ok {
t.Error("missing hardClear.enabled")
}
subs, _ := agentsMap["subagents"].(map[string]any)
if subs == nil {
t.Fatal("missing agents.subagents")
}
for _, k := range []string{"maxConcurrent", "maxSpawnDepth", "maxChildrenPerAgent", "archiveAfterMinutes", "maxRetries"} {
if _, ok := subs[k]; !ok {
t.Errorf("missing subagents.%s", k)
}
}
}