Something went wrong. Try again.
sloproxy
Something went wrong. Try again.
10 kB · 317 lines
Go
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318// Package cliproxy provides the core service implementation for the CLI Proxy API.// It includes service lifecycle management, authentication handling, file watching,// and integration with various AI service providers through a unified interface.package cliproxy
import ( "context" "fmt"
configaccess "github.com/router-for-me/CLIProxyAPI/v7/internal/access/config_access" "github.com/router-for-me/CLIProxyAPI/v7/internal/api" "github.com/router-for-me/CLIProxyAPI/v7/internal/pluginhost" "github.com/router-for-me/CLIProxyAPI/v7/internal/watcher" sdkaccess "github.com/router-for-me/CLIProxyAPI/v7/sdk/access" sdkAuth "github.com/router-for-me/CLIProxyAPI/v7/sdk/auth" coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth" "github.com/router-for-me/CLIProxyAPI/v7/sdk/config")
// Builder constructs a Service instance with customizable providers.// It provides a fluent interface for configuring all aspects of the service// including authentication, file watching, HTTP server options, and lifecycle hooks.type Builder struct { // cfg holds the application configuration. cfg *config.Config
// configPath is the path to the configuration file. configPath string
// tokenProvider handles loading token-based clients. tokenProvider TokenClientProvider
// apiKeyProvider handles loading API key-based clients. apiKeyProvider APIKeyClientProvider
// watcherFactory creates file watcher instances. watcherFactory WatcherFactory
// hooks provides lifecycle callbacks. hooks Hooks
// authManager handles legacy authentication operations. authManager *sdkAuth.Manager
// accessManager handles request authentication providers. accessManager *sdkaccess.Manager
// coreManager handles core authentication and execution. coreManager *coreauth.Manager
// cooldownStateStore overrides runtime cooldown persistence. cooldownStateStore coreauth.CooldownStateStore
// pluginHost owns dynamic plugin lifecycle and adapters. pluginHost *pluginhost.Host
// postAuthHook is called after auth record creation and before persistence. postAuthHook coreauth.PostAuthHook
// serverOptions contains additional server configuration options. serverOptions []api.ServerOption}
// Hooks allows callers to plug into service lifecycle stages.// These callbacks provide opportunities to perform custom initialization// and cleanup operations during service startup and shutdown.type Hooks struct { // OnBeforeStart is called before the service starts, allowing configuration // modifications or additional setup. OnBeforeStart func(*config.Config)
// OnAfterStart is called after the service has started successfully, // providing access to the service instance for additional operations. OnAfterStart func(*Service)}
// NewBuilder creates a Builder with default dependencies left unset.// Use the fluent interface methods to configure the service before calling Build().//// Returns:// - *Builder: A new builder instance ready for configurationfunc NewBuilder() *Builder { return &Builder{}}
// WithConfig sets the configuration instance used by the service.//// Parameters:// - cfg: The application configuration//// Returns:// - *Builder: The builder instance for method chainingfunc (b *Builder) WithConfig(cfg *config.Config) *Builder { b.cfg = cfg return b}
// WithConfigPath sets the absolute configuration file path used for reload watching.//// Parameters:// - path: The absolute path to the configuration file//// Returns:// - *Builder: The builder instance for method chainingfunc (b *Builder) WithConfigPath(path string) *Builder { b.configPath = path return b}
// WithTokenClientProvider overrides the provider responsible for token-backed clients.func (b *Builder) WithTokenClientProvider(provider TokenClientProvider) *Builder { b.tokenProvider = provider return b}
// WithAPIKeyClientProvider overrides the provider responsible for API key-backed clients.func (b *Builder) WithAPIKeyClientProvider(provider APIKeyClientProvider) *Builder { b.apiKeyProvider = provider return b}
// WithWatcherFactory allows customizing the watcher factory that handles reloads.func (b *Builder) WithWatcherFactory(factory WatcherFactory) *Builder { b.watcherFactory = factory return b}
// WithHooks registers lifecycle hooks executed around service startup.func (b *Builder) WithHooks(h Hooks) *Builder { b.hooks = h return b}
// WithAuthManager overrides the authentication manager used for token lifecycle operations.func (b *Builder) WithAuthManager(mgr *sdkAuth.Manager) *Builder { b.authManager = mgr return b}
// WithRequestAccessManager overrides the request authentication manager.func (b *Builder) WithRequestAccessManager(mgr *sdkaccess.Manager) *Builder { b.accessManager = mgr return b}
// WithCoreAuthManager overrides the runtime auth manager responsible for request execution.func (b *Builder) WithCoreAuthManager(mgr *coreauth.Manager) *Builder { b.coreManager = mgr return b}
// WithCooldownStateStore overrides the store used for runtime cooldown persistence.func (b *Builder) WithCooldownStateStore(store coreauth.CooldownStateStore) *Builder { b.cooldownStateStore = store return b}
// WithPluginHost overrides the dynamic plugin host used by the service.func (b *Builder) WithPluginHost(host *pluginhost.Host) *Builder { b.pluginHost = host return b}
// WithServerOptions appends server configuration options used during construction.func (b *Builder) WithServerOptions(opts ...api.ServerOption) *Builder { b.serverOptions = append(b.serverOptions, opts...) return b}
// WithLocalManagementPassword configures a password that is only accepted from localhost management requests.func (b *Builder) WithLocalManagementPassword(password string) *Builder { if password == "" { return b } b.serverOptions = append(b.serverOptions, api.WithLocalManagementPassword(password)) return b}
// WithPostAuthHook registers a hook to be called after an Auth record is created// but before it is persisted to storage.func (b *Builder) WithPostAuthHook(hook coreauth.PostAuthHook) *Builder { if hook == nil { return b } b.postAuthHook = hook return b}
// Build validates inputs, applies defaults, and returns a ready-to-run service.func (b *Builder) Build() (*Service, error) { if b.cfg == nil { return nil, fmt.Errorf("cliproxy: configuration is required") } if b.configPath == "" { return nil, fmt.Errorf("cliproxy: configuration path is required") } if errValidate := b.cfg.ValidateCredentialWeights(); errValidate != nil { return nil, fmt.Errorf("cliproxy: validate credential weights: %w", errValidate) } b.cfg.NormalizePluginsConfig() if errResolvePluginsDir := b.cfg.ResolvePluginsDir(); errResolvePluginsDir != nil && b.cfg.Plugins.Enabled { return nil, fmt.Errorf("cliproxy: %w", errResolvePluginsDir) }
tokenProvider := b.tokenProvider if tokenProvider == nil { tokenProvider = NewFileTokenClientProvider() }
apiKeyProvider := b.apiKeyProvider if apiKeyProvider == nil { apiKeyProvider = NewAPIKeyClientProvider() }
watcherFactory := b.watcherFactory if watcherFactory == nil { watcherFactory = defaultWatcherFactory }
authManager := b.authManager if authManager == nil { authManager = newDefaultAuthManager() }
accessManager := b.accessManager if accessManager == nil { accessManager = sdkaccess.NewManager() }
configaccess.Register(&b.cfg.SDKConfig) pluginHost := b.pluginHost if pluginHost == nil { pluginHost = pluginhost.New() } if b.cfg != nil { pluginHost.ApplyConfig(context.Background(), b.cfg) pluginHost.RegisterFrontendAuthProviders() } accessManager.SetProviders(sdkaccess.RegisteredProviders())
coreManager := b.coreManager cooldownStateStore := b.cooldownStateStore var appliedRoutingState *routingRuntimeState if coreManager == nil { tokenStore := sdkAuth.GetTokenStore() if dirSetter, ok := tokenStore.(interface{ SetBaseDir(string) }); ok && b.cfg != nil { dirSetter.SetBaseDir(b.cfg.AuthDir) } if cooldownStateStore == nil { if provider, ok := tokenStore.(coreauth.CooldownStateStoreProvider); ok { cooldownStateStore = provider.CooldownStateStore() } }
routingState := normalizedRoutingRuntimeState(b.cfg) coreManager = coreauth.NewManager(tokenStore, newRoutingSelector(routingState), nil) appliedRoutingState = &routingState } // Attach a default RoundTripper provider so providers can opt-in per-auth transports. coreManager.SetRoundTripperProvider(newDefaultRoundTripperProvider()) coreManager.SetConfig(b.cfg) coreManager.SetOAuthModelAlias(b.cfg.OAuthModelAlias) if pluginHost != nil { coreManager.SetPluginScheduler(pluginHost) }
service := &Service{ cfg: b.cfg, configPath: b.configPath, tokenProvider: tokenProvider, apiKeyProvider: apiKeyProvider, watcherFactory: watcherFactory, hooks: b.hooks, authManager: authManager, accessManager: accessManager, coreManager: coreManager, cooldownStateStore: cooldownStateStore, pluginHost: pluginHost, appliedRoutingState: appliedRoutingState, serverOptions: append([]api.ServerOption(nil), b.serverOptions...), } if b.postAuthHook != nil { service.serverOptions = append(service.serverOptions, api.WithPostAuthHook(b.postAuthHook)) } service.serverOptions = append(service.serverOptions, api.WithPostAuthPersistHook(service.runtimeAuthSyncHook()), api.WithPluginHost(pluginHost), api.WithConfigReloadHook(func(_ context.Context, _ *config.Config) { service.reloadConfigFromWatcher() }), ) return service, nil}
func (s *Service) runtimeAuthSyncHook() coreauth.PostAuthHook { return func(ctx context.Context, auth *coreauth.Auth) error { if s == nil || auth == nil || auth.ID == "" { return nil } action := watcher.AuthUpdateActionAdd if s.coreManager != nil { if _, ok := s.coreManager.GetByID(auth.ID); ok { action = watcher.AuthUpdateActionModify } } update := watcher.AuthUpdate{ Action: action, ID: auth.ID, Auth: auth, } if s.watcher != nil && s.watcher.DispatchPersistedAuthUpdate(update) { return nil } s.handleAuthUpdate(coreauth.WithSkipPersist(ctx), update) return nil }}