118 lines
3.2 KiB
Go
118 lines
3.2 KiB
Go
package service
|
|
|
|
import (
|
|
"net/url"
|
|
"os"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
|
|
"infinite-canvas/backend/internal/provider"
|
|
"infinite-canvas/backend/internal/provider/bailian"
|
|
)
|
|
|
|
var pluginsInitOnce sync.Once
|
|
|
|
func initModelCatalogPlugins() {
|
|
pluginsInitOnce.Do(func() {
|
|
provider.Register(bailian.New())
|
|
})
|
|
}
|
|
|
|
// extendChannelModelCatalog 在已通过统一安全出站链路获取的标准目录上补充厂商条目。
|
|
// 插件不持有密钥也不自行发 HTTP,避免绕过 SSRF、请求头和响应大小边界。
|
|
func extendChannelModelCatalog(baseURL string, apiFormat string, headers []OutboundHeader, catalog []ChannelModelCatalogItem) []ChannelModelCatalogItem {
|
|
initModelCatalogPlugins()
|
|
discovery := provider.MatchProvider(baseURL, outboundHeadersMap(headers))
|
|
if discovery == nil {
|
|
return catalog
|
|
}
|
|
extra := discovery.AdditionalModels(provider.DiscoveryConfig{
|
|
BaseURL: baseURL,
|
|
APIFormat: apiFormat,
|
|
Headers: outboundHeadersMap(headers),
|
|
Region: modelCatalogRegion(baseURL),
|
|
})
|
|
if len(extra) == 0 {
|
|
return catalog
|
|
}
|
|
|
|
indexByID := make(map[string]int, len(catalog)+len(extra))
|
|
for index := range catalog {
|
|
indexByID[catalog[index].ID] = index
|
|
}
|
|
for _, model := range extra {
|
|
item := providerCatalogItem(model)
|
|
if item.ID == "" {
|
|
continue
|
|
}
|
|
if index, exists := indexByID[item.ID]; exists {
|
|
catalog[index] = enrichCatalogItem(catalog[index], item)
|
|
continue
|
|
}
|
|
indexByID[item.ID] = len(catalog)
|
|
catalog = append(catalog, item)
|
|
}
|
|
sort.Slice(catalog, func(left int, right int) bool {
|
|
return catalog[left].ID < catalog[right].ID
|
|
})
|
|
return catalog
|
|
}
|
|
|
|
func providerCatalogItem(item provider.Model) ChannelModelCatalogItem {
|
|
modelType := ""
|
|
for _, capability := range item.Capability {
|
|
if normalized := normalizeCatalogModelType(capability); normalized != "" {
|
|
modelType = normalized
|
|
break
|
|
}
|
|
}
|
|
return ChannelModelCatalogItem{
|
|
ID: strings.TrimPrefix(strings.TrimSpace(item.ID), "models/"),
|
|
DisplayName: strings.TrimSpace(item.DisplayName),
|
|
ModelType: modelType,
|
|
SupportedEndpointTypes: normalizeCatalogEndpointTypes(item.SupportedEndpointTypes),
|
|
}
|
|
}
|
|
|
|
func enrichCatalogItem(current ChannelModelCatalogItem, fallback ChannelModelCatalogItem) ChannelModelCatalogItem {
|
|
if current.DisplayName == "" {
|
|
current.DisplayName = fallback.DisplayName
|
|
}
|
|
if current.ModelType == "" {
|
|
current.ModelType = fallback.ModelType
|
|
}
|
|
if len(current.SupportedEndpointTypes) == 0 {
|
|
current.SupportedEndpointTypes = fallback.SupportedEndpointTypes
|
|
}
|
|
return current
|
|
}
|
|
|
|
func outboundHeadersMap(headers []OutboundHeader) map[string]string {
|
|
result := make(map[string]string, len(headers))
|
|
for _, header := range headers {
|
|
result[header.Name] = header.Value
|
|
}
|
|
return result
|
|
}
|
|
|
|
func modelCatalogRegion(baseURL string) string {
|
|
parsed, err := url.Parse(strings.TrimSpace(baseURL))
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
host := strings.ToLower(parsed.Hostname())
|
|
if strings.Contains(host, "dashscope-us") {
|
|
return "us-east-1"
|
|
}
|
|
if strings.Contains(host, "ap-southeast-1") {
|
|
return "ap-southeast-1"
|
|
}
|
|
return "cn-beijing"
|
|
}
|
|
|
|
func (s *Service) isPluginEnabled() bool {
|
|
value := strings.ToLower(strings.TrimSpace(os.Getenv("ENABLE_PROVIDER_PLUGINS")))
|
|
return value == "true" || value == "1"
|
|
}
|