Ctrl K
ComunidadGuías…

internal/registry/model_definitions_test.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package registry

import (
	"encoding/json"
	"reflect"
	"strings"
	"testing"
)

func TestCodexConfigurationUpdateCapability(t *testing.T) {
	// Counterexample: present in every tier without support_configuration_update.
	const incapableModelID = "gpt-5.6-luna"
	for _, tier := range []struct {
		name    string
		models  func() []*ModelInfo
		capable []string
	}{
		{name: "free", models: GetCodexFreeModels, capable: []string{"gpt-6-luna"}},
		{name: "team", models: GetCodexTeamModels, capable: []string{"gpt-6-astra", "gpt-6-sol", "gpt-6-luna"}},
		{name: "plus", models: GetCodexPlusModels, capable: []string{"gpt-6-astra", "gpt-6-sol", "gpt-6-luna"}},
		{name: "pro", models: GetCodexProModels, capable: []string{"gpt-6-astra", "gpt-6-sol", "gpt-6-luna"}},
	} {
		t.Run(tier.name, func(t *testing.T) {
			byID := make(map[string]*ModelInfo)
			for _, info := range tier.models() {
				if info != nil {
					byID[info.ID] = info
				}
			}
			for _, id := range append([]string{incapableModelID}, tier.capable...) {
				info := byID[id]
				if info == nil {
					t.Errorf("%s is missing", id)
					continue
				}
				want := id != incapableModelID
				if info.SupportConfigurationUpdate != want {
					t.Errorf("%s configuration_update = %v, want %v", id, info.SupportConfigurationUpdate, want)
				}
				if cloned := cloneModelInfo(info); cloned.SupportConfigurationUpdate != want {
					t.Errorf("%s clone configuration_update = %v, want %v", id, cloned.SupportConfigurationUpdate, want)
				}
				raw, errMarshal := json.Marshal(info)
				if errMarshal != nil {
					t.Fatal(errMarshal)
				}
				if strings.Contains(string(raw), "support_configuration_update") || strings.Contains(string(raw), "SupportConfigurationUpdate") {
					t.Errorf("%s exposed configuration_update capability: %s", id, raw)
				}
			}
		})
	}

	for _, tt := range []struct {
		name string
		raw  string
		want bool
	}{
		{name: "true", raw: `{"id":"test","support_configuration_update":true}`, want: true},
		{name: "false", raw: `{"id":"test","support_configuration_update":false}`},
		{name: "absent", raw: `{"id":"test"}`},
	} {
		t.Run(tt.name, func(t *testing.T) {
			info := ModelInfo{SupportConfigurationUpdate: true}
			if errUnmarshal := json.Unmarshal([]byte(tt.raw), &info); errUnmarshal != nil {
				t.Fatal(errUnmarshal)
			}
			if info.SupportConfigurationUpdate != tt.want {
				t.Errorf("capability = %v, want %v", info.SupportConfigurationUpdate, tt.want)
			}
		})
	}
}

func TestGetStaticModelDefinitionsByChannelSupportsGeminiInteractions(t *testing.T) {
	models := GetStaticModelDefinitionsByChannel("gemini-interactions")
	if len(models) == 0 {
		t.Fatal("GetStaticModelDefinitionsByChannel(gemini-interactions) returned no models")
	}
}

func TestGetStaticModelDefinitionsByChannelSupportsKimiAndKimiAI(t *testing.T) {
	for _, channel := range []string{"kimi", "kimi-ai", "kimi.ai", "kimi.com"} {
		models := GetStaticModelDefinitionsByChannel(channel)
		if len(models) == 0 {
			t.Fatalf("GetStaticModelDefinitionsByChannel(%s) returned no models", channel)
		}
	}
}

func TestModelOverrideHeadersFromEmbeddedModels(t *testing.T) {
	const wantUA = "codex-tui/0.154.0 (Mac OS 26.5.2; arm64) iTerm.app/3.6.11 (codex-tui; 0.154.0)"
	got := ModelOverrideHeaders("gpt-5.6-luna")
	if got == nil {
		t.Fatal("ModelOverrideHeaders(gpt-5.6-luna) = nil, want headers")
	}
	if got["user-agent"] != wantUA {
		t.Fatalf("user-agent = %q, want %q", got["user-agent"], wantUA)
	}
	if got := ModelOverrideHeaders("gpt-5.4"); got != nil {
		t.Fatalf("ModelOverrideHeaders(gpt-5.4) = %#v, want nil", got)
	}
}

func TestGeminiVertexModelsUseFlashLiteReleaseID(t *testing.T) {
	const releaseID = "gemini-3.1-flash-lite"
	const previewID = releaseID + "-preview"

	for _, model := range GetGeminiVertexModels() {
		if model == nil {
			continue
		}
		if model.ID == previewID {
			t.Fatalf("Vertex model ID = %q, want release ID %q", model.ID, releaseID)
		}
		if model.ID == releaseID {
			return
		}
	}

	t.Fatalf("Vertex models do not contain %q", releaseID)
}

func TestWithXAIBuiltinsIncludesImage20(t *testing.T) {
	models := WithXAIBuiltins(nil)
	for _, model := range models {
		if model != nil && model.ID == xaiBuiltinImage20ModelID {
			if model.Created != 1786060800 {
				t.Fatalf("created = %d, want 1786060800 (2026-08-07)", model.Created)
			}
			return
		}
	}
	t.Fatalf("expected xAI builtin model %s", xaiBuiltinImage20ModelID)
}

func TestWithXAIBuiltinsIncludesSpeechModels(t *testing.T) {
	models := WithXAIBuiltins(nil)
	found := map[string]*ModelInfo{}
	for _, model := range models {
		if model == nil {
			continue
		}
		if model.ID == xaiBuiltinSpeechModelID || model.ID == xaiBuiltinSpeechVoiceModelID {
			found[model.ID] = model
		}
	}
	for _, id := range []string{xaiBuiltinSpeechModelID, xaiBuiltinSpeechVoiceModelID} {
		model := found[id]
		if model == nil {
			t.Fatalf("expected xAI builtin model %s", id)
		}
		if model.OwnedBy != "xai" || model.Type != "xai" {
			t.Fatalf("%s owned_by/type = %s/%s, want xai/xai", id, model.OwnedBy, model.Type)
		}
	}
}

func TestWithXAIBuiltinsIncludesVideo15GAAndPreviewAlias(t *testing.T) {
	models := WithXAIBuiltins(nil)
	foundGA := false
	foundPreviewAlias := false

	for _, model := range models {
		if model == nil {
			continue
		}
		if model.ID == xaiBuiltinVideo15ModelID {
			foundGA = true
		}
		if model.ID == xaiBuiltinVideo15PreviewID {
			foundPreviewAlias = true
		}
	}

	if !foundGA {
		t.Fatalf("expected xAI builtin model %s", xaiBuiltinVideo15ModelID)
	}
	if !foundPreviewAlias {
		t.Fatalf("expected xAI builtin compatibility alias %s", xaiBuiltinVideo15PreviewID)
	}
}

func TestGetXAIModelsPreservesVideo15LiteAfterCatalogRefresh(t *testing.T) {
	const liteID = "grok-imagine-video-1.5-lite"
	original := getModels()
	t.Cleanup(func() {
		modelsCatalogStore.mu.Lock()
		modelsCatalogStore.data = original
		modelsCatalogStore.mu.Unlock()
	})

	refreshed := *original
	remoteModel := &ModelInfo{ID: "refreshed-xai-model", OwnedBy: "xai", Type: "xai", DisplayName: "Refreshed model"}
	refreshed.XAI = []*ModelInfo{remoteModel}
	payload, err := json.Marshal(&refreshed)
	if err != nil {
		t.Fatal(err)
	}
	if _, err := publishCatalogBytes(payload); err != nil {
		t.Fatalf("publish catalog without Lite: %v", err)
	}

	models := GetXAIModels()
	foundLite, foundRemote := false, false
	for _, model := range models {
		if model.ID == liteID {
			foundLite = true
			if model.OwnedBy != "xai" || model.Type != "xai" ||
				!reflect.DeepEqual(model.SupportedInputModalities, []string{"text", "image"}) ||
				!reflect.DeepEqual(model.SupportedOutputModalities, []string{"video"}) {
				t.Fatalf("Lite lost xAI video capabilities after catalog refresh: %#v", model)
			}
		}
		if model.ID == remoteModel.ID {
			foundRemote = true
			if !reflect.DeepEqual(model, remoteModel) {
				t.Fatalf("refreshed model = %#v, want %#v", model, remoteModel)
			}
		}
	}
	if !foundLite || !foundRemote {
		t.Fatalf("catalog lost Lite or refreshed model: Lite=%v, refreshed=%v", foundLite, foundRemote)
	}

	const clientID = "test-xai-lite-catalog-refresh"
	registryRef := GetGlobalRegistry()
	registryRef.RegisterClient(clientID, "xai", models)
	t.Cleanup(func() { registryRef.UnregisterClient(clientID) })
	if !registryRef.ClientSupportsModel(clientID, liteID) {
		t.Fatal("xAI credential registration lost Lite support after catalog refresh")
	}
	if providers := registryRef.GetModelProviders(liteID); !reflect.DeepEqual(providers, []string{"xai"}) {
		t.Fatalf("Lite providers = %v, want [xai]", providers)
	}
}

