From 58b5e21d90c6c578c769d5d7cf3c3b49a2baccee Mon Sep 17 00:00:00 2001 From: yinwm Date: Thu, 19 Feb 2026 13:05:21 +0800 Subject: [PATCH] fix(config): support legacy config without provider field When no provider field is set but model is specified, use the user's model as ModelName for the first provider. This maintains backward compatibility with old configs that relied on implicit provider selection and ensures GetModelConfig(model) can find the model by its configured name. --- pkg/config/migration.go | 13 ++++- pkg/config/migration_test.go | 98 ++++++++++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+), 1 deletion(-) diff --git a/pkg/config/migration.go b/pkg/config/migration.go index 9b8df07bd..8eae29258 100644 --- a/pkg/config/migration.go +++ b/pkg/config/migration.go @@ -32,9 +32,13 @@ func ConvertProvidersToModelList(cfg *Config) []ModelConfig { userProvider := strings.ToLower(cfg.Agents.Defaults.Provider) userModel := cfg.Agents.Defaults.Model - var result []ModelConfig p := cfg.Providers + var result []ModelConfig + + // Track if we've applied the legacy model name fix (only for first provider) + legacyModelNameApplied := false + // Define migration rules for each provider migrations := []providerMigrationConfig{ { @@ -322,6 +326,13 @@ func ConvertProvidersToModelList(cfg *Config) []ModelConfig { if slices.Contains(m.providerNames, userProvider) && userModel != "" { // Use the user's configured model instead of default mc.Model = m.protocol + "/" + userModel + } else if userProvider == "" && userModel != "" && !legacyModelNameApplied { + // Legacy config: no explicit provider field but model is specified + // Use userModel as ModelName for the FIRST provider so GetModelConfig(model) can find it + // This maintains backward compatibility with old configs that relied on implicit provider selection + mc.ModelName = userModel + mc.Model = m.protocol + "/" + userModel + legacyModelNameApplied = true } result = append(result, mc) diff --git a/pkg/config/migration_test.go b/pkg/config/migration_test.go index 5a4f8cc8e..f5a9337a9 100644 --- a/pkg/config/migration_test.go +++ b/pkg/config/migration_test.go @@ -391,3 +391,101 @@ func TestConvertProvidersToModelList_ProviderNameAliases(t *testing.T) { }) } } + +// Test for backward compatibility: single provider without explicit provider field +// This matches the legacy config pattern where users only set model, not provider + +func TestConvertProvidersToModelList_NoProviderField_SingleProvider(t *testing.T) { + // This matches the user's actual config: + // - No provider field set + // - model = "glm-4.7" + // - Only zhipu has API key configured + cfg := &Config{ + Agents: AgentsConfig{ + Defaults: AgentDefaults{ + Provider: "", // Not set + Model: "glm-4.7", + }, + }, + Providers: ProvidersConfig{ + Zhipu: ProviderConfig{APIKey: "test-zhipu-key"}, + }, + } + + result := ConvertProvidersToModelList(cfg) + + if len(result) != 1 { + t.Fatalf("len(result) = %d, want 1", len(result)) + } + + // ModelName should be the user's model value for backward compatibility + if result[0].ModelName != "glm-4.7" { + t.Errorf("ModelName = %q, want %q (user's model for backward compatibility)", result[0].ModelName, "glm-4.7") + } + + // Model should use the user's model with protocol prefix + if result[0].Model != "openai/glm-4.7" { + t.Errorf("Model = %q, want %q", result[0].Model, "openai/glm-4.7") + } +} + +func TestConvertProvidersToModelList_NoProviderField_MultipleProviders(t *testing.T) { + // When multiple providers are configured but no provider field is set, + // the FIRST provider (in migration order) will use userModel as ModelName + // for backward compatibility with legacy implicit provider selection + cfg := &Config{ + Agents: AgentsConfig{ + Defaults: AgentDefaults{ + Provider: "", // Not set + Model: "some-model", + }, + }, + Providers: ProvidersConfig{ + OpenAI: ProviderConfig{APIKey: "openai-key"}, + Zhipu: ProviderConfig{APIKey: "zhipu-key"}, + }, + } + + result := ConvertProvidersToModelList(cfg) + + if len(result) != 2 { + t.Fatalf("len(result) = %d, want 2", len(result)) + } + + // The first provider (OpenAI in migration order) should use userModel as ModelName + // This ensures GetModelConfig("some-model") will find it + if result[0].ModelName != "some-model" { + t.Errorf("First provider ModelName = %q, want %q", result[0].ModelName, "some-model") + } + + // Other providers should use provider name as ModelName + if result[1].ModelName != "zhipu" { + t.Errorf("Second provider ModelName = %q, want %q", result[1].ModelName, "zhipu") + } +} + +func TestConvertProvidersToModelList_NoProviderField_NoModel(t *testing.T) { + // Edge case: no provider, no model + cfg := &Config{ + Agents: AgentsConfig{ + Defaults: AgentDefaults{ + Provider: "", + Model: "", + }, + }, + Providers: ProvidersConfig{ + Zhipu: ProviderConfig{APIKey: "zhipu-key"}, + }, + } + + result := ConvertProvidersToModelList(cfg) + + if len(result) != 1 { + t.Fatalf("len(result) = %d, want 1", len(result)) + } + + // Should use default provider name since no model is specified + if result[0].ModelName != "zhipu" { + t.Errorf("ModelName = %q, want %q", result[0].ModelName, "zhipu") + } +}