@@ -77,7 +77,7 @@ func TestConfig_configureProviders(t *testing.T) {
77 env := env.NewFromMap(map[string]string{
77 env := env.NewFromMap(map[string]string{
78 "OPENAI_API_KEY": "test-key",
78 "OPENAI_API_KEY": "test-key",
80− resolver := NewEnvironmentVariableResolver(env)
80+ resolver := NewShellVariableResolver(env)
81 err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
81 err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
82 require.NoError(t, err)
82 require.NoError(t, err)
83 require.Equal(t, 1, cfg.Providers.Len())
83 require.Equal(t, 1, cfg.Providers.Len())
@@ -120,7 +120,7 @@ func TestConfig_configureProvidersWithOverride(t *testing.T) {
120 env := env.NewFromMap(map[string]string{
120 env := env.NewFromMap(map[string]string{
121 "OPENAI_API_KEY": "test-key",
121 "OPENAI_API_KEY": "test-key",
123− resolver := NewEnvironmentVariableResolver(env)
123+ resolver := NewShellVariableResolver(env)
124 err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
124 err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
125 require.NoError(t, err)
125 require.NoError(t, err)
126 require.Equal(t, 1, cfg.Providers.Len())
126 require.Equal(t, 1, cfg.Providers.Len())
@@ -162,7 +162,7 @@ func TestConfig_configureProvidersWithNewProvider(t *testing.T) {
162 env := env.NewFromMap(map[string]string{
162 env := env.NewFromMap(map[string]string{
163 "OPENAI_API_KEY": "test-key",
163 "OPENAI_API_KEY": "test-key",
165− resolver := NewEnvironmentVariableResolver(env)
165+ resolver := NewShellVariableResolver(env)
166 err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
166 err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
167 require.NoError(t, err)
167 require.NoError(t, err)
168 // Should be to because of the env variable
168 // Should be to because of the env variable
@@ -198,7 +198,7 @@ func TestConfig_configureProvidersBedrockWithCredentials(t *testing.T) {
198 "AWS_ACCESS_KEY_ID": "test-key-id",
198 "AWS_ACCESS_KEY_ID": "test-key-id",
199 "AWS_SECRET_ACCESS_KEY": "test-secret-key",
199 "AWS_SECRET_ACCESS_KEY": "test-secret-key",
201− resolver := NewEnvironmentVariableResolver(env)
201+ resolver := NewShellVariableResolver(env)
202 err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
202 err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
203 require.NoError(t, err)
203 require.NoError(t, err)
204 require.Equal(t, cfg.Providers.Len(), 1)
204 require.Equal(t, cfg.Providers.Len(), 1)
@@ -224,7 +224,7 @@ func TestConfig_configureProvidersBedrockWithoutCredentials(t *testing.T) {
224 cfg := &Config{}
224 cfg := &Config{}
225 cfg.setDefaults("/tmp", "")
225 cfg.setDefaults("/tmp", "")
226 env := env.NewFromMap(map[string]string{})
226 env := env.NewFromMap(map[string]string{})
227− resolver := NewEnvironmentVariableResolver(env)
227+ resolver := NewShellVariableResolver(env)
228 err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
228 err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
229 require.NoError(t, err)
229 require.NoError(t, err)
230 // Provider should not be configured without credentials
230 // Provider should not be configured without credentials
@@ -249,7 +249,7 @@ func TestConfig_configureProvidersBedrockWithoutUnsupportedModel(t *testing.T) {
249 "AWS_ACCESS_KEY_ID": "test-key-id",
249 "AWS_ACCESS_KEY_ID": "test-key-id",
250 "AWS_SECRET_ACCESS_KEY": "test-secret-key",
250 "AWS_SECRET_ACCESS_KEY": "test-secret-key",
252− resolver := NewEnvironmentVariableResolver(env)
252+ resolver := NewShellVariableResolver(env)
253 err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
253 err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
254 require.Error(t, err)
254 require.Error(t, err)
@@ -272,7 +272,7 @@ func TestConfig_configureProvidersVertexAIWithCredentials(t *testing.T) {
272 "VERTEXAI_PROJECT": "test-project",
272 "VERTEXAI_PROJECT": "test-project",
273 "VERTEXAI_LOCATION": "us-central1",
273 "VERTEXAI_LOCATION": "us-central1",
275− resolver := NewEnvironmentVariableResolver(env)
275+ resolver := NewShellVariableResolver(env)
276 err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
276 err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
277 require.NoError(t, err)
277 require.NoError(t, err)
278 require.Equal(t, cfg.Providers.Len(), 1)
278 require.Equal(t, cfg.Providers.Len(), 1)
@@ -304,7 +304,7 @@ func TestConfig_configureProvidersVertexAIWithoutCredentials(t *testing.T) {
304 "GOOGLE_CLOUD_PROJECT": "test-project",
304 "GOOGLE_CLOUD_PROJECT": "test-project",
305 "GOOGLE_CLOUD_LOCATION": "us-central1",
305 "GOOGLE_CLOUD_LOCATION": "us-central1",
307− resolver := NewEnvironmentVariableResolver(env)
307+ resolver := NewShellVariableResolver(env)
308 err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
308 err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
309 require.NoError(t, err)
309 require.NoError(t, err)
310 // Provider should not be configured without proper credentials
310 // Provider should not be configured without proper credentials
@@ -329,7 +329,7 @@ func TestConfig_configureProvidersVertexAIMissingProject(t *testing.T) {
329 "GOOGLE_GENAI_USE_VERTEXAI": "true",
329 "GOOGLE_GENAI_USE_VERTEXAI": "true",
330 "GOOGLE_CLOUD_LOCATION": "us-central1",
330 "GOOGLE_CLOUD_LOCATION": "us-central1",
332− resolver := NewEnvironmentVariableResolver(env)
332+ resolver := NewShellVariableResolver(env)
333 err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
333 err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
334 require.NoError(t, err)
334 require.NoError(t, err)
335 // Provider should not be configured without project
335 // Provider should not be configured without project
@@ -353,7 +353,7 @@ func TestConfig_configureProvidersSetProviderID(t *testing.T) {
353 env := env.NewFromMap(map[string]string{
353 env := env.NewFromMap(map[string]string{
354 "OPENAI_API_KEY": "test-key",
354 "OPENAI_API_KEY": "test-key",
356− resolver := NewEnvironmentVariableResolver(env)
356+ resolver := NewShellVariableResolver(env)
357 err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
357 err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
358 require.NoError(t, err)
358 require.NoError(t, err)
359 require.Equal(t, cfg.Providers.Len(), 1)
359 require.Equal(t, cfg.Providers.Len(), 1)
@@ -544,7 +544,7 @@ func TestConfig_configureProvidersWithDisabledProvider(t *testing.T) {
544 env := env.NewFromMap(map[string]string{
544 env := env.NewFromMap(map[string]string{
545 "OPENAI_API_KEY": "test-key",
545 "OPENAI_API_KEY": "test-key",
547− resolver := NewEnvironmentVariableResolver(env)
547+ resolver := NewShellVariableResolver(env)
548 err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
548 err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
549 require.NoError(t, err)
549 require.NoError(t, err)
@@ -572,7 +572,7 @@ func TestConfig_configureProvidersCustomProviderValidation(t *testing.T) {
572 cfg.setDefaults("/tmp", "")
572 cfg.setDefaults("/tmp", "")
574 env := env.NewFromMap(map[string]string{})
574 env := env.NewFromMap(map[string]string{})
575− resolver := NewEnvironmentVariableResolver(env)
575+ resolver := NewShellVariableResolver(env)
576 err := cfg.configureProviders(testStore(cfg), env, resolver, []catwalk.Provider{})
576 err := cfg.configureProviders(testStore(cfg), env, resolver, []catwalk.Provider{})
577 require.NoError(t, err)
577 require.NoError(t, err)
@@ -595,7 +595,7 @@ func TestConfig_configureProvidersCustomProviderValidation(t *testing.T) {
595 cfg.setDefaults("/tmp", "")
595 cfg.setDefaults("/tmp", "")
597 env := env.NewFromMap(map[string]string{})
597 env := env.NewFromMap(map[string]string{})
598− resolver := NewEnvironmentVariableResolver(env)
598+ resolver := NewShellVariableResolver(env)
599 err := cfg.configureProviders(testStore(cfg), env, resolver, []catwalk.Provider{})
599 err := cfg.configureProviders(testStore(cfg), env, resolver, []catwalk.Provider{})
600 require.NoError(t, err)
600 require.NoError(t, err)
@@ -617,7 +617,7 @@ func TestConfig_configureProvidersCustomProviderValidation(t *testing.T) {
617 cfg.setDefaults("/tmp", "")
617 cfg.setDefaults("/tmp", "")
619 env := env.NewFromMap(map[string]string{})
619 env := env.NewFromMap(map[string]string{})
620− resolver := NewEnvironmentVariableResolver(env)
620+ resolver := NewShellVariableResolver(env)
621 err := cfg.configureProviders(testStore(cfg), env, resolver, []catwalk.Provider{})
621 err := cfg.configureProviders(testStore(cfg), env, resolver, []catwalk.Provider{})
622 require.NoError(t, err)
622 require.NoError(t, err)
@@ -642,7 +642,7 @@ func TestConfig_configureProvidersCustomProviderValidation(t *testing.T) {
642 cfg.setDefaults("/tmp", "")
642 cfg.setDefaults("/tmp", "")
644 env := env.NewFromMap(map[string]string{})
644 env := env.NewFromMap(map[string]string{})
645− resolver := NewEnvironmentVariableResolver(env)
645+ resolver := NewShellVariableResolver(env)
646 err := cfg.configureProviders(testStore(cfg), env, resolver, []catwalk.Provider{})
646 err := cfg.configureProviders(testStore(cfg), env, resolver, []catwalk.Provider{})
647 require.NoError(t, err)
647 require.NoError(t, err)
@@ -667,7 +667,7 @@ func TestConfig_configureProvidersCustomProviderValidation(t *testing.T) {
667 cfg.setDefaults("/tmp", "")
667 cfg.setDefaults("/tmp", "")
669 env := env.NewFromMap(map[string]string{})
669 env := env.NewFromMap(map[string]string{})
670− resolver := NewEnvironmentVariableResolver(env)
670+ resolver := NewShellVariableResolver(env)
671 err := cfg.configureProviders(testStore(cfg), env, resolver, []catwalk.Provider{})
671 err := cfg.configureProviders(testStore(cfg), env, resolver, []catwalk.Provider{})
672 require.NoError(t, err)
672 require.NoError(t, err)
@@ -695,7 +695,7 @@ func TestConfig_configureProvidersCustomProviderValidation(t *testing.T) {
695 cfg.setDefaults("/tmp", "")
695 cfg.setDefaults("/tmp", "")
697 env := env.NewFromMap(map[string]string{})
697 env := env.NewFromMap(map[string]string{})
698− resolver := NewEnvironmentVariableResolver(env)
698+ resolver := NewShellVariableResolver(env)
699 err := cfg.configureProviders(testStore(cfg), env, resolver, []catwalk.Provider{})
699 err := cfg.configureProviders(testStore(cfg), env, resolver, []catwalk.Provider{})
700 require.NoError(t, err)
700 require.NoError(t, err)
@@ -725,7 +725,7 @@ func TestConfig_configureProvidersCustomProviderValidation(t *testing.T) {
725 cfg.setDefaults("/tmp", "")
725 cfg.setDefaults("/tmp", "")
727 env := env.NewFromMap(map[string]string{})
727 env := env.NewFromMap(map[string]string{})
728− resolver := NewEnvironmentVariableResolver(env)
728+ resolver := NewShellVariableResolver(env)
729 err := cfg.configureProviders(testStore(cfg), env, resolver, []catwalk.Provider{})
729 err := cfg.configureProviders(testStore(cfg), env, resolver, []catwalk.Provider{})
730 require.NoError(t, err)
730 require.NoError(t, err)
@@ -760,7 +760,7 @@ func TestConfig_configureProvidersEnhancedCredentialValidation(t *testing.T) {
760 env := env.NewFromMap(map[string]string{
760 env := env.NewFromMap(map[string]string{
761 "GOOGLE_GENAI_USE_VERTEXAI": "false",
761 "GOOGLE_GENAI_USE_VERTEXAI": "false",
763− resolver := NewEnvironmentVariableResolver(env)
763+ resolver := NewShellVariableResolver(env)
764 err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
764 err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
765 require.NoError(t, err)
765 require.NoError(t, err)
@@ -791,7 +791,7 @@ func TestConfig_configureProvidersEnhancedCredentialValidation(t *testing.T) {
791 cfg.setDefaults("/tmp", "")
791 cfg.setDefaults("/tmp", "")
793 env := env.NewFromMap(map[string]string{})
793 env := env.NewFromMap(map[string]string{})
794− resolver := NewEnvironmentVariableResolver(env)
794+ resolver := NewShellVariableResolver(env)
795 err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
795 err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
796 require.NoError(t, err)
796 require.NoError(t, err)
@@ -822,7 +822,7 @@ func TestConfig_configureProvidersEnhancedCredentialValidation(t *testing.T) {
822 cfg.setDefaults("/tmp", "")
822 cfg.setDefaults("/tmp", "")
824 env := env.NewFromMap(map[string]string{})
824 env := env.NewFromMap(map[string]string{})
825− resolver := NewEnvironmentVariableResolver(env)
825+ resolver := NewShellVariableResolver(env)
826 err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
826 err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
827 require.NoError(t, err)
827 require.NoError(t, err)
@@ -855,7 +855,7 @@ func TestConfig_configureProvidersEnhancedCredentialValidation(t *testing.T) {
855 env := env.NewFromMap(map[string]string{
855 env := env.NewFromMap(map[string]string{
856 "OPENAI_API_KEY": "test-key",
856 "OPENAI_API_KEY": "test-key",
858− resolver := NewEnvironmentVariableResolver(env)
858+ resolver := NewShellVariableResolver(env)
859 err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
859 err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
860 require.NoError(t, err)
860 require.NoError(t, err)
@@ -889,7 +889,7 @@ func TestConfig_defaultModelSelection(t *testing.T) {
889 cfg := &Config{}
889 cfg := &Config{}
890 cfg.setDefaults("/tmp", "")
890 cfg.setDefaults("/tmp", "")
891 env := env.NewFromMap(map[string]string{})
891 env := env.NewFromMap(map[string]string{})
892− resolver := NewEnvironmentVariableResolver(env)
892+ resolver := NewShellVariableResolver(env)
893 err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
893 err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
894 require.NoError(t, err)
894 require.NoError(t, err)
@@ -925,7 +925,7 @@ func TestConfig_defaultModelSelection(t *testing.T) {
925 cfg := &Config{}
925 cfg := &Config{}
926 cfg.setDefaults("/tmp", "")
926 cfg.setDefaults("/tmp", "")
927 env := env.NewFromMap(map[string]string{})
927 env := env.NewFromMap(map[string]string{})
928− resolver := NewEnvironmentVariableResolver(env)
928+ resolver := NewShellVariableResolver(env)
929 err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
929 err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
930 require.NoError(t, err)
930 require.NoError(t, err)
@@ -955,7 +955,7 @@ func TestConfig_defaultModelSelection(t *testing.T) {
955 cfg := &Config{}
955 cfg := &Config{}
956 cfg.setDefaults("/tmp", "")
956 cfg.setDefaults("/tmp", "")
957 env := env.NewFromMap(map[string]string{})
957 env := env.NewFromMap(map[string]string{})
958− resolver := NewEnvironmentVariableResolver(env)
958+ resolver := NewShellVariableResolver(env)
959 err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
959 err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
960 require.NoError(t, err)
960 require.NoError(t, err)
961 _, _, err = cfg.defaultModelSelection(knownProviders)
961 _, _, err = cfg.defaultModelSelection(knownProviders)
@@ -998,7 +998,7 @@ func TestConfig_defaultModelSelection(t *testing.T) {
999 cfg.setDefaults("/tmp", "")
999 cfg.setDefaults("/tmp", "")
1000 env := env.NewFromMap(map[string]string{})
1000 env := env.NewFromMap(map[string]string{})
1001− resolver := NewEnvironmentVariableResolver(env)
1001+ resolver := NewShellVariableResolver(env)
1002 err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
1002 err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
1003 require.NoError(t, err)
1003 require.NoError(t, err)
1004 large, small, err := cfg.defaultModelSelection(knownProviders)
1004 large, small, err := cfg.defaultModelSelection(knownProviders)
@@ -1042,7 +1042,7 @@ func TestConfig_defaultModelSelection(t *testing.T) {
1043 cfg.setDefaults("/tmp", "")
1043 cfg.setDefaults("/tmp", "")
1044 env := env.NewFromMap(map[string]string{})
1044 env := env.NewFromMap(map[string]string{})
1045− resolver := NewEnvironmentVariableResolver(env)
1045+ resolver := NewShellVariableResolver(env)
1046 err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
1046 err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
1047 require.NoError(t, err)
1047 require.NoError(t, err)
1048 _, _, err = cfg.defaultModelSelection(knownProviders)
1048 _, _, err = cfg.defaultModelSelection(knownProviders)
@@ -1084,7 +1084,7 @@ func TestConfig_defaultModelSelection(t *testing.T) {
1085 cfg.setDefaults("/tmp", "")
1085 cfg.setDefaults("/tmp", "")
1086 env := env.NewFromMap(map[string]string{})
1086 env := env.NewFromMap(map[string]string{})
1087− resolver := NewEnvironmentVariableResolver(env)
1087+ resolver := NewShellVariableResolver(env)
1088 err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
1088 err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
1089 require.NoError(t, err)
1089 require.NoError(t, err)
1090 large, small, err := cfg.defaultModelSelection(knownProviders)
1090 large, small, err := cfg.defaultModelSelection(knownProviders)
@@ -1129,7 +1129,7 @@ func TestConfig_configureProvidersDisableDefaultProviders(t *testing.T) {
1129 env := env.NewFromMap(map[string]string{
1129 env := env.NewFromMap(map[string]string{
1130 "OPENAI_API_KEY": "test-key",
1130 "OPENAI_API_KEY": "test-key",
1132− resolver := NewEnvironmentVariableResolver(env)
1132+ resolver := NewShellVariableResolver(env)
1133 err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
1133 err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
1134 require.ErrorContains(t, err, "no custom providers")
1134 require.ErrorContains(t, err, "no custom providers")
@@ -1172,7 +1172,7 @@ func TestConfig_configureProvidersDisableDefaultProviders(t *testing.T) {
1172 "MY_API_KEY": "test-key",
1172 "MY_API_KEY": "test-key",
1173 "OPENAI_API_KEY": "test-key",
1173 "OPENAI_API_KEY": "test-key",
1175− resolver := NewEnvironmentVariableResolver(env)
1175+ resolver := NewShellVariableResolver(env)
1176 err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
1176 err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
1177 require.NoError(t, err)
1177 require.NoError(t, err)
@@ -1226,7 +1226,7 @@ func TestConfig_configureProvidersDisableDefaultProviders(t *testing.T) {
1226 "OPENAI_API_KEY": "test-key",
1226 "OPENAI_API_KEY": "test-key",
1227 "ANTHROPIC_API_KEY": "test-key",
1227 "ANTHROPIC_API_KEY": "test-key",
1229− resolver := NewEnvironmentVariableResolver(env)
1229+ resolver := NewShellVariableResolver(env)
1230 err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
1230 err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
1231 require.NoError(t, err)
1231 require.NoError(t, err)
@@ -1254,7 +1254,7 @@ func TestConfig_configureProvidersDisableDefaultProviders(t *testing.T) {
1254 cfg.setDefaults("/tmp", "")
1254 cfg.setDefaults("/tmp", "")
1256 env := env.NewFromMap(map[string]string{})
1256 env := env.NewFromMap(map[string]string{})
1257− resolver := NewEnvironmentVariableResolver(env)
1257+ resolver := NewShellVariableResolver(env)
1258 err := cfg.configureProviders(testStore(cfg), env, resolver, []catwalk.Provider{})
1258 err := cfg.configureProviders(testStore(cfg), env, resolver, []catwalk.Provider{})
1259 require.ErrorContains(t, err, "no custom providers")
1259 require.ErrorContains(t, err, "no custom providers")
@@ -1278,7 +1278,7 @@ func TestConfig_configureProvidersDisableDefaultProviders(t *testing.T) {
1278 cfg.setDefaults("/tmp", "")
1278 cfg.setDefaults("/tmp", "")
1280 env := env.NewFromMap(map[string]string{})
1280 env := env.NewFromMap(map[string]string{})
1281− resolver := NewEnvironmentVariableResolver(env)
1281+ resolver := NewShellVariableResolver(env)
1282 err := cfg.configureProviders(testStore(cfg), env, resolver, []catwalk.Provider{})
1282 err := cfg.configureProviders(testStore(cfg), env, resolver, []catwalk.Provider{})
1283 require.ErrorContains(t, err, "no custom providers")
1283 require.ErrorContains(t, err, "no custom providers")
@@ -1336,7 +1336,7 @@ func TestConfig_configureSelectedModels(t *testing.T) {
1336 cfg.setDefaults(dir, "")
1336 cfg.setDefaults(dir, "")
1337 store := &ConfigStore{config: cfg, globalDataPath: globalPath}
1337 store := &ConfigStore{config: cfg, globalDataPath: globalPath}
1338 env := env.NewFromMap(map[string]string{})
1338 env := env.NewFromMap(map[string]string{})
1339− resolver := NewEnvironmentVariableResolver(env)
1339+ resolver := NewShellVariableResolver(env)
1340 err := cfg.configureProviders(store, env, resolver, knownProviders)
1340 err := cfg.configureProviders(store, env, resolver, knownProviders)
1341 require.NoError(t, err)
1341 require.NoError(t, err)
@@ -1386,7 +1386,7 @@ func TestConfig_configureSelectedModels(t *testing.T) {
1387 cfg.setDefaults("/tmp", "")
1387 cfg.setDefaults("/tmp", "")
1388 env := env.NewFromMap(map[string]string{})
1388 env := env.NewFromMap(map[string]string{})
1389− resolver := NewEnvironmentVariableResolver(env)
1389+ resolver := NewShellVariableResolver(env)
1390 err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
1390 err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
1391 require.NoError(t, err)
1391 require.NoError(t, err)
@@ -1448,7 +1448,7 @@ func TestConfig_configureSelectedModels(t *testing.T) {
1449 cfg.setDefaults("/tmp", "")
1449 cfg.setDefaults("/tmp", "")
1450 env := env.NewFromMap(map[string]string{})
1450 env := env.NewFromMap(map[string]string{})
1451− resolver := NewEnvironmentVariableResolver(env)
1451+ resolver := NewShellVariableResolver(env)
1452 err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
1452 err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
1453 require.NoError(t, err)
1453 require.NoError(t, err)
@@ -1493,7 +1493,7 @@ func TestConfig_configureSelectedModels(t *testing.T) {
1494 cfg.setDefaults("/tmp", "")
1494 cfg.setDefaults("/tmp", "")
1495 env := env.NewFromMap(map[string]string{})
1495 env := env.NewFromMap(map[string]string{})
1496− resolver := NewEnvironmentVariableResolver(env)
1496+ resolver := NewShellVariableResolver(env)
1497 err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
1497 err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
1498 require.NoError(t, err)
1498 require.NoError(t, err)
@@ -1532,7 +1532,7 @@ func TestConfig_configureProviders_HyperAPIKeyFromEnv(t *testing.T) {
1532 env := env.NewFromMap(map[string]string{
1532 env := env.NewFromMap(map[string]string{
1533 "HYPER_API_KEY": "env-api-key",
1533 "HYPER_API_KEY": "env-api-key",
1535− resolver := NewEnvironmentVariableResolver(env)
1535+ resolver := NewShellVariableResolver(env)
1536 err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
1536 err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
1537 require.NoError(t, err)
1537 require.NoError(t, err)
1538 require.Equal(t, 1, cfg.Providers.Len())
1538 require.Equal(t, 1, cfg.Providers.Len())
@@ -1579,7 +1579,7 @@ func TestConfig_configureProviders_HyperAPIKeyFromConfigOverrides(t *testing.T)
1579 env := env.NewFromMap(map[string]string{
1579 env := env.NewFromMap(map[string]string{
1580 "HYPER_API_KEY": "env-api-key",
1580 "HYPER_API_KEY": "env-api-key",
1582− resolver := NewEnvironmentVariableResolver(env)
1582+ resolver := NewShellVariableResolver(env)
1583 err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
1583 err := cfg.configureProviders(testStore(cfg), env, resolver, knownProviders)
1584 require.NoError(t, err)
1584 require.NoError(t, err)
1585 require.Equal(t, 1, cfg.Providers.Len())
1585 require.Equal(t, 1, cfg.Providers.Len())
@@ -1590,18 +1590,13 @@ func TestConfig_configureProviders_HyperAPIKeyFromConfigOverrides(t *testing.T)
1590 require.Equal(t, "env-api-key", pc.APIKey)
1590 require.Equal(t, "env-api-key", pc.APIKey)
1593−// TestConfig_configureProviders_ProviderHeaderResolveFailure pins the
1593+// TestConfig_configureProviders_ProviderHeaderResolveError pins
1594−// intentional divergence at load.go:225: a provider header whose
1594+// Phase 2 design decision #14: a failing $(cmd) in a provider header
1595−// expansion fails must NOT fail the whole config load. It logs an
1595+// must fail the provider load with a clear message that names the
1596−// error, keeps the literal template in the resolved header map, and
1596+// offending header. The Phase 1 log-and-continue divergence at
1597−// moves on. The MCP contract (hard error on failed expansion) does not
1597+// load.go:225 is gone; provider headers now share the MCP error
1598−// apply here because many providers ship DefaultHeaders that reference
1598+// contract.
1599−// env vars which are legitimately unset on most hosts.
1599+func TestConfig_configureProviders_ProviderHeaderResolveError(t *testing.T) {
1601−// If this test ever flips to requiring an error, read PLAN.md "Design
1602−// decisions" item 4 before changing the production code — the
1603−// divergence is deliberate.
1604−func TestConfig_configureProviders_ProviderHeaderResolveFailure(t *testing.T) {
1605 knownProviders := []catwalk.Provider{
1600 knownProviders := []catwalk.Provider{
1607 ID: "openai",
1602 ID: "openai",
@@ -1615,13 +1610,9 @@ func TestConfig_configureProviders_ProviderHeaderResolveFailure(t *testing.T) {
1615 Providers: csync.NewMapFrom(map[string]ProviderConfig{
1610 Providers: csync.NewMapFrom(map[string]ProviderConfig{
1616 "openai": {
1611 "openai": {
1617 ExtraHeaders: map[string]string{
1612 ExtraHeaders: map[string]string{
1618− // Failing $(...) — inner command exits 1, no stdout.
1613+ // Failing $(...) — inner command exits 1. Must
1614+ // propagate as an error, not a silent truncation.
1619 "X-Broken": "$(false)",
1615 "X-Broken": "$(false)",
1620− // Unset var — nounset makes this an error too.
1621− "X-Missing": "$PROVIDER_HEADER_NEVER_SET",
1622− // Happy path: must still be resolved, proving the
1623− // failure in the other two did not abort the loop.
1624− "X-Static": "kept-literal",
@@ -1635,15 +1626,238 @@ func TestConfig_configureProviders_ProviderHeaderResolveFailure(t *testing.T) {
1635 resolver := NewShellVariableResolver(testEnv)
1626 resolver := NewShellVariableResolver(testEnv)
1637 err := cfg.configureProviders(testStore(cfg), testEnv, resolver, knownProviders)
1628 err := cfg.configureProviders(testStore(cfg), testEnv, resolver, knownProviders)
1638− require.NoError(t, err, "provider load must tolerate failing header expansion")
1629+ require.Error(t, err, "failing $(cmd) in a header must fail the provider load")
1630+ require.Contains(t, err.Error(), "X-Broken", "error must name the offending header")
1633+// TestConfig_configureProviders_CatwalkDefaultWithUnsetVarLoads pins
1634+// Phase 2 design decisions #11 and #18 from the provider angle: a
1635+// Catwalk-style default header like
1636+// "OpenAI-Organization": "$OPENAI_ORG_ID" must load cleanly under
1637+// lenient nounset (unset → "" → header dropped), not fail the load
1638+// and not leave the literal template on the wire.
1639+func TestConfig_configureProviders_CatwalkDefaultWithUnsetVarLoads(t *testing.T) {
1640+ knownProviders := []catwalk.Provider{
1643+ APIKey: "$OPENAI_API_KEY",
1644+ APIEndpoint: "https://api.openai.com/v1",
1645+ Models: []catwalk.Model{{ID: "test-model"}},
1646+ DefaultHeaders: map[string]string{
1647+ "OpenAI-Organization": "$OPENAI_ORG_ID",
1653+ cfg.setDefaults("/tmp", "")
1655+ testEnv := env.NewFromMap(map[string]string{
1656+ "OPENAI_API_KEY": "test-key",
1657+ "PATH": os.Getenv("PATH"),
1659+ resolver := NewShellVariableResolver(testEnv)
1661+ err := cfg.configureProviders(testStore(cfg), testEnv, resolver, knownProviders)
1662+ require.NoError(t, err, "optional env-gated header must not fail the load")
1640 pc, ok := cfg.Providers.Get("openai")
1664 pc, ok := cfg.Providers.Get("openai")
1641 require.True(t, ok, "openai provider must still be configured")
1665 require.True(t, ok, "openai provider must still be configured")
1666+ _, present := pc.ExtraHeaders["OpenAI-Organization"]
1667+ require.False(t, present, "header whose value resolves to empty must be absent")
1670+// TestConfig_configureProviders_LiteralEmptyHeaderDropped pins design
1671+// decision #18 for the literal case: a user-authored
1672+// "X-Custom": "" in extra_headers is absent from the resolved map.
1673+// Applies to both known- and custom-provider paths; this test
1674+// exercises the custom-provider loop.
1675+func TestConfig_configureProviders_LiteralEmptyHeaderDropped(t *testing.T) {
1677+ Providers: csync.NewMapFrom(map[string]ProviderConfig{
1679+ APIKey: "test-key",
1680+ BaseURL: "https://my-llm.example.com/v1",
1681+ Type: catwalk.TypeOpenAI,
1682+ Models: []catwalk.Model{{ID: "m"}},
1683+ ExtraHeaders: map[string]string{
1685+ "X-Kept": "present",
1690+ cfg.setDefaults("/tmp", "")
1692+ testEnv := env.NewFromMap(map[string]string{
1693+ "PATH": os.Getenv("PATH"),
1695+ resolver := NewShellVariableResolver(testEnv)
1697+ err := cfg.configureProviders(testStore(cfg), testEnv, resolver, []catwalk.Provider{})
1698+ require.NoError(t, err)
1700+ pc, ok := cfg.Providers.Get("my-llm")
1701+ require.True(t, ok)
1702+ _, present := pc.ExtraHeaders["X-Custom"]
1703+ require.False(t, present, "literal empty-string header must be dropped")
1704+ require.Equal(t, "present", pc.ExtraHeaders["X-Kept"])
1707+// TestConfig_configureProviders_EchoEmptyHeaderDropped pins design
1708+// decision #18 for the non-failing empty case: $(echo) exits 0 with
1709+// empty output, resolves cleanly to "", and must be dropped the same
1710+// way an unset bare $VAR is. Exercises the known-provider loop.
1711+func TestConfig_configureProviders_EchoEmptyHeaderDropped(t *testing.T) {
1712+ knownProviders := []catwalk.Provider{
1715+ APIKey: "$OPENAI_API_KEY",
1716+ APIEndpoint: "https://api.openai.com/v1",
1717+ Models: []catwalk.Model{{ID: "test-model"}},
1718+ DefaultHeaders: map[string]string{
1719+ "X-Empty": "$(echo)",
1720+ "X-Kept": "present",
1726+ cfg.setDefaults("/tmp", "")
1728+ testEnv := env.NewFromMap(map[string]string{
1729+ "OPENAI_API_KEY": "test-key",
1730+ "PATH": os.Getenv("PATH"),
1732+ resolver := NewShellVariableResolver(testEnv)
1734+ err := cfg.configureProviders(testStore(cfg), testEnv, resolver, knownProviders)
1735+ require.NoError(t, err)
1737+ pc, ok := cfg.Providers.Get("openai")
1738+ require.True(t, ok)
1739+ _, present := pc.ExtraHeaders["X-Empty"]
1740+ require.False(t, present, "$(echo) → empty → header must be dropped")
1741+ require.Equal(t, "present", pc.ExtraHeaders["X-Kept"])
1744+// TestConfig_configureProviders_UnsetAPIKeySkipsProvider pins Phase 2
1745+// Step 12 / design decision #15: under the lenient-nounset shell
1746+// resolver, $UNSET_API_KEY expands to ("", nil) rather than ("", err),
1747+// and the existing `v == "" || err != nil` skip path at load.go:331
1748+// still drops the provider. The slog.Warn line is emitted on the same
1749+// path but is not asserted here — internal/config/load_test.go's
1750+// TestMain replaces the default slog handler with an io.Discard
1751+// writer, so capturing that log line would require mid-test handler
1752+// swapping and a sync.Mutex dance that adds more flake surface than
1753+// signal. The observable outcome (provider absent from the map) is
1754+// what downstream code — model picker, agent wiring — actually reads,
1755+// so that's what we pin.
1756+func TestConfig_configureProviders_UnsetAPIKeySkipsProvider(t *testing.T) {
1757+ knownProviders := []catwalk.Provider{
1760+ APIKey: "$SOMETHING_UNSET",
1761+ APIEndpoint: "https://api.openai.com/v1",
1762+ Models: []catwalk.Model{{ID: "test-model"}},
1766+ // Existing user config for this known provider so the load.go:332
1767+ // `if configExists` branch fires and actually calls Providers.Del.
1768+ // Without it the provider was never in the map to begin with and
1769+ // the test would pass trivially.
1771+ Providers: csync.NewMapFrom(map[string]ProviderConfig{
1772+ "openai": {BaseURL: "custom-url"},
1775+ cfg.setDefaults("/tmp", "")
1777+ testEnv := env.NewFromMap(map[string]string{
1778+ "PATH": os.Getenv("PATH"),
1780+ resolver := NewShellVariableResolver(testEnv)
1782+ err := cfg.configureProviders(testStore(cfg), testEnv, resolver, knownProviders)
1783+ require.NoError(t, err, "skip path must not surface as a load error")
1785+ require.Equal(t, 0, cfg.Providers.Len(), "provider with unset API key must be skipped")
1786+ _, exists := cfg.Providers.Get("openai")
1787+ require.False(t, exists)
1790+// TestConfig_configureProviders_FailingAPIKeyCmdSkipsProvider pins
1791+// that the two failure modes for APIKey — ("", nil) from an unset var
1792+// under lenient nounset and ("", err) from a failing $(cmd) — are
1793+// equivalent for the skip outcome at load.go:331. The `v == "" ||
1794+// err != nil` check fires on either branch; this test locks in that
1795+// equivalence so a future refactor that splits the check into two
1796+// paths doesn't accidentally start propagating $(false) as a load
1797+// error while keeping unset-var as a silent skip (or vice versa).
1798+func TestConfig_configureProviders_FailingAPIKeyCmdSkipsProvider(t *testing.T) {
1799+ knownProviders := []catwalk.Provider{
1802+ APIKey: "$(false)",
1803+ APIEndpoint: "https://api.openai.com/v1",
1804+ Models: []catwalk.Model{{ID: "test-model"}},
1809+ Providers: csync.NewMapFrom(map[string]ProviderConfig{
1810+ "openai": {BaseURL: "custom-url"},
1813+ cfg.setDefaults("/tmp", "")
1815+ testEnv := env.NewFromMap(map[string]string{
1816+ "PATH": os.Getenv("PATH"),
1818+ resolver := NewShellVariableResolver(testEnv)
1820+ err := cfg.configureProviders(testStore(cfg), testEnv, resolver, knownProviders)
1821+ require.NoError(t, err, "failing $(cmd) in API key must skip provider, not fail load")
1823+ require.Equal(t, 0, cfg.Providers.Len(), "provider with failing $(cmd) API key must be skipped")
1824+ _, exists := cfg.Providers.Get("openai")
1825+ require.False(t, exists)
1828+// TestConfig_configureProviders_UnsetAzureEndpointSkipsProvider pins
1829+// the same contract on the Azure path at load.go:287 — APIEndpoint is
1830+// the field that gates Azure and goes through the same
1831+// `v == "" || err != nil` skip check. Covered here so both branches
1832+// of the shared skip pattern (APIKey default path and APIEndpoint
1833+// Azure path) are tested; a future refactor that unifies them can
1834+// rely on these two tests to catch drift.
1835+func TestConfig_configureProviders_UnsetAzureEndpointSkipsProvider(t *testing.T) {
1836+ knownProviders := []catwalk.Provider{
1838+ ID: catwalk.InferenceProviderAzure,
1839+ APIKey: "test-key",
1840+ APIEndpoint: "$UNSET_AZURE_ENDPOINT",
1841+ Models: []catwalk.Model{{ID: "test-model"}},
1846+ Providers: csync.NewMapFrom(map[string]ProviderConfig{
1847+ "azure": {BaseURL: ""},
1850+ cfg.setDefaults("/tmp", "")
1852+ testEnv := env.NewFromMap(map[string]string{
1853+ "PATH": os.Getenv("PATH"),
1855+ resolver := NewShellVariableResolver(testEnv)
1857+ err := cfg.configureProviders(testStore(cfg), testEnv, resolver, knownProviders)
1858+ require.NoError(t, err)
1643− // Literal template preserved for the two failing headers; the
1860+ require.Equal(t, 0, cfg.Providers.Len(), "azure provider with unset endpoint must be skipped")
1644− // happy-path header is resolved through the shell (pass-through
1861+ _, exists := cfg.Providers.Get("azure")
1645− // for a literal value).
1862+ require.False(t, exists)
1646− require.Equal(t, "$(false)", pc.ExtraHeaders["X-Broken"])
1647− require.Equal(t, "$PROVIDER_HEADER_NEVER_SET", pc.ExtraHeaders["X-Missing"])
1648− require.Equal(t, "kept-literal", pc.ExtraHeaders["X-Static"])