From 420c52b10ee8b1b6cb598298958fc68fe9a1c116 Mon Sep 17 00:00:00 2001 From: Mikhail Date: Tue, 25 Aug 2026 00:02:04 +0300 Subject: [PATCH] Simplify model inventory responses --- cmd/core-runtime/main.go | 79 +++++++++++++++++++++++++++++------ cmd/core-runtime/main_test.go | 12 +++++- 2 files changed, 78 insertions(+), 13 deletions(-) diff --git a/cmd/core-runtime/main.go b/cmd/core-runtime/main.go index c859d2c..09f629c 100644 --- a/cmd/core-runtime/main.go +++ b/cmd/core-runtime/main.go @@ -30,6 +30,7 @@ import ( "log" "net/http" "os" + "sort" "strconv" "strings" "time" @@ -1510,28 +1511,82 @@ func humanCognitiveModelInventory(candidates []capability.ResolvedCognitiveBindi if len(candidates) == 0 { return "В реестре пока нет подтверждённых подключённых моделей. Встроенный локальный канал Домового доступен всегда; внешние модели добавляются через настройки подключения. Ключи и пароли в чат не выводятся." } - lines := make([]string, 0, len(candidates)+1) - lines = append(lines, "Подключённые модели из реестра:") + type inventoryEntry struct { + name string + status string + rank int + seenAt int + } + entries := make([]inventoryEntry, 0, len(candidates)) + byName := make(map[string]int, len(candidates)) for _, candidate := range candidates { modelName := string(candidate.Model.ProviderModelID) if modelName == "" { - modelName = "имя модели не подтверждено" + continue } - status := cognitiveInventoryStatus(candidate) - pricing := cognitiveInventoryPricing(candidate.Provider) - details := []string{status, "тип " + string(candidate.Provider.Kind), "политика " + string(candidate.Binding.DataPolicy)} - if pricing != "" { - details = append(details, pricing) + status := conciseCognitiveInventoryStatus(candidate) + rank := cognitiveInventoryRank(candidate) + if index, exists := byName[modelName]; exists { + if rank < entries[index].rank { + entries[index].status = status + entries[index].rank = rank + } + continue } - if candidate.Provider.AuthMode != "" && candidate.Provider.AuthMode != capability.AuthNone { - details = append(details, "доступ "+humanAuthMode(candidate.Provider.AuthMode)) + byName[modelName] = len(entries) + entries = append(entries, inventoryEntry{name: modelName, status: status, rank: rank, seenAt: len(entries)}) + } + sort.SliceStable(entries, func(left, right int) bool { + if entries[left].rank != entries[right].rank { + return entries[left].rank < entries[right].rank } - lines = append(lines, "- "+modelName+" ("+strings.Join(details, "; ")+")") + return entries[left].seenAt < entries[right].seenAt + }) + lines := make([]string, 0, len(entries)+2) + lines = append(lines, "Доступные модели:") + unconfirmed := 0 + for _, entry := range entries { + if entry.rank >= 3 { + unconfirmed++ + continue + } + lines = append(lines, "- "+entry.name+" — "+entry.status) + } + if len(lines) == 1 { + return "Подтверждённых доступных моделей сейчас нет." + } + if unconfirmed > 0 { + lines = append(lines, fmt.Sprintf("Ещё %d моделей требуют проверки.", unconfirmed)) } - lines = append(lines, "Секреты, ключи и пароли намеренно не показываются.") return strings.Join(lines, "\n") } +func conciseCognitiveInventoryStatus(candidate capability.ResolvedCognitiveBinding) string { + if candidate.Provider.Health == capability.HealthAvailable && candidate.Model.Health == capability.HealthAvailable { + if candidate.Binding.Activation == capability.ActivationActive { + return "активна" + } + return "доступна" + } + if candidate.Provider.Health == capability.HealthDegraded || candidate.Model.Health == capability.HealthDegraded { + return "работает с ограничениями" + } + return "не подтверждена" +} + +func cognitiveInventoryRank(candidate capability.ResolvedCognitiveBinding) int { + if candidate.Provider.Health == capability.HealthAvailable && candidate.Model.Health == capability.HealthAvailable { + if candidate.Binding.Activation == capability.ActivationActive { + return 0 + } + return 1 + } + if candidate.Provider.Health == capability.HealthDegraded || candidate.Model.Health == capability.HealthDegraded { + return 2 + } + return 3 +} + func cognitiveInventoryStatus(candidate capability.ResolvedCognitiveBinding) string { if candidate.Provider.Health == capability.HealthAvailable && candidate.Model.Health == capability.HealthAvailable { if candidate.Binding.Activation == capability.ActivationActive { diff --git a/cmd/core-runtime/main_test.go b/cmd/core-runtime/main_test.go index 7b2702f..eefff00 100644 --- a/cmd/core-runtime/main_test.go +++ b/cmd/core-runtime/main_test.go @@ -74,11 +74,21 @@ func TestHumanCognitiveModelInventoryDoesNotExposeSecrets(t *testing.T) { Provider: capability.ProviderInstance{Kind: "openai-compatible-chat-v1", CredentialRef: "secret-ref", AuthMode: capability.AuthKey, BillingMode: capability.BillingPerRequest, RequestCostMicros: &cost, Health: capability.HealthAvailable}, Model: capability.ModelDescriptor{ProviderModelID: "example-model", Health: capability.HealthAvailable}, }}, nil) - if strings.Contains(text, "secret-ref") || strings.Contains(text, "25") || !strings.Contains(text, "example-model") || !strings.Contains(text, "API-ключ") { + if strings.Contains(text, "secret-ref") || strings.Contains(text, "25") || !strings.Contains(text, "example-model") || !strings.Contains(text, "активна") { t.Fatalf("unsafe or incomplete inventory: %q", text) } } +func TestHumanCognitiveModelInventoryDeduplicatesAndPrioritizesAvailable(t *testing.T) { + text := humanCognitiveModelInventory([]capability.ResolvedCognitiveBinding{ + {Binding: capability.CognitiveBinding{Activation: capability.ActivationInactive}, Provider: capability.ProviderInstance{Health: capability.HealthUnavailable}, Model: capability.ModelDescriptor{ProviderModelID: "same-model", Health: capability.HealthUnavailable}}, + {Binding: capability.CognitiveBinding{Activation: capability.ActivationActive}, Provider: capability.ProviderInstance{Health: capability.HealthAvailable}, Model: capability.ModelDescriptor{ProviderModelID: "same-model", Health: capability.HealthAvailable}}, + }, nil) + if strings.Count(text, "same-model") != 1 || !strings.Contains(text, "same-model — активна") { + t.Fatalf("expected one active model entry, got %q", text) + } +} + func TestCreatorSessionHandlerAcceptsConfiguredCredentials(t *testing.T) { handler := creatorSessionHandler("recovery", "m", "password", []byte("01234567890123456789012345678901"), func() time.Time { return time.Unix(1000, 0) }) request := httptest.NewRequest(http.MethodPost, "/v1/creator/sessions", strings.NewReader(`{"username":"m","password":"password"}`)) -- 2.54.0