Add projects
This commit is contained in:
parent
2d3a9ad623
commit
8b607dd700
1802 changed files with 503346 additions and 2 deletions
352
backend/sdk/pluginhost/host.go
Normal file
352
backend/sdk/pluginhost/host.go
Normal file
|
|
@ -0,0 +1,352 @@
|
|||
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
|
||||
}
|
||||
|
||||
// RegisteredPluginInfo describes a plugin active in the current host snapshot.
|
||||
type RegisteredPluginInfo = internalpluginhost.RegisteredPluginInfo
|
||||
|
||||
// RegisteredPluginMenu describes a plugin-owned resource menu entry.
|
||||
type RegisteredPluginMenu = internalpluginhost.RegisteredPluginMenu
|
||||
|
||||
// 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() {
|
||||
h.ShutdownAllContext(context.Background())
|
||||
}
|
||||
|
||||
// ShutdownAllContext detaches every active plugin and bounds waiting for active calls by ctx.
|
||||
func (h *Host) ShutdownAllContext(ctx context.Context) {
|
||||
if h == nil || h.inner == nil {
|
||||
return
|
||||
}
|
||||
h.inner.ShutdownAllContext(ctx)
|
||||
}
|
||||
|
||||
// PluginBusy reports whether a plugin dynamic library is loaded or being loaded.
|
||||
func (h *Host) PluginBusy(id string) bool {
|
||||
return h != nil && h.inner != nil && h.inner.PluginBusy(id)
|
||||
}
|
||||
|
||||
// UnloadPlugin removes one plugin from the active runtime and closes its dynamic library.
|
||||
func (h *Host) UnloadPlugin(id string) bool {
|
||||
return h.UnloadPluginContext(context.Background(), id)
|
||||
}
|
||||
|
||||
// UnloadPluginContext detaches one plugin and bounds waiting for active calls by ctx.
|
||||
func (h *Host) UnloadPluginContext(ctx context.Context, id string) bool {
|
||||
if h == nil || h.inner == nil {
|
||||
return false
|
||||
}
|
||||
return h.inner.UnloadPluginContext(ctx, id)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// ParseAuths lets plugin auth providers expand one credential payload into multiple auth records.
|
||||
func (h *Host) ParseAuths(ctx context.Context, req pluginapi.AuthParseRequest) ([]*coreauth.Auth, bool, error) {
|
||||
if h == nil || h.inner == nil {
|
||||
return nil, false, nil
|
||||
}
|
||||
return h.inner.ParseAuths(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)
|
||||
}
|
||||
|
||||
// HasAuthProvider reports whether an active plugin handles provider auth for provider.
|
||||
func (h *Host) HasAuthProvider(provider string) bool {
|
||||
return h != nil && h.inner != nil && h.inner.HasAuthProvider(provider)
|
||||
}
|
||||
|
||||
// StartLogin starts a provider login flow through an active auth-provider plugin.
|
||||
func (h *Host) StartLogin(ctx context.Context, provider string, baseURL string) (pluginapi.AuthLoginStartResponse, bool, error) {
|
||||
if h == nil || h.inner == nil {
|
||||
return pluginapi.AuthLoginStartResponse{}, false, nil
|
||||
}
|
||||
return h.inner.StartLogin(ctx, provider, baseURL)
|
||||
}
|
||||
|
||||
// PollLogin polls a provider login flow through an active auth-provider plugin.
|
||||
func (h *Host) PollLogin(ctx context.Context, provider, state string, metadata ...map[string]any) (pluginapi.AuthLoginPollResponse, bool, error) {
|
||||
if h == nil || h.inner == nil {
|
||||
return pluginapi.AuthLoginPollResponse{}, false, nil
|
||||
}
|
||||
return h.inner.PollLogin(ctx, provider, state, metadata...)
|
||||
}
|
||||
|
||||
// AuthDataToCoreAuth converts plugin auth data into a host auth record.
|
||||
func (h *Host) AuthDataToCoreAuth(data pluginapi.AuthData, path, fileName string) *coreauth.Auth {
|
||||
if h == nil || h.inner == nil {
|
||||
return nil
|
||||
}
|
||||
return h.inner.AuthDataToCoreAuth(data, path, fileName)
|
||||
}
|
||||
|
||||
// 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()
|
||||
}
|
||||
|
||||
// RegisteredPlugins returns active plugin metadata from the current runtime snapshot.
|
||||
func (h *Host) RegisteredPlugins() []RegisteredPluginInfo {
|
||||
if h == nil || h.inner == nil {
|
||||
return nil
|
||||
}
|
||||
return h.inner.RegisteredPlugins()
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
Loading…
Reference in a new issue