diff --git a/cmd/launch/integrations_test.go b/cmd/launch/integrations_test.go index 85527eeb8..6d84869c1 100644 --- a/cmd/launch/integrations_test.go +++ b/cmd/launch/integrations_test.go @@ -290,7 +290,7 @@ func TestParseArgs(t *testing.T) { func TestIsCloudModel(t *testing.T) { // isCloudModel now only uses Show API, so nil client always returns false t.Run("nil client returns false", func(t *testing.T) { - models := []string{"glm-5:cloud", "kimi-k2.5:cloud", "local-model"} + models := []string{"glm-5.1:cloud", "kimi-k2.5:cloud", "local-model"} for _, model := range models { if isCloudModel(context.Background(), nil, model) { t.Errorf("isCloudModel(%q) with nil client should return false", model) @@ -310,7 +310,7 @@ func names(items []ModelItem) []string { func TestBuildModelList_NoExistingModels(t *testing.T) { items, _, _, _ := buildModelList(nil, nil, "") - want := []string{"kimi-k2.5:cloud", "qwen3.5:cloud", "glm-5:cloud", "minimax-m2.7:cloud", "gemma4", "qwen3.5"} + want := []string{"kimi-k2.5:cloud", "qwen3.5:cloud", "glm-5.1:cloud", "minimax-m2.7:cloud", "gemma4", "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) } @@ -338,7 +338,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{"gemma4", "qwen3.5", "kimi-k2.5:cloud", "qwen3.5:cloud", "glm-5:cloud", "minimax-m2.7:cloud", "llama3.2", "qwen2.5"} + want := []string{"gemma4", "qwen3.5", "kimi-k2.5:cloud", "qwen3.5:cloud", "glm-5.1:cloud", "minimax-m2.7: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) } @@ -347,14 +347,14 @@ func TestBuildModelList_OnlyLocalModels_CloudRecsAtBottom(t *testing.T) { func TestBuildModelList_BothCloudAndLocal_RegularSort(t *testing.T) { existing := []modelInfo{ {Name: "llama3.2:latest", Remote: false}, - {Name: "glm-5:cloud", Remote: true}, + {Name: "glm-5.1:cloud", Remote: true}, } items, _, _, _ := buildModelList(existing, nil, "") got := names(items) // All recs pinned at top (cloud before local in mixed case), then non-recs - want := []string{"kimi-k2.5:cloud", "qwen3.5:cloud", "glm-5:cloud", "minimax-m2.7:cloud", "gemma4", "qwen3.5", "llama3.2"} + want := []string{"kimi-k2.5:cloud", "qwen3.5:cloud", "glm-5.1:cloud", "minimax-m2.7:cloud", "gemma4", "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) } @@ -363,7 +363,7 @@ func TestBuildModelList_BothCloudAndLocal_RegularSort(t *testing.T) { func TestBuildModelList_PreCheckedFirst(t *testing.T) { existing := []modelInfo{ {Name: "llama3.2:latest", Remote: false}, - {Name: "glm-5:cloud", Remote: true}, + {Name: "glm-5.1:cloud", Remote: true}, } items, _, _, _ := buildModelList(existing, []string{"llama3.2"}, "") @@ -410,14 +410,14 @@ func TestBuildModelList_CurrentDefaultFirstAmongCheckedNonRec(t *testing.T) { func TestBuildModelList_ExistingRecommendedMarked(t *testing.T) { existing := []modelInfo{ {Name: "gemma4", Remote: false}, - {Name: "glm-5:cloud", Remote: true}, + {Name: "glm-5.1:cloud", Remote: true}, } items, _, _, _ := buildModelList(existing, nil, "") for _, item := range items { switch item.Name { - case "gemma4", "glm-5:cloud": + case "gemma4", "glm-5.1:cloud": if strings.HasSuffix(item.Description, "(not downloaded)") { t.Errorf("installed recommended %q should not have '(not downloaded)' suffix, got %q", item.Name, item.Description) } @@ -436,16 +436,16 @@ func TestBuildModelList_ExistingRecommendedMarked(t *testing.T) { func TestBuildModelList_ExistingCloudModelsNotPushedToBottom(t *testing.T) { existing := []modelInfo{ {Name: "gemma4", Remote: false}, - {Name: "glm-5:cloud", Remote: true}, + {Name: "glm-5.1:cloud", Remote: true}, } items, _, _, _ := buildModelList(existing, nil, "") got := names(items) - // gemma4 and glm-5:cloud are installed so they sort normally; + // gemma4 and glm-5.1:cloud are installed so they sort normally; // 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{"kimi-k2.5:cloud", "qwen3.5:cloud", "glm-5:cloud", "minimax-m2.7:cloud", "gemma4", "qwen3.5"} + want := []string{"kimi-k2.5:cloud", "qwen3.5:cloud", "glm-5.1:cloud", "minimax-m2.7:cloud", "gemma4", "qwen3.5"} if diff := cmp.Diff(want, got); diff != "" { t.Errorf("all recs, cloud first in mixed case (-want +got):\n%s", diff) } @@ -463,7 +463,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{"kimi-k2.5:cloud", "qwen3.5:cloud", "glm-5:cloud", "minimax-m2.7:cloud", "gemma4", "qwen3.5", "llama3.2"} + want := []string{"kimi-k2.5:cloud", "qwen3.5:cloud", "glm-5.1:cloud", "minimax-m2.7:cloud", "gemma4", "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) } @@ -519,7 +519,7 @@ func TestBuildModelList_LatestTagStripped(t *testing.T) { func TestBuildModelList_ReturnsExistingAndCloudMaps(t *testing.T) { existing := []modelInfo{ {Name: "llama3.2:latest", Remote: false}, - {Name: "glm-5:cloud", Remote: true}, + {Name: "glm-5.1:cloud", Remote: true}, } _, _, existingModels, cloudModels := buildModelList(existing, nil, "") @@ -527,15 +527,15 @@ func TestBuildModelList_ReturnsExistingAndCloudMaps(t *testing.T) { if !existingModels["llama3.2"] { t.Error("llama3.2 should be in existingModels") } - if !existingModels["glm-5:cloud"] { - t.Error("glm-5:cloud should be in existingModels") + if !existingModels["glm-5.1:cloud"] { + t.Error("glm-5.1:cloud should be in existingModels") } if existingModels["gemma4"] { t.Error("gemma4 should not be in existingModels (it's a recommendation)") } - if !cloudModels["glm-5:cloud"] { - t.Error("glm-5:cloud should be in cloudModels") + if !cloudModels["glm-5.1:cloud"] { + t.Error("glm-5.1:cloud should be in cloudModels") } if !cloudModels["kimi-k2.5:cloud"] { t.Error("kimi-k2.5:cloud should be in cloudModels (recommended cloud)") @@ -558,7 +558,7 @@ func TestBuildModelList_RecommendedFieldSet(t *testing.T) { for _, item := range items { switch item.Name { - case "gemma4", "qwen3.5", "glm-5:cloud", "kimi-k2.5:cloud", "qwen3.5:cloud": + case "gemma4", "qwen3.5", "glm-5.1:cloud", "kimi-k2.5:cloud", "qwen3.5:cloud": if !item.Recommended { t.Errorf("%q should have Recommended=true", item.Name) } @@ -573,14 +573,14 @@ func TestBuildModelList_RecommendedFieldSet(t *testing.T) { func TestBuildModelList_MixedCase_CloudRecsFirst(t *testing.T) { existing := []modelInfo{ {Name: "llama3.2:latest", Remote: false}, - {Name: "glm-5:cloud", Remote: true}, + {Name: "glm-5.1:cloud", Remote: true}, } items, _, _, _ := buildModelList(existing, nil, "") got := names(items) // Cloud recs should sort before local recs in mixed case - cloudIdx := slices.Index(got, "glm-5:cloud") + cloudIdx := slices.Index(got, "glm-5.1:cloud") localIdx := slices.Index(got, "gemma4") if cloudIdx > localIdx { t.Errorf("cloud recs should be before local recs in mixed case, got %v", got) @@ -597,7 +597,7 @@ func TestBuildModelList_OnlyLocal_LocalRecsFirst(t *testing.T) { // Local recs should sort before cloud recs in only-local case localIdx := slices.Index(got, "gemma4") - cloudIdx := slices.Index(got, "glm-5:cloud") + cloudIdx := slices.Index(got, "glm-5.1:cloud") if localIdx > cloudIdx { t.Errorf("local recs should be before cloud recs in only-local case, got %v", got) } @@ -616,7 +616,7 @@ func TestBuildModelList_RecsAboveNonRecs(t *testing.T) { lastRecIdx := -1 firstNonRecIdx := len(got) for i, name := range got { - isRec := name == "gemma4" || name == "qwen3.5" || name == "minimax-m2.7:cloud" || name == "glm-5:cloud" || name == "kimi-k2.5:cloud" || name == "qwen3.5:cloud" + isRec := name == "gemma4" || name == "qwen3.5" || name == "minimax-m2.7:cloud" || name == "glm-5.1:cloud" || name == "kimi-k2.5:cloud" || name == "qwen3.5:cloud" if isRec && i > lastRecIdx { lastRecIdx = i } @@ -632,7 +632,7 @@ func TestBuildModelList_RecsAboveNonRecs(t *testing.T) { func TestBuildModelList_CheckedBeforeRecs(t *testing.T) { existing := []modelInfo{ {Name: "llama3.2:latest", Remote: false}, - {Name: "glm-5:cloud", Remote: true}, + {Name: "glm-5.1:cloud", Remote: true}, } items, _, _, _ := buildModelList(existing, []string{"llama3.2"}, "") @@ -713,7 +713,7 @@ func TestLauncherClientFilterDisabledCloudModels_ChecksStatusOncePerInvocation(t apiClient: api.NewClient(u, srv.Client()), } - filtered := client.filterDisabledCloudModels(context.Background(), []string{"llama3.2", "glm-5:cloud", "qwen3.5:cloud"}) + filtered := client.filterDisabledCloudModels(context.Background(), []string{"llama3.2", "glm-5.1:cloud", "qwen3.5:cloud"}) if diff := cmp.Diff([]string{"llama3.2"}, filtered); diff != "" { t.Fatalf("filtered models mismatch (-want +got):\n%s", diff) } @@ -930,11 +930,11 @@ func TestShowOrPullWithPolicy_CloudModelNotFound_FailsEarlyForAllPolicies(t *tes u, _ := url.Parse(srv.URL) client := api.NewClient(u, srv.Client()) - err := showOrPullWithPolicy(context.Background(), client, "glm-5:cloud", policy, true) + err := showOrPullWithPolicy(context.Background(), client, "glm-5.1:cloud", policy, true) if err == nil { t.Fatalf("expected cloud model not-found error for policy %d", policy) } - if !strings.Contains(err.Error(), `model "glm-5:cloud" not found`) { + if !strings.Contains(err.Error(), `model "glm-5.1:cloud" not found`) { t.Fatalf("expected not-found error for policy %d, got %v", policy, err) } if pullCalled { @@ -976,7 +976,7 @@ func TestShowOrPullWithPolicy_CloudModelDisabled_FailsWithCloudDisabledError(t * u, _ := url.Parse(srv.URL) client := api.NewClient(u, srv.Client()) - err := showOrPullWithPolicy(context.Background(), client, "glm-5:cloud", policy, true) + err := showOrPullWithPolicy(context.Background(), client, "glm-5.1:cloud", policy, true) if err == nil { t.Fatalf("expected cloud disabled error for policy %d", policy) } @@ -1128,11 +1128,11 @@ func TestShowOrPull_CloudModel_NotFoundDoesNotPull(t *testing.T) { u, _ := url.Parse(srv.URL) client := api.NewClient(u, srv.Client()) - err := showOrPullWithPolicy(context.Background(), client, "glm-5:cloud", missingModelPromptPull, true) + err := showOrPullWithPolicy(context.Background(), client, "glm-5.1:cloud", missingModelPromptPull, true) if err == nil { t.Error("ShowOrPull should return not-found error for cloud model") } - if !strings.Contains(err.Error(), `model "glm-5:cloud" not found`) { + if !strings.Contains(err.Error(), `model "glm-5.1:cloud" not found`) { t.Errorf("expected cloud model not-found error, got: %v", err) } if pullCalled { diff --git a/cmd/launch/models.go b/cmd/launch/models.go index fbdccbb0b..7c45ddfe0 100644 --- a/cmd/launch/models.go +++ b/cmd/launch/models.go @@ -23,7 +23,7 @@ import ( var recommendedModels = []ModelItem{ {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: "glm-5.1:cloud", Description: "Reasoning and code generation", Recommended: true}, {Name: "minimax-m2.7:cloud", Description: "Fast, efficient coding and real-world productivity", Recommended: true}, {Name: "gemma4", Description: "Reasoning and code generation locally", Recommended: true}, {Name: "qwen3.5", Description: "Reasoning, coding, and visual understanding locally", Recommended: true}, @@ -47,9 +47,11 @@ var cloudModelLimits = map[string]cloudModelLimit{ "cogito-2.1:671b": {Context: 163_840, Output: 65_536}, "deepseek-v3.1:671b": {Context: 163_840, Output: 163_840}, "deepseek-v3.2": {Context: 163_840, Output: 65_536}, + "gemma4:31b": {Context: 262_144, Output: 131_072}, "glm-4.6": {Context: 202_752, Output: 131_072}, "glm-4.7": {Context: 202_752, Output: 131_072}, "glm-5": {Context: 202_752, Output: 131_072}, + "glm-5.1": {Context: 202_752, Output: 131_072}, "gpt-oss:120b": {Context: 131_072, Output: 131_072}, "gpt-oss:20b": {Context: 131_072, Output: 131_072}, "kimi-k2:1t": {Context: 262_144, Output: 262_144}, diff --git a/cmd/launch/opencode_test.go b/cmd/launch/opencode_test.go index 62d1558e6..c2e9b3fb0 100644 --- a/cmd/launch/opencode_test.go +++ b/cmd/launch/opencode_test.go @@ -736,6 +736,8 @@ func TestLookupCloudModelLimit(t *testing.T) { {"glm-4.7", false, 0, 0}, {"glm-4.7:cloud", true, 202_752, 131_072}, {"glm-5:cloud", true, 202_752, 131_072}, + {"glm-5.1:cloud", true, 202_752, 131_072}, + {"gemma4:31b-cloud", true, 262_144, 131_072}, {"gpt-oss:120b-cloud", true, 131_072, 131_072}, {"gpt-oss:20b-cloud", true, 131_072, 131_072}, {"kimi-k2.5", false, 0, 0},