f235b8ce90
- Move cmd/agentu/main.go → main.go (project root) - Move internal/llm/ → pkg/llm/ (leaf package, no internal deps) - Move internal/config/ → pkg/config/ (leaf package, no internal deps) - Update all import paths: agentu/internal/llm → agentu/pkg/llm (14 files) - Update all import paths: agentu/internal/config → agentu/pkg/config (3 files) - Update README: build/run commands, CLI flags section - agent/session/tools/tui stay in internal/ (app-specific business logic) Build: go build . | Test: go test ./... | Vet: go vet ./...
373 lines
8.4 KiB
Go
373 lines
8.4 KiB
Go
package config
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestLoadExpandsEnvAndDefaults(t *testing.T) {
|
|
dir := t.TempDir()
|
|
t.Setenv("AGENTU_TEST_KEY", "sk-test")
|
|
path := filepath.Join(dir, "agentu.yaml")
|
|
if err := os.WriteFile(path, []byte(`
|
|
providers:
|
|
test:
|
|
base_url: https://example.com
|
|
api_key: ${AGENTU_TEST_KEY}
|
|
models:
|
|
- id: test-model
|
|
agent:
|
|
working_dir: .
|
|
`), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
cfg, err := Load(path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
provider := cfg.Providers[cfg.DefaultProviderName()]
|
|
if provider.APIKey != "sk-test" {
|
|
t.Fatalf("api key = %q", provider.APIKey)
|
|
}
|
|
if providerName := cfg.DefaultProviderName(); providerName != "test" {
|
|
t.Fatalf("default provider = %q", providerName)
|
|
}
|
|
if cfg.Agent.ToolTimeout != 30*time.Second {
|
|
t.Fatalf("tool timeout = %s", cfg.Agent.ToolTimeout)
|
|
}
|
|
if !filepath.IsAbs(cfg.Agent.WorkingDir) {
|
|
t.Fatalf("working dir is not absolute: %s", cfg.Agent.WorkingDir)
|
|
}
|
|
}
|
|
|
|
func TestLoadMinimalModelObjectConfig(t *testing.T) {
|
|
dir := t.TempDir()
|
|
t.Setenv("AGENTU_TEST_KEY", "sk-test")
|
|
path := filepath.Join(dir, "agentu.yaml")
|
|
if err := os.WriteFile(path, []byte(`
|
|
providers:
|
|
test:
|
|
base_url: https://example.com
|
|
api_key: ${AGENTU_TEST_KEY}
|
|
models:
|
|
- id: model-a
|
|
thinking: none
|
|
context: 256000
|
|
`), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
cfg, err := Load(path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
provider := cfg.Providers["test"]
|
|
if provider.Model != "model-a" {
|
|
t.Fatalf("model = %q", provider.Model)
|
|
}
|
|
if len(provider.Models) != 1 || provider.Models[0] != "model-a" {
|
|
t.Fatalf("models = %#v", provider.Models)
|
|
}
|
|
if provider.ContextTokens != 256000 {
|
|
t.Fatalf("context tokens = %d", provider.ContextTokens)
|
|
}
|
|
if !provider.ThinkingConfigured || provider.Thinking != "none" || provider.ThinkingParam != "thinking" {
|
|
t.Fatalf("thinking config = %#v", provider)
|
|
}
|
|
if cfg.Agent.ToolTimeout != 30*time.Second {
|
|
t.Fatalf("tool timeout = %s", cfg.Agent.ToolTimeout)
|
|
}
|
|
if cfg.Agent.SystemPrompt == "" {
|
|
t.Fatal("system prompt default was not applied")
|
|
}
|
|
}
|
|
|
|
func TestLoadRejectsStringModelEntries(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "agentu.yaml")
|
|
if err := os.WriteFile(path, []byte(`
|
|
providers:
|
|
test:
|
|
base_url: https://example.com
|
|
api_key: sk-test
|
|
models:
|
|
- test-model
|
|
`), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
_, err := Load(path)
|
|
if err == nil || !strings.Contains(err.Error(), "cannot unmarshal") {
|
|
t.Fatalf("err = %v", err)
|
|
}
|
|
}
|
|
|
|
func TestLoadMaxContextTokens(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "agentu.yaml")
|
|
if err := os.WriteFile(path, []byte(`
|
|
providers:
|
|
test:
|
|
base_url: https://example.com
|
|
api_key: sk-test
|
|
models:
|
|
- id: test-model
|
|
agent:
|
|
max_context_tokens: 120000
|
|
`), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
cfg, err := Load(path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if cfg.Agent.MaxContextTokens != 120000 {
|
|
t.Fatalf("max context tokens = %d", cfg.Agent.MaxContextTokens)
|
|
}
|
|
}
|
|
|
|
func TestLoadRejectsNegativeMaxContextTokens(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "agentu.yaml")
|
|
if err := os.WriteFile(path, []byte(`
|
|
providers:
|
|
test:
|
|
base_url: https://example.com
|
|
api_key: sk-test
|
|
models:
|
|
- id: test-model
|
|
agent:
|
|
max_context_tokens: -1
|
|
`), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
_, err := Load(path)
|
|
if err == nil || !strings.Contains(err.Error(), "agent.max_context_tokens") {
|
|
t.Fatalf("err = %v", err)
|
|
}
|
|
}
|
|
|
|
func TestLoadUsesDefaultConfigPath(t *testing.T) {
|
|
dir := t.TempDir()
|
|
t.Setenv("HOME", dir)
|
|
t.Setenv("AGENTU_TEST_KEY", "sk-test")
|
|
configDir := filepath.Join(dir, ".agentu")
|
|
if err := os.MkdirAll(configDir, 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
path := filepath.Join(configDir, "config.yaml")
|
|
if err := os.WriteFile(path, []byte(`
|
|
providers:
|
|
test:
|
|
base_url: https://example.com
|
|
api_key: ${AGENTU_TEST_KEY}
|
|
models:
|
|
- id: test-model
|
|
`), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
cfg, err := Load("")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got := cfg.DefaultProviderName(); got != "test" {
|
|
t.Fatalf("default provider = %q", got)
|
|
}
|
|
}
|
|
|
|
func TestResolvePathExpandsHome(t *testing.T) {
|
|
dir := t.TempDir()
|
|
t.Setenv("HOME", dir)
|
|
|
|
got, err := ResolvePath("~/.agentu/config.yaml")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
want := filepath.Join(dir, ".agentu", "config.yaml")
|
|
if got != want {
|
|
t.Fatalf("path = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestLoadDefaultSystemPromptDiscouragesEagerTools(t *testing.T) {
|
|
dir := t.TempDir()
|
|
t.Setenv("AGENTU_TEST_KEY", "sk-test")
|
|
path := filepath.Join(dir, "agentu.yaml")
|
|
if err := os.WriteFile(path, []byte(`
|
|
providers:
|
|
test:
|
|
base_url: https://example.com
|
|
api_key: ${AGENTU_TEST_KEY}
|
|
models:
|
|
- id: test-model
|
|
`), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
cfg, err := Load(path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, want := range []string{"Answer simple conversation directly", "Use tools only when"} {
|
|
if !strings.Contains(cfg.Agent.SystemPrompt, want) {
|
|
t.Fatalf("system prompt missing %q: %s", want, cfg.Agent.SystemPrompt)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestLoadMultiProviderConfig(t *testing.T) {
|
|
dir := t.TempDir()
|
|
t.Setenv("AGENTU_TEST_KEY", "sk-test")
|
|
path := filepath.Join(dir, "agentu.yaml")
|
|
if err := os.WriteFile(path, []byte(`
|
|
providers:
|
|
loveuer:
|
|
base_url: https://example.com
|
|
api_key: ${AGENTU_TEST_KEY}
|
|
models:
|
|
- id: model-a
|
|
thinking: xhigh
|
|
- id: model-b
|
|
backup:
|
|
base_url: https://backup.example.com
|
|
api_key: ${AGENTU_TEST_KEY}
|
|
models:
|
|
- id: model-c
|
|
`), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
cfg, err := Load(path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got := cfg.DefaultProviderName(); got != "backup" {
|
|
t.Fatalf("default provider = %q", got)
|
|
}
|
|
provider := cfg.Providers["loveuer"]
|
|
if got := provider.Model; got != "model-a" {
|
|
t.Fatalf("active model = %q", got)
|
|
}
|
|
if got := cfg.Providers["backup"].Model; got != "model-c" {
|
|
t.Fatalf("backup default model = %q", got)
|
|
}
|
|
if !provider.ThinkingConfigured || provider.Thinking != "xhigh" || provider.ThinkingParam != "thinking" {
|
|
t.Fatalf("thinking config = %#v", provider)
|
|
}
|
|
}
|
|
|
|
func TestLoadDefaultProviderNameIsDeterministic(t *testing.T) {
|
|
dir := t.TempDir()
|
|
t.Setenv("AGENTU_TEST_KEY", "sk-test")
|
|
path := filepath.Join(dir, "agentu.yaml")
|
|
if err := os.WriteFile(path, []byte(`
|
|
providers:
|
|
two:
|
|
base_url: https://two.example.com
|
|
api_key: ${AGENTU_TEST_KEY}
|
|
models:
|
|
- id: model-b
|
|
one:
|
|
base_url: https://one.example.com
|
|
api_key: ${AGENTU_TEST_KEY}
|
|
models:
|
|
- id: model-a
|
|
`), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
cfg, err := Load(path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got := cfg.DefaultProviderName(); got != "one" {
|
|
t.Fatalf("default provider = %q", got)
|
|
}
|
|
}
|
|
|
|
func TestLoadRejectsInvalidThinking(t *testing.T) {
|
|
dir := t.TempDir()
|
|
t.Setenv("AGENTU_TEST_KEY", "sk-test")
|
|
path := filepath.Join(dir, "agentu.yaml")
|
|
if err := os.WriteFile(path, []byte(`
|
|
providers:
|
|
test:
|
|
base_url: https://example.com
|
|
api_key: ${AGENTU_TEST_KEY}
|
|
models:
|
|
- id: test-model
|
|
thinking: huge
|
|
`), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
_, err := Load(path)
|
|
if err == nil || !strings.Contains(err.Error(), "thinking") {
|
|
t.Fatalf("err = %v", err)
|
|
}
|
|
}
|
|
|
|
func TestLoadRequiresProviderFields(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "agentu.yaml")
|
|
if err := os.WriteFile(path, []byte(`
|
|
providers:
|
|
test:
|
|
base_url: https://example.com
|
|
`), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
_, err := Load(path)
|
|
if err == nil {
|
|
t.Fatal("expected error")
|
|
}
|
|
}
|
|
|
|
func TestLoadRejectsLegacyProviderConfig(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "agentu.yaml")
|
|
if err := os.WriteFile(path, []byte(`
|
|
provider:
|
|
base_url: https://example.com
|
|
api_key: sk-test
|
|
models:
|
|
- id: test-model
|
|
`), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
_, err := Load(path)
|
|
if err == nil || !strings.Contains(err.Error(), "field provider not found") {
|
|
t.Fatalf("err = %v", err)
|
|
}
|
|
}
|
|
|
|
func TestLoadRejectsActiveProviderConfig(t *testing.T) {
|
|
dir := t.TempDir()
|
|
path := filepath.Join(dir, "agentu.yaml")
|
|
if err := os.WriteFile(path, []byte(`
|
|
active_provider: loveuer
|
|
providers:
|
|
loveuer:
|
|
base_url: https://example.com
|
|
api_key: sk-test
|
|
models:
|
|
- id: test-model
|
|
`), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
_, err := Load(path)
|
|
if err == nil || !strings.Contains(err.Error(), "field active_provider not found") {
|
|
t.Fatalf("err = %v", err)
|
|
}
|
|
}
|