diff --git a/cmd/config/codex_test.go b/cmd/config/codex_test.go index 9c18910be..e886fc4ef 100644 --- a/cmd/config/codex_test.go +++ b/cmd/config/codex_test.go @@ -16,7 +16,7 @@ func TestCodexArgs(t *testing.T) { }{ {"with model", "llama3.2", nil, []string{"--oss", "-m", "llama3.2"}}, {"empty model", "", nil, []string{"--oss"}}, - {"with model and profile", "qwen3-coder", []string{"-p", "myprofile"}, []string{"--oss", "-m", "qwen3-coder", "-p", "myprofile"}}, + {"with model and profile", "qwen3.5", []string{"-p", "myprofile"}, []string{"--oss", "-m", "qwen3.5", "-p", "myprofile"}}, {"with sandbox flag", "llama3.2", []string{"--sandbox", "workspace-write"}, []string{"--oss", "-m", "llama3.2", "--sandbox", "workspace-write"}}, } diff --git a/cmd/config/integrations.go b/cmd/config/integrations.go index b9502e9b3..e5522d562 100644 --- a/cmd/config/integrations.go +++ b/cmd/config/integrations.go @@ -66,11 +66,12 @@ var integrations = map[string]Runner{ // recommendedModels are shown when the user has no models or as suggestions. // Order matters: local models first, then cloud models. var recommendedModels = []ModelItem{ - {Name: "minimax-m2.5:cloud", Description: "Fast, efficient coding and real-world productivity", Recommended: true}, - {Name: "glm-5:cloud", Description: "Reasoning and code generation", Recommended: true}, {Name: "kimi-k2.5:cloud", Description: "Multimodal reasoning with subagents", Recommended: true}, + {Name: "qwen3.5:cloud", Description: "Reasoning, coding, and agentic tool use with vision", Recommended: true}, + {Name: "glm-5:cloud", Description: "Reasoning and code generation", Recommended: true}, + {Name: "minimax-m2.5:cloud", Description: "Fast, efficient coding and real-world productivity", Recommended: true}, {Name: "glm-4.7-flash", Description: "Reasoning and code generation locally", Recommended: true}, - {Name: "qwen3:8b", Description: "Efficient all-purpose assistant", Recommended: true}, + {Name: "qwen3.5", Description: "Reasoning, coding, and visual understanding locally", Recommended: true}, } // cloudModelLimits maps cloud model base names to their token limits. @@ -98,7 +99,7 @@ var cloudModelLimits = map[string]cloudModelLimit{ // recommendedVRAM maps local recommended models to their approximate VRAM requirement. var recommendedVRAM = map[string]string{ "glm-4.7-flash": "~25GB", - "qwen3:8b": "~11GB", + "qwen3.5": "~11GB", } // integrationAliases are hidden from the interactive selector but work as CLI arguments. diff --git a/cmd/config/integrations_test.go b/cmd/config/integrations_test.go index 2eca19fd9..8f4c262df 100644 --- a/cmd/config/integrations_test.go +++ b/cmd/config/integrations_test.go @@ -420,7 +420,7 @@ func names(items []ModelItem) []string { func TestBuildModelList_NoExistingModels(t *testing.T) { items, _, _, _ := buildModelList(nil, nil, "") - want := []string{"minimax-m2.5:cloud", "glm-5:cloud", "kimi-k2.5:cloud", "glm-4.7-flash", "qwen3:8b"} + want := []string{"kimi-k2.5:cloud", "qwen3.5:cloud", "glm-5:cloud", "minimax-m2.5:cloud", "glm-4.7-flash", "qwen3.5"} if diff := cmp.Diff(want, names(items)); diff != "" { t.Errorf("with no existing models, items should be recommended in order (-want +got):\n%s", diff) } @@ -448,7 +448,7 @@ func TestBuildModelList_OnlyLocalModels_CloudRecsAtBottom(t *testing.T) { got := names(items) // Recommended pinned at top (local recs first, then cloud recs when only-local), then installed non-recs - want := []string{"glm-4.7-flash", "qwen3:8b", "minimax-m2.5:cloud", "glm-5:cloud", "kimi-k2.5:cloud", "llama3.2", "qwen2.5"} + want := []string{"glm-4.7-flash", "qwen3.5", "kimi-k2.5:cloud", "qwen3.5:cloud", "glm-5:cloud", "minimax-m2.5:cloud", "llama3.2", "qwen2.5"} if diff := cmp.Diff(want, got); diff != "" { t.Errorf("recs pinned at top, local recs before cloud recs (-want +got):\n%s", diff) } @@ -464,7 +464,7 @@ func TestBuildModelList_BothCloudAndLocal_RegularSort(t *testing.T) { got := names(items) // All recs pinned at top (cloud before local in mixed case), then non-recs - want := []string{"minimax-m2.5:cloud", "glm-5:cloud", "kimi-k2.5:cloud", "glm-4.7-flash", "qwen3:8b", "llama3.2"} + want := []string{"kimi-k2.5:cloud", "qwen3.5:cloud", "glm-5:cloud", "minimax-m2.5:cloud", "glm-4.7-flash", "qwen3.5", "llama3.2"} if diff := cmp.Diff(want, got); diff != "" { t.Errorf("recs pinned at top, cloud recs first in mixed case (-want +got):\n%s", diff) } @@ -498,11 +498,11 @@ func TestBuildModelList_ExistingRecommendedMarked(t *testing.T) { if strings.HasSuffix(item.Description, "(not downloaded)") { t.Errorf("installed recommended %q should not have '(not downloaded)' suffix, got %q", item.Name, item.Description) } - case "qwen3:8b": + case "qwen3.5": if !strings.HasSuffix(item.Description, "(not downloaded)") { t.Errorf("non-installed recommended %q should have '(not downloaded)' suffix, got %q", item.Name, item.Description) } - case "minimax-m2.5:cloud", "kimi-k2.5:cloud": + case "minimax-m2.5:cloud", "kimi-k2.5:cloud", "qwen3.5:cloud": if strings.HasSuffix(item.Description, "(not downloaded)") { t.Errorf("cloud model %q should not have '(not downloaded)' suffix, got %q", item.Name, item.Description) } @@ -520,9 +520,9 @@ func TestBuildModelList_ExistingCloudModelsNotPushedToBottom(t *testing.T) { got := names(items) // glm-4.7-flash and glm-5:cloud are installed so they sort normally; - // kimi-k2.5:cloud and qwen3:8b are not installed so they go to the bottom + // kimi-k2.5:cloud, qwen3.5:cloud, and qwen3.5 are not installed so they go to the bottom // All recs: cloud first in mixed case, then local, in rec order within each - want := []string{"minimax-m2.5:cloud", "glm-5:cloud", "kimi-k2.5:cloud", "glm-4.7-flash", "qwen3:8b"} + want := []string{"kimi-k2.5:cloud", "qwen3.5:cloud", "glm-5:cloud", "minimax-m2.5:cloud", "glm-4.7-flash", "qwen3.5"} if diff := cmp.Diff(want, got); diff != "" { t.Errorf("all recs, cloud first in mixed case (-want +got):\n%s", diff) } @@ -540,7 +540,7 @@ func TestBuildModelList_HasRecommendedCloudModel_OnlyNonInstalledAtBottom(t *tes // kimi-k2.5:cloud is installed so it sorts normally; // the rest of the recommendations are not installed so they go to the bottom // All recs pinned at top (cloud first in mixed case), then non-recs - want := []string{"minimax-m2.5:cloud", "glm-5:cloud", "kimi-k2.5:cloud", "glm-4.7-flash", "qwen3:8b", "llama3.2"} + want := []string{"kimi-k2.5:cloud", "qwen3.5:cloud", "glm-5:cloud", "minimax-m2.5:cloud", "glm-4.7-flash", "qwen3.5", "llama3.2"} if diff := cmp.Diff(want, got); diff != "" { t.Errorf("recs pinned at top, cloud first in mixed case (-want +got):\n%s", diff) } @@ -617,6 +617,9 @@ func TestBuildModelList_ReturnsExistingAndCloudMaps(t *testing.T) { if !cloudModels["kimi-k2.5:cloud"] { t.Error("kimi-k2.5:cloud should be in cloudModels (recommended cloud)") } + if !cloudModels["qwen3.5:cloud"] { + t.Error("qwen3.5:cloud should be in cloudModels (recommended cloud)") + } if cloudModels["llama3.2"] { t.Error("llama3.2 should not be in cloudModels") } @@ -632,7 +635,7 @@ func TestBuildModelList_RecommendedFieldSet(t *testing.T) { for _, item := range items { switch item.Name { - case "glm-4.7-flash", "qwen3:8b", "glm-5:cloud", "kimi-k2.5:cloud": + case "glm-4.7-flash", "qwen3.5", "glm-5:cloud", "kimi-k2.5:cloud", "qwen3.5:cloud": if !item.Recommended { t.Errorf("%q should have Recommended=true", item.Name) } @@ -690,7 +693,7 @@ func TestBuildModelList_RecsAboveNonRecs(t *testing.T) { lastRecIdx := -1 firstNonRecIdx := len(got) for i, name := range got { - isRec := name == "glm-4.7-flash" || name == "qwen3:8b" || name == "minimax-m2.5:cloud" || name == "glm-5:cloud" || name == "kimi-k2.5:cloud" + isRec := name == "glm-4.7-flash" || name == "qwen3.5" || name == "minimax-m2.5:cloud" || name == "glm-5:cloud" || name == "kimi-k2.5:cloud" || name == "qwen3.5:cloud" if isRec && i > lastRecIdx { lastRecIdx = i } @@ -796,7 +799,7 @@ func TestResolveEditorLaunchModels_FiltersAndSkipsPickerWhenLocalRemains(t *test pickerCalled := false models, err := resolveEditorModels("droid", []string{"llama3.2", "glm-5:cloud"}, func() ([]string, error) { pickerCalled = true - return []string{"qwen3:8b"}, nil + return []string{"qwen3.5"}, nil }) if err != nil { t.Fatalf("resolveEditorLaunchModels returned error: %v", err) @@ -942,9 +945,9 @@ func TestShowOrPull_ShowCalledWithCorrectModel(t *testing.T) { u, _ := url.Parse(srv.URL) client := api.NewClient(u, srv.Client()) - _ = ShowOrPull(context.Background(), client, "qwen3:8b") - if receivedModel != "qwen3:8b" { - t.Errorf("expected Show to be called with %q, got %q", "qwen3:8b", receivedModel) + _ = ShowOrPull(context.Background(), client, "qwen3.5") + if receivedModel != "qwen3.5" { + t.Errorf("expected Show to be called with %q, got %q", "qwen3.5", receivedModel) } } @@ -1424,12 +1427,12 @@ func TestListIntegrationInfos(t *testing.T) { func TestBuildModelList_Descriptions(t *testing.T) { t.Run("installed recommended has base description", func(t *testing.T) { existing := []modelInfo{ - {Name: "qwen3:8b", Remote: false}, + {Name: "qwen3.5", Remote: false}, } items, _, _, _ := buildModelList(existing, nil, "") for _, item := range items { - if item.Name == "qwen3:8b" { + if item.Name == "qwen3.5" { if strings.HasSuffix(item.Description, "install?") { t.Errorf("installed model should not have 'install?' suffix, got %q", item.Description) } @@ -1439,38 +1442,38 @@ func TestBuildModelList_Descriptions(t *testing.T) { return } } - t.Error("qwen3:8b not found in items") + t.Error("qwen3.5 not found in items") }) t.Run("not-installed local rec has VRAM in description", func(t *testing.T) { items, _, _, _ := buildModelList(nil, nil, "") for _, item := range items { - if item.Name == "qwen3:8b" { + if item.Name == "qwen3.5" { if !strings.Contains(item.Description, "~11GB") { - t.Errorf("not-installed qwen3:8b should show VRAM hint, got %q", item.Description) + t.Errorf("not-installed qwen3.5 should show VRAM hint, got %q", item.Description) } return } } - t.Error("qwen3:8b not found in items") + t.Error("qwen3.5 not found in items") }) t.Run("installed local rec omits VRAM", func(t *testing.T) { existing := []modelInfo{ - {Name: "qwen3:8b", Remote: false}, + {Name: "qwen3.5", Remote: false}, } items, _, _, _ := buildModelList(existing, nil, "") for _, item := range items { - if item.Name == "qwen3:8b" { + if item.Name == "qwen3.5" { if strings.Contains(item.Description, "~11GB") { - t.Errorf("installed qwen3:8b should not show VRAM hint, got %q", item.Description) + t.Errorf("installed qwen3.5 should not show VRAM hint, got %q", item.Description) } return } } - t.Error("qwen3:8b not found in items") + t.Error("qwen3.5 not found in items") }) } @@ -1530,11 +1533,11 @@ func TestIntegrationModels(t *testing.T) { }) t.Run("returns all saved models", func(t *testing.T) { - if err := SaveIntegration("droid", []string{"llama3.2", "qwen3:8b"}); err != nil { + if err := SaveIntegration("droid", []string{"llama3.2", "qwen3.5"}); err != nil { t.Fatal(err) } got := IntegrationModels("droid") - want := []string{"llama3.2", "qwen3:8b"} + want := []string{"llama3.2", "qwen3.5"} if diff := cmp.Diff(want, got); diff != "" { t.Errorf("IntegrationModels mismatch (-want +got):\n%s", diff) } diff --git a/cmd/config/opencode_test.go b/cmd/config/opencode_test.go index 9f7744892..bd02bbbf0 100644 --- a/cmd/config/opencode_test.go +++ b/cmd/config/opencode_test.go @@ -723,6 +723,8 @@ func TestLookupCloudModelLimit(t *testing.T) { {"kimi-k2.5:cloud", true, 262_144, 262_144}, {"deepseek-v3.2", false, 0, 0}, {"deepseek-v3.2:cloud", true, 163_840, 65_536}, + {"qwen3.5", false, 0, 0}, + {"qwen3.5:cloud", true, 262_144, 32_768}, {"qwen3-coder:480b", false, 0, 0}, {"qwen3-coder:480b:cloud", true, 262_144, 65_536}, {"qwen3-coder-next:cloud", true, 262_144, 32_768}, diff --git a/docs/cli.mdx b/docs/cli.mdx index ecceee41d..ca03a5227 100644 --- a/docs/cli.mdx +++ b/docs/cli.mdx @@ -40,7 +40,7 @@ ollama launch claude Launch with a specific model: ``` -ollama launch claude --model qwen3-coder +ollama launch claude --model qwen3.5 ``` Configure without launching: