156 lines
5.2 KiB
Go
156 lines
5.2 KiB
Go
package pluginhost
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"strings"
|
|
|
|
"github.com/router-for-me/CLIProxyAPI/v7/sdk/pluginapi"
|
|
log "github.com/sirupsen/logrus"
|
|
)
|
|
|
|
func (h *Host) RouteModel(ctx context.Context, req pluginapi.ModelRouteRequest) (pluginapi.ModelRouteResponse, bool) {
|
|
return h.RouteModelExcept(ctx, req, "")
|
|
}
|
|
|
|
func (h *Host) HasModelRouters() bool {
|
|
return h.HasModelRoutersExcept("")
|
|
}
|
|
|
|
func (h *Host) HasModelRoutersExcept(skipPluginID string) bool {
|
|
if h == nil {
|
|
return false
|
|
}
|
|
skipPluginID = strings.TrimSpace(skipPluginID)
|
|
for _, record := range h.activeRecords() {
|
|
if record.plugin.Capabilities.ModelRouter != nil && !h.isPluginFused(record.id) && record.id != skipPluginID {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (h *Host) RouteModelExcept(ctx context.Context, req pluginapi.ModelRouteRequest, skipPluginID string) (pluginapi.ModelRouteResponse, bool) {
|
|
if h == nil {
|
|
return pluginapi.ModelRouteResponse{}, false
|
|
}
|
|
skipPluginID = strings.TrimSpace(skipPluginID)
|
|
req.AvailableProviders = h.availableProvidersSnapshot()
|
|
for _, record := range h.activeRecords() {
|
|
router := record.plugin.Capabilities.ModelRouter
|
|
if router == nil || h.isPluginFused(record.id) || record.id == skipPluginID {
|
|
continue
|
|
}
|
|
nextReq := cloneModelRouteRequest(req)
|
|
nextReq.Plugin = clonePluginMetadata(record.meta)
|
|
nextReq.PluginID = record.id
|
|
resp, ok := h.callModelRouter(ctx, record.id, router, nextReq)
|
|
if !ok || !resp.Handled {
|
|
continue
|
|
}
|
|
resp, valid := normalizeModelRouteResponse(record.id, resp)
|
|
if !valid {
|
|
log.WithFields(log.Fields{"plugin_id": record.id, "target_kind": resp.TargetKind, "target": resp.Target}).Warn("pluginhost: model router returned invalid target")
|
|
continue
|
|
}
|
|
switch resp.TargetKind {
|
|
case pluginapi.ModelRouteTargetProvider:
|
|
if !h.HasBuiltinProvider(resp.Target) {
|
|
log.WithFields(log.Fields{"plugin_id": record.id, "target_provider": resp.Target}).Warn("pluginhost: model router returned unavailable provider")
|
|
continue
|
|
}
|
|
return resp, true
|
|
case pluginapi.ModelRouteTargetSelf, pluginapi.ModelRouteTargetExecutor:
|
|
if !h.executorPluginReady(resp.Target, nextReq) {
|
|
log.WithFields(log.Fields{"plugin_id": record.id, "target_plugin_id": resp.Target}).Warn("pluginhost: model router returned unavailable executor plugin")
|
|
continue
|
|
}
|
|
return resp, true
|
|
default:
|
|
log.WithFields(log.Fields{"plugin_id": record.id, "target_kind": resp.TargetKind}).Warn("pluginhost: model router returned unsupported target kind")
|
|
continue
|
|
}
|
|
}
|
|
return pluginapi.ModelRouteResponse{}, false
|
|
}
|
|
|
|
func (h *Host) callModelRouter(ctx context.Context, pluginID string, router pluginapi.ModelRouter, req pluginapi.ModelRouteRequest) (out pluginapi.ModelRouteResponse, ok bool) {
|
|
if h == nil || router == nil || h.isPluginFused(pluginID) {
|
|
return pluginapi.ModelRouteResponse{}, false
|
|
}
|
|
defer func() {
|
|
if recovered := recover(); recovered != nil {
|
|
h.fusePlugin(pluginID, "ModelRouter.RouteModel", recovered)
|
|
out = pluginapi.ModelRouteResponse{}
|
|
ok = false
|
|
}
|
|
}()
|
|
resp, errRoute := router.RouteModel(ctx, req)
|
|
if errRoute != nil {
|
|
log.WithField("plugin_id", pluginID).WithError(errRoute).Warn("pluginhost: model router failed")
|
|
return pluginapi.ModelRouteResponse{}, false
|
|
}
|
|
return resp, true
|
|
}
|
|
|
|
func normalizeModelRouteResponse(routerPluginID string, resp pluginapi.ModelRouteResponse) (pluginapi.ModelRouteResponse, bool) {
|
|
resp.TargetModel = strings.TrimSpace(resp.TargetModel)
|
|
switch resp.TargetKind {
|
|
case pluginapi.ModelRouteTargetSelf:
|
|
resp.Target = strings.TrimSpace(routerPluginID)
|
|
if resp.Target == "" {
|
|
return pluginapi.ModelRouteResponse{}, false
|
|
}
|
|
return resp, true
|
|
case pluginapi.ModelRouteTargetExecutor:
|
|
resp.Target = strings.TrimSpace(resp.Target)
|
|
if resp.Target == "" {
|
|
return pluginapi.ModelRouteResponse{}, false
|
|
}
|
|
return resp, true
|
|
case pluginapi.ModelRouteTargetProvider:
|
|
resp.Target = strings.ToLower(strings.TrimSpace(resp.Target))
|
|
if resp.Target == "" {
|
|
return pluginapi.ModelRouteResponse{}, false
|
|
}
|
|
return resp, true
|
|
default:
|
|
return pluginapi.ModelRouteResponse{}, false
|
|
}
|
|
}
|
|
|
|
func cloneModelRouteRequest(req pluginapi.ModelRouteRequest) pluginapi.ModelRouteRequest {
|
|
req.Headers = cloneHeader(req.Headers)
|
|
req.Query = cloneValues(req.Query)
|
|
req.Body = bytes.Clone(req.Body)
|
|
req.Metadata = cloneInterceptorMetadata(req.Metadata)
|
|
req.AvailableProviders = cloneStringSlice(req.AvailableProviders)
|
|
return req
|
|
}
|
|
|
|
// HasBuiltinProvider reports whether a built-in provider currently has at least one
|
|
// registered auth record.
|
|
func (h *Host) HasBuiltinProvider(provider string) bool {
|
|
if h == nil || h.authManager == nil {
|
|
return false
|
|
}
|
|
return h.authManager.HasProviderAuth(provider)
|
|
}
|
|
|
|
// BuiltinProviders returns built-in provider keys that currently have auth registered.
|
|
func (h *Host) BuiltinProviders() []string {
|
|
if h == nil || h.authManager == nil {
|
|
return nil
|
|
}
|
|
return h.authManager.AvailableProviders()
|
|
}
|
|
|
|
// availableProvidersSnapshot returns a defensive copy of BuiltinProviders for routing input.
|
|
func (h *Host) availableProvidersSnapshot() []string {
|
|
providers := h.BuiltinProviders()
|
|
if len(providers) == 0 {
|
|
return nil
|
|
}
|
|
return cloneStringSlice(providers)
|
|
}
|