Ctrl K
ComunidadGuías…

internal/pluginhost/snapshot.go

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

Archivo original ↗Preguntar a KiBot
package pluginhost

import (
	"reflect"
	"sort"
	"strings"
	"sync"

	"github.com/router-for-me/CLIProxyAPI/v8/sdk/pluginapi"
)

type capabilityRecord struct {
	id       string
	path     string
	version  string
	priority int
	meta     pluginapi.Metadata
	plugin   pluginapi.Plugin
}

type Snapshot struct {
	enabled                   bool
	records                   []capabilityRecord
	quotaSupportedProvidersMu sync.RWMutex
	quotaSupportedProviders   map[string][]string
}

// RegisteredPluginInfo describes a plugin that is active in the current runtime snapshot.
type RegisteredPluginInfo struct {
	ID                   string
	Priority             int
	Metadata             pluginapi.Metadata
	SupportsOAuth        bool
	OAuthProvider        string
	OAuthProviders       []string
	OAuthProviderEntries []pluginapi.AuthProviderEntry
	SupportsQuota        bool
	QuotaProvider        string
	Menus                []RegisteredPluginMenu
}

// RegisteredPluginMenu describes a plugin-owned resource menu entry.
type RegisteredPluginMenu struct {
	Path        string
	Menu        string
	Description string
}

func emptySnapshot() *Snapshot {
	return &Snapshot{
		quotaSupportedProviders: make(map[string][]string),
	}
}

func (h *Host) activeRecords() []capabilityRecord {
	return h.activeRecordsFromSnapshot(h.Snapshot())
}

func (h *Host) activeRecordsFromSnapshot(snap *Snapshot) []capabilityRecord {
	if snap == nil || len(snap.records) == 0 {
		return nil
	}
	out := make([]capabilityRecord, 0, len(snap.records))
	for _, record := range snap.records {
		if h.recordCurrent(record) {
			out = append(out, record)
		}
	}
	return out
}

// RegisteredPlugins returns a stable copy of plugin metadata in the current runtime snapshot.
func (h *Host) RegisteredPlugins() []RegisteredPluginInfo {
	records := h.activeRecords()
	if len(records) == 0 {
		return nil
	}
	menusByPlugin := h.registeredPluginMenus()
	out := make([]RegisteredPluginInfo, 0, len(records))
	for _, record := range records {
		hasOAuth := hasRecordAuthProvider(record)
		oauthProvider := ""
		var oauthProviders []string
		var oauthProviderEntries []pluginapi.AuthProviderEntry
		if hasOAuth && !h.isPluginFused(record.id) {
			if identifiers, entries, okIdentifiers := h.callRecordAuthProviderMetadata(record); okIdentifiers && len(identifiers) > 0 {
				oauthProvider = identifiers[0]
				oauthProviders = identifiers
				oauthProviderEntries = entries
			}
		}
		quotaProvider := record.plugin.Capabilities.QuotaProvider
		quotaIdentifier := ""
		if quotaProvider != nil && !h.isPluginFused(record.id) {
			if identifier, okIdentifier := h.callQuotaIdentifier(record.id, quotaProvider); okIdentifier {
				quotaIdentifier = identifier
			}
		}
		out = append(out, RegisteredPluginInfo{
			ID:                   record.id,
			Priority:             record.priority,
			Metadata:             clonePluginMetadata(record.meta),
			SupportsOAuth:        hasOAuth,
			OAuthProvider:        oauthProvider,
			OAuthProviders:       oauthProviders,
			OAuthProviderEntries: oauthProviderEntries,
			SupportsQuota:        quotaProvider != nil,
			QuotaProvider:        quotaIdentifier,
			Menus:                menusByPlugin[record.id],
		})
	}
	return out
}

// PluginRegistered reports whether a plugin is active in the current runtime snapshot.
func (h *Host) PluginRegistered(id string) bool {
	if h == nil {
		return false
	}
	id = strings.TrimSpace(id)
	if id == "" {
		return false
	}
	for _, record := range h.activeRecords() {
		if record.id == id {
			return true
		}
	}
	return false
}

func (h *Host) registeredPluginMenus() map[string][]RegisteredPluginMenu {
	out := make(map[string][]RegisteredPluginMenu)
	if h == nil {
		return out
	}
	h.mu.Lock()
	defer h.mu.Unlock()
	for _, record := range h.resourceRoutes {
		menu := strings.TrimSpace(record.route.Menu)
		if menu == "" {
			continue
		}
		out[record.pluginID] = append(out[record.pluginID], RegisteredPluginMenu{
			Path:        strings.TrimSpace(record.route.Path),
			Menu:        menu,
			Description: strings.TrimSpace(record.route.Description),
		})
	}
	for pluginID := range out {
		sort.SliceStable(out[pluginID], func(i, j int) bool {
			return out[pluginID][i].Path < out[pluginID][j].Path
		})
	}
	return out
}

func sortRecords(records []capabilityRecord) {
	sort.SliceStable(records, func(i, j int) bool {
		if records[i].priority == records[j].priority {
			return records[i].id < records[j].id
		}
		return records[i].priority > records[j].priority
	})
}

func clonePluginMetadata(meta pluginapi.Metadata) pluginapi.Metadata {
	if len(meta.ConfigFields) == 0 {
		return meta
	}
	meta.ConfigFields = cloneConfigFields(meta.ConfigFields)
	return meta
}

func cloneConfigFields(fields []pluginapi.ConfigField) []pluginapi.ConfigField {
	if len(fields) == 0 {
		return nil
	}
	out := make([]pluginapi.ConfigField, len(fields))
	copy(out, fields)
	for index := range out {
		out[index].EnumValues = append([]string(nil), fields[index].EnumValues...)
		out[index].Default = cloneConfigFieldValue(fields[index].Default)
	}
	return out
}

func cloneConfigFieldValue(value any) any {
	if value == nil {
		return nil
	}
	visited := make(map[metadataCloneVisit]reflect.Value)
	return cloneInterceptorMetadataAny(reflect.ValueOf(value), visited)
}