@@ -93,7 +93,7 @@ func TestInit_SingletonBehavior(t *testing.T) {
9393
9494 require.NoError(t, err1)
9595 require.NoError(t, err2)
96− assert.Same(t, cfg1, cfg2) // Should be the same instance
96+ assert.Same(t, cfg1, cfg2)
9797}
9898
9999func TestGet_BeforeInitialization(t *testing.T) {
@@ -124,7 +124,7 @@ func TestLoadConfig_NoConfigFiles(t *testing.T) {
124124 cfg, err := Init(cwdDir, false)
125125
126126 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)
128128 assert.Equal(t, defaultContextPaths, cfg.Options.ContextPaths)
129129}
130130
@@ -133,7 +133,6 @@ func TestLoadConfig_OnlyGlobalConfig(t *testing.T) {
133133 testConfigDir = t.TempDir()
134134 cwdDir := t.TempDir()
135135
136− // Create global config file
137136 globalConfig := Config{
138137 Providers: map[provider.InferenceProvider]ProviderConfig{
139138 provider.InferenceProviderOpenAI: {
@@ -187,7 +186,6 @@ func TestLoadConfig_OnlyLocalConfig(t *testing.T) {
187186 testConfigDir = t.TempDir()
188187 cwdDir := t.TempDir()
189188
190− // Create local config file
191189 localConfig := Config{
192190 Providers: map[provider.InferenceProvider]ProviderConfig{
193191 provider.InferenceProviderAnthropic: {
@@ -239,7 +237,6 @@ func TestLoadConfig_BothGlobalAndLocal(t *testing.T) {
239237 testConfigDir = t.TempDir()
240238 cwdDir := t.TempDir()
241239
242− // Create global config
243240 globalConfig := Config{
244241 Providers: map[provider.InferenceProvider]ProviderConfig{
245242 provider.InferenceProviderOpenAI: {
@@ -279,7 +276,6 @@ func TestLoadConfig_BothGlobalAndLocal(t *testing.T) {
279276 require.NoError(t, err)
280277 require.NoError(t, os.WriteFile(configPath, data, 0o644))
281278
282− // Create local config that overrides and adds
283279 localConfig := Config{
284280 Providers: map[provider.InferenceProvider]ProviderConfig{
285281 provider.InferenceProviderOpenAI: {
@@ -327,14 +323,11 @@ func TestLoadConfig_BothGlobalAndLocal(t *testing.T) {
327323 require.NoError(t, err)
328324 assert.Len(t, cfg.Providers, 2)
329325
330− // Check that local config overrode global
331326 openaiProvider := cfg.Providers[provider.InferenceProviderOpenAI]
332327 assert.Equal(t, "local-key", openaiProvider.APIKey)
333328
334− // Check that local config added new provider
335329 assert.Contains(t, cfg.Providers, provider.InferenceProviderAnthropic)
336330
337− // Check that context paths were merged
338331 assert.Contains(t, cfg.Options.ContextPaths, "global-context.md")
339332 assert.Contains(t, cfg.Options.ContextPaths, "local-context.md")
340333 assert.True(t, cfg.Options.TUI.CompactMode)
@@ -345,7 +338,6 @@ func TestLoadConfig_MalformedGlobalJSON(t *testing.T) {
345338 testConfigDir = t.TempDir()
346339 cwdDir := t.TempDir()
347340
348− // Create malformed global config
349341 configPath := filepath.Join(testConfigDir, "crush.json")
350342 require.NoError(t, os.MkdirAll(filepath.Dir(configPath), 0o755))
351343 require.NoError(t, os.WriteFile(configPath, []byte(`{invalid json`), 0o644))
@@ -359,7 +351,6 @@ func TestLoadConfig_MalformedLocalJSON(t *testing.T) {
359351 testConfigDir = t.TempDir()
360352 cwdDir := t.TempDir()
361353
362− // Create malformed local config
363354 localConfigPath := filepath.Join(cwdDir, "crush.json")
364355 require.NoError(t, os.WriteFile(localConfigPath, []byte(`{invalid json`), 0o644))
365356
@@ -409,7 +400,6 @@ func TestEnvVars_AllSupportedAPIKeys(t *testing.T) {
409400 testConfigDir = t.TempDir()
410401 cwdDir := t.TempDir()
411402
412− // Set all supported API keys
413403 os.Setenv("ANTHROPIC_API_KEY", "test-anthropic-key")
414404 os.Setenv("OPENAI_API_KEY", "test-openai-key")
415405 os.Setenv("GEMINI_API_KEY", "test-gemini-key")
@@ -421,7 +411,6 @@ func TestEnvVars_AllSupportedAPIKeys(t *testing.T) {
421411 require.NoError(t, err)
422412 assert.Len(t, cfg.Providers, 5)
423413
424− // Verify each provider is configured correctly
425414 anthropicProvider := cfg.Providers[provider.InferenceProviderAnthropic]
426415 assert.Equal(t, "test-anthropic-key", anthropicProvider.APIKey)
427416 assert.Equal(t, provider.TypeAnthropic, anthropicProvider.ProviderType)
@@ -449,7 +438,6 @@ func TestEnvVars_PartialEnvironmentVariables(t *testing.T) {
449438 testConfigDir = t.TempDir()
450439 cwdDir := t.TempDir()
451440
452− // Set only some API keys
453441 os.Setenv("ANTHROPIC_API_KEY", "test-anthropic-key")
454442 os.Setenv("OPENAI_API_KEY", "test-openai-key")
455443
@@ -467,7 +455,6 @@ func TestEnvVars_VertexAIConfiguration(t *testing.T) {
467455 testConfigDir = t.TempDir()
468456 cwdDir := t.TempDir()
469457
470− // Set VertexAI environment variables
471458 os.Setenv("GOOGLE_GENAI_USE_VERTEXAI", "true")
472459 os.Setenv("GOOGLE_CLOUD_PROJECT", "test-project")
473460 os.Setenv("GOOGLE_CLOUD_LOCATION", "us-central1")
@@ -488,7 +475,6 @@ func TestEnvVars_VertexAIWithoutUseFlag(t *testing.T) {
488475 testConfigDir = t.TempDir()
489476 cwdDir := t.TempDir()
490477
491− // Set Google Cloud vars but not the use flag
492478 os.Setenv("GOOGLE_CLOUD_PROJECT", "test-project")
493479 os.Setenv("GOOGLE_CLOUD_LOCATION", "us-central1")
494480
@@ -503,7 +489,6 @@ func TestEnvVars_AWSBedrockWithAccessKeys(t *testing.T) {
503489 testConfigDir = t.TempDir()
504490 cwdDir := t.TempDir()
505491
506− // Set AWS credentials
507492 os.Setenv("AWS_ACCESS_KEY_ID", "test-access-key")
508493 os.Setenv("AWS_SECRET_ACCESS_KEY", "test-secret-key")
509494 os.Setenv("AWS_DEFAULT_REGION", "us-east-1")
@@ -523,7 +508,6 @@ func TestEnvVars_AWSBedrockWithProfile(t *testing.T) {
523508 testConfigDir = t.TempDir()
524509 cwdDir := t.TempDir()
525510
526− // Set AWS profile
527511 os.Setenv("AWS_PROFILE", "test-profile")
528512 os.Setenv("AWS_REGION", "eu-west-1")
529513
@@ -541,7 +525,6 @@ func TestEnvVars_AWSBedrockWithContainerCredentials(t *testing.T) {
541525 testConfigDir = t.TempDir()
542526 cwdDir := t.TempDir()
543527
544− // Set AWS container credentials
545528 os.Setenv("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI", "/v2/credentials/test")
546529 os.Setenv("AWS_DEFAULT_REGION", "ap-southeast-1")
547530
@@ -556,7 +539,6 @@ func TestEnvVars_AWSBedrockRegionPriority(t *testing.T) {
556539 testConfigDir = t.TempDir()
557540 cwdDir := t.TempDir()
558541
559− // Set both region variables - AWS_DEFAULT_REGION should take priority
560542 os.Setenv("AWS_ACCESS_KEY_ID", "test-key")
561543 os.Setenv("AWS_SECRET_ACCESS_KEY", "test-secret")
562544 os.Setenv("AWS_DEFAULT_REGION", "us-west-2")
@@ -574,7 +556,6 @@ func TestEnvVars_AWSBedrockFallbackRegion(t *testing.T) {
574556 testConfigDir = t.TempDir()
575557 cwdDir := t.TempDir()
576558
577− // Set only AWS_REGION (not AWS_DEFAULT_REGION)
578559 os.Setenv("AWS_ACCESS_KEY_ID", "test-key")
579560 os.Setenv("AWS_SECRET_ACCESS_KEY", "test-secret")
580561 os.Setenv("AWS_REGION", "us-east-1")
@@ -591,7 +572,6 @@ func TestEnvVars_NoAWSCredentials(t *testing.T) {
591572 testConfigDir = t.TempDir()
592573 cwdDir := t.TempDir()
593574
594− // Don't set any AWS credentials
595575 cfg, err := Init(cwdDir, false)
596576
597577 require.NoError(t, err)
@@ -603,15 +583,12 @@ func TestEnvVars_CustomEnvironmentVariables(t *testing.T) {
603583 testConfigDir = t.TempDir()
604584 cwdDir := t.TempDir()
605585
606− // Test that environment variables are properly resolved from provider definitions
607− // This test assumes the provider system uses $VARIABLE_NAME format
608586 os.Setenv("ANTHROPIC_API_KEY", "resolved-anthropic-key")
609587
610588 cfg, err := Init(cwdDir, false)
611589
612590 require.NoError(t, err)
613591 if len(cfg.Providers) > 0 {
614− // Verify that the environment variable was resolved
615592 if anthropicProvider, exists := cfg.Providers[provider.InferenceProviderAnthropic]; exists {
616593 assert.Equal(t, "resolved-anthropic-key", anthropicProvider.APIKey)
617594 }
@@ -623,11 +600,11 @@ func TestEnvVars_CombinedEnvironmentVariables(t *testing.T) {
623600 testConfigDir = t.TempDir()
624601 cwdDir := t.TempDir()
625602
626− // Set multiple types of environment variables
627603 os.Setenv("ANTHROPIC_API_KEY", "test-anthropic")
628604 os.Setenv("OPENAI_API_KEY", "test-openai")
629605 os.Setenv("GOOGLE_GENAI_USE_VERTEXAI", "true")
630606 os.Setenv("GOOGLE_CLOUD_PROJECT", "test-project")
607+ os.Setenv("GOOGLE_CLOUD_LOCATION", "us-central1")
631608 os.Setenv("AWS_ACCESS_KEY_ID", "test-aws-key")
632609 os.Setenv("AWS_SECRET_ACCESS_KEY", "test-aws-secret")
633610 os.Setenv("AWS_DEFAULT_REGION", "us-west-1")
@@ -636,7 +613,6 @@ func TestEnvVars_CombinedEnvironmentVariables(t *testing.T) {
636613
637614 require.NoError(t, err)
638615
639− // Should have API key providers + VertexAI + Bedrock
640616 expectedProviders := []provider.InferenceProvider{
641617 provider.InferenceProviderAnthropic,
642618 provider.InferenceProviderOpenAI,
@@ -696,14 +672,11 @@ func TestHasAWSCredentials_NoCredentials(t *testing.T) {
696672 assert.False(t, hasAWSCredentials())
697673}
698674
699−// Provider Configuration Tests
700−
701675func TestProviderMerging_GlobalToBase(t *testing.T) {
702676 reset()
703677 testConfigDir = t.TempDir()
704678 cwdDir := t.TempDir()
705679
706− // Create global config with provider
707680 globalConfig := Config{
708681 Providers: map[provider.InferenceProvider]ProviderConfig{
709682 provider.InferenceProviderOpenAI: {
@@ -719,6 +692,12 @@ func TestProviderMerging_GlobalToBase(t *testing.T) {
719692 ContextWindow: 8192,
720693 DefaultMaxTokens: 4096,
721694 },
695+ {
696+ ID: "gpt-3.5-turbo",
697+ Name: "GPT-3.5 Turbo",
698+ ContextWindow: 4096,
699+ DefaultMaxTokens: 2048,
700+ },
722701 },
723702 },
724703 },
@@ -739,7 +718,7 @@ func TestProviderMerging_GlobalToBase(t *testing.T) {
739718 assert.Equal(t, "global-openai-key", openaiProvider.APIKey)
740719 assert.Equal(t, "gpt-4", openaiProvider.DefaultLargeModel)
741720 assert.Equal(t, "gpt-3.5-turbo", openaiProvider.DefaultSmallModel)
742− assert.Len(t, openaiProvider.Models, 1)
721+ assert.Len(t, openaiProvider.Models, 2)
743722}
744723
745724func TestProviderMerging_LocalToBase(t *testing.T) {
@@ -747,7 +726,6 @@ func TestProviderMerging_LocalToBase(t *testing.T) {
747726 testConfigDir = t.TempDir()
748727 cwdDir := t.TempDir()
749728
750− // Create local config with provider
751729 localConfig := Config{
752730 Providers: map[provider.InferenceProvider]ProviderConfig{
753731 provider.InferenceProviderAnthropic: {
@@ -755,6 +733,25 @@ func TestProviderMerging_LocalToBase(t *testing.T) {
755733 APIKey: "local-anthropic-key",
756734 ProviderType: provider.TypeAnthropic,
757735 DefaultLargeModel: "claude-3-opus",
736+ DefaultSmallModel: "claude-3-haiku",
737+ Models: []Model{
738+ {
739+ ID: "claude-3-opus",
740+ Name: "Claude 3 Opus",
741+ ContextWindow: 200000,
742+ DefaultMaxTokens: 4096,
743+ CostPer1MIn: 15.0,
744+ CostPer1MOut: 75.0,
745+ },
746+ {
747+ ID: "claude-3-haiku",
748+ Name: "Claude 3 Haiku",
749+ ContextWindow: 200000,
750+ DefaultMaxTokens: 4096,
751+ CostPer1MIn: 0.25,
752+ CostPer1MOut: 1.25,
753+ },
754+ },
758755 },
759756 },
760757 }
@@ -772,6 +769,8 @@ func TestProviderMerging_LocalToBase(t *testing.T) {
772769 anthropicProvider := cfg.Providers[provider.InferenceProviderAnthropic]
773770 assert.Equal(t, "local-anthropic-key", anthropicProvider.APIKey)
774771 assert.Equal(t, "claude-3-opus", anthropicProvider.DefaultLargeModel)
772+ assert.Equal(t, "claude-3-haiku", anthropicProvider.DefaultSmallModel)
773+ assert.Len(t, anthropicProvider.Models, 2)
775774}
776775
777776func TestProviderMerging_ConflictingSettings(t *testing.T) {
@@ -779,7 +778,6 @@ func TestProviderMerging_ConflictingSettings(t *testing.T) {
779778 testConfigDir = t.TempDir()
780779 cwdDir := t.TempDir()
781780
782− // Create global config
783781 globalConfig := Config{
784782 Providers: map[provider.InferenceProvider]ProviderConfig{
785783 provider.InferenceProviderOpenAI: {
@@ -788,6 +786,26 @@ func TestProviderMerging_ConflictingSettings(t *testing.T) {
788786 ProviderType: provider.TypeOpenAI,
789787 DefaultLargeModel: "gpt-4",
790788 DefaultSmallModel: "gpt-3.5-turbo",
789+ Models: []Model{
790+ {
791+ ID: "gpt-4",
792+ Name: "GPT-4",
793+ ContextWindow: 8192,
794+ DefaultMaxTokens: 4096,
795+ },
796+ {
797+ ID: "gpt-3.5-turbo",
798+ Name: "GPT-3.5 Turbo",
799+ ContextWindow: 4096,
800+ DefaultMaxTokens: 2048,
801+ },
802+ {
803+ ID: "gpt-4-turbo",
804+ Name: "GPT-4 Turbo",
805+ ContextWindow: 128000,
806+ DefaultMaxTokens: 4096,
807+ },
808+ },
791809 },
792810 },
793811 }
@@ -804,7 +822,6 @@ func TestProviderMerging_ConflictingSettings(t *testing.T) {
804822 provider.InferenceProviderOpenAI: {
805823 APIKey: "local-key",
806824 DefaultLargeModel: "gpt-4-turbo",
807− // Test disabled separately - don't disable here as it causes nil pointer
808825 },
809826 },
810827 }
@@ -819,11 +836,9 @@ func TestProviderMerging_ConflictingSettings(t *testing.T) {
819836 require.NoError(t, err)
820837
821838 openaiProvider := cfg.Providers[provider.InferenceProviderOpenAI]
822− // Local should override global
823839 assert.Equal(t, "local-key", openaiProvider.APIKey)
824840 assert.Equal(t, "gpt-4-turbo", openaiProvider.DefaultLargeModel)
825− assert.False(t, openaiProvider.Disabled) // Should not be disabled
826− // Global values should remain where not overridden
841+ assert.False(t, openaiProvider.Disabled)
827842 assert.Equal(t, "gpt-3.5-turbo", openaiProvider.DefaultSmallModel)
828843}
829844
@@ -834,22 +849,51 @@ func TestProviderMerging_CustomVsKnownProviders(t *testing.T) {
834849
835850 customProviderID := provider.InferenceProvider("custom-provider")
836851
837− // Create config with both known and custom providers
838852 globalConfig := Config{
839853 Providers: map[provider.InferenceProvider]ProviderConfig{
840− // Known provider - some fields should not be overrideable
841854 provider.InferenceProviderOpenAI: {
842− ID: provider.InferenceProviderOpenAI,
843− APIKey: "openai-key",
844− BaseURL: "should-not-override",
845− ProviderType: provider.TypeAnthropic, // Should not override
855+ ID: provider.InferenceProviderOpenAI,
856+ APIKey: "openai-key",
857+ BaseURL: "should-not-override",
858+ ProviderType: provider.TypeAnthropic,
859+ DefaultLargeModel: "gpt-4",
860+ DefaultSmallModel: "gpt-3.5-turbo",
861+ Models: []Model{
862+ {
863+ ID: "gpt-4",
864+ Name: "GPT-4",
865+ ContextWindow: 8192,
866+ DefaultMaxTokens: 4096,
867+ },
868+ {
869+ ID: "gpt-3.5-turbo",
870+ Name: "GPT-3.5 Turbo",
871+ ContextWindow: 4096,
872+ DefaultMaxTokens: 2048,
873+ },
874+ },
846875 },
847− // Custom provider - all fields should be configurable
848876 customProviderID: {
849− ID: customProviderID,
850− APIKey: "custom-key",
851− BaseURL: "https://custom.api.com",
852− ProviderType: provider.TypeOpenAI,
877+ ID: customProviderID,
878+ APIKey: "custom-key",
879+ BaseURL: "https://custom.api.com",
880+ ProviderType: provider.TypeOpenAI,
881+ DefaultLargeModel: "custom-large",
882+ DefaultSmallModel: "custom-small",
883+ Models: []Model{
884+ {
885+ ID: "custom-large",
886+ Name: "Custom Large",
887+ ContextWindow: 8192,
888+ DefaultMaxTokens: 4096,
889+ },
890+ {
891+ ID: "custom-small",
892+ Name: "Custom Small",
893+ ContextWindow: 4096,
894+ DefaultMaxTokens: 2048,
895+ },
896+ },
853897 },
854898 },
855899 }
@@ -882,14 +926,12 @@ func TestProviderMerging_CustomVsKnownProviders(t *testing.T) {
882926
883927 require.NoError(t, err)
884928
885− // Known provider should not have BaseURL/ProviderType overridden
886929 openaiProvider := cfg.Providers[provider.InferenceProviderOpenAI]
887930 assert.NotEqual(t, "https://should-not-change.com", openaiProvider.BaseURL)
888931 assert.NotEqual(t, provider.TypeGemini, openaiProvider.ProviderType)
889932
890− // Custom provider should have all fields configurable
891933 customProvider := cfg.Providers[customProviderID]
892− assert.Equal(t, "custom-key", customProvider.APIKey) // Should preserve from global
934+ assert.Equal(t, "custom-key", customProvider.APIKey)
893935 assert.Equal(t, "https://updated-custom.api.com", customProvider.BaseURL)
894936 assert.Equal(t, provider.TypeOpenAI, customProvider.ProviderType)
895937}
@@ -901,14 +943,12 @@ func TestProviderValidation_CustomProviderMissingBaseURL(t *testing.T) {
901943
902944 customProviderID := provider.InferenceProvider("custom-provider")
903945
904− // Create config with custom provider missing BaseURL
905946 globalConfig := Config{
906947 Providers: map[provider.InferenceProvider]ProviderConfig{
907948 customProviderID: {
908949 ID: customProviderID,
909950 APIKey: "custom-key",
910951 ProviderType: provider.TypeOpenAI,
911− // Missing BaseURL
912952 },
913953 },
914954 }
@@ -922,7 +962,6 @@ func TestProviderValidation_CustomProviderMissingBaseURL(t *testing.T) {
922962 cfg, err := Init(cwdDir, false)
923963
924964 require.NoError(t, err)
925− // Provider should be filtered out due to validation failure
926965 assert.NotContains(t, cfg.Providers, customProviderID)
927966}
928967
@@ -939,7 +978,6 @@ func TestProviderValidation_CustomProviderMissingAPIKey(t *testing.T) {
939978 ID: customProviderID,
940979 BaseURL: "https://custom.api.com",
941980 ProviderType: provider.TypeOpenAI,
942− // Missing APIKey
943981 },
944982 },
945983 }
@@ -994,10 +1032,26 @@ func TestProviderValidation_KnownProviderValid(t *testing.T) {
9941032 globalConfig := Config{
9951033 Providers: map[provider.InferenceProvider]ProviderConfig{
9961034 provider.InferenceProviderOpenAI: {
997− ID: provider.InferenceProviderOpenAI,
998− APIKey: "openai-key",
999− ProviderType: provider.TypeOpenAI,
1000− // BaseURL not required for known providers
1035+ ID: provider.InferenceProviderOpenAI,
1036+ APIKey: "openai-key",
1037+ ProviderType: provider.TypeOpenAI,
1038+ DefaultLargeModel: "gpt-4",
1039+ DefaultSmallModel: "gpt-3.5-turbo",
1040+ Models: []Model{
1041+ {
1042+ ID: "gpt-4",
1043+ Name: "GPT-4",
1044+ ContextWindow: 8192,
1045+ DefaultMaxTokens: 4096,
1046+ },
1047+ {
1048+ ID: "gpt-3.5-turbo",
1049+ Name: "GPT-3.5 Turbo",
1050+ ContextWindow: 4096,
1051+ DefaultMaxTokens: 2048,
1052+ },
1053+ },
1054+
10011055 },
10021056 },
10031057 }
@@ -1022,10 +1076,48 @@ func TestProviderValidation_DisabledProvider(t *testing.T) {
10221076 globalConfig := Config{
10231077 Providers: map[provider.InferenceProvider]ProviderConfig{
10241078 provider.InferenceProviderOpenAI: {
1025− ID: provider.InferenceProviderOpenAI,
1026− APIKey: "openai-key",
1027− ProviderType: provider.TypeOpenAI,
1028− Disabled: true,
1079+ ID: provider.InferenceProviderOpenAI,
1080+ APIKey: "openai-key",
1081+ ProviderType: provider.TypeOpenAI,
1082+ Disabled: true,
1083+ DefaultLargeModel: "gpt-4",
1084+ DefaultSmallModel: "gpt-3.5-turbo",
1085+ Models: []Model{
1086+ {
1087+ ID: "gpt-4",
1088+ Name: "GPT-4",
1089+ ContextWindow: 8192,
1090+ DefaultMaxTokens: 4096,
1091+ },
1092+ {
1093+ ID: "gpt-3.5-turbo",
1094+ Name: "GPT-3.5 Turbo",
1095+ ContextWindow: 4096,
1096+ DefaultMaxTokens: 2048,
1097+ },
1098+ },
1099+ },
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",
1107+ Models: []Model{
1108+ {
1109+ ID: "claude-3-opus",
1110+ Name: "Claude 3 Opus",
1111+ ContextWindow: 200000,
1112+ DefaultMaxTokens: 4096,
1113+ },
1114+ {
1115+ ID: "claude-3-haiku",
1116+ Name: "Claude 3 Haiku",
1117+ ContextWindow: 200000,
1118+ DefaultMaxTokens: 4096,
1119+ },
1120+ },
10291121 },
10301122 },
10311123 }
@@ -1039,9 +1131,10 @@ func TestProviderValidation_DisabledProvider(t *testing.T) {
10391131 cfg, err := Init(cwdDir, false)
10401132
10411133 require.NoError(t, err)
1042− // Disabled providers should still be in the config but marked as disabled
10431134 assert.Contains(t, cfg.Providers, provider.InferenceProviderOpenAI)
10441135 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)
10451138}
10461139
10471140func TestProviderModels_AddingNewModels(t *testing.T) {
@@ -1052,9 +1145,11 @@ func TestProviderModels_AddingNewModels(t *testing.T) {
10521145 globalConfig := Config{
10531146 Providers: map[provider.InferenceProvider]ProviderConfig{
10541147 provider.InferenceProviderOpenAI: {
1055− ID: provider.InferenceProviderOpenAI,
1056− APIKey: "openai-key",
1057− ProviderType: provider.TypeOpenAI,
1148+ ID: provider.InferenceProviderOpenAI,
1149+ APIKey: "openai-key",
1150+ ProviderType: provider.TypeOpenAI,
1151+ DefaultLargeModel: "gpt-4",
1152+ DefaultSmallModel: "gpt-4-turbo",
10581153 Models: []Model{
10591154 {
10601155 ID: "gpt-4",
@@ -1098,7 +1193,7 @@ func TestProviderModels_AddingNewModels(t *testing.T) {
10981193 require.NoError(t, err)
10991194
11001195 openaiProvider := cfg.Providers[provider.InferenceProviderOpenAI]
1101− assert.Len(t, openaiProvider.Models, 2) // Should have both models
1196+ assert.Len(t, openaiProvider.Models, 2)
11021197
11031198 modelIDs := make([]string, len(openaiProvider.Models))
11041199 for i, model := range openaiProvider.Models {
@@ -1116,9 +1211,11 @@ func TestProviderModels_DuplicateModelHandling(t *testing.T) {
11161211 globalConfig := Config{
11171212 Providers: map[provider.InferenceProvider]ProviderConfig{
11181213 provider.InferenceProviderOpenAI: {
1119− ID: provider.InferenceProviderOpenAI,
1120− APIKey: "openai-key",
1121− ProviderType: provider.TypeOpenAI,
1214+ ID: provider.InferenceProviderOpenAI,
1215+ APIKey: "openai-key",
1216+ ProviderType: provider.TypeOpenAI,
1217+ DefaultLargeModel: "gpt-4",
1218+ DefaultSmallModel: "gpt-4",
11221219 Models: []Model{
11231220 {
11241221 ID: "gpt-4",
@@ -1136,7 +1233,7 @@ func TestProviderModels_DuplicateModelHandling(t *testing.T) {
11361233 provider.InferenceProviderOpenAI: {
11371234 Models: []Model{
11381235 {
1139− ID: "gpt-4", // Same ID as global
1236+ ID: "gpt-4",
11401237 Name: "GPT-4 Updated",
11411238 ContextWindow: 16384,
11421239 DefaultMaxTokens: 8192,
@@ -1162,13 +1259,12 @@ func TestProviderModels_DuplicateModelHandling(t *testing.T) {
11621259 require.NoError(t, err)
11631260
11641261 openaiProvider := cfg.Providers[provider.InferenceProviderOpenAI]
1165− assert.Len(t, openaiProvider.Models, 1) // Should not duplicate
1262+ assert.Len(t, openaiProvider.Models, 1)
11661263
1167− // Should keep the original model (global config)
11681264 model := openaiProvider.Models[0]
11691265 assert.Equal(t, "gpt-4", model.ID)
1170− assert.Equal(t, "GPT-4", model.Name) // Original name
1171− assert.Equal(t, int64(8192), model.ContextWindow) // Original context window
1266+ assert.Equal(t, "GPT-4", model.Name)
1267+ assert.Equal(t, int64(8192), model.ContextWindow)
11721268}
11731269
11741270func TestProviderModels_ModelCostAndCapabilities(t *testing.T) {
@@ -1179,9 +1275,11 @@ func TestProviderModels_ModelCostAndCapabilities(t *testing.T) {
11791275 globalConfig := Config{
11801276 Providers: map[provider.InferenceProvider]ProviderConfig{
11811277 provider.InferenceProviderOpenAI: {
1182− ID: provider.InferenceProviderOpenAI,
1183− APIKey: "openai-key",
1184− ProviderType: provider.TypeOpenAI,
1278+ ID: provider.InferenceProviderOpenAI,
1279+ APIKey: "openai-key",
1280+ ProviderType: provider.TypeOpenAI,
1281+ DefaultLargeModel: "gpt-4",
1282+ DefaultSmallModel: "gpt-4",
11851283 Models: []Model{
11861284 {
11871285 ID: "gpt-4",
@@ -1224,14 +1322,11 @@ func TestProviderModels_ModelCostAndCapabilities(t *testing.T) {
12241322 assert.True(t, model.SupportsImages)
12251323}
12261324
1227−// Agent Configuration Tests
1228−
12291325func TestDefaultAgents_CoderAgent(t *testing.T) {
12301326 reset()
12311327 testConfigDir = t.TempDir()
12321328 cwdDir := t.TempDir()
12331329
1234− // Set up a provider so we can test agent configuration
12351330 os.Setenv("ANTHROPIC_API_KEY", "test-key")
12361331
12371332 cfg, err := Init(cwdDir, false)
@@ -1246,7 +1341,6 @@ func TestDefaultAgents_CoderAgent(t *testing.T) {
12461341 assert.Equal(t, LargeModel, coderAgent.Model)
12471342 assert.False(t, coderAgent.Disabled)
12481343 assert.Equal(t, cfg.Options.ContextPaths, coderAgent.ContextPaths)
1249− // Coder agent should have all tools available (nil means all tools)
12501344 assert.Nil(t, coderAgent.AllowedTools)
12511345}
12521346
@@ -1255,7 +1349,6 @@ func TestDefaultAgents_TaskAgent(t *testing.T) {
12551349 testConfigDir = t.TempDir()
12561350 cwdDir := t.TempDir()
12571351
1258− // Set up a provider so we can test agent configuration
12591352 os.Setenv("ANTHROPIC_API_KEY", "test-key")
12601353
12611354 cfg, err := Init(cwdDir, false)
@@ -1271,11 +1364,9 @@ func TestDefaultAgents_TaskAgent(t *testing.T) {
12711364 assert.False(t, taskAgent.Disabled)
12721365 assert.Equal(t, cfg.Options.ContextPaths, taskAgent.ContextPaths)
12731366
1274− // Task agent should have restricted tools
12751367 expectedTools := []string{"glob", "grep", "ls", "sourcegraph", "view"}
12761368 assert.Equal(t, expectedTools, taskAgent.AllowedTools)
12771369
1278− // Task agent should have no MCPs or LSPs by default
12791370 assert.Equal(t, map[string][]string{}, taskAgent.AllowedMCP)
12801371 assert.Equal(t, []string{}, taskAgent.AllowedLSP)
12811372}
@@ -1285,10 +1376,8 @@ func TestAgentMerging_CustomAgent(t *testing.T) {
12851376 testConfigDir = t.TempDir()
12861377 cwdDir := t.TempDir()
12871378
1288− // Set up a provider
12891379 os.Setenv("ANTHROPIC_API_KEY", "test-key")
12901380
1291− // Create config with custom agent
12921381 globalConfig := Config{
12931382 Agents: map[AgentID]Agent{
12941383 AgentID("custom-agent"): {
@@ -1302,6 +1391,23 @@ func TestAgentMerging_CustomAgent(t *testing.T) {
13021391 ContextPaths: []string{"custom-context.md"},
13031392 },
13041393 },
1394+ MCP: map[string]MCP{
1395+ "mcp1": {
1396+ Type: MCPStdio,
1397+ Command: "test-mcp-command",
1398+ Args: []string{"--test"},
1399+ },
1400+ },
1401+ LSP: map[string]LSPConfig{
1402+ "typescript": {
1403+ Command: "typescript-language-server",
1404+ Args: []string{"--stdio"},
1405+ },
1406+ "go": {
1407+ Command: "gopls",
1408+ Args: []string{},
1409+ },
1410+ },
13051411 }
13061412
13071413 configPath := filepath.Join(testConfigDir, "crush.json")
@@ -1314,7 +1420,6 @@ func TestAgentMerging_CustomAgent(t *testing.T) {
13141420
13151421 require.NoError(t, err)
13161422
1317− // Should have default agents plus custom agent
13181423 assert.Contains(t, cfg.Agents, AgentCoder)
13191424 assert.Contains(t, cfg.Agents, AgentTask)
13201425 assert.Contains(t, cfg.Agents, AgentID("custom-agent"))
@@ -1326,7 +1431,6 @@ func TestAgentMerging_CustomAgent(t *testing.T) {
13261431 assert.Equal(t, []string{"glob", "grep"}, customAgent.AllowedTools)
13271432 assert.Equal(t, map[string][]string{"mcp1": {"tool1", "tool2"}}, customAgent.AllowedMCP)
13281433 assert.Equal(t, []string{"typescript", "go"}, customAgent.AllowedLSP)
1329− // Context paths should be additive (default + custom)
13301434 expectedContextPaths := append(defaultContextPaths, "custom-context.md")
13311435 assert.Equal(t, expectedContextPaths, customAgent.ContextPaths)
13321436}
@@ -1336,17 +1440,28 @@ func TestAgentMerging_ModifyDefaultCoderAgent(t *testing.T) {
13361440 testConfigDir = t.TempDir()
13371441 cwdDir := t.TempDir()
13381442
1339− // Set up a provider
13401443 os.Setenv("ANTHROPIC_API_KEY", "test-key")
13411444
1342− // Create config that modifies the default coder agent
13431445 globalConfig := Config{
13441446 Agents: map[AgentID]Agent{
13451447 AgentCoder: {
1346− Model: SmallModel, // Change from default LargeModel
1448+ Model: SmallModel,
13471449 AllowedMCP: map[string][]string{"mcp1": {"tool1"}},
13481450 AllowedLSP: []string{"typescript"},
1349− ContextPaths: []string{"coder-specific.md"}, // Should be additive
1451+ ContextPaths: []string{"coder-specific.md"},
1452+ },
1453+ },
1454+ MCP: map[string]MCP{
1455+ "mcp1": {
1456+ Type: MCPStdio,
1457+ Command: "test-mcp-command",
1458+ Args: []string{"--test"},
1459+ },
1460+ },
1461+ LSP: map[string]LSPConfig{
1462+ "typescript": {
1463+ Command: "typescript-language-server",
1464+ Args: []string{"--stdio"},
13501465 },
13511466 },
13521467 }
@@ -1362,16 +1477,13 @@ func TestAgentMerging_ModifyDefaultCoderAgent(t *testing.T) {
13621477 require.NoError(t, err)
13631478
13641479 coderAgent := cfg.Agents[AgentCoder]
1365− // Should preserve default values for unspecified fields
13661480 assert.Equal(t, AgentCoder, coderAgent.ID)
13671481 assert.Equal(t, "Coder", coderAgent.Name)
13681482 assert.Equal(t, "An agent that helps with executing coding tasks.", coderAgent.Description)
13691483
1370− // Context paths should be additive (default + custom)
13711484 expectedContextPaths := append(cfg.Options.ContextPaths, "coder-specific.md")
13721485 assert.Equal(t, expectedContextPaths, coderAgent.ContextPaths)
13731486
1374− // Should update specified fields
13751487 assert.Equal(t, SmallModel, coderAgent.Model)
13761488 assert.Equal(t, map[string][]string{"mcp1": {"tool1"}}, coderAgent.AllowedMCP)
13771489 assert.Equal(t, []string{"typescript"}, coderAgent.AllowedLSP)
@@ -1382,22 +1494,31 @@ func TestAgentMerging_ModifyDefaultTaskAgent(t *testing.T) {
13821494 testConfigDir = t.TempDir()
13831495 cwdDir := t.TempDir()
13841496
1385− // Set up a provider
13861497 os.Setenv("ANTHROPIC_API_KEY", "test-key")
13871498
1388− // Create config that modifies the default task agent
1389− // Note: Only model, MCP, and LSP should be configurable for known agents
13901499 globalConfig := Config{
13911500 Agents: map[AgentID]Agent{
13921501 AgentTask: {
1393− Model: SmallModel, // Should be updated
1394− AllowedMCP: map[string][]string{"search-mcp": nil}, // Should be updated
1395− AllowedLSP: []string{"python"}, // Should be updated
1396− // These should be ignored for known agents:
1397− Name: "Search Agent", // Should be ignored
1398− Description: "Custom search agent", // Should be ignored
1399− Disabled: true, // Should be ignored
1400− AllowedTools: []string{"glob", "grep", "view"}, // Should be ignored
1502+ Model: SmallModel,
1503+ AllowedMCP: map[string][]string{"search-mcp": nil},
1504+ AllowedLSP: []string{"python"},
1505+ Name: "Search Agent",
1506+ Description: "Custom search agent",
1507+ Disabled: true,
1508+ AllowedTools: []string{"glob", "grep", "view"},
1509+ },
1510+ },
1511+ MCP: map[string]MCP{
1512+ "search-mcp": {
1513+ Type: MCPStdio,
1514+ Command: "search-mcp-command",
1515+ Args: []string{"--search"},
1516+ },
1517+ },
1518+ LSP: map[string]LSPConfig{
1519+ "python": {
1520+ Command: "pylsp",
1521+ Args: []string{},
14011522 },
14021523 },
14031524 }
@@ -1413,13 +1534,11 @@ func TestAgentMerging_ModifyDefaultTaskAgent(t *testing.T) {
14131534 require.NoError(t, err)
14141535
14151536 taskAgent := cfg.Agents[AgentTask]
1416− // Should preserve default values for protected fields
1417− assert.Equal(t, "Task", taskAgent.Name) // Should remain default
1418− assert.Equal(t, "An agent that helps with searching for context and finding implementation details.", taskAgent.Description) // Should remain default
1419− assert.False(t, taskAgent.Disabled) // Should remain default
1420− assert.Equal(t, []string{"glob", "grep", "ls", "sourcegraph", "view"}, taskAgent.AllowedTools) // Should remain default
1537+ assert.Equal(t, "Task", taskAgent.Name)
1538+ assert.Equal(t, "An agent that helps with searching for context and finding implementation details.", taskAgent.Description)
1539+ assert.False(t, taskAgent.Disabled)
1540+ assert.Equal(t, []string{"glob", "grep", "ls", "sourcegraph", "view"}, taskAgent.AllowedTools)
14211541
1422− // Should update configurable fields
14231542 assert.Equal(t, SmallModel, taskAgent.Model)
14241543 assert.Equal(t, map[string][]string{"search-mcp": nil}, taskAgent.AllowedMCP)
14251544 assert.Equal(t, []string{"python"}, taskAgent.AllowedLSP)
@@ -1430,10 +1549,8 @@ func TestAgentMerging_LocalOverridesGlobal(t *testing.T) {
14301549 testConfigDir = t.TempDir()
14311550 cwdDir := t.TempDir()
14321551
1433− // Set up a provider
14341552 os.Setenv("ANTHROPIC_API_KEY", "test-key")
14351553
1436− // Create global config with custom agent
14371554 globalConfig := Config{
14381555 Agents: map[AgentID]Agent{
14391556 AgentID("test-agent"): {
@@ -1464,6 +1581,13 @@ func TestAgentMerging_LocalOverridesGlobal(t *testing.T) {
14641581 AllowedMCP: map[string][]string{"local-mcp": {"tool1"}},
14651582 },
14661583 },
1584+ MCP: map[string]MCP{
1585+ "local-mcp": {
1586+ Type: MCPStdio,
1587+ Command: "local-mcp-command",
1588+ Args: []string{"--local"},
1589+ },
1590+ },
14671591 }
14681592
14691593 localConfigPath := filepath.Join(cwdDir, "crush.json")
@@ -1476,7 +1600,6 @@ func TestAgentMerging_LocalOverridesGlobal(t *testing.T) {
14761600 require.NoError(t, err)
14771601
14781602 testAgent := cfg.Agents[AgentID("test-agent")]
1479− // Local should override global
14801603 assert.Equal(t, "Local Agent", testAgent.Name)
14811604 assert.Equal(t, "Local description", testAgent.Description)
14821605 assert.Equal(t, SmallModel, testAgent.Model)
@@ -1490,10 +1613,8 @@ func TestAgentModelTypeAssignment(t *testing.T) {
14901613 testConfigDir = t.TempDir()
14911614 cwdDir := t.TempDir()
14921615
1493− // Set up a provider
14941616 os.Setenv("ANTHROPIC_API_KEY", "test-key")
14951617
1496− // Create config with agents using different model types
14971618 globalConfig := Config{
14981619 Agents: map[AgentID]Agent{
14991620 AgentID("large-agent"): {
@@ -1509,7 +1630,6 @@ func TestAgentModelTypeAssignment(t *testing.T) {
15091630 AgentID("default-agent"): {
15101631 ID: AgentID("default-agent"),
15111632 Name: "Default Model Agent",
1512− // No model specified - should default to LargeModel
15131633 },
15141634 },
15151635 }
@@ -1526,7 +1646,7 @@ func TestAgentModelTypeAssignment(t *testing.T) {
15261646
15271647 assert.Equal(t, LargeModel, cfg.Agents[AgentID("large-agent")].Model)
15281648 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)
15301650}
15311651
15321652func TestAgentContextPathOverrides(t *testing.T) {
@@ -1534,10 +1654,8 @@ func TestAgentContextPathOverrides(t *testing.T) {
15341654 testConfigDir = t.TempDir()
15351655 cwdDir := t.TempDir()
15361656
1537− // Set up a provider
15381657 os.Setenv("ANTHROPIC_API_KEY", "test-key")
15391658
1540− // Create config with custom context paths
15411659 globalConfig := Config{
15421660 Options: Options{
15431661 ContextPaths: []string{"global-context.md", "shared-context.md"},
@@ -1551,7 +1669,6 @@ func TestAgentContextPathOverrides(t *testing.T) {
15511669 AgentID("default-context-agent"): {
15521670 ID: AgentID("default-context-agent"),
15531671 Name: "Default Context Agent",
1554− // No ContextPaths specified - should use global
15551672 },
15561673 },
15571674 }
@@ -1566,32 +1683,25 @@ func TestAgentContextPathOverrides(t *testing.T) {
15661683
15671684 require.NoError(t, err)
15681685
1569− // Agent with custom context paths should have default + global + custom paths (additive)
15701686 customAgent := cfg.Agents[AgentID("custom-context-agent")]
15711687 expectedCustomPaths := append(defaultContextPaths, "global-context.md", "shared-context.md", "agent-specific.md", "custom.md")
15721688 assert.Equal(t, expectedCustomPaths, customAgent.ContextPaths)
15731689
1574− // Agent without custom context paths should use global + defaults
15751690 defaultAgent := cfg.Agents[AgentID("default-context-agent")]
15761691 expectedContextPaths := append(defaultContextPaths, "global-context.md", "shared-context.md")
15771692 assert.Equal(t, expectedContextPaths, defaultAgent.ContextPaths)
15781693
1579− // Default agents should also use the merged context paths
15801694 coderAgent := cfg.Agents[AgentCoder]
15811695 assert.Equal(t, expectedContextPaths, coderAgent.ContextPaths)
15821696}
15831697
1584−// Options and Settings Tests
1585−
15861698func TestOptionsMerging_ContextPaths(t *testing.T) {
15871699 reset()
15881700 testConfigDir = t.TempDir()
15891701 cwdDir := t.TempDir()
15901702
1591− // Set up a provider
15921703 os.Setenv("ANTHROPIC_API_KEY", "test-key")
15931704
1594− // Create global config with context paths
15951705 globalConfig := Config{
15961706 Options: Options{
15971707 ContextPaths: []string{"global1.md", "global2.md"},
@@ -1604,7 +1714,6 @@ func TestOptionsMerging_ContextPaths(t *testing.T) {
16041714 require.NoError(t, err)
16051715 require.NoError(t, os.WriteFile(configPath, data, 0o644))
16061716
1607− // Create local config with additional context paths
16081717 localConfig := Config{
16091718 Options: Options{
16101719 ContextPaths: []string{"local1.md", "local2.md"},
@@ -1620,7 +1729,6 @@ func TestOptionsMerging_ContextPaths(t *testing.T) {
16201729
16211730 require.NoError(t, err)
16221731
1623− // Context paths should be merged: defaults + global + local
16241732 expectedContextPaths := append(defaultContextPaths, "global1.md", "global2.md", "local1.md", "local2.md")
16251733 assert.Equal(t, expectedContextPaths, cfg.Options.ContextPaths)
16261734}
@@ -1630,14 +1738,12 @@ func TestOptionsMerging_TUIOptions(t *testing.T) {
16301738 testConfigDir = t.TempDir()
16311739 cwdDir := t.TempDir()
16321740
1633− // Set up a provider
16341741 os.Setenv("ANTHROPIC_API_KEY", "test-key")
16351742
1636− // Create global config with TUI options
16371743 globalConfig := Config{
16381744 Options: Options{
16391745 TUI: TUIOptions{
1640− CompactMode: false, // Default value
1746+ CompactMode: false,
16411747 },
16421748 },
16431749 }
@@ -1648,7 +1754,6 @@ func TestOptionsMerging_TUIOptions(t *testing.T) {
16481754 require.NoError(t, err)
16491755 require.NoError(t, os.WriteFile(configPath, data, 0o644))
16501756
1651− // Create local config that enables compact mode
16521757 localConfig := Config{
16531758 Options: Options{
16541759 TUI: TUIOptions{
@@ -1666,7 +1771,6 @@ func TestOptionsMerging_TUIOptions(t *testing.T) {
16661771
16671772 require.NoError(t, err)
16681773
1669− // Local config should override global
16701774 assert.True(t, cfg.Options.TUI.CompactMode)
16711775}
16721776
@@ -1675,10 +1779,8 @@ func TestOptionsMerging_DebugFlags(t *testing.T) {
16751779 testConfigDir = t.TempDir()
16761780 cwdDir := t.TempDir()
16771781
1678− // Set up a provider
16791782 os.Setenv("ANTHROPIC_API_KEY", "test-key")
16801783
1681− // Create global config with debug flags
16821784 globalConfig := Config{
16831785 Options: Options{
16841786 Debug: false,
@@ -1693,7 +1795,6 @@ func TestOptionsMerging_DebugFlags(t *testing.T) {
16931795 require.NoError(t, err)
16941796 require.NoError(t, os.WriteFile(configPath, data, 0o644))
16951797
1696− // Create local config that enables debug flags
16971798 localConfig := Config{
16981799 Options: Options{
16991800 DebugLSP: true,
@@ -1710,10 +1811,9 @@ func TestOptionsMerging_DebugFlags(t *testing.T) {
17101811
17111812 require.NoError(t, err)
17121813
1713− // Local config should override global for boolean flags
1714− assert.False(t, cfg.Options.Debug) // Not set in local, remains global value
1715− assert.True(t, cfg.Options.DebugLSP) // Set to true in local
1716− assert.True(t, cfg.Options.DisableAutoSummarize) // Set to true in local
1814+ assert.False(t, cfg.Options.Debug)
1815+ assert.True(t, cfg.Options.DebugLSP)
1816+ assert.True(t, cfg.Options.DisableAutoSummarize)
17171817}
17181818
17191819func TestOptionsMerging_DataDirectory(t *testing.T) {
@@ -1721,10 +1821,8 @@ func TestOptionsMerging_DataDirectory(t *testing.T) {
17211821 testConfigDir = t.TempDir()
17221822 cwdDir := t.TempDir()
17231823
1724− // Set up a provider
17251824 os.Setenv("ANTHROPIC_API_KEY", "test-key")
17261825
1727− // Create global config with custom data directory
17281826 globalConfig := Config{
17291827 Options: Options{
17301828 DataDirectory: "global-data",
@@ -1737,7 +1835,6 @@ func TestOptionsMerging_DataDirectory(t *testing.T) {
17371835 require.NoError(t, err)
17381836 require.NoError(t, os.WriteFile(configPath, data, 0o644))
17391837
1740− // Create local config with different data directory
17411838 localConfig := Config{
17421839 Options: Options{
17431840 DataDirectory: "local-data",
@@ -1753,7 +1850,6 @@ func TestOptionsMerging_DataDirectory(t *testing.T) {
17531850
17541851 require.NoError(t, err)
17551852
1756− // Local config should override global
17571853 assert.Equal(t, "local-data", cfg.Options.DataDirectory)
17581854}
17591855
@@ -1762,15 +1858,12 @@ func TestOptionsMerging_DefaultValues(t *testing.T) {
17621858 testConfigDir = t.TempDir()
17631859 cwdDir := t.TempDir()
17641860
1765− // Set up a provider
17661861 os.Setenv("ANTHROPIC_API_KEY", "test-key")
17671862
1768− // No config files - should use defaults
17691863 cfg, err := Init(cwdDir, false)
17701864
17711865 require.NoError(t, err)
17721866
1773− // Should have default values
17741867 assert.Equal(t, defaultDataDirectory, cfg.Options.DataDirectory)
17751868 assert.Equal(t, defaultContextPaths, cfg.Options.ContextPaths)
17761869 assert.False(t, cfg.Options.TUI.CompactMode)
@@ -1784,10 +1877,8 @@ func TestOptionsMerging_DebugFlagFromInit(t *testing.T) {
17841877 testConfigDir = t.TempDir()
17851878 cwdDir := t.TempDir()
17861879
1787− // Set up a provider
17881880 os.Setenv("ANTHROPIC_API_KEY", "test-key")
17891881
1790− // Create config with debug false
17911882 globalConfig := Config{
17921883 Options: Options{
17931884 Debug: false,
@@ -1800,7 +1891,6 @@ func TestOptionsMerging_DebugFlagFromInit(t *testing.T) {
18001891 require.NoError(t, err)
18011892 require.NoError(t, os.WriteFile(configPath, data, 0o644))
18021893
1803− // Init with debug=true should override config
18041894 cfg, err := Init(cwdDir, true)
18051895
18061896 require.NoError(t, err)
@@ -1895,85 +1985,20 @@ func TestModelSelection_PreferredModelSelection(t *testing.T) {
18951985 assert.Equal(t, cfg.Models.Large.Provider, cfg.Models.Small.Provider)
18961986}
18971987
1898−func TestModelSelection_GetAgentModel(t *testing.T) {
1899− reset()
1900− testConfigDir = t.TempDir()
1901− cwdDir := t.TempDir()
1902−
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",
1912− Models: []Model{
1913− {
1914− ID: "gpt-4",
1915− Name: "GPT-4",
1916− ContextWindow: 8192,
1917− DefaultMaxTokens: 4096,
1918− CanReason: true,
1919− SupportsImages: true,
1920− },
1921− {
1922− ID: "gpt-3.5-turbo",
1923− Name: "GPT-3.5 Turbo",
1924− ContextWindow: 4096,
1925− DefaultMaxTokens: 2048,
1926− CanReason: false,
1927− SupportsImages: false,
1928− },
1929− },
1930− },
1931− },
1932− }
1933−
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))
1939−
1940− _, err = Init(cwdDir, false)
1941−
1942− require.NoError(t, err)
1943−
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)
1950−
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)
1954−}
1955−
1956−func TestModelSelection_GetAgentModelWithCustomModelType(t *testing.T) {
1988+func TestValidation_InvalidModelReference(t *testing.T) {
19571989 reset()
19581990 testConfigDir = t.TempDir()
19591991 cwdDir := t.TempDir()
19601992
1961− // Set up provider and custom agent with SmallModel
19621993 globalConfig := Config{
19631994 Providers: map[provider.InferenceProvider]ProviderConfig{
19641995 provider.InferenceProviderOpenAI: {
19651996 ID: provider.InferenceProviderOpenAI,
19661997 APIKey: "test-key",
19671998 ProviderType: provider.TypeOpenAI,
1968− DefaultLargeModel: "gpt-4",
1999+ DefaultLargeModel: "non-existent-model",
19692000 DefaultSmallModel: "gpt-3.5-turbo",
19702001 Models: []Model{
1971− {
1972− ID: "gpt-4",
1973− Name: "GPT-4",
1974− ContextWindow: 8192,
1975− DefaultMaxTokens: 4096,
1976− },
19772002 {
19782003 ID: "gpt-3.5-turbo",
19792004 Name: "GPT-3.5 Turbo",
@@ -1983,54 +2008,6 @@ func TestModelSelection_GetAgentModelWithCustomModelType(t *testing.T) {
19832008 },
19842009 },
19852010 },
1986− Agents: map[AgentID]Agent{
1987− AgentID("small-agent"): {
1988− ID: AgentID("small-agent"),
1989− Name: "Small Agent",
1990− Model: SmallModel,
1991− },
1992− },
1993− }
1994−
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))
2000−
2001− _, err = Init(cwdDir, false)
2002−
2003− require.NoError(t, err)
2004−
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)
2009−}
2010−
2011−func TestModelSelection_GetAgentProvider(t *testing.T) {
2012− reset()
2013− testConfigDir = t.TempDir()
2014− cwdDir := t.TempDir()
2015−
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",
2025− },
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",
2032− },
2033− },
20342011 }
20352012
20362013 configPath := filepath.Join(testConfigDir, "crush.json")
@@ -2040,27 +2017,18 @@ func TestModelSelection_GetAgentProvider(t *testing.T) {
20402017 require.NoError(t, os.WriteFile(configPath, data, 0o644))
20412018
20422019 _, err = Init(cwdDir, false)
2043−
2044− require.NoError(t, err)
2045−
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)
2020+ assert.Error(t, err)
20512021}
20522022
2053−func TestModelSelection_GetProviderModel(t *testing.T) {
2023+func TestValidation_EmptyAPIKey(t *testing.T) {
20542024 reset()
20552025 testConfigDir = t.TempDir()
20562026 cwdDir := t.TempDir()
20572027
2058− // Set up provider with specific models
20592028 globalConfig := Config{
20602029 Providers: map[provider.InferenceProvider]ProviderConfig{
20612030 provider.InferenceProviderOpenAI: {
20622031 ID: provider.InferenceProviderOpenAI,
2063− APIKey: "test-key",
20642032 ProviderType: provider.TypeOpenAI,
20652033 Models: []Model{
20662034 {
@@ -2068,16 +2036,6 @@ func TestModelSelection_GetProviderModel(t *testing.T) {
20682036 Name: "GPT-4",
20692037 ContextWindow: 8192,
20702038 DefaultMaxTokens: 4096,
2071− CostPer1MIn: 30.0,
2072− CostPer1MOut: 60.0,
2073− },
2074− {
2075− ID: "gpt-3.5-turbo",
2076− Name: "GPT-3.5 Turbo",
2077− ContextWindow: 4096,
2078− DefaultMaxTokens: 2048,
2079− CostPer1MIn: 1.5,
2080− CostPer1MOut: 2.0,
20812039 },
20822040 },
20832041 },
@@ -2091,107 +2049,22 @@ func TestModelSelection_GetProviderModel(t *testing.T) {
20912049 require.NoError(t, os.WriteFile(configPath, data, 0o644))
20922050
20932051 _, err = Init(cwdDir, false)
2094−
2095− require.NoError(t, err)
2096−
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)
2103−
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)
2108−
2109− // Test non-existent model
2110− nonExistentModel := GetProviderModel(provider.InferenceProviderOpenAI, "non-existent")
2111− assert.Empty(t, nonExistentModel.ID)
2052+ assert.Error(t, err)
21122053}
21132054
2114−func TestModelSelection_GetModel(t *testing.T) {
2055+func TestValidation_InvalidAgentModelType(t *testing.T) {
21152056 reset()
21162057 testConfigDir = t.TempDir()
21172058 cwdDir := t.TempDir()
21182059
2119− // Set up provider with models
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",
2128− Models: []Model{
2129− {
2130− ID: "gpt-4",
2131− Name: "GPT-4",
2132− ContextWindow: 8192,
2133− DefaultMaxTokens: 4096,
2134− },
2135− {
2136− ID: "gpt-3.5-turbo",
2137− Name: "GPT-3.5 Turbo",
2138− ContextWindow: 4096,
2139− DefaultMaxTokens: 2048,
2140− },
2141− },
2142− },
2143− },
2144− }
2145−
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))
2151−
2152− _, err = Init(cwdDir, false)
2153−
2154− require.NoError(t, err)
2155−
2156− // Test GetModel
2157− largeModel := GetModel(LargeModel)
2158− assert.Equal(t, "gpt-4", largeModel.ID)
2159− assert.Equal(t, "GPT-4", largeModel.Name)
2160−
2161− smallModel := GetModel(SmallModel)
2162− assert.Equal(t, "gpt-3.5-turbo", smallModel.ID)
2163− assert.Equal(t, "GPT-3.5 Turbo", smallModel.Name)
2164−}
2165−
2166−func TestModelSelection_UpdatePreferredModel(t *testing.T) {
2167− reset()
2168− testConfigDir = t.TempDir()
2169− cwdDir := t.TempDir()
2060+ os.Setenv("ANTHROPIC_API_KEY", "test-key")
21702061
2171− // Set up multiple providers with OpenAI first to ensure it's selected initially
21722062 globalConfig := Config{
2173− Providers: map[provider.InferenceProvider]ProviderConfig{
2174− provider.InferenceProviderOpenAI: {
2175− ID: provider.InferenceProviderOpenAI,
2176− APIKey: "openai-key",
2177− ProviderType: provider.TypeOpenAI,
2178− DefaultLargeModel: "gpt-4",
2179− DefaultSmallModel: "gpt-3.5-turbo",
2180− Models: []Model{
2181− {ID: "gpt-4", Name: "GPT-4"},
2182− {ID: "gpt-3.5-turbo", Name: "GPT-3.5 Turbo"},
2183− },
2184− },
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",
2191− Models: []Model{
2192− {ID: "claude-3-opus", Name: "Claude 3 Opus"},
2193− {ID: "claude-3-haiku", Name: "Claude 3 Haiku"},
2194− },
2063+ Agents: map[AgentID]Agent{
2064+ AgentID("invalid-agent"): {
2065+ ID: AgentID("invalid-agent"),
2066+ Name: "Invalid Agent",
2067+ Model: ModelType("invalid"),
21952068 },
21962069 },
21972070 }
@@ -2203,97 +2076,5 @@ func TestModelSelection_UpdatePreferredModel(t *testing.T) {
22032076 require.NoError(t, os.WriteFile(configPath, data, 0o644))
22042077
22052078 _, err = Init(cwdDir, false)
2206−
2207− require.NoError(t, err)
2208−
2209− // Get initial preferred models (should be OpenAI since it's listed first)
2210− initialLargeModel := GetModel(LargeModel)
2211− initialSmallModel := GetModel(SmallModel)
2212−
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)
2216−
2217− // Update preferred models to Anthropic
2218− newLargeModel := PreferredModel{
2219− ModelID: "gpt-4",
2220− Provider: provider.InferenceProviderOpenAI,
2221− }
2222− newSmallModel := PreferredModel{
2223− ModelID: "gpt-3.5-turbo",
2224− Provider: provider.InferenceProviderOpenAI,
2225− }
2226−
2227− err = UpdatePreferredModel(LargeModel, newLargeModel)
2228− require.NoError(t, err)
2229−
2230− err = UpdatePreferredModel(SmallModel, newSmallModel)
2231− require.NoError(t, err)
2232−
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)
2237−
2238− updatedSmallModel := GetModel(SmallModel)
2239− assert.Equal(t, "gpt-3.5-turbo", updatedSmallModel.ID)
2240− assert.NotEqual(t, initialSmallModel.ID, updatedSmallModel.ID)
2241−}
2242−
2243−func TestModelSelection_InvalidModelType(t *testing.T) {
2244− reset()
2245− testConfigDir = t.TempDir()
2246− cwdDir := t.TempDir()
2247−
2248− // Set up a provider
2249− os.Setenv("ANTHROPIC_API_KEY", "test-key")
2250−
2251− _, err := Init(cwdDir, false)
2252− require.NoError(t, err)
2253−
2254− // Test UpdatePreferredModel with invalid model type
2255− invalidModel := PreferredModel{
2256− ModelID: "some-model",
2257− Provider: provider.InferenceProviderAnthropic,
2258− }
2259−
2260− err = UpdatePreferredModel(ModelType("invalid"), invalidModel)
22612079 assert.Error(t, err)
2262− assert.Contains(t, err.Error(), "unknown model type")
2263−}
2264−
2265−func TestModelSelection_NonExistentAgent(t *testing.T) {
2266− reset()
2267− testConfigDir = t.TempDir()
2268− cwdDir := t.TempDir()
2269−
2270− // Set up a provider
2271− os.Setenv("ANTHROPIC_API_KEY", "test-key")
2272−
2273− _, err := Init(cwdDir, false)
2274− require.NoError(t, err)
2275−
2276− // Test GetAgentModel with non-existent agent
2277− nonExistentModel := GetAgentModel(AgentID("non-existent"))
2278− assert.Empty(t, nonExistentModel.ID)
2279−
2280− // Test GetAgentProvider with non-existent agent
2281− nonExistentProvider := GetAgentProvider(AgentID("non-existent"))
2282− assert.Empty(t, nonExistentProvider.ID)
2283−}
2284−
2285−func TestModelSelection_NonExistentProvider(t *testing.T) {
2286− reset()
2287− testConfigDir = t.TempDir()
2288− cwdDir := t.TempDir()
2289−
2290− // Set up a provider
2291− os.Setenv("ANTHROPIC_API_KEY", "test-key")
2292−
2293− _, err := Init(cwdDir, false)
2294− require.NoError(t, err)
2295−
2296− // Test GetProviderModel with non-existent provider
2297− nonExistentModel := GetProviderModel(provider.InferenceProvider("non-existent"), "some-model")
2298− assert.Empty(t, nonExistentModel.ID)
22992080}