From 338c7dc528c4e53a1d81db8741ff98d8c0f67205 Mon Sep 17 00:00:00 2001 From: mrigankad Date: Fri, 20 Feb 2026 18:14:37 +0530 Subject: [PATCH] fix(config): resolve nvidia provider model lookup failure for custom model names When a user configured provider=nvidia with a model like kimi-k2.5 (or moonshotai/kimi-k2.5), ConvertProvidersToModelList updated mc.Model but left mc.ModelName as "nvidia". GetModelConfig then searched for ModelName=="kimi-k2.5" and found nothing, producing the "no API key configured for model" error. Two issues fixed: - mc.ModelName is now set to userModel so GetModelConfig(userModel) finds the migrated entry. - The protocol prefix is added with a strict HasPrefix check, so model IDs that already contain "/" but are NOT protocol-prefixed (e.g. Nvidia's "moonshotai/kimi-k2.5") are correctly wrapped as "nvidia/moonshotai/kimi-k2.5" instead of being passed through with the wrong protocol. Co-Authored-By: Claude Sonnet 4.6 --- pkg/config/migration.go | 12 ++++- pkg/config/migration_test.go | 101 ++++++++++++++++++++++++++++++++++- 2 files changed, 109 insertions(+), 4 deletions(-) diff --git a/pkg/config/migration.go b/pkg/config/migration.go index 689e2312f..b91ae0479 100644 --- a/pkg/config/migration.go +++ b/pkg/config/migration.go @@ -335,8 +335,16 @@ func ConvertProvidersToModelList(cfg *Config) []ModelConfig { // Check if this is the user's configured provider if slices.Contains(m.providerNames, userProvider) && userModel != "" { - // Use the user's configured model instead of default - mc.Model = buildModelWithProtocol(m.protocol, userModel) + // Use the user's configured model instead of default. + // Add protocol prefix unless the model already starts with it. + // This handles API-specific model IDs that contain "/" (e.g. "moonshotai/kimi-k2.5" on Nvidia). + if strings.HasPrefix(strings.ToLower(userModel), m.protocol+"/") { + mc.Model = userModel + } else { + mc.Model = m.protocol + "/" + userModel + } + // Update ModelName so GetModelConfig(userModel) can find this entry. + mc.ModelName = 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 diff --git a/pkg/config/migration_test.go b/pkg/config/migration_test.go index b9a333f9e..86c6a38b8 100644 --- a/pkg/config/migration_test.go +++ b/pkg/config/migration_test.go @@ -327,14 +327,16 @@ func TestConvertProvidersToModelList_MultipleProviders_PreservesUserModel(t *tes t.Fatalf("len(result) = %d, want 2", len(result)) } - // Find each provider and verify model + // Find each provider and verify model. + // After migration, user-selected provider ModelName becomes the user's model name. for _, mc := range result { switch mc.ModelName { case "openai": if mc.Model != "openai/gpt-5.2" { t.Errorf("OpenAI Model = %q, want %q (default)", mc.Model, "openai/gpt-5.2") } - case "deepseek": + case "deepseek-reasoner": + // The user's explicit provider (deepseek) now uses the user's model as ModelName if mc.Model != "deepseek/deepseek-reasoner" { t.Errorf("DeepSeek Model = %q, want %q (user's)", mc.Model, "deepseek/deepseek-reasoner") } @@ -549,3 +551,98 @@ func TestConvertProvidersToModelList_LegacyModelWithProtocolPrefix(t *testing.T) t.Errorf("Model = %q, want %q (should not duplicate prefix)", result[0].Model, "openrouter/auto") } } + +// Tests for nvidia provider with custom (third-party) model names. +// Nvidia's API hosts models from various vendors using identifiers like "moonshotai/kimi-k2.5". + +func TestConvertProvidersToModelList_Nvidia_SimpleModel(t *testing.T) { + cfg := &Config{ + Agents: AgentsConfig{ + Defaults: AgentDefaults{ + Provider: "nvidia", + Model: "kimi-k2.5", + }, + }, + Providers: ProvidersConfig{ + Nvidia: ProviderConfig{APIKey: "nvapi-xxx", APIBase: "https://integrate.api.nvidia.com/v1"}, + }, + } + + result := ConvertProvidersToModelList(cfg) + + if len(result) != 1 { + t.Fatalf("len(result) = %d, want 1", len(result)) + } + + // ModelName must match the user's model so GetModelConfig("kimi-k2.5") can find it + if result[0].ModelName != "kimi-k2.5" { + t.Errorf("ModelName = %q, want %q", result[0].ModelName, "kimi-k2.5") + } + + // Model should be prefixed with the nvidia protocol + if result[0].Model != "nvidia/kimi-k2.5" { + t.Errorf("Model = %q, want %q", result[0].Model, "nvidia/kimi-k2.5") + } +} + +func TestConvertProvidersToModelList_Nvidia_VendorPrefixedModel(t *testing.T) { + // Nvidia hosts third-party models like "moonshotai/kimi-k2.5". + // The "/" is part of the model ID, not a protocol separator. + cfg := &Config{ + Agents: AgentsConfig{ + Defaults: AgentDefaults{ + Provider: "nvidia", + Model: "moonshotai/kimi-k2.5", + }, + }, + Providers: ProvidersConfig{ + Nvidia: ProviderConfig{APIKey: "nvapi-xxx", APIBase: "https://integrate.api.nvidia.com/v1"}, + }, + } + + result := ConvertProvidersToModelList(cfg) + + if len(result) != 1 { + t.Fatalf("len(result) = %d, want 1", len(result)) + } + + // ModelName must match the user's model so GetModelConfig("moonshotai/kimi-k2.5") can find it + if result[0].ModelName != "moonshotai/kimi-k2.5" { + t.Errorf("ModelName = %q, want %q", result[0].ModelName, "moonshotai/kimi-k2.5") + } + + // Model should be wrapped with nvidia/ protocol prefix so the provider is resolved correctly + if result[0].Model != "nvidia/moonshotai/kimi-k2.5" { + t.Errorf("Model = %q, want %q", result[0].Model, "nvidia/moonshotai/kimi-k2.5") + } +} + +func TestConvertProvidersToModelList_Nvidia_ExplicitNvidiaPrefix(t *testing.T) { + // User specifies nvidia/kimi-k2.5 - should not double-prefix. + cfg := &Config{ + Agents: AgentsConfig{ + Defaults: AgentDefaults{ + Provider: "nvidia", + Model: "nvidia/kimi-k2.5", + }, + }, + Providers: ProvidersConfig{ + Nvidia: ProviderConfig{APIKey: "nvapi-xxx"}, + }, + } + + result := ConvertProvidersToModelList(cfg) + + if len(result) != 1 { + t.Fatalf("len(result) = %d, want 1", len(result)) + } + + if result[0].ModelName != "nvidia/kimi-k2.5" { + t.Errorf("ModelName = %q, want %q", result[0].ModelName, "nvidia/kimi-k2.5") + } + + // Should NOT produce nvidia/nvidia/kimi-k2.5 + if result[0].Model != "nvidia/kimi-k2.5" { + t.Errorf("Model = %q, want %q (should not double-prefix)", result[0].Model, "nvidia/kimi-k2.5") + } +}