Files
CLIProxyAPI/internal/routing/providers/apikey.go
이대희 89907231c1 feat(routing): implement unified model routing with OAuth and API key providers
- Added a new routing package to manage provider registration and model resolution.
- Introduced Router, Executor, and Provider interfaces to handle different provider types.
- Implemented OAuthProvider and APIKeyProvider to support OAuth and API key authentication.
- Enhanced DefaultModelMapper to include OAuth model alias handling and fallback mechanisms.
- Updated context management in API handlers to preserve fallback models.
- Added tests for routing logic and provider selection.
- Enhanced Claude request conversion to handle reasoning content based on thinking mode.
2026-01-31 13:55:43 +08:00

157 lines
4.2 KiB
Go

package providers
import (
"context"
"errors"
"net/http"
"strings"
"sync"
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
"github.com/router-for-me/CLIProxyAPI/v6/internal/routing"
"github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor"
)
// APIKeyProvider wraps API key configs as routing.Provider.
type APIKeyProvider struct {
name string
provider string // claude, gemini, codex, vertex
keys []APIKeyEntry
mu sync.RWMutex
client HTTPClient
}
// APIKeyEntry represents a single API key configuration.
type APIKeyEntry struct {
APIKey string
BaseURL string
Models []config.ClaudeModel // Using ClaudeModel as generic model alias
}
// HTTPClient interface for making HTTP requests.
type HTTPClient interface {
Do(req *http.Request) (*http.Response, error)
}
// NewAPIKeyProvider creates a new API key provider.
func NewAPIKeyProvider(name, provider string, client HTTPClient) *APIKeyProvider {
return &APIKeyProvider{
name: name,
provider: provider,
keys: make([]APIKeyEntry, 0),
client: client,
}
}
// Name returns the provider name.
func (p *APIKeyProvider) Name() string {
return p.name
}
// Type returns ProviderTypeAPIKey.
func (p *APIKeyProvider) Type() routing.ProviderType {
return routing.ProviderTypeAPIKey
}
// SupportsModel checks if the model is supported by this provider.
func (p *APIKeyProvider) SupportsModel(model string) bool {
p.mu.RLock()
defer p.mu.RUnlock()
for _, key := range p.keys {
for _, m := range key.Models {
if strings.EqualFold(m.Alias, model) || strings.EqualFold(m.Name, model) {
return true
}
}
}
return false
}
// Available always returns true for API keys (unless explicitly disabled).
func (p *APIKeyProvider) Available(model string) bool {
return p.SupportsModel(model)
}
// Priority returns the priority (API key is lower priority than OAuth).
func (p *APIKeyProvider) Priority() int {
return 20
}
// Execute sends the request using the API key.
func (p *APIKeyProvider) Execute(ctx context.Context, model string, req executor.Request) (executor.Response, error) {
key := p.selectKey(model)
if key == nil {
return executor.Response{}, ErrNoMatchingAPIKey
}
// Resolve the actual model name from alias
actualModel := p.resolveModel(key, model)
// Execute via HTTP client
return p.executeHTTP(ctx, key, actualModel, req)
}
// ExecuteStream sends a streaming request.
func (p *APIKeyProvider) ExecuteStream(ctx context.Context, model string, req executor.Request) (
<-chan executor.StreamChunk, error) {
key := p.selectKey(model)
if key == nil {
return nil, ErrNoMatchingAPIKey
}
actualModel := p.resolveModel(key, model)
return p.executeHTTPStream(ctx, key, actualModel, req)
}
// AddKey adds an API key entry.
func (p *APIKeyProvider) AddKey(entry APIKeyEntry) {
p.mu.Lock()
defer p.mu.Unlock()
p.keys = append(p.keys, entry)
}
// selectKey selects a key that supports the model.
func (p *APIKeyProvider) selectKey(model string) *APIKeyEntry {
p.mu.RLock()
defer p.mu.RUnlock()
for _, key := range p.keys {
for _, m := range key.Models {
if strings.EqualFold(m.Alias, model) || strings.EqualFold(m.Name, model) {
return &key
}
}
}
return nil
}
// resolveModel resolves alias to actual model name.
func (p *APIKeyProvider) resolveModel(key *APIKeyEntry, requested string) string {
for _, m := range key.Models {
if strings.EqualFold(m.Alias, requested) {
return m.Name
}
}
return requested
}
// executeHTTP makes the HTTP request.
func (p *APIKeyProvider) executeHTTP(ctx context.Context, key *APIKeyEntry, model string, req executor.Request) (executor.Response, error) {
// TODO: implement actual HTTP execution
// This is a placeholder - actual implementation would build HTTP request
return executor.Response{}, errors.New("not yet implemented")
}
// executeHTTPStream makes a streaming HTTP request.
func (p *APIKeyProvider) executeHTTPStream(ctx context.Context, key *APIKeyEntry, model string, req executor.Request) (
<-chan executor.StreamChunk, error) {
// TODO: implement actual HTTP streaming
return nil, errors.New("not yet implemented")
}
// Errors
var (
ErrNoMatchingAPIKey = errors.New("no API key supports the requested model")
)