func TestWithXAIBuiltinsReplacesVideo15LiteWithoutMutatingCatalog(t *testing.T) {
	const liteID = "grok-imagine-video-1.5-lite"
	stale := &ModelInfo{ID: liteID, DisplayName: "Stale Lite", SupportedInputModalities: []string{"text"}}
	duplicate := &ModelInfo{ID: " GROK-IMAGINE-VIDEO-1.5-LITE ", DisplayName: "Duplicate Lite"}
	custom := &ModelInfo{ID: "custom-xai-model", DisplayName: "Custom model"}
	input := []*ModelInfo{stale, custom, duplicate}
	for range 2 {
		models := WithXAIBuiltins(input)
		liteCount := 0
		foundCustom := false
		for _, model := range models {
			if strings.EqualFold(strings.TrimSpace(model.ID), liteID) {
				liteCount++
				if model.ID != liteID || model.DisplayName != "Grok Imagine Video 1.5 Lite" ||
					!reflect.DeepEqual(model.SupportedInputModalities, []string{"text", "image"}) {
					t.Fatalf("Lite builtin did not replace stale metadata: %#v", model)
				}
				model.SupportedInputModalities[0] = "mutated"
			}
			if model.ID == custom.ID {
				foundCustom = reflect.DeepEqual(model, custom)
			}
		}
		if liteCount != 1 || !foundCustom {
			t.Fatalf("Lite count = %d, custom preserved = %v, want 1 and true", liteCount, foundCustom)
		}
	}
	if input[0] != stale || input[1] != custom || input[2] != duplicate ||
		stale.DisplayName != "Stale Lite" || !reflect.DeepEqual(stale.SupportedInputModalities, []string{"text"}) ||
		duplicate.ID != " GROK-IMAGINE-VIDEO-1.5-LITE " || duplicate.DisplayName != "Duplicate Lite" {
		t.Fatal("builtin merge mutated the input catalog")
	}
}

func TestAntigravityWebSearchModelForRequiresRequestedModelCapability(t *testing.T) {
	registryRef := GetGlobalRegistry()
	registryRef.RegisterClient("test-antigravity-websearch-route", "antigravity", []*ModelInfo{
		{ID: "gemini-route-test"},
		{ID: "gemini-web-search-test", SupportsWebSearch: true},
	})
	registryRef.RegisterClient("test-gemini-websearch-route", "gemini", []*ModelInfo{
		{ID: "gemini-cross-provider-route"},
		{ID: "gemini-cross-provider-search", SupportsWebSearch: true},
	})
	t.Cleanup(func() {
		registryRef.UnregisterClient("test-antigravity-websearch-route")
		registryRef.UnregisterClient("test-gemini-websearch-route")
	})

	if got := AntigravityWebSearchModelFor("gemini-route-test"); got != "" {
		t.Fatalf("route model without web search support should not get fallback model, got %q", got)
	}
	if got := AntigravityWebSearchModelFor("gemini-route-test(high)"); got != "" {
		t.Fatalf("suffix route model without web search support should not get fallback model, got %q", got)
	}
	if got := AntigravityWebSearchModelFor("gemini-web-search-test"); got != "gemini-web-search-test" {
		t.Fatalf("AntigravityWebSearchModelFor capable model = %q, want itself", got)
	}
	if got := AntigravityWebSearchModelFor("gemini-cross-provider-route"); got != "" {
		t.Fatalf("cross-provider model should not get Antigravity web search model, got %q", got)
	}
	if got := AntigravityWebSearchModelFor("unknown-model"); got != "" {
		t.Fatalf("unknown model should not get Antigravity web search model, got %q", got)
	}
}

func TestValidateModelsCatalog_Meta(t *testing.T) {
	valid := &staticModelsJSON{
		Meta: []*ModelInfo{
			{ID: "muse-spark-1.3"},
		},
	}
	if err := validateModelsCatalog(valid); err != nil {
		t.Fatalf("expected valid Meta catalog to pass, got: %v", err)
	}

	withNull := &staticModelsJSON{
		Meta: []*ModelInfo{nil},
	}
	if err := validateModelsCatalog(withNull); err == nil {
		t.Fatal("expected error for Meta section with null model, got nil")
	}

	withEmptyID := &staticModelsJSON{
		Meta: []*ModelInfo{{ID: " "}},
	}
	if err := validateModelsCatalog(withEmptyID); err == nil {
		t.Fatal("expected error for Meta section with empty model id, got nil")
	}

	withDuplicate := &staticModelsJSON{
		Meta: []*ModelInfo{
			{ID: "muse-spark-1.3"},
			{ID: "muse-spark-1.3"},
		},
	}
	if err := validateModelsCatalog(withDuplicate); err == nil {
		t.Fatal("expected error for Meta section with duplicate model id, got nil")
	}
}

