mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-07-02 02:54:19 +08:00
279 lines
8.0 KiB
Go
279 lines
8.0 KiB
Go
package pluginhost
|
|
|
|
import (
|
|
"context"
|
|
|
|
internalconfig "github.com/router-for-me/CLIProxyAPI/v7/internal/config"
|
|
internalpluginhost "github.com/router-for-me/CLIProxyAPI/v7/internal/pluginhost"
|
|
internalregistry "github.com/router-for-me/CLIProxyAPI/v7/internal/registry"
|
|
coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth"
|
|
"github.com/router-for-me/CLIProxyAPI/v7/sdk/pluginapi"
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
// ModelInfo describes a plugin-provided model using public plugin SDK types.
|
|
type ModelInfo = pluginapi.ModelInfo
|
|
|
|
// ThinkingSupport describes plugin-provided thinking controls.
|
|
type ThinkingSupport = pluginapi.ThinkingSupport
|
|
|
|
// OAuthModelAlias defines a model ID alias for OAuth/file-backed auth channels.
|
|
type OAuthModelAlias struct {
|
|
Name string
|
|
Alias string
|
|
Fork bool
|
|
}
|
|
|
|
// RuntimeConfig is the public plugin host configuration used by embedders.
|
|
type RuntimeConfig struct {
|
|
Enabled bool
|
|
Dir string
|
|
AuthDir string
|
|
ProxyURL string
|
|
ForceModelPrefix bool
|
|
OAuthModelAlias map[string][]OAuthModelAlias
|
|
OAuthExcludedModels map[string][]string
|
|
Configs map[string]PluginInstanceConfig
|
|
}
|
|
|
|
// PluginInstanceConfig stores host-owned plugin settings and the original plugin YAML subtree.
|
|
type PluginInstanceConfig struct {
|
|
Enabled *bool
|
|
Priority int
|
|
Raw yaml.Node
|
|
}
|
|
|
|
// AuthModelResult is the public result for per-auth model discovery.
|
|
type AuthModelResult struct {
|
|
Provider string
|
|
Models []ModelInfo
|
|
Auth *coreauth.Auth
|
|
Handled bool
|
|
Err error
|
|
}
|
|
|
|
// Host wraps the internal plugin host behind a public SDK surface.
|
|
type Host struct {
|
|
inner *internalpluginhost.Host
|
|
}
|
|
|
|
// New creates a plugin host.
|
|
func New() *Host {
|
|
return &Host{inner: internalpluginhost.New()}
|
|
}
|
|
|
|
// ApplyConfig applies plugin runtime configuration.
|
|
func (h *Host) ApplyConfig(ctx context.Context, cfg RuntimeConfig) {
|
|
if h == nil || h.inner == nil {
|
|
return
|
|
}
|
|
internalCfg := runtimeConfigToInternalConfig(cfg)
|
|
h.inner.ApplyConfig(ctx, internalCfg)
|
|
}
|
|
|
|
// ShutdownAll unloads every active plugin.
|
|
func (h *Host) ShutdownAll() {
|
|
if h == nil || h.inner == nil {
|
|
return
|
|
}
|
|
h.inner.ShutdownAll()
|
|
}
|
|
|
|
// ParseAuth lets plugin auth providers parse a credential payload.
|
|
func (h *Host) ParseAuth(ctx context.Context, req pluginapi.AuthParseRequest) (*coreauth.Auth, bool, error) {
|
|
if h == nil || h.inner == nil {
|
|
return nil, false, nil
|
|
}
|
|
return h.inner.ParseAuth(ctx, req)
|
|
}
|
|
|
|
// ModelsForAuth lets plugin model providers discover auth-bound models.
|
|
func (h *Host) ModelsForAuth(ctx context.Context, auth *coreauth.Auth) AuthModelResult {
|
|
if h == nil || h.inner == nil {
|
|
return AuthModelResult{}
|
|
}
|
|
result := h.inner.ModelsForAuth(ctx, auth)
|
|
return AuthModelResult{
|
|
Provider: result.Provider,
|
|
Models: registryModelsToPluginModels(result.Models),
|
|
Auth: result.Auth,
|
|
Handled: result.Handled,
|
|
Err: result.Err,
|
|
}
|
|
}
|
|
|
|
// ModelsForProvider returns static models registered for a provider by plugins.
|
|
func (h *Host) ModelsForProvider(provider string) []ModelInfo {
|
|
if h == nil || h.inner == nil {
|
|
return nil
|
|
}
|
|
return registryModelsToPluginModels(h.inner.ModelsForProvider(provider))
|
|
}
|
|
|
|
// RefreshAuth lets plugin auth providers refresh a credential.
|
|
func (h *Host) RefreshAuth(ctx context.Context, auth *coreauth.Auth) (*coreauth.Auth, bool, error) {
|
|
if h == nil || h.inner == nil {
|
|
return nil, false, nil
|
|
}
|
|
return h.inner.RefreshAuth(ctx, auth)
|
|
}
|
|
|
|
// PickAuth lets a scheduler plugin choose an auth candidate.
|
|
func (h *Host) PickAuth(ctx context.Context, req pluginapi.SchedulerPickRequest) (pluginapi.SchedulerPickResponse, bool, error) {
|
|
if h == nil || h.inner == nil {
|
|
return pluginapi.SchedulerPickResponse{}, false, nil
|
|
}
|
|
return h.inner.PickAuth(ctx, req)
|
|
}
|
|
|
|
// HasScheduler reports whether any active plugin provides a scheduler.
|
|
func (h *Host) HasScheduler() bool {
|
|
return h != nil && h.inner != nil && h.inner.HasScheduler()
|
|
}
|
|
|
|
func runtimeConfigToInternalConfig(cfg RuntimeConfig) *internalconfig.Config {
|
|
out := &internalconfig.Config{
|
|
SDKConfig: internalconfig.SDKConfig{
|
|
ProxyURL: cfg.ProxyURL,
|
|
ForceModelPrefix: cfg.ForceModelPrefix,
|
|
},
|
|
AuthDir: cfg.AuthDir,
|
|
OAuthExcludedModels: cloneStringSliceMap(cfg.OAuthExcludedModels),
|
|
OAuthModelAlias: oauthModelAliasToInternal(cfg.OAuthModelAlias),
|
|
Plugins: internalconfig.PluginsConfig{
|
|
Enabled: cfg.Enabled,
|
|
Dir: cfg.Dir,
|
|
Configs: pluginConfigsToInternal(cfg.Configs),
|
|
},
|
|
}
|
|
out.NormalizePluginsConfig()
|
|
out.SanitizeOAuthModelAlias()
|
|
return out
|
|
}
|
|
|
|
func pluginConfigsToInternal(in map[string]PluginInstanceConfig) map[string]internalconfig.PluginInstanceConfig {
|
|
if len(in) == 0 {
|
|
return nil
|
|
}
|
|
out := make(map[string]internalconfig.PluginInstanceConfig, len(in))
|
|
for id, item := range in {
|
|
out[id] = internalconfig.PluginInstanceConfig{
|
|
Enabled: item.Enabled,
|
|
Priority: item.Priority,
|
|
Raw: *deepCopyYAMLNode(&item.Raw),
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func oauthModelAliasToInternal(in map[string][]OAuthModelAlias) map[string][]internalconfig.OAuthModelAlias {
|
|
if len(in) == 0 {
|
|
return nil
|
|
}
|
|
out := make(map[string][]internalconfig.OAuthModelAlias, len(in))
|
|
for provider, aliases := range in {
|
|
if len(aliases) == 0 {
|
|
continue
|
|
}
|
|
items := make([]internalconfig.OAuthModelAlias, 0, len(aliases))
|
|
for _, alias := range aliases {
|
|
items = append(items, internalconfig.OAuthModelAlias{
|
|
Name: alias.Name,
|
|
Alias: alias.Alias,
|
|
Fork: alias.Fork,
|
|
})
|
|
}
|
|
out[provider] = items
|
|
}
|
|
if len(out) == 0 {
|
|
return nil
|
|
}
|
|
return out
|
|
}
|
|
|
|
func registryModelsToPluginModels(models []*internalregistry.ModelInfo) []ModelInfo {
|
|
if len(models) == 0 {
|
|
return nil
|
|
}
|
|
out := make([]ModelInfo, 0, len(models))
|
|
for _, model := range models {
|
|
if model == nil {
|
|
continue
|
|
}
|
|
out = append(out, registryModelToPluginModel(model))
|
|
}
|
|
return out
|
|
}
|
|
|
|
func registryModelToPluginModel(model *internalregistry.ModelInfo) ModelInfo {
|
|
if model == nil {
|
|
return ModelInfo{}
|
|
}
|
|
return ModelInfo{
|
|
ID: model.ID,
|
|
Object: model.Object,
|
|
Created: model.Created,
|
|
OwnedBy: model.OwnedBy,
|
|
Type: model.Type,
|
|
DisplayName: model.DisplayName,
|
|
Name: model.Name,
|
|
Version: model.Version,
|
|
Description: model.Description,
|
|
InputTokenLimit: int64(model.InputTokenLimit),
|
|
OutputTokenLimit: int64(model.OutputTokenLimit),
|
|
SupportedGenerationMethods: cloneStringSlice(model.SupportedGenerationMethods),
|
|
ContextLength: int64(model.ContextLength),
|
|
MaxCompletionTokens: int64(model.MaxCompletionTokens),
|
|
SupportedParameters: cloneStringSlice(model.SupportedParameters),
|
|
SupportedInputModalities: cloneStringSlice(model.SupportedInputModalities),
|
|
SupportedOutputModalities: cloneStringSlice(model.SupportedOutputModalities),
|
|
Thinking: thinkingSupportToPlugin(model.Thinking),
|
|
UserDefined: model.UserDefined,
|
|
}
|
|
}
|
|
|
|
func thinkingSupportToPlugin(thinking *internalregistry.ThinkingSupport) *ThinkingSupport {
|
|
if thinking == nil {
|
|
return nil
|
|
}
|
|
return &ThinkingSupport{
|
|
Min: thinking.Min,
|
|
Max: thinking.Max,
|
|
ZeroAllowed: thinking.ZeroAllowed,
|
|
DynamicAllowed: thinking.DynamicAllowed,
|
|
Levels: cloneStringSlice(thinking.Levels),
|
|
}
|
|
}
|
|
|
|
func cloneStringSlice(in []string) []string {
|
|
if len(in) == 0 {
|
|
return nil
|
|
}
|
|
return append([]string(nil), in...)
|
|
}
|
|
|
|
func cloneStringSliceMap(in map[string][]string) map[string][]string {
|
|
if len(in) == 0 {
|
|
return nil
|
|
}
|
|
out := make(map[string][]string, len(in))
|
|
for key, values := range in {
|
|
out[key] = cloneStringSlice(values)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func deepCopyYAMLNode(node *yaml.Node) *yaml.Node {
|
|
if node == nil {
|
|
return &yaml.Node{}
|
|
}
|
|
copyNode := *node
|
|
if len(node.Content) > 0 {
|
|
copyNode.Content = make([]*yaml.Node, 0, len(node.Content))
|
|
for _, child := range node.Content {
|
|
copyNode.Content = append(copyNode.Content, deepCopyYAMLNode(child))
|
|
}
|
|
}
|
|
return ©Node
|
|
}
|