@@ -760,3 +760,390 @@ func TestConfig_configureProvidersEnhancedCredentialValidation(t *testing.T) {
760760 assert.True(t, exists)
761761 })
762762}
763+
764+func TestConfig_defaultModelSelection(t *testing.T) {
765+ t.Run("default behavior uses the default models for given provider", func(t *testing.T) {
766+ knownProviders := []provider.Provider{
767+ {
768+ ID: "openai",
769+ APIKey: "abc",
770+ DefaultLargeModelID: "large-model",
771+ DefaultSmallModelID: "small-model",
772+ Models: []provider.Model{
773+ {
774+ ID: "large-model",
775+ DefaultMaxTokens: 1000,
776+ },
777+ {
778+ ID: "small-model",
779+ DefaultMaxTokens: 500,
780+ },
781+ },
782+ },
783+ }
784+
785+ cfg := &Config{}
786+ cfg.setDefaults("/tmp")
787+ env := env.NewFromMap(map[string]string{})
788+ resolver := NewEnvironmentVariableResolver(env)
789+ err := cfg.configureProviders(env, resolver, knownProviders)
790+ assert.NoError(t, err)
791+
792+ large, small, err := cfg.defaultModelSelection(knownProviders)
793+ assert.NoError(t, err)
794+ assert.Equal(t, "large-model", large.Model)
795+ assert.Equal(t, "openai", large.Provider)
796+ assert.Equal(t, int64(1000), large.MaxTokens)
797+ assert.Equal(t, "small-model", small.Model)
798+ assert.Equal(t, "openai", small.Provider)
799+ assert.Equal(t, int64(500), small.MaxTokens)
800+ })
801+ t.Run("should error if no providers configured", func(t *testing.T) {
802+ knownProviders := []provider.Provider{
803+ {
804+ ID: "openai",
805+ APIKey: "$MISSING_KEY",
806+ DefaultLargeModelID: "large-model",
807+ DefaultSmallModelID: "small-model",
808+ Models: []provider.Model{
809+ {
810+ ID: "large-model",
811+ DefaultMaxTokens: 1000,
812+ },
813+ {
814+ ID: "small-model",
815+ DefaultMaxTokens: 500,
816+ },
817+ },
818+ },
819+ }
820+
821+ cfg := &Config{}
822+ cfg.setDefaults("/tmp")
823+ env := env.NewFromMap(map[string]string{})
824+ resolver := NewEnvironmentVariableResolver(env)
825+ err := cfg.configureProviders(env, resolver, knownProviders)
826+ assert.NoError(t, err)
827+
828+ _, _, err = cfg.defaultModelSelection(knownProviders)
829+ assert.Error(t, err)
830+ })
831+ t.Run("should error if model is missing", func(t *testing.T) {
832+ knownProviders := []provider.Provider{
833+ {
834+ ID: "openai",
835+ APIKey: "abc",
836+ DefaultLargeModelID: "large-model",
837+ DefaultSmallModelID: "small-model",
838+ Models: []provider.Model{
839+ {
840+ ID: "not-large-model",
841+ DefaultMaxTokens: 1000,
842+ },
843+ {
844+ ID: "small-model",
845+ DefaultMaxTokens: 500,
846+ },
847+ },
848+ },
849+ }
850+
851+ cfg := &Config{}
852+ cfg.setDefaults("/tmp")
853+ env := env.NewFromMap(map[string]string{})
854+ resolver := NewEnvironmentVariableResolver(env)
855+ err := cfg.configureProviders(env, resolver, knownProviders)
856+ assert.NoError(t, err)
857+ _, _, err = cfg.defaultModelSelection(knownProviders)
858+ assert.Error(t, err)
859+ })
860+
861+ t.Run("should configure the default models with a custom provider", func(t *testing.T) {
862+ knownProviders := []provider.Provider{
863+ {
864+ ID: "openai",
865+ APIKey: "$MISSING", // will not be included in the config
866+ DefaultLargeModelID: "large-model",
867+ DefaultSmallModelID: "small-model",
868+ Models: []provider.Model{
869+ {
870+ ID: "not-large-model",
871+ DefaultMaxTokens: 1000,
872+ },
873+ {
874+ ID: "small-model",
875+ DefaultMaxTokens: 500,
876+ },
877+ },
878+ },
879+ }
880+
881+ cfg := &Config{
882+ Providers: map[string]ProviderConfig{
883+ "custom": {
884+ APIKey: "test-key",
885+ BaseURL: "https://api.custom.com/v1",
886+ Models: []provider.Model{
887+ {
888+ ID: "model",
889+ DefaultMaxTokens: 600,
890+ },
891+ },
892+ },
893+ },
894+ }
895+ cfg.setDefaults("/tmp")
896+ env := env.NewFromMap(map[string]string{})
897+ resolver := NewEnvironmentVariableResolver(env)
898+ err := cfg.configureProviders(env, resolver, knownProviders)
899+ assert.NoError(t, err)
900+ large, small, err := cfg.defaultModelSelection(knownProviders)
901+ assert.NoError(t, err)
902+ assert.Equal(t, "model", large.Model)
903+ assert.Equal(t, "custom", large.Provider)
904+ assert.Equal(t, int64(600), large.MaxTokens)
905+ assert.Equal(t, "model", small.Model)
906+ assert.Equal(t, "custom", small.Provider)
907+ assert.Equal(t, int64(600), small.MaxTokens)
908+ })
909+
910+ t.Run("should fail if no model configured", func(t *testing.T) {
911+ knownProviders := []provider.Provider{
912+ {
913+ ID: "openai",
914+ APIKey: "$MISSING", // will not be included in the config
915+ DefaultLargeModelID: "large-model",
916+ DefaultSmallModelID: "small-model",
917+ Models: []provider.Model{
918+ {
919+ ID: "not-large-model",
920+ DefaultMaxTokens: 1000,
921+ },
922+ {
923+ ID: "small-model",
924+ DefaultMaxTokens: 500,
925+ },
926+ },
927+ },
928+ }
929+
930+ cfg := &Config{
931+ Providers: map[string]ProviderConfig{
932+ "custom": {
933+ APIKey: "test-key",
934+ BaseURL: "https://api.custom.com/v1",
935+ Models: []provider.Model{},
936+ },
937+ },
938+ }
939+ cfg.setDefaults("/tmp")
940+ env := env.NewFromMap(map[string]string{})
941+ resolver := NewEnvironmentVariableResolver(env)
942+ err := cfg.configureProviders(env, resolver, knownProviders)
943+ assert.NoError(t, err)
944+ _, _, err = cfg.defaultModelSelection(knownProviders)
945+ assert.Error(t, err)
946+ })
947+ t.Run("should use the default provider first", func(t *testing.T) {
948+ knownProviders := []provider.Provider{
949+ {
950+ ID: "openai",
951+ APIKey: "set",
952+ DefaultLargeModelID: "large-model",
953+ DefaultSmallModelID: "small-model",
954+ Models: []provider.Model{
955+ {
956+ ID: "large-model",
957+ DefaultMaxTokens: 1000,
958+ },
959+ {
960+ ID: "small-model",
961+ DefaultMaxTokens: 500,
962+ },
963+ },
964+ },
965+ }
966+
967+ cfg := &Config{
968+ Providers: map[string]ProviderConfig{
969+ "custom": {
970+ APIKey: "test-key",
971+ BaseURL: "https://api.custom.com/v1",
972+ Models: []provider.Model{
973+ {
974+ ID: "large-model",
975+ DefaultMaxTokens: 1000,
976+ },
977+ },
978+ },
979+ },
980+ }
981+ cfg.setDefaults("/tmp")
982+ env := env.NewFromMap(map[string]string{})
983+ resolver := NewEnvironmentVariableResolver(env)
984+ err := cfg.configureProviders(env, resolver, knownProviders)
985+ assert.NoError(t, err)
986+ large, small, err := cfg.defaultModelSelection(knownProviders)
987+ assert.NoError(t, err)
988+ assert.Equal(t, "large-model", large.Model)
989+ assert.Equal(t, "openai", large.Provider)
990+ assert.Equal(t, int64(1000), large.MaxTokens)
991+ assert.Equal(t, "small-model", small.Model)
992+ assert.Equal(t, "openai", small.Provider)
993+ assert.Equal(t, int64(500), small.MaxTokens)
994+ })
995+}
996+
997+func TestConfig_configureSelectedModels(t *testing.T) {
998+ t.Run("should override defaults", func(t *testing.T) {
999+ knownProviders := []provider.Provider{
1000+ {
1001+ ID: "openai",
1002+ APIKey: "abc",
1003+ DefaultLargeModelID: "large-model",
1004+ DefaultSmallModelID: "small-model",
1005+ Models: []provider.Model{
1006+ {
1007+ ID: "larger-model",
1008+ DefaultMaxTokens: 2000,
1009+ },
1010+ {
1011+ ID: "large-model",
1012+ DefaultMaxTokens: 1000,
1013+ },
1014+ {
1015+ ID: "small-model",
1016+ DefaultMaxTokens: 500,
1017+ },
1018+ },
1019+ },
1020+ }
1021+
1022+ cfg := &Config{
1023+ Models: map[SelectedModelType]SelectedModel{
1024+ "large": {
1025+ Model: "larger-model",
1026+ },
1027+ },
1028+ }
1029+ cfg.setDefaults("/tmp")
1030+ env := env.NewFromMap(map[string]string{})
1031+ resolver := NewEnvironmentVariableResolver(env)
1032+ err := cfg.configureProviders(env, resolver, knownProviders)
1033+ assert.NoError(t, err)
1034+
1035+ err = cfg.configureSelectedModels(knownProviders)
1036+ assert.NoError(t, err)
1037+ large := cfg.Models[SelectedModelTypeLarge]
1038+ small := cfg.Models[SelectedModelTypeSmall]
1039+ assert.Equal(t, "larger-model", large.Model)
1040+ assert.Equal(t, "openai", large.Provider)
1041+ assert.Equal(t, int64(2000), large.MaxTokens)
1042+ assert.Equal(t, "small-model", small.Model)
1043+ assert.Equal(t, "openai", small.Provider)
1044+ assert.Equal(t, int64(500), small.MaxTokens)
1045+ })
1046+ t.Run("should be possible to use multiple providers", func(t *testing.T) {
1047+ knownProviders := []provider.Provider{
1048+ {
1049+ ID: "openai",
1050+ APIKey: "abc",
1051+ DefaultLargeModelID: "large-model",
1052+ DefaultSmallModelID: "small-model",
1053+ Models: []provider.Model{
1054+ {
1055+ ID: "large-model",
1056+ DefaultMaxTokens: 1000,
1057+ },
1058+ {
1059+ ID: "small-model",
1060+ DefaultMaxTokens: 500,
1061+ },
1062+ },
1063+ },
1064+ {
1065+ ID: "anthropic",
1066+ APIKey: "abc",
1067+ DefaultLargeModelID: "a-large-model",
1068+ DefaultSmallModelID: "a-small-model",
1069+ Models: []provider.Model{
1070+ {
1071+ ID: "a-large-model",
1072+ DefaultMaxTokens: 1000,
1073+ },
1074+ {
1075+ ID: "a-small-model",
1076+ DefaultMaxTokens: 200,
1077+ },
1078+ },
1079+ },
1080+ }
1081+
1082+ cfg := &Config{
1083+ Models: map[SelectedModelType]SelectedModel{
1084+ "small": {
1085+ Model: "a-small-model",
1086+ Provider: "anthropic",
1087+ MaxTokens: 300,
1088+ },
1089+ },
1090+ }
1091+ cfg.setDefaults("/tmp")
1092+ env := env.NewFromMap(map[string]string{})
1093+ resolver := NewEnvironmentVariableResolver(env)
1094+ err := cfg.configureProviders(env, resolver, knownProviders)
1095+ assert.NoError(t, err)
1096+
1097+ err = cfg.configureSelectedModels(knownProviders)
1098+ assert.NoError(t, err)
1099+ large := cfg.Models[SelectedModelTypeLarge]
1100+ small := cfg.Models[SelectedModelTypeSmall]
1101+ assert.Equal(t, "large-model", large.Model)
1102+ assert.Equal(t, "openai", large.Provider)
1103+ assert.Equal(t, int64(1000), large.MaxTokens)
1104+ assert.Equal(t, "a-small-model", small.Model)
1105+ assert.Equal(t, "anthropic", small.Provider)
1106+ assert.Equal(t, int64(300), small.MaxTokens)
1107+ })
1108+
1109+ t.Run("should override the max tokens only", func(t *testing.T) {
1110+ knownProviders := []provider.Provider{
1111+ {
1112+ ID: "openai",
1113+ APIKey: "abc",
1114+ DefaultLargeModelID: "large-model",
1115+ DefaultSmallModelID: "small-model",
1116+ Models: []provider.Model{
1117+ {
1118+ ID: "large-model",
1119+ DefaultMaxTokens: 1000,
1120+ },
1121+ {
1122+ ID: "small-model",
1123+ DefaultMaxTokens: 500,
1124+ },
1125+ },
1126+ },
1127+ }
1128+
1129+ cfg := &Config{
1130+ Models: map[SelectedModelType]SelectedModel{
1131+ "large": {
1132+ MaxTokens: 100,
1133+ },
1134+ },
1135+ }
1136+ cfg.setDefaults("/tmp")
1137+ env := env.NewFromMap(map[string]string{})
1138+ resolver := NewEnvironmentVariableResolver(env)
1139+ err := cfg.configureProviders(env, resolver, knownProviders)
1140+ assert.NoError(t, err)
1141+
1142+ err = cfg.configureSelectedModels(knownProviders)
1143+ assert.NoError(t, err)
1144+ large := cfg.Models[SelectedModelTypeLarge]
1145+ assert.Equal(t, "large-model", large.Model)
1146+ assert.Equal(t, "openai", large.Provider)
1147+ assert.Equal(t, int64(100), large.MaxTokens)
1148+ })
1149+}