diff --git a/sdk/access/reconcile.go b/internal/access/reconcile.go similarity index 80% rename from sdk/access/reconcile.go rename to internal/access/reconcile.go index ca4d7679..d279dc95 100644 --- a/sdk/access/reconcile.go +++ b/internal/access/reconcile.go @@ -6,18 +6,20 @@ import ( "strings" "github.com/router-for-me/CLIProxyAPI/v6/internal/config" + "github.com/router-for-me/CLIProxyAPI/v6/sdk/access" + sdkConfig "github.com/router-for-me/CLIProxyAPI/v6/sdk/config" ) // ReconcileProviders builds the desired provider list by reusing existing providers when possible // and creating or removing providers only when their configuration changed. It returns the final // ordered provider slice along with the identifiers of providers that were added, updated, or // removed compared to the previous configuration. -func ReconcileProviders(oldCfg, newCfg *config.Config, existing []Provider) (result []Provider, added, updated, removed []string, err error) { +func ReconcileProviders(oldCfg, newCfg *config.Config, existing []access.Provider) (result []access.Provider, added, updated, removed []string, err error) { if newCfg == nil { return nil, nil, nil, nil, nil } - existingMap := make(map[string]Provider, len(existing)) + existingMap := make(map[string]access.Provider, len(existing)) for _, provider := range existing { if provider == nil { continue @@ -28,11 +30,11 @@ func ReconcileProviders(oldCfg, newCfg *config.Config, existing []Provider) (res oldCfgMap := accessProviderMap(oldCfg) newEntries := collectProviderEntries(newCfg) - result = make([]Provider, 0, len(newEntries)) + result = make([]access.Provider, 0, len(newEntries)) finalIDs := make(map[string]struct{}, len(newEntries)) isInlineProvider := func(id string) bool { - return strings.EqualFold(id, config.DefaultAccessProviderName) + return strings.EqualFold(id, sdkConfig.DefaultAccessProviderName) } appendChange := func(list *[]string, id string) { if isInlineProvider(id) { @@ -58,7 +60,7 @@ func ReconcileProviders(oldCfg, newCfg *config.Config, existing []Provider) (res } } - provider, buildErr := buildProvider(providerCfg, newCfg) + provider, buildErr := access.BuildProvider(providerCfg, &newCfg.SDKConfig) if buildErr != nil { return nil, nil, nil, nil, buildErr } @@ -76,7 +78,7 @@ func ReconcileProviders(oldCfg, newCfg *config.Config, existing []Provider) (res } if len(result) == 0 && len(newCfg.APIKeys) > 0 { - config.SyncInlineAPIKeys(newCfg, newCfg.APIKeys) + sdkConfig.SyncInlineAPIKeys(&newCfg.SDKConfig, newCfg.APIKeys) if providerCfg := newCfg.ConfigAPIKeyProvider(); providerCfg != nil { key := providerIdentifier(providerCfg) if key != "" { @@ -86,7 +88,7 @@ func ReconcileProviders(oldCfg, newCfg *config.Config, existing []Provider) (res if existingProvider, okExisting := existingMap[key]; okExisting { result = append(result, existingProvider) } else { - provider, buildErr := buildProvider(providerCfg, newCfg) + provider, buildErr := access.BuildProvider(providerCfg, &newCfg.SDKConfig) if buildErr != nil { return nil, nil, nil, nil, buildErr } @@ -98,7 +100,7 @@ func ReconcileProviders(oldCfg, newCfg *config.Config, existing []Provider) (res result = append(result, provider) } } else { - provider, buildErr := buildProvider(providerCfg, newCfg) + provider, buildErr := access.BuildProvider(providerCfg, &newCfg.SDKConfig) if buildErr != nil { return nil, nil, nil, nil, buildErr } @@ -110,7 +112,7 @@ func ReconcileProviders(oldCfg, newCfg *config.Config, existing []Provider) (res result = append(result, provider) } } else { - provider, buildErr := buildProvider(providerCfg, newCfg) + provider, buildErr := access.BuildProvider(providerCfg, &newCfg.SDKConfig) if buildErr != nil { return nil, nil, nil, nil, buildErr } @@ -144,8 +146,8 @@ func ReconcileProviders(oldCfg, newCfg *config.Config, existing []Provider) (res return result, added, updated, removed, nil } -func accessProviderMap(cfg *config.Config) map[string]*config.AccessProvider { - result := make(map[string]*config.AccessProvider) +func accessProviderMap(cfg *config.Config) map[string]*sdkConfig.AccessProvider { + result := make(map[string]*sdkConfig.AccessProvider) if cfg == nil { return result } @@ -170,8 +172,8 @@ func accessProviderMap(cfg *config.Config) map[string]*config.AccessProvider { return result } -func collectProviderEntries(cfg *config.Config) []*config.AccessProvider { - entries := make([]*config.AccessProvider, 0, len(cfg.Access.Providers)) +func collectProviderEntries(cfg *config.Config) []*sdkConfig.AccessProvider { + entries := make([]*sdkConfig.AccessProvider, 0, len(cfg.Access.Providers)) if cfg == nil { return entries } @@ -187,7 +189,7 @@ func collectProviderEntries(cfg *config.Config) []*config.AccessProvider { return entries } -func providerIdentifier(provider *config.AccessProvider) string { +func providerIdentifier(provider *sdkConfig.AccessProvider) string { if provider == nil { return "" } @@ -198,13 +200,13 @@ func providerIdentifier(provider *config.AccessProvider) string { if typ == "" { return "" } - if strings.EqualFold(typ, config.AccessProviderTypeConfigAPIKey) { - return config.DefaultAccessProviderName + if strings.EqualFold(typ, sdkConfig.AccessProviderTypeConfigAPIKey) { + return sdkConfig.DefaultAccessProviderName } return typ } -func providerConfigEqual(a, b *config.AccessProvider) bool { +func providerConfigEqual(a, b *sdkConfig.AccessProvider) bool { if a == nil || b == nil { return a == nil && b == nil } diff --git a/internal/api/handlers/management/config_lists.go b/internal/api/handlers/management/config_lists.go index f9230984..668ae24b 100644 --- a/internal/api/handlers/management/config_lists.go +++ b/internal/api/handlers/management/config_lists.go @@ -6,6 +6,7 @@ import ( "github.com/gin-gonic/gin" "github.com/router-for-me/CLIProxyAPI/v6/internal/config" + sdkConfig "github.com/router-for-me/CLIProxyAPI/v6/sdk/config" ) // Generic helpers for list[string] @@ -106,13 +107,13 @@ func (h *Handler) deleteFromStringList(c *gin.Context, target *[]string, after f // api-keys func (h *Handler) GetAPIKeys(c *gin.Context) { c.JSON(200, gin.H{"api-keys": h.cfg.APIKeys}) } func (h *Handler) PutAPIKeys(c *gin.Context) { - h.putStringList(c, func(v []string) { config.SyncInlineAPIKeys(h.cfg, v) }, nil) + h.putStringList(c, func(v []string) { sdkConfig.SyncInlineAPIKeys(&h.cfg.SDKConfig, v) }, nil) } func (h *Handler) PatchAPIKeys(c *gin.Context) { - h.patchStringList(c, &h.cfg.APIKeys, func() { config.SyncInlineAPIKeys(h.cfg, h.cfg.APIKeys) }) + h.patchStringList(c, &h.cfg.APIKeys, func() { sdkConfig.SyncInlineAPIKeys(&h.cfg.SDKConfig, h.cfg.APIKeys) }) } func (h *Handler) DeleteAPIKeys(c *gin.Context) { - h.deleteFromStringList(c, &h.cfg.APIKeys, func() { config.SyncInlineAPIKeys(h.cfg, h.cfg.APIKeys) }) + h.deleteFromStringList(c, &h.cfg.APIKeys, func() { sdkConfig.SyncInlineAPIKeys(&h.cfg.SDKConfig, h.cfg.APIKeys) }) } // generative-language-api-key diff --git a/internal/api/server.go b/internal/api/server.go index 5609c493..7f09a037 100644 --- a/internal/api/server.go +++ b/internal/api/server.go @@ -16,6 +16,7 @@ import ( "time" "github.com/gin-gonic/gin" + "github.com/router-for-me/CLIProxyAPI/v6/internal/access" managementHandlers "github.com/router-for-me/CLIProxyAPI/v6/internal/api/handlers/management" "github.com/router-for-me/CLIProxyAPI/v6/internal/api/middleware" "github.com/router-for-me/CLIProxyAPI/v6/internal/config" @@ -25,8 +26,8 @@ import ( sdkaccess "github.com/router-for-me/CLIProxyAPI/v6/sdk/access" "github.com/router-for-me/CLIProxyAPI/v6/sdk/api/handlers" "github.com/router-for-me/CLIProxyAPI/v6/sdk/api/handlers/claude" - gemini2 "github.com/router-for-me/CLIProxyAPI/v6/sdk/api/handlers/gemini" - openai2 "github.com/router-for-me/CLIProxyAPI/v6/sdk/api/handlers/openai" + "github.com/router-for-me/CLIProxyAPI/v6/sdk/api/handlers/gemini" + "github.com/router-for-me/CLIProxyAPI/v6/sdk/api/handlers/openai" "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" log "github.com/sirupsen/logrus" ) @@ -224,11 +225,11 @@ func NewServer(cfg *config.Config, authManager *auth.Manager, accessManager *sdk // setupRoutes configures the API routes for the server. // It defines the endpoints and associates them with their respective handlers. func (s *Server) setupRoutes() { - openaiHandlers := openai2.NewOpenAIAPIHandler(s.handlers) - geminiHandlers := gemini2.NewGeminiAPIHandler(s.handlers) - geminiCLIHandlers := gemini2.NewGeminiCLIAPIHandler(s.handlers) + openaiHandlers := openai.NewOpenAIAPIHandler(s.handlers) + geminiHandlers := gemini.NewGeminiAPIHandler(s.handlers) + geminiCLIHandlers := gemini.NewGeminiCLIAPIHandler(s.handlers) claudeCodeHandlers := claude.NewClaudeCodeAPIHandler(s.handlers) - openaiResponsesHandlers := openai2.NewOpenAIResponsesAPIHandler(s.handlers) + openaiResponsesHandlers := openai.NewOpenAIResponsesAPIHandler(s.handlers) // OpenAI compatible API routes v1 := s.engine.Group("/v1") @@ -469,7 +470,7 @@ func (s *Server) watchKeepAlive() { // that routes to different handlers based on the User-Agent header. // If User-Agent starts with "claude-cli", it routes to Claude handler, // otherwise it routes to OpenAI handler. -func (s *Server) unifiedModelsHandler(openaiHandler *openai2.OpenAIAPIHandler, claudeHandler *claude.ClaudeCodeAPIHandler) gin.HandlerFunc { +func (s *Server) unifiedModelsHandler(openaiHandler *openai.OpenAIAPIHandler, claudeHandler *claude.ClaudeCodeAPIHandler) gin.HandlerFunc { return func(c *gin.Context) { userAgent := c.GetHeader("User-Agent") @@ -552,7 +553,7 @@ func (s *Server) applyAccessConfig(oldCfg, newCfg *config.Config) { return } existing := s.accessManager.Providers() - providers, added, updated, removed, err := sdkaccess.ReconcileProviders(oldCfg, newCfg, existing) + providers, added, updated, removed, err := access.ReconcileProviders(oldCfg, newCfg, existing) if err != nil { log.Errorf("failed to reconcile request auth providers: %v", err) return diff --git a/internal/config/config.go b/internal/config/config.go index cd3d9c48..97f056c1 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -31,12 +31,6 @@ type Config struct { // UsageStatisticsEnabled toggles in-memory usage aggregation; when false, usage data is discarded. UsageStatisticsEnabled bool `yaml:"usage-statistics-enabled" json:"usage-statistics-enabled"` - // APIKeys is a list of keys for authenticating clients to this proxy server. - APIKeys []string `yaml:"api-keys" json:"api-keys"` - - // Access holds request authentication provider configuration. - Access AccessConfig `yaml:"auth" json:"auth"` - // QuotaExceeded defines the behavior when a quota is exceeded. QuotaExceeded QuotaExceeded `yaml:"quota-exceeded" json:"quota-exceeded"` @@ -62,38 +56,6 @@ type Config struct { GeminiWeb GeminiWebConfig `yaml:"gemini-web" json:"gemini-web"` } -// AccessConfig groups request authentication providers. -type AccessConfig struct { - // Providers lists configured authentication providers. - Providers []AccessProvider `yaml:"providers" json:"providers"` -} - -// AccessProvider describes a request authentication provider entry. -type AccessProvider struct { - // Name is the instance identifier for the provider. - Name string `yaml:"name" json:"name"` - - // Type selects the provider implementation registered via the SDK. - Type string `yaml:"type" json:"type"` - - // SDK optionally names a third-party SDK module providing this provider. - SDK string `yaml:"sdk,omitempty" json:"sdk,omitempty"` - - // APIKeys lists inline keys for providers that require them. - APIKeys []string `yaml:"api-keys,omitempty" json:"api-keys,omitempty"` - - // Config passes provider-specific options to the implementation. - Config map[string]any `yaml:"config,omitempty" json:"config,omitempty"` -} - -const ( - // AccessProviderTypeConfigAPIKey is the built-in provider validating inline API keys. - AccessProviderTypeConfigAPIKey = "config-api-key" - - // DefaultAccessProviderName is applied when no provider name is supplied. - DefaultAccessProviderName = "config-inline" -) - // GeminiWebConfig nests Gemini Web related options under 'gemini-web'. type GeminiWebConfig struct { // Context enables JSON-based conversation reuse. @@ -232,43 +194,6 @@ func LoadConfig(configFile string) (*Config, error) { return &cfg, nil } -// SyncInlineAPIKeys updates the inline API key provider and top-level APIKeys field. -func SyncInlineAPIKeys(cfg *Config, keys []string) { - if cfg == nil { - return - } - cloned := append([]string(nil), keys...) - cfg.APIKeys = cloned - if provider := cfg.ConfigAPIKeyProvider(); provider != nil { - if provider.Name == "" { - provider.Name = DefaultAccessProviderName - } - provider.APIKeys = cloned - return - } - cfg.Access.Providers = append(cfg.Access.Providers, AccessProvider{ - Name: DefaultAccessProviderName, - Type: AccessProviderTypeConfigAPIKey, - APIKeys: cloned, - }) -} - -// ConfigAPIKeyProvider returns the first inline API key provider if present. -func (c *Config) ConfigAPIKeyProvider() *AccessProvider { - if c == nil { - return nil - } - for i := range c.Access.Providers { - if c.Access.Providers[i].Type == AccessProviderTypeConfigAPIKey { - if c.Access.Providers[i].Name == "" { - c.Access.Providers[i].Name = DefaultAccessProviderName - } - return &c.Access.Providers[i] - } - } - return nil -} - func syncInlineAccessProvider(cfg *Config) { if cfg == nil { return @@ -277,9 +202,9 @@ func syncInlineAccessProvider(cfg *Config) { if len(cfg.APIKeys) == 0 { return } - cfg.Access.Providers = append(cfg.Access.Providers, AccessProvider{ - Name: DefaultAccessProviderName, - Type: AccessProviderTypeConfigAPIKey, + cfg.Access.Providers = append(cfg.Access.Providers, config.AccessProvider{ + Name: config.DefaultAccessProviderName, + Type: config.AccessProviderTypeConfigAPIKey, APIKeys: append([]string(nil), cfg.APIKeys...), }) return @@ -289,9 +214,9 @@ func syncInlineAccessProvider(cfg *Config) { if len(cfg.APIKeys) == 0 { return } - cfg.Access.Providers = append(cfg.Access.Providers, AccessProvider{ - Name: DefaultAccessProviderName, - Type: AccessProviderTypeConfigAPIKey, + cfg.Access.Providers = append(cfg.Access.Providers, config.AccessProvider{ + Name: config.DefaultAccessProviderName, + Type: config.AccessProviderTypeConfigAPIKey, APIKeys: append([]string(nil), cfg.APIKeys...), }) return diff --git a/sdk/access/providers/configapikey/provider.go b/sdk/access/providers/configapikey/provider.go index f8f9dce6..6d52a05b 100644 --- a/sdk/access/providers/configapikey/provider.go +++ b/sdk/access/providers/configapikey/provider.go @@ -5,8 +5,8 @@ import ( "net/http" "strings" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" sdkaccess "github.com/router-for-me/CLIProxyAPI/v6/sdk/access" + "github.com/router-for-me/CLIProxyAPI/v6/sdk/config" ) type provider struct { @@ -18,7 +18,7 @@ func init() { sdkaccess.RegisterProvider(config.AccessProviderTypeConfigAPIKey, newProvider) } -func newProvider(cfg *config.AccessProvider, _ *config.Config) (sdkaccess.Provider, error) { +func newProvider(cfg *config.AccessProvider, _ *config.SDKConfig) (sdkaccess.Provider, error) { name := cfg.Name if name == "" { name = config.DefaultAccessProviderName diff --git a/sdk/access/registry.go b/sdk/access/registry.go index 21a9db56..f2eb4d1c 100644 --- a/sdk/access/registry.go +++ b/sdk/access/registry.go @@ -6,7 +6,7 @@ import ( "net/http" "sync" - "github.com/router-for-me/CLIProxyAPI/v6/internal/config" + "github.com/router-for-me/CLIProxyAPI/v6/sdk/config" ) // Provider validates credentials for incoming requests. @@ -23,7 +23,7 @@ type Result struct { } // ProviderFactory builds a provider from configuration data. -type ProviderFactory func(cfg *config.AccessProvider, root *config.Config) (Provider, error) +type ProviderFactory func(cfg *config.AccessProvider, root *config.SDKConfig) (Provider, error) var ( registryMu sync.RWMutex @@ -40,7 +40,7 @@ func RegisterProvider(typ string, factory ProviderFactory) { registryMu.Unlock() } -func buildProvider(cfg *config.AccessProvider, root *config.Config) (Provider, error) { +func BuildProvider(cfg *config.AccessProvider, root *config.SDKConfig) (Provider, error) { if cfg == nil { return nil, fmt.Errorf("access: nil provider config") } @@ -58,7 +58,7 @@ func buildProvider(cfg *config.AccessProvider, root *config.Config) (Provider, e } // BuildProviders constructs providers declared in configuration. -func BuildProviders(root *config.Config) ([]Provider, error) { +func BuildProviders(root *config.SDKConfig) ([]Provider, error) { if root == nil { return nil, nil } @@ -68,7 +68,7 @@ func BuildProviders(root *config.Config) ([]Provider, error) { if providerCfg.Type == "" { continue } - provider, err := buildProvider(providerCfg, root) + provider, err := BuildProvider(providerCfg, root) if err != nil { return nil, err } @@ -77,7 +77,7 @@ func BuildProviders(root *config.Config) ([]Provider, error) { if len(providers) == 0 && len(root.APIKeys) > 0 { config.SyncInlineAPIKeys(root, root.APIKeys) if providerCfg := root.ConfigAPIKeyProvider(); providerCfg != nil { - provider, err := buildProvider(providerCfg, root) + provider, err := BuildProvider(providerCfg, root) if err != nil { return nil, err } diff --git a/sdk/cliproxy/builder.go b/sdk/cliproxy/builder.go index f0fcf4a0..5688fb20 100644 --- a/sdk/cliproxy/builder.go +++ b/sdk/cliproxy/builder.go @@ -184,7 +184,7 @@ func (b *Builder) Build() (*Service, error) { if accessManager == nil { accessManager = sdkaccess.NewManager() } - providers, err := sdkaccess.BuildProviders(b.cfg) + providers, err := sdkaccess.BuildProviders(&b.cfg.SDKConfig) if err != nil { return nil, err } diff --git a/sdk/cliproxy/service.go b/sdk/cliproxy/service.go index 904eff4c..b779f728 100644 --- a/sdk/cliproxy/service.go +++ b/sdk/cliproxy/service.go @@ -12,6 +12,7 @@ import ( "sync" "time" + "github.com/router-for-me/CLIProxyAPI/v6/internal/access" "github.com/router-for-me/CLIProxyAPI/v6/internal/api" "github.com/router-for-me/CLIProxyAPI/v6/internal/config" geminiwebclient "github.com/router-for-me/CLIProxyAPI/v6/internal/provider/gemini-web" @@ -115,7 +116,7 @@ func (s *Service) refreshAccessProviders(cfg *config.Config) { s.cfgMu.RUnlock() existing := s.accessManager.Providers() - providers, added, updated, removed, err := sdkaccess.ReconcileProviders(oldCfg, cfg, existing) + providers, added, updated, removed, err := access.ReconcileProviders(oldCfg, cfg, existing) if err != nil { log.Errorf("failed to reconcile request auth providers: %v", err) return diff --git a/sdk/config/config.go b/sdk/config/config.go index 178e7be2..6170afb3 100644 --- a/sdk/config/config.go +++ b/sdk/config/config.go @@ -11,4 +11,79 @@ type SDKConfig struct { // RequestLog enables or disables detailed request logging functionality. RequestLog bool `yaml:"request-log" json:"request-log"` + + // APIKeys is a list of keys for authenticating clients to this proxy server. + APIKeys []string `yaml:"api-keys" json:"api-keys"` + + // Access holds request authentication provider configuration. + Access AccessConfig `yaml:"auth" json:"auth"` +} + +// AccessConfig groups request authentication providers. +type AccessConfig struct { + // Providers lists configured authentication providers. + Providers []AccessProvider `yaml:"providers" json:"providers"` +} + +// AccessProvider describes a request authentication provider entry. +type AccessProvider struct { + // Name is the instance identifier for the provider. + Name string `yaml:"name" json:"name"` + + // Type selects the provider implementation registered via the SDK. + Type string `yaml:"type" json:"type"` + + // SDK optionally names a third-party SDK module providing this provider. + SDK string `yaml:"sdk,omitempty" json:"sdk,omitempty"` + + // APIKeys lists inline keys for providers that require them. + APIKeys []string `yaml:"api-keys,omitempty" json:"api-keys,omitempty"` + + // Config passes provider-specific options to the implementation. + Config map[string]any `yaml:"config,omitempty" json:"config,omitempty"` +} + +const ( + // AccessProviderTypeConfigAPIKey is the built-in provider validating inline API keys. + AccessProviderTypeConfigAPIKey = "config-api-key" + + // DefaultAccessProviderName is applied when no provider name is supplied. + DefaultAccessProviderName = "config-inline" +) + +// SyncInlineAPIKeys updates the inline API key provider and top-level APIKeys field. +func SyncInlineAPIKeys(cfg *SDKConfig, keys []string) { + if cfg == nil { + return + } + cloned := append([]string(nil), keys...) + cfg.APIKeys = cloned + if provider := cfg.ConfigAPIKeyProvider(); provider != nil { + if provider.Name == "" { + provider.Name = DefaultAccessProviderName + } + provider.APIKeys = cloned + return + } + cfg.Access.Providers = append(cfg.Access.Providers, AccessProvider{ + Name: DefaultAccessProviderName, + Type: AccessProviderTypeConfigAPIKey, + APIKeys: cloned, + }) +} + +// ConfigAPIKeyProvider returns the first inline API key provider if present. +func (c *SDKConfig) ConfigAPIKeyProvider() *AccessProvider { + if c == nil { + return nil + } + for i := range c.Access.Providers { + if c.Access.Providers[i].Type == AccessProviderTypeConfigAPIKey { + if c.Access.Providers[i].Name == "" { + c.Access.Providers[i].Name = DefaultAccessProviderName + } + return &c.Access.Providers[i] + } + } + return nil }