func TestWithCodexBuiltinsIncludesImage25And26Models(t *testing.T) {
	models := WithCodexBuiltins(nil)
	expectedModels := map[string]string{
		"gpt-image-2.5-flare":    "GPT Image 2.5 Flare",
		"gpt-image-2.5-sunburst": "GPT Image 2.5 Sunburst",
		"gpt-image-2.5":          "GPT Image 2.5",
		"gpt-image-2.6-flare":    "GPT Image 2.6 Flare",
		"gpt-image-2.6-sunburst": "GPT Image 2.6 Sunburst",
		"gpt-image-2.6":          "GPT Image 2.6",
	}

	found := make(map[string]*ModelInfo)
	for _, model := range models {
		if model != nil {
			if _, ok := expectedModels[model.ID]; ok {
				found[model.ID] = model
			}
		}
	}

	for id, wantDisplayName := range expectedModels {
		model, ok := found[id]
		if !ok {
			t.Fatalf("expected builtin model %s in WithCodexBuiltins", id)
		}
		if model.DisplayName != wantDisplayName {
			t.Errorf("model %s DisplayName = %q, want %q", id, model.DisplayName, wantDisplayName)
		}
		if model.Object != "model" {
			t.Errorf("model %s Object = %q, want model", id, model.Object)
		}
		if model.OwnedBy != "openai" {
			t.Errorf("model %s OwnedBy = %q, want openai", id, model.OwnedBy)
		}
		if model.Type != "openai" {
			t.Errorf("model %s Type = %q, want openai", id, model.Type)
		}
		if model.Version != id {
			t.Errorf("model %s Version = %q, want %q", id, model.Version, id)
		}
		if model.Created != 1704067200 {
			t.Errorf("model %s Created = %d, want 1704067200", id, model.Created)
		}
	}
}

func TestGetDevinModelsFallback(t *testing.T) {
	devinModels := GetDevinModels()
	if len(devinModels) == 0 {
		t.Fatal("GetDevinModels() returned empty list")
	}

	foundSWE2 := false
	foundFable := false
	foundGemini38 := false
	foundGrok46 := false
	foundDeepSeekV4Flash := false
	foundDeepSeekV41Flash := false
	for _, m := range devinModels {
		if m != nil && m.ID == "devin/swe-2" {
			foundSWE2 = true
			if m.Type != "devin" {
				t.Errorf("devin/swe-2 Type = %q, want devin", m.Type)
			}
		}
		if m != nil && m.ID == "devin/claude-fable-5-1" {
			foundFable = true
		}
		if m != nil && m.ID == "devin/gemini-3-8-flash" {
			foundGemini38 = true
			if m.OwnedBy != "google" {
				t.Errorf("devin/gemini-3-8-flash OwnedBy = %q, want google", m.OwnedBy)
			}
		}
		if m != nil && m.ID == "devin/grok-4-6" {
			foundGrok46 = true
			if m.OwnedBy != "xai" {
				t.Errorf("devin/grok-4-6 OwnedBy = %q, want xai", m.OwnedBy)
			}
		}
		if m != nil && m.ID == "devin/deepseek-v4-flash" {
			foundDeepSeekV4Flash = true
			if m.OwnedBy != "deepseek" {
				t.Errorf("devin/deepseek-v4-flash OwnedBy = %q, want deepseek", m.OwnedBy)
			}
		}
		if m != nil && m.ID == "devin/deepseek-v4-1-flash-1017" {
			foundDeepSeekV41Flash = true
			if m.OwnedBy != "deepseek" {
				t.Errorf("devin/deepseek-v4-1-flash-1017 OwnedBy = %q, want deepseek", m.OwnedBy)
			}
		}
	}
	if !foundSWE2 {
		t.Error("expected devin/swe-2 in GetDevinModels()")
	}
	if !foundFable {
		t.Error("expected devin/claude-fable-5-1 in GetDevinModels()")
	}
	if !foundGemini38 {
		t.Error("expected devin/gemini-3-8-flash in GetDevinModels()")
	}
	if !foundGrok46 {
		t.Error("expected devin/grok-4-6 in GetDevinModels()")
	}
	if !foundDeepSeekV4Flash {
		t.Error("expected devin/deepseek-v4-flash in GetDevinModels()")
	}
	if !foundDeepSeekV41Flash {
		t.Error("expected devin/deepseek-v4-1-flash-1017 in GetDevinModels()")
	}

	byChannel := GetStaticModelDefinitionsByChannel("devin")
	if len(byChannel) == 0 {
		t.Fatal("GetStaticModelDefinitionsByChannel(\"devin\") returned empty list")
	}

	info := LookupStaticModelInfo("devin/swe-2")
	if info == nil {
		t.Fatal("LookupStaticModelInfo(\"devin/swe-2\") = nil, want valid model")
	}
	if info.DisplayName != "SWE-2" {
		t.Errorf("info.DisplayName = %q, want SWE-2", info.DisplayName)
	}
}