@@ -93,7 +93,7 @@ func TestInit_SingletonBehavior(t *testing.T) {
94 require.NoError(t, err1)
94 require.NoError(t, err1)
95 require.NoError(t, err2)
95 require.NoError(t, err2)
96− assert.Same(t, cfg1, cfg2) // Should be the same instance
96+ assert.Same(t, cfg1, cfg2)
99func TestGet_BeforeInitialization(t *testing.T) {
99func TestGet_BeforeInitialization(t *testing.T) {
@@ -124,7 +124,7 @@ func TestLoadConfig_NoConfigFiles(t *testing.T) {
124 cfg, err := Init(cwdDir, false)
124 cfg, err := Init(cwdDir, false)
126 require.NoError(t, err)
126 require.NoError(t, err)
127− assert.Len(t, cfg.Providers, 0) // No providers without env vars or config files
127+ assert.Len(t, cfg.Providers, 0)
128 assert.Equal(t, defaultContextPaths, cfg.Options.ContextPaths)
128 assert.Equal(t, defaultContextPaths, cfg.Options.ContextPaths)
@@ -133,7 +133,6 @@ func TestLoadConfig_OnlyGlobalConfig(t *testing.T) {
133 testConfigDir = t.TempDir()
133 testConfigDir = t.TempDir()
134 cwdDir := t.TempDir()
134 cwdDir := t.TempDir()
136− // Create global config file
137 globalConfig := Config{
136 globalConfig := Config{
138 Providers: map[provider.InferenceProvider]ProviderConfig{
137 Providers: map[provider.InferenceProvider]ProviderConfig{
139 provider.InferenceProviderOpenAI: {
138 provider.InferenceProviderOpenAI: {
@@ -187,7 +186,6 @@ func TestLoadConfig_OnlyLocalConfig(t *testing.T) {
187 testConfigDir = t.TempDir()
186 testConfigDir = t.TempDir()
188 cwdDir := t.TempDir()
187 cwdDir := t.TempDir()
190− // Create local config file
191 localConfig := Config{
189 localConfig := Config{
192 Providers: map[provider.InferenceProvider]ProviderConfig{
190 Providers: map[provider.InferenceProvider]ProviderConfig{
193 provider.InferenceProviderAnthropic: {
191 provider.InferenceProviderAnthropic: {
@@ -239,7 +237,6 @@ func TestLoadConfig_BothGlobalAndLocal(t *testing.T) {
239 testConfigDir = t.TempDir()
237 testConfigDir = t.TempDir()
240 cwdDir := t.TempDir()
238 cwdDir := t.TempDir()
242− // Create global config
243 globalConfig := Config{
240 globalConfig := Config{
244 Providers: map[provider.InferenceProvider]ProviderConfig{
241 Providers: map[provider.InferenceProvider]ProviderConfig{
245 provider.InferenceProviderOpenAI: {
242 provider.InferenceProviderOpenAI: {
@@ -279,7 +276,6 @@ func TestLoadConfig_BothGlobalAndLocal(t *testing.T) {
279 require.NoError(t, err)
276 require.NoError(t, err)
280 require.NoError(t, os.WriteFile(configPath, data, 0o644))
277 require.NoError(t, os.WriteFile(configPath, data, 0o644))
282− // Create local config that overrides and adds
283 localConfig := Config{
279 localConfig := Config{
284 Providers: map[provider.InferenceProvider]ProviderConfig{
280 Providers: map[provider.InferenceProvider]ProviderConfig{
285 provider.InferenceProviderOpenAI: {
281 provider.InferenceProviderOpenAI: {
@@ -327,14 +323,11 @@ func TestLoadConfig_BothGlobalAndLocal(t *testing.T) {
327 require.NoError(t, err)
323 require.NoError(t, err)
328 assert.Len(t, cfg.Providers, 2)
324 assert.Len(t, cfg.Providers, 2)
330− // Check that local config overrode global
331 openaiProvider := cfg.Providers[provider.InferenceProviderOpenAI]
326 openaiProvider := cfg.Providers[provider.InferenceProviderOpenAI]
332 assert.Equal(t, "local-key", openaiProvider.APIKey)
327 assert.Equal(t, "local-key", openaiProvider.APIKey)
334− // Check that local config added new provider
335 assert.Contains(t, cfg.Providers, provider.InferenceProviderAnthropic)
329 assert.Contains(t, cfg.Providers, provider.InferenceProviderAnthropic)
337− // Check that context paths were merged
338 assert.Contains(t, cfg.Options.ContextPaths, "global-context.md")
331 assert.Contains(t, cfg.Options.ContextPaths, "global-context.md")
339 assert.Contains(t, cfg.Options.ContextPaths, "local-context.md")
332 assert.Contains(t, cfg.Options.ContextPaths, "local-context.md")
340 assert.True(t, cfg.Options.TUI.CompactMode)
333 assert.True(t, cfg.Options.TUI.CompactMode)
@@ -345,7 +338,6 @@ func TestLoadConfig_MalformedGlobalJSON(t *testing.T) {
345 testConfigDir = t.TempDir()
338 testConfigDir = t.TempDir()
346 cwdDir := t.TempDir()
339 cwdDir := t.TempDir()
348− // Create malformed global config
349 configPath := filepath.Join(testConfigDir, "crush.json")
341 configPath := filepath.Join(testConfigDir, "crush.json")
350 require.NoError(t, os.MkdirAll(filepath.Dir(configPath), 0o755))
342 require.NoError(t, os.MkdirAll(filepath.Dir(configPath), 0o755))
351 require.NoError(t, os.WriteFile(configPath, []byte(`{invalid json`), 0o644))
343 require.NoError(t, os.WriteFile(configPath, []byte(`{invalid json`), 0o644))
@@ -359,7 +351,6 @@ func TestLoadConfig_MalformedLocalJSON(t *testing.T) {
359 testConfigDir = t.TempDir()
351 testConfigDir = t.TempDir()
360 cwdDir := t.TempDir()
352 cwdDir := t.TempDir()
362− // Create malformed local config
363 localConfigPath := filepath.Join(cwdDir, "crush.json")
354 localConfigPath := filepath.Join(cwdDir, "crush.json")
364 require.NoError(t, os.WriteFile(localConfigPath, []byte(`{invalid json`), 0o644))
355 require.NoError(t, os.WriteFile(localConfigPath, []byte(`{invalid json`), 0o644))
@@ -409,7 +400,6 @@ func TestEnvVars_AllSupportedAPIKeys(t *testing.T) {
409 testConfigDir = t.TempDir()
400 testConfigDir = t.TempDir()
410 cwdDir := t.TempDir()
401 cwdDir := t.TempDir()
412− // Set all supported API keys
413 os.Setenv("ANTHROPIC_API_KEY", "test-anthropic-key")
403 os.Setenv("ANTHROPIC_API_KEY", "test-anthropic-key")
414 os.Setenv("OPENAI_API_KEY", "test-openai-key")
404 os.Setenv("OPENAI_API_KEY", "test-openai-key")
415 os.Setenv("GEMINI_API_KEY", "test-gemini-key")
405 os.Setenv("GEMINI_API_KEY", "test-gemini-key")
@@ -421,7 +411,6 @@ func TestEnvVars_AllSupportedAPIKeys(t *testing.T) {
421 require.NoError(t, err)
411 require.NoError(t, err)
422 assert.Len(t, cfg.Providers, 5)
412 assert.Len(t, cfg.Providers, 5)
424− // Verify each provider is configured correctly
425 anthropicProvider := cfg.Providers[provider.InferenceProviderAnthropic]
414 anthropicProvider := cfg.Providers[provider.InferenceProviderAnthropic]
426 assert.Equal(t, "test-anthropic-key", anthropicProvider.APIKey)
415 assert.Equal(t, "test-anthropic-key", anthropicProvider.APIKey)
427 assert.Equal(t, provider.TypeAnthropic, anthropicProvider.ProviderType)
416 assert.Equal(t, provider.TypeAnthropic, anthropicProvider.ProviderType)
@@ -449,7 +438,6 @@ func TestEnvVars_PartialEnvironmentVariables(t *testing.T) {
449 testConfigDir = t.TempDir()
438 testConfigDir = t.TempDir()
450 cwdDir := t.TempDir()
439 cwdDir := t.TempDir()
452− // Set only some API keys
453 os.Setenv("ANTHROPIC_API_KEY", "test-anthropic-key")
441 os.Setenv("ANTHROPIC_API_KEY", "test-anthropic-key")
454 os.Setenv("OPENAI_API_KEY", "test-openai-key")
442 os.Setenv("OPENAI_API_KEY", "test-openai-key")
@@ -467,7 +455,6 @@ func TestEnvVars_VertexAIConfiguration(t *testing.T) {
467 testConfigDir = t.TempDir()
455 testConfigDir = t.TempDir()
468 cwdDir := t.TempDir()
456 cwdDir := t.TempDir()
470− // Set VertexAI environment variables
471 os.Setenv("GOOGLE_GENAI_USE_VERTEXAI", "true")
458 os.Setenv("GOOGLE_GENAI_USE_VERTEXAI", "true")
472 os.Setenv("GOOGLE_CLOUD_PROJECT", "test-project")
459 os.Setenv("GOOGLE_CLOUD_PROJECT", "test-project")
473 os.Setenv("GOOGLE_CLOUD_LOCATION", "us-central1")
460 os.Setenv("GOOGLE_CLOUD_LOCATION", "us-central1")
@@ -488,7 +475,6 @@ func TestEnvVars_VertexAIWithoutUseFlag(t *testing.T) {
488 testConfigDir = t.TempDir()
475 testConfigDir = t.TempDir()
489 cwdDir := t.TempDir()
476 cwdDir := t.TempDir()
491− // Set Google Cloud vars but not the use flag
492 os.Setenv("GOOGLE_CLOUD_PROJECT", "test-project")
478 os.Setenv("GOOGLE_CLOUD_PROJECT", "test-project")
493 os.Setenv("GOOGLE_CLOUD_LOCATION", "us-central1")
479 os.Setenv("GOOGLE_CLOUD_LOCATION", "us-central1")
@@ -503,7 +489,6 @@ func TestEnvVars_AWSBedrockWithAccessKeys(t *testing.T) {
503 testConfigDir = t.TempDir()
489 testConfigDir = t.TempDir()
504 cwdDir := t.TempDir()
490 cwdDir := t.TempDir()
506− // Set AWS credentials
507 os.Setenv("AWS_ACCESS_KEY_ID", "test-access-key")
492 os.Setenv("AWS_ACCESS_KEY_ID", "test-access-key")
508 os.Setenv("AWS_SECRET_ACCESS_KEY", "test-secret-key")
493 os.Setenv("AWS_SECRET_ACCESS_KEY", "test-secret-key")
509 os.Setenv("AWS_DEFAULT_REGION", "us-east-1")
494 os.Setenv("AWS_DEFAULT_REGION", "us-east-1")
@@ -523,7 +508,6 @@ func TestEnvVars_AWSBedrockWithProfile(t *testing.T) {
523 testConfigDir = t.TempDir()
508 testConfigDir = t.TempDir()
524 cwdDir := t.TempDir()
509 cwdDir := t.TempDir()
527 os.Setenv("AWS_PROFILE", "test-profile")
511 os.Setenv("AWS_PROFILE", "test-profile")
528 os.Setenv("AWS_REGION", "eu-west-1")
512 os.Setenv("AWS_REGION", "eu-west-1")
@@ -541,7 +525,6 @@ func TestEnvVars_AWSBedrockWithContainerCredentials(t *testing.T) {
541 testConfigDir = t.TempDir()
525 testConfigDir = t.TempDir()
542 cwdDir := t.TempDir()
526 cwdDir := t.TempDir()
544− // Set AWS container credentials
545 os.Setenv("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI", "/v2/credentials/test")
528 os.Setenv("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI", "/v2/credentials/test")
546 os.Setenv("AWS_DEFAULT_REGION", "ap-southeast-1")
529 os.Setenv("AWS_DEFAULT_REGION", "ap-southeast-1")
@@ -556,7 +539,6 @@ func TestEnvVars_AWSBedrockRegionPriority(t *testing.T) {
556 testConfigDir = t.TempDir()
539 testConfigDir = t.TempDir()
557 cwdDir := t.TempDir()
540 cwdDir := t.TempDir()
559− // Set both region variables - AWS_DEFAULT_REGION should take priority
560 os.Setenv("AWS_ACCESS_KEY_ID", "test-key")
542 os.Setenv("AWS_ACCESS_KEY_ID", "test-key")
561 os.Setenv("AWS_SECRET_ACCESS_KEY", "test-secret")
543 os.Setenv("AWS_SECRET_ACCESS_KEY", "test-secret")
562 os.Setenv("AWS_DEFAULT_REGION", "us-west-2")
544 os.Setenv("AWS_DEFAULT_REGION", "us-west-2")
@@ -574,7 +556,6 @@ func TestEnvVars_AWSBedrockFallbackRegion(t *testing.T) {
574 testConfigDir = t.TempDir()
556 testConfigDir = t.TempDir()
575 cwdDir := t.TempDir()
557 cwdDir := t.TempDir()
577− // Set only AWS_REGION (not AWS_DEFAULT_REGION)
578 os.Setenv("AWS_ACCESS_KEY_ID", "test-key")
559 os.Setenv("AWS_ACCESS_KEY_ID", "test-key")
579 os.Setenv("AWS_SECRET_ACCESS_KEY", "test-secret")
560 os.Setenv("AWS_SECRET_ACCESS_KEY", "test-secret")
580 os.Setenv("AWS_REGION", "us-east-1")
561 os.Setenv("AWS_REGION", "us-east-1")
@@ -591,7 +572,6 @@ func TestEnvVars_NoAWSCredentials(t *testing.T) {
591 testConfigDir = t.TempDir()
572 testConfigDir = t.TempDir()
592 cwdDir := t.TempDir()
573 cwdDir := t.TempDir()
594− // Don't set any AWS credentials
595 cfg, err := Init(cwdDir, false)
575 cfg, err := Init(cwdDir, false)
597 require.NoError(t, err)
577 require.NoError(t, err)
@@ -603,15 +583,12 @@ func TestEnvVars_CustomEnvironmentVariables(t *testing.T) {
603 testConfigDir = t.TempDir()
583 testConfigDir = t.TempDir()
604 cwdDir := t.TempDir()
584 cwdDir := t.TempDir()
606− // Test that environment variables are properly resolved from provider definitions
607− // This test assumes the provider system uses $VARIABLE_NAME format
608 os.Setenv("ANTHROPIC_API_KEY", "resolved-anthropic-key")
586 os.Setenv("ANTHROPIC_API_KEY", "resolved-anthropic-key")
610 cfg, err := Init(cwdDir, false)
588 cfg, err := Init(cwdDir, false)
612 require.NoError(t, err)
590 require.NoError(t, err)
613 if len(cfg.Providers) > 0 {
591 if len(cfg.Providers) > 0 {
614− // Verify that the environment variable was resolved
615 if anthropicProvider, exists := cfg.Providers[provider.InferenceProviderAnthropic]; exists {
592 if anthropicProvider, exists := cfg.Providers[provider.InferenceProviderAnthropic]; exists {
616 assert.Equal(t, "resolved-anthropic-key", anthropicProvider.APIKey)
593 assert.Equal(t, "resolved-anthropic-key", anthropicProvider.APIKey)
@@ -623,11 +600,11 @@ func TestEnvVars_CombinedEnvironmentVariables(t *testing.T) {
623 testConfigDir = t.TempDir()
600 testConfigDir = t.TempDir()
624 cwdDir := t.TempDir()
601 cwdDir := t.TempDir()
626− // Set multiple types of environment variables
627 os.Setenv("ANTHROPIC_API_KEY", "test-anthropic")
603 os.Setenv("ANTHROPIC_API_KEY", "test-anthropic")
628 os.Setenv("OPENAI_API_KEY", "test-openai")
604 os.Setenv("OPENAI_API_KEY", "test-openai")
629 os.Setenv("GOOGLE_GENAI_USE_VERTEXAI", "true")
605 os.Setenv("GOOGLE_GENAI_USE_VERTEXAI", "true")
630 os.Setenv("GOOGLE_CLOUD_PROJECT", "test-project")
606 os.Setenv("GOOGLE_CLOUD_PROJECT", "test-project")
607+ os.Setenv("GOOGLE_CLOUD_LOCATION", "us-central1")
631 os.Setenv("AWS_ACCESS_KEY_ID", "test-aws-key")
608 os.Setenv("AWS_ACCESS_KEY_ID", "test-aws-key")
632 os.Setenv("AWS_SECRET_ACCESS_KEY", "test-aws-secret")
609 os.Setenv("AWS_SECRET_ACCESS_KEY", "test-aws-secret")
633 os.Setenv("AWS_DEFAULT_REGION", "us-west-1")
610 os.Setenv("AWS_DEFAULT_REGION", "us-west-1")
@@ -636,7 +613,6 @@ func TestEnvVars_CombinedEnvironmentVariables(t *testing.T) {
637 require.NoError(t, err)
614 require.NoError(t, err)
639− // Should have API key providers + VertexAI + Bedrock
640 expectedProviders := []provider.InferenceProvider{
616 expectedProviders := []provider.InferenceProvider{
641 provider.InferenceProviderAnthropic,
617 provider.InferenceProviderAnthropic,
642 provider.InferenceProviderOpenAI,
618 provider.InferenceProviderOpenAI,
@@ -696,14 +672,11 @@ func TestHasAWSCredentials_NoCredentials(t *testing.T) {
696 assert.False(t, hasAWSCredentials())
672 assert.False(t, hasAWSCredentials())
699−// Provider Configuration Tests
701func TestProviderMerging_GlobalToBase(t *testing.T) {
675func TestProviderMerging_GlobalToBase(t *testing.T) {
703 testConfigDir = t.TempDir()
677 testConfigDir = t.TempDir()
704 cwdDir := t.TempDir()
678 cwdDir := t.TempDir()
706− // Create global config with provider
707 globalConfig := Config{
680 globalConfig := Config{
708 Providers: map[provider.InferenceProvider]ProviderConfig{
681 Providers: map[provider.InferenceProvider]ProviderConfig{
709 provider.InferenceProviderOpenAI: {
682 provider.InferenceProviderOpenAI: {
@@ -719,6 +692,12 @@ func TestProviderMerging_GlobalToBase(t *testing.T) {
719 ContextWindow: 8192,
692 ContextWindow: 8192,
720 DefaultMaxTokens: 4096,
693 DefaultMaxTokens: 4096,
696+ ID: "gpt-3.5-turbo",
697+ Name: "GPT-3.5 Turbo",
698+ ContextWindow: 4096,
699+ DefaultMaxTokens: 2048,
@@ -739,7 +718,7 @@ func TestProviderMerging_GlobalToBase(t *testing.T) {
739 assert.Equal(t, "global-openai-key", openaiProvider.APIKey)
718 assert.Equal(t, "global-openai-key", openaiProvider.APIKey)
740 assert.Equal(t, "gpt-4", openaiProvider.DefaultLargeModel)
719 assert.Equal(t, "gpt-4", openaiProvider.DefaultLargeModel)
741 assert.Equal(t, "gpt-3.5-turbo", openaiProvider.DefaultSmallModel)
720 assert.Equal(t, "gpt-3.5-turbo", openaiProvider.DefaultSmallModel)
742− assert.Len(t, openaiProvider.Models, 1)
721+ assert.Len(t, openaiProvider.Models, 2)
745func TestProviderMerging_LocalToBase(t *testing.T) {
724func TestProviderMerging_LocalToBase(t *testing.T) {
@@ -747,7 +726,6 @@ func TestProviderMerging_LocalToBase(t *testing.T) {
747 testConfigDir = t.TempDir()
726 testConfigDir = t.TempDir()
748 cwdDir := t.TempDir()
727 cwdDir := t.TempDir()
750− // Create local config with provider
751 localConfig := Config{
729 localConfig := Config{
752 Providers: map[provider.InferenceProvider]ProviderConfig{
730 Providers: map[provider.InferenceProvider]ProviderConfig{
753 provider.InferenceProviderAnthropic: {
731 provider.InferenceProviderAnthropic: {
@@ -755,6 +733,25 @@ func TestProviderMerging_LocalToBase(t *testing.T) {
755 APIKey: "local-anthropic-key",
733 APIKey: "local-anthropic-key",
756 ProviderType: provider.TypeAnthropic,
734 ProviderType: provider.TypeAnthropic,
757 DefaultLargeModel: "claude-3-opus",
735 DefaultLargeModel: "claude-3-opus",
736+ DefaultSmallModel: "claude-3-haiku",
739+ ID: "claude-3-opus",
740+ Name: "Claude 3 Opus",
741+ ContextWindow: 200000,
742+ DefaultMaxTokens: 4096,
747+ ID: "claude-3-haiku",
748+ Name: "Claude 3 Haiku",
749+ ContextWindow: 200000,
750+ DefaultMaxTokens: 4096,
@@ -772,6 +769,8 @@ func TestProviderMerging_LocalToBase(t *testing.T) {
772 anthropicProvider := cfg.Providers[provider.InferenceProviderAnthropic]
769 anthropicProvider := cfg.Providers[provider.InferenceProviderAnthropic]
773 assert.Equal(t, "local-anthropic-key", anthropicProvider.APIKey)
770 assert.Equal(t, "local-anthropic-key", anthropicProvider.APIKey)
774 assert.Equal(t, "claude-3-opus", anthropicProvider.DefaultLargeModel)
771 assert.Equal(t, "claude-3-opus", anthropicProvider.DefaultLargeModel)
772+ assert.Equal(t, "claude-3-haiku", anthropicProvider.DefaultSmallModel)
773+ assert.Len(t, anthropicProvider.Models, 2)
777func TestProviderMerging_ConflictingSettings(t *testing.T) {
776func TestProviderMerging_ConflictingSettings(t *testing.T) {
@@ -779,7 +778,6 @@ func TestProviderMerging_ConflictingSettings(t *testing.T) {
779 testConfigDir = t.TempDir()
778 testConfigDir = t.TempDir()
780 cwdDir := t.TempDir()
779 cwdDir := t.TempDir()
782− // Create global config
783 globalConfig := Config{
781 globalConfig := Config{
784 Providers: map[provider.InferenceProvider]ProviderConfig{
782 Providers: map[provider.InferenceProvider]ProviderConfig{
785 provider.InferenceProviderOpenAI: {
783 provider.InferenceProviderOpenAI: {
@@ -788,6 +786,26 @@ func TestProviderMerging_ConflictingSettings(t *testing.T) {
788 ProviderType: provider.TypeOpenAI,
786 ProviderType: provider.TypeOpenAI,
789 DefaultLargeModel: "gpt-4",
787 DefaultLargeModel: "gpt-4",
790 DefaultSmallModel: "gpt-3.5-turbo",
788 DefaultSmallModel: "gpt-3.5-turbo",
793+ ContextWindow: 8192,
794+ DefaultMaxTokens: 4096,
797+ ID: "gpt-3.5-turbo",
798+ Name: "GPT-3.5 Turbo",
799+ ContextWindow: 4096,
800+ DefaultMaxTokens: 2048,
804+ Name: "GPT-4 Turbo",
805+ ContextWindow: 128000,
806+ DefaultMaxTokens: 4096,
@@ -804,7 +822,6 @@ func TestProviderMerging_ConflictingSettings(t *testing.T) {
804 provider.InferenceProviderOpenAI: {
822 provider.InferenceProviderOpenAI: {
805 APIKey: "local-key",
823 APIKey: "local-key",
806 DefaultLargeModel: "gpt-4-turbo",
824 DefaultLargeModel: "gpt-4-turbo",
807− // Test disabled separately - don't disable here as it causes nil pointer
@@ -819,11 +836,9 @@ func TestProviderMerging_ConflictingSettings(t *testing.T) {
819 require.NoError(t, err)
836 require.NoError(t, err)
821 openaiProvider := cfg.Providers[provider.InferenceProviderOpenAI]
838 openaiProvider := cfg.Providers[provider.InferenceProviderOpenAI]
822− // Local should override global
823 assert.Equal(t, "local-key", openaiProvider.APIKey)
839 assert.Equal(t, "local-key", openaiProvider.APIKey)
824 assert.Equal(t, "gpt-4-turbo", openaiProvider.DefaultLargeModel)
840 assert.Equal(t, "gpt-4-turbo", openaiProvider.DefaultLargeModel)
825− assert.False(t, openaiProvider.Disabled) // Should not be disabled
841+ assert.False(t, openaiProvider.Disabled)
826− // Global values should remain where not overridden
827 assert.Equal(t, "gpt-3.5-turbo", openaiProvider.DefaultSmallModel)
842 assert.Equal(t, "gpt-3.5-turbo", openaiProvider.DefaultSmallModel)
@@ -834,22 +849,51 @@ func TestProviderMerging_CustomVsKnownProviders(t *testing.T) {
835 customProviderID := provider.InferenceProvider("custom-provider")
850 customProviderID := provider.InferenceProvider("custom-provider")
837− // Create config with both known and custom providers
838 globalConfig := Config{
852 globalConfig := Config{
839 Providers: map[provider.InferenceProvider]ProviderConfig{
853 Providers: map[provider.InferenceProvider]ProviderConfig{
840− // Known provider - some fields should not be overrideable
841 provider.InferenceProviderOpenAI: {
854 provider.InferenceProviderOpenAI: {
842− ID: provider.InferenceProviderOpenAI,
855+ ID: provider.InferenceProviderOpenAI,
843− APIKey: "openai-key",
856+ APIKey: "openai-key",
844− BaseURL: "should-not-override",
857+ BaseURL: "should-not-override",
845− ProviderType: provider.TypeAnthropic, // Should not override
858+ ProviderType: provider.TypeAnthropic,
859+ DefaultLargeModel: "gpt-4",
860+ DefaultSmallModel: "gpt-3.5-turbo",
865+ ContextWindow: 8192,
866+ DefaultMaxTokens: 4096,
869+ ID: "gpt-3.5-turbo",
870+ Name: "GPT-3.5 Turbo",
871+ ContextWindow: 4096,
872+ DefaultMaxTokens: 2048,
847− // Custom provider - all fields should be configurable
848 customProviderID: {
876 customProviderID: {
849− ID: customProviderID,
877+ ID: customProviderID,
850− APIKey: "custom-key",
878+ APIKey: "custom-key",
851− BaseURL: "https://custom.api.com",
879+ BaseURL: "https://custom.api.com",
852− ProviderType: provider.TypeOpenAI,
880+ ProviderType: provider.TypeOpenAI,
881+ DefaultLargeModel: "custom-large",
882+ DefaultSmallModel: "custom-small",
886+ Name: "Custom Large",
887+ ContextWindow: 8192,
888+ DefaultMaxTokens: 4096,
892+ Name: "Custom Small",
893+ ContextWindow: 4096,
894+ DefaultMaxTokens: 2048,
@@ -882,14 +926,12 @@ func TestProviderMerging_CustomVsKnownProviders(t *testing.T) {
883 require.NoError(t, err)
927 require.NoError(t, err)
885− // Known provider should not have BaseURL/ProviderType overridden
886 openaiProvider := cfg.Providers[provider.InferenceProviderOpenAI]
929 openaiProvider := cfg.Providers[provider.InferenceProviderOpenAI]
887 assert.NotEqual(t, "https://should-not-change.com", openaiProvider.BaseURL)
930 assert.NotEqual(t, "https://should-not-change.com", openaiProvider.BaseURL)
888 assert.NotEqual(t, provider.TypeGemini, openaiProvider.ProviderType)
931 assert.NotEqual(t, provider.TypeGemini, openaiProvider.ProviderType)
890− // Custom provider should have all fields configurable
891 customProvider := cfg.Providers[customProviderID]
933 customProvider := cfg.Providers[customProviderID]
892− assert.Equal(t, "custom-key", customProvider.APIKey) // Should preserve from global
934+ assert.Equal(t, "custom-key", customProvider.APIKey)
893 assert.Equal(t, "https://updated-custom.api.com", customProvider.BaseURL)
935 assert.Equal(t, "https://updated-custom.api.com", customProvider.BaseURL)
894 assert.Equal(t, provider.TypeOpenAI, customProvider.ProviderType)
936 assert.Equal(t, provider.TypeOpenAI, customProvider.ProviderType)
@@ -901,14 +943,12 @@ func TestProviderValidation_CustomProviderMissingBaseURL(t *testing.T) {
902 customProviderID := provider.InferenceProvider("custom-provider")
944 customProviderID := provider.InferenceProvider("custom-provider")
904− // Create config with custom provider missing BaseURL
905 globalConfig := Config{
946 globalConfig := Config{
906 Providers: map[provider.InferenceProvider]ProviderConfig{
947 Providers: map[provider.InferenceProvider]ProviderConfig{
907 customProviderID: {
948 customProviderID: {
908 ID: customProviderID,
949 ID: customProviderID,
909 APIKey: "custom-key",
950 APIKey: "custom-key",
910 ProviderType: provider.TypeOpenAI,
951 ProviderType: provider.TypeOpenAI,
@@ -922,7 +962,6 @@ func TestProviderValidation_CustomProviderMissingBaseURL(t *testing.T) {
922 cfg, err := Init(cwdDir, false)
962 cfg, err := Init(cwdDir, false)
924 require.NoError(t, err)
964 require.NoError(t, err)
925− // Provider should be filtered out due to validation failure
926 assert.NotContains(t, cfg.Providers, customProviderID)
965 assert.NotContains(t, cfg.Providers, customProviderID)
@@ -939,7 +978,6 @@ func TestProviderValidation_CustomProviderMissingAPIKey(t *testing.T) {
939 ID: customProviderID,
978 ID: customProviderID,
940 BaseURL: "https://custom.api.com",
979 BaseURL: "https://custom.api.com",
941 ProviderType: provider.TypeOpenAI,
980 ProviderType: provider.TypeOpenAI,
@@ -994,10 +1032,26 @@ func TestProviderValidation_KnownProviderValid(t *testing.T) {
994 globalConfig := Config{
1032 globalConfig := Config{
995 Providers: map[provider.InferenceProvider]ProviderConfig{
1033 Providers: map[provider.InferenceProvider]ProviderConfig{
996 provider.InferenceProviderOpenAI: {
1034 provider.InferenceProviderOpenAI: {
997− ID: provider.InferenceProviderOpenAI,
1035+ ID: provider.InferenceProviderOpenAI,
998− APIKey: "openai-key",
1036+ APIKey: "openai-key",
999− ProviderType: provider.TypeOpenAI,
1037+ ProviderType: provider.TypeOpenAI,
1000− // BaseURL not required for known providers
1038+ DefaultLargeModel: "gpt-4",
1039+ DefaultSmallModel: "gpt-3.5-turbo",
1044+ ContextWindow: 8192,
1045+ DefaultMaxTokens: 4096,
1048+ ID: "gpt-3.5-turbo",
1049+ Name: "GPT-3.5 Turbo",
1050+ ContextWindow: 4096,
1051+ DefaultMaxTokens: 2048,
@@ -1022,10 +1076,48 @@ func TestProviderValidation_DisabledProvider(t *testing.T) {
1022 globalConfig := Config{
1076 globalConfig := Config{
1023 Providers: map[provider.InferenceProvider]ProviderConfig{
1077 Providers: map[provider.InferenceProvider]ProviderConfig{
1024 provider.InferenceProviderOpenAI: {
1078 provider.InferenceProviderOpenAI: {
1025− ID: provider.InferenceProviderOpenAI,
1079+ ID: provider.InferenceProviderOpenAI,
1026− APIKey: "openai-key",
1080+ APIKey: "openai-key",
1027− ProviderType: provider.TypeOpenAI,
1081+ ProviderType: provider.TypeOpenAI,
1028− Disabled: true,
1082+ Disabled: true,
1083+ DefaultLargeModel: "gpt-4",
1084+ DefaultSmallModel: "gpt-3.5-turbo",
1089+ ContextWindow: 8192,
1090+ DefaultMaxTokens: 4096,
1093+ ID: "gpt-3.5-turbo",
1094+ Name: "GPT-3.5 Turbo",
1095+ ContextWindow: 4096,
1096+ DefaultMaxTokens: 2048,
1100+ provider.InferenceProviderAnthropic: {
1101+ ID: provider.InferenceProviderAnthropic,
1102+ APIKey: "anthropic-key",
1103+ ProviderType: provider.TypeAnthropic,
1104+ Disabled: false, // This one is enabled
1105+ DefaultLargeModel: "claude-3-opus",
1106+ DefaultSmallModel: "claude-3-haiku",
1109+ ID: "claude-3-opus",
1110+ Name: "Claude 3 Opus",
1111+ ContextWindow: 200000,
1112+ DefaultMaxTokens: 4096,
1115+ ID: "claude-3-haiku",
1116+ Name: "Claude 3 Haiku",
1117+ ContextWindow: 200000,
1118+ DefaultMaxTokens: 4096,
@@ -1039,9 +1131,10 @@ func TestProviderValidation_DisabledProvider(t *testing.T) {
1039 cfg, err := Init(cwdDir, false)
1131 cfg, err := Init(cwdDir, false)
1041 require.NoError(t, err)
1133 require.NoError(t, err)
1042− // Disabled providers should still be in the config but marked as disabled
1043 assert.Contains(t, cfg.Providers, provider.InferenceProviderOpenAI)
1134 assert.Contains(t, cfg.Providers, provider.InferenceProviderOpenAI)
1044 assert.True(t, cfg.Providers[provider.InferenceProviderOpenAI].Disabled)
1135 assert.True(t, cfg.Providers[provider.InferenceProviderOpenAI].Disabled)
1136+ assert.Contains(t, cfg.Providers, provider.InferenceProviderAnthropic)
1137+ assert.False(t, cfg.Providers[provider.InferenceProviderAnthropic].Disabled)
1047func TestProviderModels_AddingNewModels(t *testing.T) {
1140func TestProviderModels_AddingNewModels(t *testing.T) {
@@ -1052,9 +1145,11 @@ func TestProviderModels_AddingNewModels(t *testing.T) {
1052 globalConfig := Config{
1145 globalConfig := Config{
1053 Providers: map[provider.InferenceProvider]ProviderConfig{
1146 Providers: map[provider.InferenceProvider]ProviderConfig{
1054 provider.InferenceProviderOpenAI: {
1147 provider.InferenceProviderOpenAI: {
1055− ID: provider.InferenceProviderOpenAI,
1148+ ID: provider.InferenceProviderOpenAI,
1056− APIKey: "openai-key",
1149+ APIKey: "openai-key",
1057− ProviderType: provider.TypeOpenAI,
1150+ ProviderType: provider.TypeOpenAI,
1151+ DefaultLargeModel: "gpt-4",
1152+ DefaultSmallModel: "gpt-4-turbo",
1058 Models: []Model{
1153 Models: []Model{
1060 ID: "gpt-4",
1155 ID: "gpt-4",
@@ -1098,7 +1193,7 @@ func TestProviderModels_AddingNewModels(t *testing.T) {
1098 require.NoError(t, err)
1193 require.NoError(t, err)
1100 openaiProvider := cfg.Providers[provider.InferenceProviderOpenAI]
1195 openaiProvider := cfg.Providers[provider.InferenceProviderOpenAI]
1101− assert.Len(t, openaiProvider.Models, 2) // Should have both models
1196+ assert.Len(t, openaiProvider.Models, 2)
1103 modelIDs := make([]string, len(openaiProvider.Models))
1198 modelIDs := make([]string, len(openaiProvider.Models))
1104 for i, model := range openaiProvider.Models {
1199 for i, model := range openaiProvider.Models {
@@ -1116,9 +1211,11 @@ func TestProviderModels_DuplicateModelHandling(t *testing.T) {
1116 globalConfig := Config{
1211 globalConfig := Config{
1117 Providers: map[provider.InferenceProvider]ProviderConfig{
1212 Providers: map[provider.InferenceProvider]ProviderConfig{
1118 provider.InferenceProviderOpenAI: {
1213 provider.InferenceProviderOpenAI: {
1119− ID: provider.InferenceProviderOpenAI,
1214+ ID: provider.InferenceProviderOpenAI,
1120− APIKey: "openai-key",
1215+ APIKey: "openai-key",
1121− ProviderType: provider.TypeOpenAI,
1216+ ProviderType: provider.TypeOpenAI,
1217+ DefaultLargeModel: "gpt-4",
1218+ DefaultSmallModel: "gpt-4",
1122 Models: []Model{
1219 Models: []Model{
1124 ID: "gpt-4",
1221 ID: "gpt-4",
@@ -1136,7 +1233,7 @@ func TestProviderModels_DuplicateModelHandling(t *testing.T) {
1136 provider.InferenceProviderOpenAI: {
1233 provider.InferenceProviderOpenAI: {
1137 Models: []Model{
1234 Models: []Model{
1139− ID: "gpt-4", // Same ID as global
1236+ ID: "gpt-4",
1140 Name: "GPT-4 Updated",
1237 Name: "GPT-4 Updated",
1141 ContextWindow: 16384,
1238 ContextWindow: 16384,
1142 DefaultMaxTokens: 8192,
1239 DefaultMaxTokens: 8192,
@@ -1162,13 +1259,12 @@ func TestProviderModels_DuplicateModelHandling(t *testing.T) {
1162 require.NoError(t, err)
1259 require.NoError(t, err)
1164 openaiProvider := cfg.Providers[provider.InferenceProviderOpenAI]
1261 openaiProvider := cfg.Providers[provider.InferenceProviderOpenAI]
1165− assert.Len(t, openaiProvider.Models, 1) // Should not duplicate
1262+ assert.Len(t, openaiProvider.Models, 1)
1167− // Should keep the original model (global config)
1168 model := openaiProvider.Models[0]
1264 model := openaiProvider.Models[0]
1169 assert.Equal(t, "gpt-4", model.ID)
1265 assert.Equal(t, "gpt-4", model.ID)
1170− assert.Equal(t, "GPT-4", model.Name) // Original name
1266+ assert.Equal(t, "GPT-4", model.Name)
1171− assert.Equal(t, int64(8192), model.ContextWindow) // Original context window
1267+ assert.Equal(t, int64(8192), model.ContextWindow)
1174func TestProviderModels_ModelCostAndCapabilities(t *testing.T) {
1270func TestProviderModels_ModelCostAndCapabilities(t *testing.T) {
@@ -1179,9 +1275,11 @@ func TestProviderModels_ModelCostAndCapabilities(t *testing.T) {
1179 globalConfig := Config{
1275 globalConfig := Config{
1180 Providers: map[provider.InferenceProvider]ProviderConfig{
1276 Providers: map[provider.InferenceProvider]ProviderConfig{
1181 provider.InferenceProviderOpenAI: {
1277 provider.InferenceProviderOpenAI: {
1182− ID: provider.InferenceProviderOpenAI,
1278+ ID: provider.InferenceProviderOpenAI,
1183− APIKey: "openai-key",
1279+ APIKey: "openai-key",
1184− ProviderType: provider.TypeOpenAI,
1280+ ProviderType: provider.TypeOpenAI,
1281+ DefaultLargeModel: "gpt-4",
1282+ DefaultSmallModel: "gpt-4",
1185 Models: []Model{
1283 Models: []Model{
1187 ID: "gpt-4",
1285 ID: "gpt-4",
@@ -1224,14 +1322,11 @@ func TestProviderModels_ModelCostAndCapabilities(t *testing.T) {
1224 assert.True(t, model.SupportsImages)
1322 assert.True(t, model.SupportsImages)
1227−// Agent Configuration Tests
1229func TestDefaultAgents_CoderAgent(t *testing.T) {
1325func TestDefaultAgents_CoderAgent(t *testing.T) {
1231 testConfigDir = t.TempDir()
1327 testConfigDir = t.TempDir()
1232 cwdDir := t.TempDir()
1328 cwdDir := t.TempDir()
1234− // Set up a provider so we can test agent configuration
1235 os.Setenv("ANTHROPIC_API_KEY", "test-key")
1330 os.Setenv("ANTHROPIC_API_KEY", "test-key")
1237 cfg, err := Init(cwdDir, false)
1332 cfg, err := Init(cwdDir, false)
@@ -1246,7 +1341,6 @@ func TestDefaultAgents_CoderAgent(t *testing.T) {
1246 assert.Equal(t, LargeModel, coderAgent.Model)
1341 assert.Equal(t, LargeModel, coderAgent.Model)
1247 assert.False(t, coderAgent.Disabled)
1342 assert.False(t, coderAgent.Disabled)
1248 assert.Equal(t, cfg.Options.ContextPaths, coderAgent.ContextPaths)
1343 assert.Equal(t, cfg.Options.ContextPaths, coderAgent.ContextPaths)
1249− // Coder agent should have all tools available (nil means all tools)
1250 assert.Nil(t, coderAgent.AllowedTools)
1344 assert.Nil(t, coderAgent.AllowedTools)
@@ -1255,7 +1349,6 @@ func TestDefaultAgents_TaskAgent(t *testing.T) {
1255 testConfigDir = t.TempDir()
1349 testConfigDir = t.TempDir()
1256 cwdDir := t.TempDir()
1350 cwdDir := t.TempDir()
1258− // Set up a provider so we can test agent configuration
1259 os.Setenv("ANTHROPIC_API_KEY", "test-key")
1352 os.Setenv("ANTHROPIC_API_KEY", "test-key")
1261 cfg, err := Init(cwdDir, false)
1354 cfg, err := Init(cwdDir, false)
@@ -1271,11 +1364,9 @@ func TestDefaultAgents_TaskAgent(t *testing.T) {
1271 assert.False(t, taskAgent.Disabled)
1364 assert.False(t, taskAgent.Disabled)
1272 assert.Equal(t, cfg.Options.ContextPaths, taskAgent.ContextPaths)
1365 assert.Equal(t, cfg.Options.ContextPaths, taskAgent.ContextPaths)
1274− // Task agent should have restricted tools
1275 expectedTools := []string{"glob", "grep", "ls", "sourcegraph", "view"}
1367 expectedTools := []string{"glob", "grep", "ls", "sourcegraph", "view"}
1276 assert.Equal(t, expectedTools, taskAgent.AllowedTools)
1368 assert.Equal(t, expectedTools, taskAgent.AllowedTools)
1278− // Task agent should have no MCPs or LSPs by default
1279 assert.Equal(t, map[string][]string{}, taskAgent.AllowedMCP)
1370 assert.Equal(t, map[string][]string{}, taskAgent.AllowedMCP)
1280 assert.Equal(t, []string{}, taskAgent.AllowedLSP)
1371 assert.Equal(t, []string{}, taskAgent.AllowedLSP)
@@ -1285,10 +1376,8 @@ func TestAgentMerging_CustomAgent(t *testing.T) {
1285 testConfigDir = t.TempDir()
1376 testConfigDir = t.TempDir()
1286 cwdDir := t.TempDir()
1377 cwdDir := t.TempDir()
1288− // Set up a provider
1289 os.Setenv("ANTHROPIC_API_KEY", "test-key")
1379 os.Setenv("ANTHROPIC_API_KEY", "test-key")
1291− // Create config with custom agent
1292 globalConfig := Config{
1381 globalConfig := Config{
1293 Agents: map[AgentID]Agent{
1382 Agents: map[AgentID]Agent{
1294 AgentID("custom-agent"): {
1383 AgentID("custom-agent"): {
@@ -1302,6 +1391,23 @@ func TestAgentMerging_CustomAgent(t *testing.T) {
1302 ContextPaths: []string{"custom-context.md"},
1391 ContextPaths: []string{"custom-context.md"},
1394+ MCP: map[string]MCP{
1397+ Command: "test-mcp-command",
1398+ Args: []string{"--test"},
1401+ LSP: map[string]LSPConfig{
1403+ Command: "typescript-language-server",
1404+ Args: []string{"--stdio"},
1307 configPath := filepath.Join(testConfigDir, "crush.json")
1413 configPath := filepath.Join(testConfigDir, "crush.json")
@@ -1314,7 +1420,6 @@ func TestAgentMerging_CustomAgent(t *testing.T) {
1315 require.NoError(t, err)
1421 require.NoError(t, err)
1317− // Should have default agents plus custom agent
1318 assert.Contains(t, cfg.Agents, AgentCoder)
1423 assert.Contains(t, cfg.Agents, AgentCoder)
1319 assert.Contains(t, cfg.Agents, AgentTask)
1424 assert.Contains(t, cfg.Agents, AgentTask)
1320 assert.Contains(t, cfg.Agents, AgentID("custom-agent"))
1425 assert.Contains(t, cfg.Agents, AgentID("custom-agent"))
@@ -1326,7 +1431,6 @@ func TestAgentMerging_CustomAgent(t *testing.T) {
1326 assert.Equal(t, []string{"glob", "grep"}, customAgent.AllowedTools)
1431 assert.Equal(t, []string{"glob", "grep"}, customAgent.AllowedTools)
1327 assert.Equal(t, map[string][]string{"mcp1": {"tool1", "tool2"}}, customAgent.AllowedMCP)
1432 assert.Equal(t, map[string][]string{"mcp1": {"tool1", "tool2"}}, customAgent.AllowedMCP)
1328 assert.Equal(t, []string{"typescript", "go"}, customAgent.AllowedLSP)
1433 assert.Equal(t, []string{"typescript", "go"}, customAgent.AllowedLSP)
1329− // Context paths should be additive (default + custom)
1330 expectedContextPaths := append(defaultContextPaths, "custom-context.md")
1434 expectedContextPaths := append(defaultContextPaths, "custom-context.md")
1331 assert.Equal(t, expectedContextPaths, customAgent.ContextPaths)
1435 assert.Equal(t, expectedContextPaths, customAgent.ContextPaths)
@@ -1336,17 +1440,28 @@ func TestAgentMerging_ModifyDefaultCoderAgent(t *testing.T) {
1336 testConfigDir = t.TempDir()
1440 testConfigDir = t.TempDir()
1337 cwdDir := t.TempDir()
1441 cwdDir := t.TempDir()
1339− // Set up a provider
1340 os.Setenv("ANTHROPIC_API_KEY", "test-key")
1443 os.Setenv("ANTHROPIC_API_KEY", "test-key")
1342− // Create config that modifies the default coder agent
1343 globalConfig := Config{
1445 globalConfig := Config{
1344 Agents: map[AgentID]Agent{
1446 Agents: map[AgentID]Agent{
1345 AgentCoder: {
1447 AgentCoder: {
1346− Model: SmallModel, // Change from default LargeModel
1448+ Model: SmallModel,
1347 AllowedMCP: map[string][]string{"mcp1": {"tool1"}},
1449 AllowedMCP: map[string][]string{"mcp1": {"tool1"}},
1348 AllowedLSP: []string{"typescript"},
1450 AllowedLSP: []string{"typescript"},
1349− ContextPaths: []string{"coder-specific.md"}, // Should be additive
1451+ ContextPaths: []string{"coder-specific.md"},
1454+ MCP: map[string]MCP{
1457+ Command: "test-mcp-command",
1458+ Args: []string{"--test"},
1461+ LSP: map[string]LSPConfig{
1463+ Command: "typescript-language-server",
1464+ Args: []string{"--stdio"},
@@ -1362,16 +1477,13 @@ func TestAgentMerging_ModifyDefaultCoderAgent(t *testing.T) {
1362 require.NoError(t, err)
1477 require.NoError(t, err)
1364 coderAgent := cfg.Agents[AgentCoder]
1479 coderAgent := cfg.Agents[AgentCoder]
1365− // Should preserve default values for unspecified fields
1366 assert.Equal(t, AgentCoder, coderAgent.ID)
1480 assert.Equal(t, AgentCoder, coderAgent.ID)
1367 assert.Equal(t, "Coder", coderAgent.Name)
1481 assert.Equal(t, "Coder", coderAgent.Name)
1368 assert.Equal(t, "An agent that helps with executing coding tasks.", coderAgent.Description)
1482 assert.Equal(t, "An agent that helps with executing coding tasks.", coderAgent.Description)
1370− // Context paths should be additive (default + custom)
1371 expectedContextPaths := append(cfg.Options.ContextPaths, "coder-specific.md")
1484 expectedContextPaths := append(cfg.Options.ContextPaths, "coder-specific.md")
1372 assert.Equal(t, expectedContextPaths, coderAgent.ContextPaths)
1485 assert.Equal(t, expectedContextPaths, coderAgent.ContextPaths)
1374− // Should update specified fields
1375 assert.Equal(t, SmallModel, coderAgent.Model)
1487 assert.Equal(t, SmallModel, coderAgent.Model)
1376 assert.Equal(t, map[string][]string{"mcp1": {"tool1"}}, coderAgent.AllowedMCP)
1488 assert.Equal(t, map[string][]string{"mcp1": {"tool1"}}, coderAgent.AllowedMCP)
1377 assert.Equal(t, []string{"typescript"}, coderAgent.AllowedLSP)
1489 assert.Equal(t, []string{"typescript"}, coderAgent.AllowedLSP)
@@ -1382,22 +1494,31 @@ func TestAgentMerging_ModifyDefaultTaskAgent(t *testing.T) {
1382 testConfigDir = t.TempDir()
1494 testConfigDir = t.TempDir()
1383 cwdDir := t.TempDir()
1495 cwdDir := t.TempDir()
1385− // Set up a provider
1386 os.Setenv("ANTHROPIC_API_KEY", "test-key")
1497 os.Setenv("ANTHROPIC_API_KEY", "test-key")
1388− // Create config that modifies the default task agent
1389− // Note: Only model, MCP, and LSP should be configurable for known agents
1390 globalConfig := Config{
1499 globalConfig := Config{
1391 Agents: map[AgentID]Agent{
1500 Agents: map[AgentID]Agent{
1392 AgentTask: {
1501 AgentTask: {
1393− Model: SmallModel, // Should be updated
1502+ Model: SmallModel,
1394− AllowedMCP: map[string][]string{"search-mcp": nil}, // Should be updated
1503+ AllowedMCP: map[string][]string{"search-mcp": nil},
1395− AllowedLSP: []string{"python"}, // Should be updated
1504+ AllowedLSP: []string{"python"},
1396− // These should be ignored for known agents:
1397− Name: "Search Agent", // Should be ignored
1505+ Name: "Search Agent",
1398− Description: "Custom search agent", // Should be ignored
1506+ Description: "Custom search agent",
1399− Disabled: true, // Should be ignored
1507+ Disabled: true,
1400− AllowedTools: []string{"glob", "grep", "view"}, // Should be ignored
1508+ AllowedTools: []string{"glob", "grep", "view"},
1511+ MCP: map[string]MCP{
1514+ Command: "search-mcp-command",
1515+ Args: []string{"--search"},
1518+ LSP: map[string]LSPConfig{
@@ -1413,13 +1534,11 @@ func TestAgentMerging_ModifyDefaultTaskAgent(t *testing.T) {
1413 require.NoError(t, err)
1534 require.NoError(t, err)
1415 taskAgent := cfg.Agents[AgentTask]
1536 taskAgent := cfg.Agents[AgentTask]
1416− // Should preserve default values for protected fields
1417− assert.Equal(t, "Task", taskAgent.Name) // Should remain default
1537+ assert.Equal(t, "Task", taskAgent.Name)
1418− assert.Equal(t, "An agent that helps with searching for context and finding implementation details.", taskAgent.Description) // Should remain default
1538+ assert.Equal(t, "An agent that helps with searching for context and finding implementation details.", taskAgent.Description)
1419− assert.False(t, taskAgent.Disabled) // Should remain default
1539+ assert.False(t, taskAgent.Disabled)
1420− assert.Equal(t, []string{"glob", "grep", "ls", "sourcegraph", "view"}, taskAgent.AllowedTools) // Should remain default
1540+ assert.Equal(t, []string{"glob", "grep", "ls", "sourcegraph", "view"}, taskAgent.AllowedTools)
1422− // Should update configurable fields
1423 assert.Equal(t, SmallModel, taskAgent.Model)
1542 assert.Equal(t, SmallModel, taskAgent.Model)
1424 assert.Equal(t, map[string][]string{"search-mcp": nil}, taskAgent.AllowedMCP)
1543 assert.Equal(t, map[string][]string{"search-mcp": nil}, taskAgent.AllowedMCP)
1425 assert.Equal(t, []string{"python"}, taskAgent.AllowedLSP)
1544 assert.Equal(t, []string{"python"}, taskAgent.AllowedLSP)
@@ -1430,10 +1549,8 @@ func TestAgentMerging_LocalOverridesGlobal(t *testing.T) {
1430 testConfigDir = t.TempDir()
1549 testConfigDir = t.TempDir()
1431 cwdDir := t.TempDir()
1550 cwdDir := t.TempDir()
1433− // Set up a provider
1434 os.Setenv("ANTHROPIC_API_KEY", "test-key")
1552 os.Setenv("ANTHROPIC_API_KEY", "test-key")
1436− // Create global config with custom agent
1437 globalConfig := Config{
1554 globalConfig := Config{
1438 Agents: map[AgentID]Agent{
1555 Agents: map[AgentID]Agent{
1439 AgentID("test-agent"): {
1556 AgentID("test-agent"): {
@@ -1464,6 +1581,13 @@ func TestAgentMerging_LocalOverridesGlobal(t *testing.T) {
1464 AllowedMCP: map[string][]string{"local-mcp": {"tool1"}},
1581 AllowedMCP: map[string][]string{"local-mcp": {"tool1"}},
1584+ MCP: map[string]MCP{
1587+ Command: "local-mcp-command",
1588+ Args: []string{"--local"},
1469 localConfigPath := filepath.Join(cwdDir, "crush.json")
1593 localConfigPath := filepath.Join(cwdDir, "crush.json")
@@ -1476,7 +1600,6 @@ func TestAgentMerging_LocalOverridesGlobal(t *testing.T) {
1476 require.NoError(t, err)
1600 require.NoError(t, err)
1478 testAgent := cfg.Agents[AgentID("test-agent")]
1602 testAgent := cfg.Agents[AgentID("test-agent")]
1479− // Local should override global
1480 assert.Equal(t, "Local Agent", testAgent.Name)
1603 assert.Equal(t, "Local Agent", testAgent.Name)
1481 assert.Equal(t, "Local description", testAgent.Description)
1604 assert.Equal(t, "Local description", testAgent.Description)
1482 assert.Equal(t, SmallModel, testAgent.Model)
1605 assert.Equal(t, SmallModel, testAgent.Model)
@@ -1490,10 +1613,8 @@ func TestAgentModelTypeAssignment(t *testing.T) {
1490 testConfigDir = t.TempDir()
1613 testConfigDir = t.TempDir()
1491 cwdDir := t.TempDir()
1614 cwdDir := t.TempDir()
1493− // Set up a provider
1494 os.Setenv("ANTHROPIC_API_KEY", "test-key")
1616 os.Setenv("ANTHROPIC_API_KEY", "test-key")
1496− // Create config with agents using different model types
1497 globalConfig := Config{
1618 globalConfig := Config{
1498 Agents: map[AgentID]Agent{
1619 Agents: map[AgentID]Agent{
1499 AgentID("large-agent"): {
1620 AgentID("large-agent"): {
@@ -1509,7 +1630,6 @@ func TestAgentModelTypeAssignment(t *testing.T) {
1509 AgentID("default-agent"): {
1630 AgentID("default-agent"): {
1510 ID: AgentID("default-agent"),
1631 ID: AgentID("default-agent"),
1511 Name: "Default Model Agent",
1632 Name: "Default Model Agent",
1512− // No model specified - should default to LargeModel
@@ -1526,7 +1646,7 @@ func TestAgentModelTypeAssignment(t *testing.T) {
1527 assert.Equal(t, LargeModel, cfg.Agents[AgentID("large-agent")].Model)
1647 assert.Equal(t, LargeModel, cfg.Agents[AgentID("large-agent")].Model)
1528 assert.Equal(t, SmallModel, cfg.Agents[AgentID("small-agent")].Model)
1648 assert.Equal(t, SmallModel, cfg.Agents[AgentID("small-agent")].Model)
1529− assert.Equal(t, LargeModel, cfg.Agents[AgentID("default-agent")].Model) // Should default to LargeModel
1649+ assert.Equal(t, LargeModel, cfg.Agents[AgentID("default-agent")].Model)
1532func TestAgentContextPathOverrides(t *testing.T) {
1652func TestAgentContextPathOverrides(t *testing.T) {
@@ -1534,10 +1654,8 @@ func TestAgentContextPathOverrides(t *testing.T) {
1534 testConfigDir = t.TempDir()
1654 testConfigDir = t.TempDir()
1535 cwdDir := t.TempDir()
1655 cwdDir := t.TempDir()
1537− // Set up a provider
1538 os.Setenv("ANTHROPIC_API_KEY", "test-key")
1657 os.Setenv("ANTHROPIC_API_KEY", "test-key")
1540− // Create config with custom context paths
1541 globalConfig := Config{
1659 globalConfig := Config{
1542 Options: Options{
1660 Options: Options{
1543 ContextPaths: []string{"global-context.md", "shared-context.md"},
1661 ContextPaths: []string{"global-context.md", "shared-context.md"},
@@ -1551,7 +1669,6 @@ func TestAgentContextPathOverrides(t *testing.T) {
1551 AgentID("default-context-agent"): {
1669 AgentID("default-context-agent"): {
1552 ID: AgentID("default-context-agent"),
1670 ID: AgentID("default-context-agent"),
1553 Name: "Default Context Agent",
1671 Name: "Default Context Agent",
1554− // No ContextPaths specified - should use global
@@ -1566,32 +1683,25 @@ func TestAgentContextPathOverrides(t *testing.T) {
1567 require.NoError(t, err)
1684 require.NoError(t, err)
1569− // Agent with custom context paths should have default + global + custom paths (additive)
1570 customAgent := cfg.Agents[AgentID("custom-context-agent")]
1686 customAgent := cfg.Agents[AgentID("custom-context-agent")]
1571 expectedCustomPaths := append(defaultContextPaths, "global-context.md", "shared-context.md", "agent-specific.md", "custom.md")
1687 expectedCustomPaths := append(defaultContextPaths, "global-context.md", "shared-context.md", "agent-specific.md", "custom.md")
1572 assert.Equal(t, expectedCustomPaths, customAgent.ContextPaths)
1688 assert.Equal(t, expectedCustomPaths, customAgent.ContextPaths)
1574− // Agent without custom context paths should use global + defaults
1575 defaultAgent := cfg.Agents[AgentID("default-context-agent")]
1690 defaultAgent := cfg.Agents[AgentID("default-context-agent")]
1576 expectedContextPaths := append(defaultContextPaths, "global-context.md", "shared-context.md")
1691 expectedContextPaths := append(defaultContextPaths, "global-context.md", "shared-context.md")
1577 assert.Equal(t, expectedContextPaths, defaultAgent.ContextPaths)
1692 assert.Equal(t, expectedContextPaths, defaultAgent.ContextPaths)
1579− // Default agents should also use the merged context paths
1580 coderAgent := cfg.Agents[AgentCoder]
1694 coderAgent := cfg.Agents[AgentCoder]
1581 assert.Equal(t, expectedContextPaths, coderAgent.ContextPaths)
1695 assert.Equal(t, expectedContextPaths, coderAgent.ContextPaths)
1584−// Options and Settings Tests
1586func TestOptionsMerging_ContextPaths(t *testing.T) {
1698func TestOptionsMerging_ContextPaths(t *testing.T) {
1588 testConfigDir = t.TempDir()
1700 testConfigDir = t.TempDir()
1589 cwdDir := t.TempDir()
1701 cwdDir := t.TempDir()
1591− // Set up a provider
1592 os.Setenv("ANTHROPIC_API_KEY", "test-key")
1703 os.Setenv("ANTHROPIC_API_KEY", "test-key")
1594− // Create global config with context paths
1595 globalConfig := Config{
1705 globalConfig := Config{
1596 Options: Options{
1706 Options: Options{
1597 ContextPaths: []string{"global1.md", "global2.md"},
1707 ContextPaths: []string{"global1.md", "global2.md"},
@@ -1604,7 +1714,6 @@ func TestOptionsMerging_ContextPaths(t *testing.T) {
1604 require.NoError(t, err)
1714 require.NoError(t, err)
1605 require.NoError(t, os.WriteFile(configPath, data, 0o644))
1715 require.NoError(t, os.WriteFile(configPath, data, 0o644))
1607− // Create local config with additional context paths
1608 localConfig := Config{
1717 localConfig := Config{
1609 Options: Options{
1718 Options: Options{
1610 ContextPaths: []string{"local1.md", "local2.md"},
1719 ContextPaths: []string{"local1.md", "local2.md"},
@@ -1620,7 +1729,6 @@ func TestOptionsMerging_ContextPaths(t *testing.T) {
1621 require.NoError(t, err)
1730 require.NoError(t, err)
1623− // Context paths should be merged: defaults + global + local
1624 expectedContextPaths := append(defaultContextPaths, "global1.md", "global2.md", "local1.md", "local2.md")
1732 expectedContextPaths := append(defaultContextPaths, "global1.md", "global2.md", "local1.md", "local2.md")
1625 assert.Equal(t, expectedContextPaths, cfg.Options.ContextPaths)
1733 assert.Equal(t, expectedContextPaths, cfg.Options.ContextPaths)
@@ -1630,14 +1738,12 @@ func TestOptionsMerging_TUIOptions(t *testing.T) {
1630 testConfigDir = t.TempDir()
1738 testConfigDir = t.TempDir()
1631 cwdDir := t.TempDir()
1739 cwdDir := t.TempDir()
1633− // Set up a provider
1634 os.Setenv("ANTHROPIC_API_KEY", "test-key")
1741 os.Setenv("ANTHROPIC_API_KEY", "test-key")
1636− // Create global config with TUI options
1637 globalConfig := Config{
1743 globalConfig := Config{
1638 Options: Options{
1744 Options: Options{
1639 TUI: TUIOptions{
1745 TUI: TUIOptions{
1640− CompactMode: false, // Default value
1746+ CompactMode: false,
@@ -1648,7 +1754,6 @@ func TestOptionsMerging_TUIOptions(t *testing.T) {
1648 require.NoError(t, err)
1754 require.NoError(t, err)
1649 require.NoError(t, os.WriteFile(configPath, data, 0o644))
1755 require.NoError(t, os.WriteFile(configPath, data, 0o644))
1651− // Create local config that enables compact mode
1652 localConfig := Config{
1757 localConfig := Config{
1653 Options: Options{
1758 Options: Options{
1654 TUI: TUIOptions{
1759 TUI: TUIOptions{
@@ -1666,7 +1771,6 @@ func TestOptionsMerging_TUIOptions(t *testing.T) {
1667 require.NoError(t, err)
1772 require.NoError(t, err)
1669− // Local config should override global
1670 assert.True(t, cfg.Options.TUI.CompactMode)
1774 assert.True(t, cfg.Options.TUI.CompactMode)
@@ -1675,10 +1779,8 @@ func TestOptionsMerging_DebugFlags(t *testing.T) {
1675 testConfigDir = t.TempDir()
1779 testConfigDir = t.TempDir()
1676 cwdDir := t.TempDir()
1780 cwdDir := t.TempDir()
1678− // Set up a provider
1679 os.Setenv("ANTHROPIC_API_KEY", "test-key")
1782 os.Setenv("ANTHROPIC_API_KEY", "test-key")
1681− // Create global config with debug flags
1682 globalConfig := Config{
1784 globalConfig := Config{
1683 Options: Options{
1785 Options: Options{
1684 Debug: false,
1786 Debug: false,
@@ -1693,7 +1795,6 @@ func TestOptionsMerging_DebugFlags(t *testing.T) {
1693 require.NoError(t, err)
1795 require.NoError(t, err)
1694 require.NoError(t, os.WriteFile(configPath, data, 0o644))
1796 require.NoError(t, os.WriteFile(configPath, data, 0o644))
1696− // Create local config that enables debug flags
1697 localConfig := Config{
1798 localConfig := Config{
1698 Options: Options{
1799 Options: Options{
1699 DebugLSP: true,
1800 DebugLSP: true,
@@ -1710,10 +1811,9 @@ func TestOptionsMerging_DebugFlags(t *testing.T) {
1711 require.NoError(t, err)
1812 require.NoError(t, err)
1713− // Local config should override global for boolean flags
1714− assert.False(t, cfg.Options.Debug) // Not set in local, remains global value
1814+ assert.False(t, cfg.Options.Debug)
1715− assert.True(t, cfg.Options.DebugLSP) // Set to true in local
1815+ assert.True(t, cfg.Options.DebugLSP)
1716− assert.True(t, cfg.Options.DisableAutoSummarize) // Set to true in local
1816+ assert.True(t, cfg.Options.DisableAutoSummarize)
1719func TestOptionsMerging_DataDirectory(t *testing.T) {
1819func TestOptionsMerging_DataDirectory(t *testing.T) {
@@ -1721,10 +1821,8 @@ func TestOptionsMerging_DataDirectory(t *testing.T) {
1721 testConfigDir = t.TempDir()
1821 testConfigDir = t.TempDir()
1722 cwdDir := t.TempDir()
1822 cwdDir := t.TempDir()
1724− // Set up a provider
1725 os.Setenv("ANTHROPIC_API_KEY", "test-key")
1824 os.Setenv("ANTHROPIC_API_KEY", "test-key")
1727− // Create global config with custom data directory
1728 globalConfig := Config{
1826 globalConfig := Config{
1729 Options: Options{
1827 Options: Options{
1730 DataDirectory: "global-data",
1828 DataDirectory: "global-data",
@@ -1737,7 +1835,6 @@ func TestOptionsMerging_DataDirectory(t *testing.T) {
1737 require.NoError(t, err)
1835 require.NoError(t, err)
1738 require.NoError(t, os.WriteFile(configPath, data, 0o644))
1836 require.NoError(t, os.WriteFile(configPath, data, 0o644))
1740− // Create local config with different data directory
1741 localConfig := Config{
1838 localConfig := Config{
1742 Options: Options{
1839 Options: Options{
1743 DataDirectory: "local-data",
1840 DataDirectory: "local-data",
@@ -1753,7 +1850,6 @@ func TestOptionsMerging_DataDirectory(t *testing.T) {
1754 require.NoError(t, err)
1851 require.NoError(t, err)
1756− // Local config should override global
1757 assert.Equal(t, "local-data", cfg.Options.DataDirectory)
1853 assert.Equal(t, "local-data", cfg.Options.DataDirectory)
@@ -1762,15 +1858,12 @@ func TestOptionsMerging_DefaultValues(t *testing.T) {
1762 testConfigDir = t.TempDir()
1858 testConfigDir = t.TempDir()
1763 cwdDir := t.TempDir()
1859 cwdDir := t.TempDir()
1765− // Set up a provider
1766 os.Setenv("ANTHROPIC_API_KEY", "test-key")
1861 os.Setenv("ANTHROPIC_API_KEY", "test-key")
1768− // No config files - should use defaults
1769 cfg, err := Init(cwdDir, false)
1863 cfg, err := Init(cwdDir, false)
1771 require.NoError(t, err)
1865 require.NoError(t, err)
1773− // Should have default values
1774 assert.Equal(t, defaultDataDirectory, cfg.Options.DataDirectory)
1867 assert.Equal(t, defaultDataDirectory, cfg.Options.DataDirectory)
1775 assert.Equal(t, defaultContextPaths, cfg.Options.ContextPaths)
1868 assert.Equal(t, defaultContextPaths, cfg.Options.ContextPaths)
1776 assert.False(t, cfg.Options.TUI.CompactMode)
1869 assert.False(t, cfg.Options.TUI.CompactMode)
@@ -1784,10 +1877,8 @@ func TestOptionsMerging_DebugFlagFromInit(t *testing.T) {
1784 testConfigDir = t.TempDir()
1877 testConfigDir = t.TempDir()
1785 cwdDir := t.TempDir()
1878 cwdDir := t.TempDir()
1787− // Set up a provider
1788 os.Setenv("ANTHROPIC_API_KEY", "test-key")
1880 os.Setenv("ANTHROPIC_API_KEY", "test-key")
1790− // Create config with debug false
1791 globalConfig := Config{
1882 globalConfig := Config{
1792 Options: Options{
1883 Options: Options{
1793 Debug: false,
1884 Debug: false,
@@ -1800,7 +1891,6 @@ func TestOptionsMerging_DebugFlagFromInit(t *testing.T) {
1800 require.NoError(t, err)
1891 require.NoError(t, err)
1801 require.NoError(t, os.WriteFile(configPath, data, 0o644))
1892 require.NoError(t, os.WriteFile(configPath, data, 0o644))
1803− // Init with debug=true should override config
1804 cfg, err := Init(cwdDir, true)
1894 cfg, err := Init(cwdDir, true)
1806 require.NoError(t, err)
1896 require.NoError(t, err)
@@ -1895,85 +1985,20 @@ func TestModelSelection_PreferredModelSelection(t *testing.T) {
1895 assert.Equal(t, cfg.Models.Large.Provider, cfg.Models.Small.Provider)
1985 assert.Equal(t, cfg.Models.Large.Provider, cfg.Models.Small.Provider)
1898−func TestModelSelection_GetAgentModel(t *testing.T) {
1988+func TestValidation_InvalidModelReference(t *testing.T) {
1900− testConfigDir = t.TempDir()
1901− cwdDir := t.TempDir()
1903− // Set up a provider with known models
1904− globalConfig := Config{
1905− Providers: map[provider.InferenceProvider]ProviderConfig{
1906− provider.InferenceProviderOpenAI: {
1907− ID: provider.InferenceProviderOpenAI,
1908− APIKey: "test-key",
1909− ProviderType: provider.TypeOpenAI,
1910− DefaultLargeModel: "gpt-4",
1911− DefaultSmallModel: "gpt-3.5-turbo",
1916− ContextWindow: 8192,
1917− DefaultMaxTokens: 4096,
1919− SupportsImages: true,
1922− ID: "gpt-3.5-turbo",
1923− Name: "GPT-3.5 Turbo",
1924− ContextWindow: 4096,
1925− DefaultMaxTokens: 2048,
1927− SupportsImages: false,
1934− configPath := filepath.Join(testConfigDir, "crush.json")
1935− require.NoError(t, os.MkdirAll(filepath.Dir(configPath), 0o755))
1936− data, err := json.Marshal(globalConfig)
1937− require.NoError(t, err)
1938− require.NoError(t, os.WriteFile(configPath, data, 0o644))
1940− _, err = Init(cwdDir, false)
1942− require.NoError(t, err)
1944− // Test GetAgentModel for default agents
1945− coderModel := GetAgentModel(AgentCoder)
1946− assert.Equal(t, "gpt-4", coderModel.ID) // Coder uses LargeModel
1947− assert.Equal(t, "GPT-4", coderModel.Name)
1948− assert.True(t, coderModel.CanReason)
1949− assert.True(t, coderModel.SupportsImages)
1951− taskModel := GetAgentModel(AgentTask)
1952− assert.Equal(t, "gpt-4", taskModel.ID) // Task also uses LargeModel by default
1953− assert.Equal(t, "GPT-4", taskModel.Name)
1956−func TestModelSelection_GetAgentModelWithCustomModelType(t *testing.T) {
1958 testConfigDir = t.TempDir()
1990 testConfigDir = t.TempDir()
1959 cwdDir := t.TempDir()
1991 cwdDir := t.TempDir()
1961− // Set up provider and custom agent with SmallModel
1962 globalConfig := Config{
1993 globalConfig := Config{
1963 Providers: map[provider.InferenceProvider]ProviderConfig{
1994 Providers: map[provider.InferenceProvider]ProviderConfig{
1964 provider.InferenceProviderOpenAI: {
1995 provider.InferenceProviderOpenAI: {
1965 ID: provider.InferenceProviderOpenAI,
1996 ID: provider.InferenceProviderOpenAI,
1966 APIKey: "test-key",
1997 APIKey: "test-key",
1967 ProviderType: provider.TypeOpenAI,
1998 ProviderType: provider.TypeOpenAI,
1968− DefaultLargeModel: "gpt-4",
1999+ DefaultLargeModel: "non-existent-model",
1969 DefaultSmallModel: "gpt-3.5-turbo",
2000 DefaultSmallModel: "gpt-3.5-turbo",
1970 Models: []Model{
2001 Models: []Model{
1974− ContextWindow: 8192,
1975− DefaultMaxTokens: 4096,
1978 ID: "gpt-3.5-turbo",
2003 ID: "gpt-3.5-turbo",
1979 Name: "GPT-3.5 Turbo",
2004 Name: "GPT-3.5 Turbo",
@@ -1983,54 +2008,6 @@ func TestModelSelection_GetAgentModelWithCustomModelType(t *testing.T) {
1986− Agents: map[AgentID]Agent{
1987− AgentID("small-agent"): {
1988− ID: AgentID("small-agent"),
1989− Name: "Small Agent",
1995− configPath := filepath.Join(testConfigDir, "crush.json")
1996− require.NoError(t, os.MkdirAll(filepath.Dir(configPath), 0o755))
1997− data, err := json.Marshal(globalConfig)
1998− require.NoError(t, err)
1999− require.NoError(t, os.WriteFile(configPath, data, 0o644))
2001− _, err = Init(cwdDir, false)
2003− require.NoError(t, err)
2005− // Test GetAgentModel for custom agent with SmallModel
2006− smallAgentModel := GetAgentModel(AgentID("small-agent"))
2007− assert.Equal(t, "gpt-3.5-turbo", smallAgentModel.ID)
2008− assert.Equal(t, "GPT-3.5 Turbo", smallAgentModel.Name)
2011−func TestModelSelection_GetAgentProvider(t *testing.T) {
2013− testConfigDir = t.TempDir()
2014− cwdDir := t.TempDir()
2016− // Set up multiple providers
2017− globalConfig := Config{
2018− Providers: map[provider.InferenceProvider]ProviderConfig{
2019− provider.InferenceProviderOpenAI: {
2020− ID: provider.InferenceProviderOpenAI,
2021− APIKey: "openai-key",
2022− ProviderType: provider.TypeOpenAI,
2023− DefaultLargeModel: "gpt-4",
2024− DefaultSmallModel: "gpt-3.5-turbo",
2026− provider.InferenceProviderAnthropic: {
2027− ID: provider.InferenceProviderAnthropic,
2028− APIKey: "anthropic-key",
2029− ProviderType: provider.TypeAnthropic,
2030− DefaultLargeModel: "claude-3-opus",
2031− DefaultSmallModel: "claude-3-haiku",
2036 configPath := filepath.Join(testConfigDir, "crush.json")
2013 configPath := filepath.Join(testConfigDir, "crush.json")
@@ -2040,27 +2017,18 @@ func TestModelSelection_GetAgentProvider(t *testing.T) {
2040 require.NoError(t, os.WriteFile(configPath, data, 0o644))
2017 require.NoError(t, os.WriteFile(configPath, data, 0o644))
2042 _, err = Init(cwdDir, false)
2019 _, err = Init(cwdDir, false)
2043−
2020+ assert.Error(t, err)
2044− require.NoError(t, err)
2046− // Test GetAgentProvider
2047− coderProvider := GetAgentProvider(AgentCoder)
2048− assert.NotEmpty(t, coderProvider.ID)
2049− assert.NotEmpty(t, coderProvider.APIKey)
2050− assert.NotEmpty(t, coderProvider.ProviderType)
2053−func TestModelSelection_GetProviderModel(t *testing.T) {
2023+func TestValidation_EmptyAPIKey(t *testing.T) {
2055 testConfigDir = t.TempDir()
2025 testConfigDir = t.TempDir()
2056 cwdDir := t.TempDir()
2026 cwdDir := t.TempDir()
2058− // Set up provider with specific models
2059 globalConfig := Config{
2028 globalConfig := Config{
2060 Providers: map[provider.InferenceProvider]ProviderConfig{
2029 Providers: map[provider.InferenceProvider]ProviderConfig{
2061 provider.InferenceProviderOpenAI: {
2030 provider.InferenceProviderOpenAI: {
2062 ID: provider.InferenceProviderOpenAI,
2031 ID: provider.InferenceProviderOpenAI,
2063− APIKey: "test-key",
2064 ProviderType: provider.TypeOpenAI,
2032 ProviderType: provider.TypeOpenAI,
2065 Models: []Model{
2033 Models: []Model{
@@ -2068,16 +2036,6 @@ func TestModelSelection_GetProviderModel(t *testing.T) {
2068 Name: "GPT-4",
2036 Name: "GPT-4",
2069 ContextWindow: 8192,
2037 ContextWindow: 8192,
2070 DefaultMaxTokens: 4096,
2038 DefaultMaxTokens: 4096,
2072− CostPer1MOut: 60.0,
2075− ID: "gpt-3.5-turbo",
2076− Name: "GPT-3.5 Turbo",
2077− ContextWindow: 4096,
2078− DefaultMaxTokens: 2048,
@@ -2091,107 +2049,22 @@ func TestModelSelection_GetProviderModel(t *testing.T) {
2091 require.NoError(t, os.WriteFile(configPath, data, 0o644))
2049 require.NoError(t, os.WriteFile(configPath, data, 0o644))
2093 _, err = Init(cwdDir, false)
2051 _, err = Init(cwdDir, false)
2094−
2052+ assert.Error(t, err)
2095− require.NoError(t, err)
2097− // Test GetProviderModel
2098− gpt4Model := GetProviderModel(provider.InferenceProviderOpenAI, "gpt-4")
2099− assert.Equal(t, "gpt-4", gpt4Model.ID)
2100− assert.Equal(t, "GPT-4", gpt4Model.Name)
2101− assert.Equal(t, int64(8192), gpt4Model.ContextWindow)
2102− assert.Equal(t, 30.0, gpt4Model.CostPer1MIn)
2104− gpt35Model := GetProviderModel(provider.InferenceProviderOpenAI, "gpt-3.5-turbo")
2105− assert.Equal(t, "gpt-3.5-turbo", gpt35Model.ID)
2106− assert.Equal(t, "GPT-3.5 Turbo", gpt35Model.Name)
2107− assert.Equal(t, 1.5, gpt35Model.CostPer1MIn)
2109− // Test non-existent model
2110− nonExistentModel := GetProviderModel(provider.InferenceProviderOpenAI, "non-existent")
2111− assert.Empty(t, nonExistentModel.ID)
2114−func TestModelSelection_GetModel(t *testing.T) {
2055+func TestValidation_InvalidAgentModelType(t *testing.T) {
2116 testConfigDir = t.TempDir()
2057 testConfigDir = t.TempDir()
2117 cwdDir := t.TempDir()
2058 cwdDir := t.TempDir()
2119− // Set up provider with models
2060+ os.Setenv("ANTHROPIC_API_KEY", "test-key")
2120− globalConfig := Config{
2121− Providers: map[provider.InferenceProvider]ProviderConfig{
2122− provider.InferenceProviderOpenAI: {
2123− ID: provider.InferenceProviderOpenAI,
2124− APIKey: "test-key",
2125− ProviderType: provider.TypeOpenAI,
2126− DefaultLargeModel: "gpt-4",
2127− DefaultSmallModel: "gpt-3.5-turbo",
2132− ContextWindow: 8192,
2133− DefaultMaxTokens: 4096,
2136− ID: "gpt-3.5-turbo",
2137− Name: "GPT-3.5 Turbo",
2138− ContextWindow: 4096,
2139− DefaultMaxTokens: 2048,
2146− configPath := filepath.Join(testConfigDir, "crush.json")
2147− require.NoError(t, os.MkdirAll(filepath.Dir(configPath), 0o755))
2148− data, err := json.Marshal(globalConfig)
2149− require.NoError(t, err)
2150− require.NoError(t, os.WriteFile(configPath, data, 0o644))
2152− _, err = Init(cwdDir, false)
2154− require.NoError(t, err)
2157− largeModel := GetModel(LargeModel)
2158− assert.Equal(t, "gpt-4", largeModel.ID)
2159− assert.Equal(t, "GPT-4", largeModel.Name)
2161− smallModel := GetModel(SmallModel)
2162− assert.Equal(t, "gpt-3.5-turbo", smallModel.ID)
2163− assert.Equal(t, "GPT-3.5 Turbo", smallModel.Name)
2166−func TestModelSelection_UpdatePreferredModel(t *testing.T) {
2168− testConfigDir = t.TempDir()
2169− cwdDir := t.TempDir()
2171− // Set up multiple providers with OpenAI first to ensure it's selected initially
2172 globalConfig := Config{
2062 globalConfig := Config{
2173− Providers: map[provider.InferenceProvider]ProviderConfig{
2063+ Agents: map[AgentID]Agent{
2174− provider.InferenceProviderOpenAI: {
2064+ AgentID("invalid-agent"): {
2175− ID: provider.InferenceProviderOpenAI,
2065+ ID: AgentID("invalid-agent"),
2176− APIKey: "openai-key",
2066+ Name: "Invalid Agent",
2177− ProviderType: provider.TypeOpenAI,
2067+ Model: ModelType("invalid"),
2178− DefaultLargeModel: "gpt-4",
2179− DefaultSmallModel: "gpt-3.5-turbo",
2181− {ID: "gpt-4", Name: "GPT-4"},
2182− {ID: "gpt-3.5-turbo", Name: "GPT-3.5 Turbo"},
2185− provider.InferenceProviderAnthropic: {
2186− ID: provider.InferenceProviderAnthropic,
2187− APIKey: "anthropic-key",
2188− ProviderType: provider.TypeAnthropic,
2189− DefaultLargeModel: "claude-3-opus",
2190− DefaultSmallModel: "claude-3-haiku",
2192− {ID: "claude-3-opus", Name: "Claude 3 Opus"},
2193− {ID: "claude-3-haiku", Name: "Claude 3 Haiku"},
@@ -2203,97 +2076,5 @@ func TestModelSelection_UpdatePreferredModel(t *testing.T) {
2203 require.NoError(t, os.WriteFile(configPath, data, 0o644))
2076 require.NoError(t, os.WriteFile(configPath, data, 0o644))
2205 _, err = Init(cwdDir, false)
2078 _, err = Init(cwdDir, false)
2207− require.NoError(t, err)
2209− // Get initial preferred models (should be OpenAI since it's listed first)
2210− initialLargeModel := GetModel(LargeModel)
2211− initialSmallModel := GetModel(SmallModel)
2213− // Verify initial models are OpenAI models
2214− assert.Equal(t, "claude-3-opus", initialLargeModel.ID)
2215− assert.Equal(t, "claude-3-haiku", initialSmallModel.ID)
2217− // Update preferred models to Anthropic
2218− newLargeModel := PreferredModel{
2220− Provider: provider.InferenceProviderOpenAI,
2222− newSmallModel := PreferredModel{
2223− ModelID: "gpt-3.5-turbo",
2224− Provider: provider.InferenceProviderOpenAI,
2227− err = UpdatePreferredModel(LargeModel, newLargeModel)
2228− require.NoError(t, err)
2230− err = UpdatePreferredModel(SmallModel, newSmallModel)
2231− require.NoError(t, err)
2233− // Verify models were updated
2234− updatedLargeModel := GetModel(LargeModel)
2235− assert.Equal(t, "gpt-4", updatedLargeModel.ID)
2236− assert.NotEqual(t, initialLargeModel.ID, updatedLargeModel.ID)
2238− updatedSmallModel := GetModel(SmallModel)
2239− assert.Equal(t, "gpt-3.5-turbo", updatedSmallModel.ID)
2240− assert.NotEqual(t, initialSmallModel.ID, updatedSmallModel.ID)
2243−func TestModelSelection_InvalidModelType(t *testing.T) {
2245− testConfigDir = t.TempDir()
2246− cwdDir := t.TempDir()
2248− // Set up a provider
2249− os.Setenv("ANTHROPIC_API_KEY", "test-key")
2251− _, err := Init(cwdDir, false)
2252− require.NoError(t, err)
2254− // Test UpdatePreferredModel with invalid model type
2255− invalidModel := PreferredModel{
2256− ModelID: "some-model",
2257− Provider: provider.InferenceProviderAnthropic,
2260− err = UpdatePreferredModel(ModelType("invalid"), invalidModel)
2261 assert.Error(t, err)
2079 assert.Error(t, err)
2262− assert.Contains(t, err.Error(), "unknown model type")
2265−func TestModelSelection_NonExistentAgent(t *testing.T) {
2267− testConfigDir = t.TempDir()
2268− cwdDir := t.TempDir()
2270− // Set up a provider
2271− os.Setenv("ANTHROPIC_API_KEY", "test-key")
2273− _, err := Init(cwdDir, false)
2274− require.NoError(t, err)
2276− // Test GetAgentModel with non-existent agent
2277− nonExistentModel := GetAgentModel(AgentID("non-existent"))
2278− assert.Empty(t, nonExistentModel.ID)
2280− // Test GetAgentProvider with non-existent agent
2281− nonExistentProvider := GetAgentProvider(AgentID("non-existent"))
2282− assert.Empty(t, nonExistentProvider.ID)
2285−func TestModelSelection_NonExistentProvider(t *testing.T) {
2287− testConfigDir = t.TempDir()
2288− cwdDir := t.TempDir()
2290− // Set up a provider
2291− os.Setenv("ANTHROPIC_API_KEY", "test-key")
2293− _, err := Init(cwdDir, false)
2294− require.NoError(t, err)
2296− // Test GetProviderModel with non-existent provider
2297− nonExistentModel := GetProviderModel(provider.InferenceProvider("non-existent"), "some-model")
2298− assert.Empty(t, nonExistentModel.ID)