Skip to content

Commit 24ddc02

Browse files
committed
refactor(catalog): split v1.go into v1_defaults.go
Move default catalog construction, legacy ModelCatalog conversion, and pricing/capability sanitization+validation helpers into v1_defaults.go. Schema types, parsing, validation, compilation, loading, and lookups remain in v1.go. Pure mechanical split, no behavior changes.
1 parent 6627b14 commit 24ddc02

2 files changed

Lines changed: 390 additions & 378 deletions

File tree

catalog/v1.go

Lines changed: 3 additions & 378 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ import (
1313
"time"
1414
)
1515

16+
// Default catalog construction, legacy conversion, and sanitization helpers
17+
// live in v1_defaults.go.
18+
1619
const (
1720
CatalogV1SchemaVersion = "model-catalog/v1"
1821
// DefaultCatalogV1URL is the published model-catalog/v1 document.
@@ -635,381 +638,3 @@ func SplitOfferingIDV1(id string) (deploymentID, nativeModelID string, ok bool)
635638
left, right, found := strings.Cut(id, ":")
636639
return left, right, found && left != "" && right != ""
637640
}
638-
639-
func defaultProvidersV1() map[string]ProviderV1 {
640-
return map[string]ProviderV1{
641-
"anthropic": {ID: "anthropic", Name: "Anthropic"},
642-
"openai": {ID: "openai", Name: "OpenAI"},
643-
"google": {ID: "google", Name: "Google"},
644-
"xai": {ID: "xai", Name: "xAI"},
645-
"openrouter": {ID: "openrouter", Name: "OpenRouter"},
646-
"canopywave": {ID: "canopywave", Name: "CanopyWave"},
647-
"zai_payg": {ID: "zai_payg", Name: "Z.AI Pay-as-you-go"},
648-
"zai_coding": {ID: "zai_coding", Name: "Z.AI Coding Plan"},
649-
"ollama": {ID: "ollama", Name: "Ollama"},
650-
"opencodego": {ID: "opencodego", Name: "OpenCode Go"},
651-
"moonshotai": {ID: "moonshotai", Name: "Moonshot AI"},
652-
"kimi": {ID: "kimi", Name: "Kimi (Moonshot)"},
653-
"xiaomi_mimo_payg": {ID: "xiaomi_mimo_payg", Name: "Xiaomi MiMo (Pay-as-you-go)"},
654-
"xiaomi_mimo_token_plan": {ID: "xiaomi_mimo_token_plan", Name: "Xiaomi MiMo (Token Plan)"},
655-
"deepseek": {ID: "deepseek", Name: "DeepSeek"},
656-
}
657-
}
658-
659-
func defaultAPIProtocolsV1() map[string]APIProtocolV1 {
660-
return map[string]APIProtocolV1{
661-
"anthropic-messages": {ID: "anthropic-messages", Name: "Anthropic Messages"},
662-
"openai-chat-completions": {ID: "openai-chat-completions", Name: "OpenAI Chat Completions"},
663-
"gemini-generate-content": {ID: "gemini-generate-content", Name: "Gemini generateContent"},
664-
}
665-
}
666-
667-
func defaultDeploymentsV1() map[string]DeploymentV1 {
668-
return map[string]DeploymentV1{
669-
"anthropic-direct": deployment("anthropic-direct", "Anthropic", "anthropic", "anthropic-messages", "anthropic", NativeModelIDCatalogKnown),
670-
"anthropic-bedrock": deployment("anthropic-bedrock", "Anthropic on Bedrock", "anthropic", "anthropic-messages", "anthropic-bedrock", NativeModelIDCatalogKnown),
671-
"anthropic-vertex": deployment("anthropic-vertex", "Anthropic on Vertex", "anthropic", "anthropic-messages", "anthropic-vertex", NativeModelIDCatalogKnown),
672-
"openai-direct": deployment("openai-direct", "OpenAI", "openai", "openai-chat-completions", "openai", NativeModelIDCatalogKnown),
673-
"openai-azure": azureDeployment(),
674-
"gemini-direct": deployment("gemini-direct", "Gemini", "google", "gemini-generate-content", "gemini", NativeModelIDCatalogKnown),
675-
"gemini-vertex": deployment("gemini-vertex", "Gemini on Vertex", "google", "gemini-generate-content", "gemini-vertex", NativeModelIDCatalogKnown),
676-
"grok-direct": deployment("grok-direct", "Grok", "xai", "openai-chat-completions", "grok", NativeModelIDCatalogKnown),
677-
"openrouter": deployment("openrouter", "OpenRouter", "openrouter", "openai-chat-completions", "openrouter", NativeModelIDDiscovered),
678-
"zai_payg-direct": deployment("zai_payg-direct", "Z.AI Pay-as-you-go", "zai_payg", "openai-chat-completions", "zai_payg", NativeModelIDCatalogKnown),
679-
"zai_coding-direct": deployment("zai_coding-direct", "Z.AI Coding Plan", "zai_coding", "openai-chat-completions", "zai_coding", NativeModelIDCatalogKnown),
680-
"canopywave": deployment("canopywave", "CanopyWave", "canopywave", "openai-chat-completions", "canopywave", NativeModelIDDiscovered),
681-
"ollama-local": localDeployment(),
682-
"opencodego": deployment("opencodego", "OpenCode Go", "opencodego", "openai-chat-completions", "opencodego", NativeModelIDDiscovered),
683-
"kimi-direct": deployment("kimi-direct", "Kimi (Moonshot)", "kimi", "openai-chat-completions", "kimi", NativeModelIDDiscovered),
684-
"xiaomi_mimo_payg-direct": deployment("xiaomi_mimo_payg-direct", "Xiaomi MiMo Pay-as-you-go", "xiaomi_mimo_payg", "openai-chat-completions", "xiaomi_mimo", NativeModelIDDiscovered),
685-
"xiaomi_mimo_token_plan-direct": deployment("xiaomi_mimo_token_plan-direct", "Xiaomi MiMo Token Plan", "xiaomi_mimo_token_plan", "openai-chat-completions", "xiaomi_mimo", NativeModelIDDiscovered),
686-
"deepseek-direct": deployment("deepseek-direct", "DeepSeek", "deepseek", "openai-chat-completions", "deepseek", NativeModelIDCatalogKnown),
687-
}
688-
}
689-
690-
func deployment(id, name, providerID, protocolID, adapter string, source NativeModelIDSource) DeploymentV1 {
691-
return DeploymentV1{ID: id, Name: name, ProviderID: providerID, APIProtocolID: protocolID, AdapterConstructor: adapter, NativeModelIDSource: source}
692-
}
693-
694-
func azureDeployment() DeploymentV1 {
695-
d := deployment("openai-azure", "Azure OpenAI", "openai", "openai-chat-completions", "openai-azure", NativeModelIDUserConfigured)
696-
d.ModelMappingsRequired = true
697-
return d
698-
}
699-
700-
func localDeployment() DeploymentV1 {
701-
d := deployment("ollama-local", "Ollama local", "ollama", "openai-chat-completions", "ollama", NativeModelIDDiscovered)
702-
d.Local = true
703-
return d
704-
}
705-
706-
func defaultOfferingTemplatesV1(generatedAt time.Time) []ModelOfferingTemplateV1 {
707-
var out []ModelOfferingTemplateV1
708-
for _, model := range testOpenAIModels {
709-
canonical := canonicalModelID("openai", model.ID)
710-
out = append(out, ModelOfferingTemplateV1{
711-
ID: "openai-azure:" + canonical,
712-
CanonicalModelID: canonical,
713-
DeploymentID: "openai-azure",
714-
NativeModelIDSource: NativeModelIDUserConfigured,
715-
MappingRequired: true,
716-
Capabilities: capabilitySetFromLegacy(model),
717-
Pricing: pricingFromLegacy(model, generatedAt, "embedded"),
718-
})
719-
}
720-
return out
721-
}
722-
723-
func appendDerivedDeploymentOfferings(offerings []ModelOfferingV1) []ModelOfferingV1 {
724-
seen := make(map[string]bool, len(offerings))
725-
for _, offering := range offerings {
726-
seen[offering.ID] = true
727-
}
728-
addCopy := func(source ModelOfferingV1, deploymentID string) {
729-
copied := source
730-
copied.DeploymentID = deploymentID
731-
copied.ID = deploymentID + ":" + source.NativeModelID
732-
if !seen[copied.ID] {
733-
seen[copied.ID] = true
734-
offerings = append(offerings, copied)
735-
}
736-
}
737-
for _, offering := range append([]ModelOfferingV1(nil), offerings...) {
738-
switch offering.DeploymentID {
739-
case "anthropic-direct":
740-
addCopy(offering, "anthropic-bedrock")
741-
addCopy(offering, "anthropic-vertex")
742-
case "gemini-direct":
743-
addCopy(offering, "gemini-vertex")
744-
}
745-
}
746-
return offerings
747-
}
748-
749-
func legacyDeploymentAndOwner(provider string) (deploymentID, ownerProviderID string) {
750-
switch provider {
751-
case "anthropic":
752-
return "anthropic-direct", "anthropic"
753-
case "openai":
754-
return "openai-direct", "openai"
755-
case "azure":
756-
return "openai-azure", "openai"
757-
case "grok":
758-
return "grok-direct", "xai"
759-
case "gemini":
760-
return "gemini-direct", "google"
761-
case "bedrock":
762-
return "anthropic-bedrock", "anthropic"
763-
case "vertex":
764-
return "gemini-vertex", "google"
765-
case "openrouter":
766-
return "openrouter", "openrouter"
767-
case "zai_payg":
768-
return "zai_payg-direct", "zai_payg"
769-
case "zai_coding":
770-
return "zai_coding-direct", "zai_coding"
771-
case "canopywave":
772-
return "canopywave", "canopywave"
773-
case "ollama":
774-
return "ollama-local", "ollama"
775-
case "opencodego":
776-
return "opencodego", "opencodego"
777-
case "kimi", "moonshotai":
778-
return "kimi-direct", "kimi"
779-
case "xiaomi_mimo", "xiaomi_mimo_payg":
780-
return "xiaomi_mimo_payg-direct", "xiaomi_mimo_payg"
781-
case "xiaomi_mimo_token_plan":
782-
return "xiaomi_mimo_token_plan-direct", "xiaomi_mimo_token_plan"
783-
case "deepseek":
784-
return "deepseek-direct", "deepseek"
785-
default:
786-
return "", ""
787-
}
788-
}
789-
790-
func canonicalModelID(ownerProviderID, nativeID string) string {
791-
if strings.Contains(nativeID, "/") {
792-
owner, _, _ := strings.Cut(nativeID, "/")
793-
if owner != "" && ownerProviderID == canonicalProviderID(owner) {
794-
return nativeID
795-
}
796-
}
797-
if ownerProviderID == "zai_payg" && strings.HasPrefix(nativeID, "zai/") {
798-
return "zai_payg/" + strings.TrimPrefix(nativeID, "zai/")
799-
}
800-
return ownerProviderID + "/" + nativeID
801-
}
802-
803-
// CanonicalProviderID normalizes legacy provider aliases (e.g. gemini -> google).
804-
func CanonicalProviderID(providerID string) string {
805-
return canonicalProviderID(providerID)
806-
}
807-
808-
func canonicalProviderID(providerID string) string {
809-
switch providerID {
810-
case "gemini":
811-
return "google"
812-
case "grok":
813-
return "xai"
814-
// No legacy aliases — zai_payg and zai_coding are the only valid IDs.
815-
case "moonshotai":
816-
return "moonshotai"
817-
case "xiaomi-mimo", "xiaomi_mimo", "xiaomi-mimo-payg":
818-
return "xiaomi_mimo_payg"
819-
case "xiaomi-mimo-token-plan":
820-
return "xiaomi_mimo_token_plan"
821-
default:
822-
return providerID
823-
}
824-
}
825-
826-
func capabilitySetFromLegacy(entry ModelCatalogEntry) CapabilitySetV1 {
827-
set := CapabilitySetV1{
828-
ServerTools: map[string]CapabilityState{},
829-
MaxInputTokens: entry.ContextWindow,
830-
MaxOutputTokens: entry.MaxOutput,
831-
}
832-
for _, tool := range entry.ServerTools {
833-
if tool != "" {
834-
set.ServerTools[tool] = CapabilitySupported
835-
}
836-
}
837-
if len(set.ServerTools) == 0 {
838-
set.ServerTools = nil
839-
}
840-
for _, feat := range entry.ServerTools {
841-
switch strings.ToLower(strings.TrimSpace(feat)) {
842-
case "function-calling", "tools":
843-
set.FunctionCalling = CapabilitySupported
844-
case "thinking:enabled":
845-
set.ExplicitThinkingBudget = CapabilitySupported
846-
set.ThinkingTypes = append(set.ThinkingTypes, "enabled")
847-
case "thinking:adaptive":
848-
set.AdaptiveThinking = CapabilitySupported
849-
set.ThinkingTypes = append(set.ThinkingTypes, "adaptive")
850-
case "effort":
851-
set.Effort = CapabilitySupported
852-
case "structured_output":
853-
set.StructuredOutput = CapabilitySupported
854-
case "code_execution":
855-
set.CodeExecution = CapabilitySupported
856-
case "citations":
857-
set.Citations = CapabilitySupported
858-
case "pdf_input":
859-
set.PDFInput = CapabilitySupported
860-
case "image_input":
861-
set.ImageInput = CapabilitySupported
862-
}
863-
}
864-
// Parse effort levels from features (format: "effort:low,medium,high")
865-
for _, feat := range entry.ServerTools {
866-
if strings.HasPrefix(strings.ToLower(feat), "effort:") {
867-
levels := strings.TrimPrefix(strings.ToLower(feat), "effort:")
868-
set.EffortLevels = strings.Split(levels, ",")
869-
}
870-
}
871-
return set
872-
}
873-
874-
func pricingFromLegacy(entry ModelCatalogEntry, effectiveAt time.Time, source string) PricingV1 {
875-
in := entry.InputPricePer1M
876-
out := entry.OutputPricePer1M
877-
if in < 0 || out < 0 {
878-
return PricingV1{
879-
Status: PricingUnknown,
880-
Currency: "USD",
881-
EffectiveAt: effectiveAt,
882-
Source: source,
883-
}
884-
}
885-
pricing := PricingV1{
886-
Status: PricingKnown,
887-
Currency: "USD",
888-
EffectiveAt: effectiveAt,
889-
RatesPer1M: map[string]float64{"input_tokens": in, "output_tokens": out},
890-
Source: source,
891-
}
892-
if in == 0 && out == 0 {
893-
pricing.Status = PricingUnknown
894-
pricing.RatesPer1M = nil
895-
if strings.Contains(entry.ID, ":free") {
896-
pricing.Status = PricingFree
897-
pricing.RatesPer1M = map[string]float64{"input_tokens": 0, "output_tokens": 0}
898-
}
899-
}
900-
return pricing
901-
}
902-
903-
// SanitizeCatalogV1Pricing drops invalid rate dimensions (e.g. negative OpenRouter prices).
904-
func SanitizeCatalogV1Pricing(c *CatalogV1) {
905-
if c == nil {
906-
return
907-
}
908-
for i := range c.Offerings {
909-
c.Offerings[i].Pricing = sanitizePricingV1(c.Offerings[i].Pricing)
910-
}
911-
for i := range c.OfferingTemplates {
912-
c.OfferingTemplates[i].Pricing = sanitizePricingV1(c.OfferingTemplates[i].Pricing)
913-
}
914-
}
915-
916-
func sanitizePricingV1(p PricingV1) PricingV1 {
917-
if len(p.RatesPer1M) == 0 {
918-
return p
919-
}
920-
clean := make(map[string]float64, len(p.RatesPer1M))
921-
for dim, rate := range p.RatesPer1M {
922-
if dim == "" || rate < 0 {
923-
continue
924-
}
925-
clean[dim] = rate
926-
}
927-
if len(clean) == 0 {
928-
p.Status = PricingUnknown
929-
p.RatesPer1M = nil
930-
return p
931-
}
932-
p.RatesPer1M = clean
933-
if p.Status == PricingKnown && (p.Currency == "" || len(p.RatesPer1M) == 0) {
934-
p.Status = PricingUnknown
935-
p.RatesPer1M = nil
936-
}
937-
return p
938-
}
939-
940-
func uniqueNonEmpty(values ...string) []string {
941-
seen := map[string]bool{}
942-
var out []string
943-
for _, value := range values {
944-
value = strings.TrimSpace(value)
945-
if value == "" || seen[value] {
946-
continue
947-
}
948-
seen[value] = true
949-
out = append(out, value)
950-
}
951-
return out
952-
}
953-
954-
func looksCanonicalModelID(value string) bool {
955-
owner, model, ok := strings.Cut(value, "/")
956-
return ok && owner != "" && model != "" && !strings.ContainsAny(value, " \t\r\n")
957-
}
958-
959-
func validNativeModelIDSource(source NativeModelIDSource) bool {
960-
switch source {
961-
case NativeModelIDCatalogKnown, NativeModelIDDiscovered, NativeModelIDUserConfigured, NativeModelIDCatalogOrUser:
962-
return true
963-
default:
964-
return false
965-
}
966-
}
967-
968-
func validatePricing(problems *[]string, id string, pricing PricingV1) {
969-
switch pricing.Status {
970-
case PricingKnown, PricingPartial:
971-
if pricing.Currency == "" || len(pricing.RatesPer1M) == 0 {
972-
*problems = append(*problems, fmt.Sprintf("%s pricing is missing currency or rates", id))
973-
}
974-
case PricingUnknown:
975-
if len(pricing.RatesPer1M) > 0 {
976-
*problems = append(*problems, fmt.Sprintf("%s unknown pricing must not include rates", id))
977-
}
978-
case PricingFree:
979-
if pricing.Currency == "" {
980-
*problems = append(*problems, fmt.Sprintf("%s free pricing missing currency", id))
981-
}
982-
default:
983-
*problems = append(*problems, fmt.Sprintf("%s invalid pricing status %q", id, pricing.Status))
984-
}
985-
for dim, rate := range pricing.RatesPer1M {
986-
if dim == "" || rate < 0 {
987-
*problems = append(*problems, fmt.Sprintf("%s invalid pricing dimension %q", id, dim))
988-
}
989-
}
990-
}
991-
992-
func validateCapabilities(problems *[]string, id string, capabilities CapabilitySetV1) {
993-
valid := func(state CapabilityState) bool {
994-
return state == "" || state == CapabilitySupported || state == CapabilityUnsupported || state == CapabilityUnknown
995-
}
996-
if !valid(capabilities.FunctionCalling) {
997-
*problems = append(*problems, fmt.Sprintf("%s invalid function_calling capability", id))
998-
}
999-
if !valid(capabilities.ExplicitThinkingBudget) {
1000-
*problems = append(*problems, fmt.Sprintf("%s invalid explicit_thinking_budget capability", id))
1001-
}
1002-
for tool, state := range capabilities.ServerTools {
1003-
if tool == "" || !valid(state) {
1004-
*problems = append(*problems, fmt.Sprintf("%s invalid server tool capability", id))
1005-
}
1006-
}
1007-
}
1008-
1009-
func cloneMap[T any](in map[string]T) map[string]T {
1010-
out := make(map[string]T, len(in))
1011-
for key, value := range in {
1012-
out[key] = value
1013-
}
1014-
return out
1015-
}

0 commit comments

Comments
 (0)