mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-02-19 04:40:52 +08:00
feat(auth): improve OpenAI compatibility normalization and API key handling
- Refined trimming and normalization logic for `baseURL` and `apiKey` attributes. - Updated `Authorization` header logic to omit empty API keys. - Enhanced compatibility processing by handling empty `api-key-entries`. - Improved legacy format fallback and added safeguards for empty credentials across executor paths.
This commit is contained in:
@@ -40,8 +40,8 @@ func (e *OpenAICompatExecutor) PrepareRequest(_ *http.Request, _ *cliproxyauth.A
|
|||||||
|
|
||||||
func (e *OpenAICompatExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, req cliproxyexecutor.Request, opts cliproxyexecutor.Options) (cliproxyexecutor.Response, error) {
|
func (e *OpenAICompatExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, req cliproxyexecutor.Request, opts cliproxyexecutor.Options) (cliproxyexecutor.Response, error) {
|
||||||
baseURL, apiKey := e.resolveCredentials(auth)
|
baseURL, apiKey := e.resolveCredentials(auth)
|
||||||
if baseURL == "" || apiKey == "" {
|
if baseURL == "" {
|
||||||
return cliproxyexecutor.Response{}, statusErr{code: http.StatusUnauthorized, msg: "missing provider baseURL or apiKey"}
|
return cliproxyexecutor.Response{}, statusErr{code: http.StatusUnauthorized, msg: "missing provider baseURL"}
|
||||||
}
|
}
|
||||||
reporter := newUsageReporter(ctx, e.Identifier(), req.Model, auth)
|
reporter := newUsageReporter(ctx, e.Identifier(), req.Model, auth)
|
||||||
|
|
||||||
@@ -60,7 +60,9 @@ func (e *OpenAICompatExecutor) Execute(ctx context.Context, auth *cliproxyauth.A
|
|||||||
return cliproxyexecutor.Response{}, err
|
return cliproxyexecutor.Response{}, err
|
||||||
}
|
}
|
||||||
httpReq.Header.Set("Content-Type", "application/json")
|
httpReq.Header.Set("Content-Type", "application/json")
|
||||||
httpReq.Header.Set("Authorization", "Bearer "+apiKey)
|
if apiKey != "" {
|
||||||
|
httpReq.Header.Set("Authorization", "Bearer "+apiKey)
|
||||||
|
}
|
||||||
httpReq.Header.Set("User-Agent", "cli-proxy-openai-compat")
|
httpReq.Header.Set("User-Agent", "cli-proxy-openai-compat")
|
||||||
|
|
||||||
httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, 0)
|
httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, 0)
|
||||||
@@ -89,8 +91,8 @@ func (e *OpenAICompatExecutor) Execute(ctx context.Context, auth *cliproxyauth.A
|
|||||||
|
|
||||||
func (e *OpenAICompatExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.Auth, req cliproxyexecutor.Request, opts cliproxyexecutor.Options) (<-chan cliproxyexecutor.StreamChunk, error) {
|
func (e *OpenAICompatExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.Auth, req cliproxyexecutor.Request, opts cliproxyexecutor.Options) (<-chan cliproxyexecutor.StreamChunk, error) {
|
||||||
baseURL, apiKey := e.resolveCredentials(auth)
|
baseURL, apiKey := e.resolveCredentials(auth)
|
||||||
if baseURL == "" || apiKey == "" {
|
if baseURL == "" {
|
||||||
return nil, statusErr{code: http.StatusUnauthorized, msg: "missing provider baseURL or apiKey"}
|
return nil, statusErr{code: http.StatusUnauthorized, msg: "missing provider baseURL"}
|
||||||
}
|
}
|
||||||
reporter := newUsageReporter(ctx, e.Identifier(), req.Model, auth)
|
reporter := newUsageReporter(ctx, e.Identifier(), req.Model, auth)
|
||||||
from := opts.SourceFormat
|
from := opts.SourceFormat
|
||||||
@@ -107,7 +109,9 @@ func (e *OpenAICompatExecutor) ExecuteStream(ctx context.Context, auth *cliproxy
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
httpReq.Header.Set("Content-Type", "application/json")
|
httpReq.Header.Set("Content-Type", "application/json")
|
||||||
httpReq.Header.Set("Authorization", "Bearer "+apiKey)
|
if apiKey != "" {
|
||||||
|
httpReq.Header.Set("Authorization", "Bearer "+apiKey)
|
||||||
|
}
|
||||||
httpReq.Header.Set("User-Agent", "cli-proxy-openai-compat")
|
httpReq.Header.Set("User-Agent", "cli-proxy-openai-compat")
|
||||||
httpReq.Header.Set("Accept", "text/event-stream")
|
httpReq.Header.Set("Accept", "text/event-stream")
|
||||||
httpReq.Header.Set("Cache-Control", "no-cache")
|
httpReq.Header.Set("Cache-Control", "no-cache")
|
||||||
@@ -171,8 +175,8 @@ func (e *OpenAICompatExecutor) resolveCredentials(auth *cliproxyauth.Auth) (base
|
|||||||
return "", ""
|
return "", ""
|
||||||
}
|
}
|
||||||
if auth.Attributes != nil {
|
if auth.Attributes != nil {
|
||||||
baseURL = auth.Attributes["base_url"]
|
baseURL = strings.TrimSpace(auth.Attributes["base_url"])
|
||||||
apiKey = auth.Attributes["api_key"]
|
apiKey = strings.TrimSpace(auth.Attributes["api_key"])
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -799,23 +799,23 @@ func (w *Watcher) SnapshotCoreAuths() []*coreauth.Auth {
|
|||||||
base := strings.TrimSpace(compat.BaseURL)
|
base := strings.TrimSpace(compat.BaseURL)
|
||||||
|
|
||||||
// Handle new APIKeyEntries format (preferred)
|
// Handle new APIKeyEntries format (preferred)
|
||||||
|
createdEntries := 0
|
||||||
if len(compat.APIKeyEntries) > 0 {
|
if len(compat.APIKeyEntries) > 0 {
|
||||||
for j := range compat.APIKeyEntries {
|
for j := range compat.APIKeyEntries {
|
||||||
entry := &compat.APIKeyEntries[j]
|
entry := &compat.APIKeyEntries[j]
|
||||||
key := strings.TrimSpace(entry.APIKey)
|
key := strings.TrimSpace(entry.APIKey)
|
||||||
if key == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
proxyURL := strings.TrimSpace(entry.ProxyURL)
|
proxyURL := strings.TrimSpace(entry.ProxyURL)
|
||||||
idKind := fmt.Sprintf("openai-compatibility:%s", providerName)
|
idKind := fmt.Sprintf("openai-compatibility:%s", providerName)
|
||||||
id, token := idGen.next(idKind, key, base, proxyURL)
|
id, token := idGen.next(idKind, key, base, proxyURL)
|
||||||
attrs := map[string]string{
|
attrs := map[string]string{
|
||||||
"source": fmt.Sprintf("config:%s[%s]", providerName, token),
|
"source": fmt.Sprintf("config:%s[%s]", providerName, token),
|
||||||
"base_url": base,
|
"base_url": base,
|
||||||
"api_key": key,
|
|
||||||
"compat_name": compat.Name,
|
"compat_name": compat.Name,
|
||||||
"provider_key": providerName,
|
"provider_key": providerName,
|
||||||
}
|
}
|
||||||
|
if key != "" {
|
||||||
|
attrs["api_key"] = key
|
||||||
|
}
|
||||||
if hash := computeOpenAICompatModelsHash(compat.Models); hash != "" {
|
if hash := computeOpenAICompatModelsHash(compat.Models); hash != "" {
|
||||||
attrs["models_hash"] = hash
|
attrs["models_hash"] = hash
|
||||||
}
|
}
|
||||||
@@ -830,6 +830,7 @@ func (w *Watcher) SnapshotCoreAuths() []*coreauth.Auth {
|
|||||||
UpdatedAt: now,
|
UpdatedAt: now,
|
||||||
}
|
}
|
||||||
out = append(out, a)
|
out = append(out, a)
|
||||||
|
createdEntries++
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Handle legacy APIKeys format for backward compatibility
|
// Handle legacy APIKeys format for backward compatibility
|
||||||
@@ -843,10 +844,10 @@ func (w *Watcher) SnapshotCoreAuths() []*coreauth.Auth {
|
|||||||
attrs := map[string]string{
|
attrs := map[string]string{
|
||||||
"source": fmt.Sprintf("config:%s[%s]", providerName, token),
|
"source": fmt.Sprintf("config:%s[%s]", providerName, token),
|
||||||
"base_url": base,
|
"base_url": base,
|
||||||
"api_key": key,
|
|
||||||
"compat_name": compat.Name,
|
"compat_name": compat.Name,
|
||||||
"provider_key": providerName,
|
"provider_key": providerName,
|
||||||
}
|
}
|
||||||
|
attrs["api_key"] = key
|
||||||
if hash := computeOpenAICompatModelsHash(compat.Models); hash != "" {
|
if hash := computeOpenAICompatModelsHash(compat.Models); hash != "" {
|
||||||
attrs["models_hash"] = hash
|
attrs["models_hash"] = hash
|
||||||
}
|
}
|
||||||
@@ -860,8 +861,32 @@ func (w *Watcher) SnapshotCoreAuths() []*coreauth.Auth {
|
|||||||
UpdatedAt: now,
|
UpdatedAt: now,
|
||||||
}
|
}
|
||||||
out = append(out, a)
|
out = append(out, a)
|
||||||
|
createdEntries++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if createdEntries == 0 {
|
||||||
|
idKind := fmt.Sprintf("openai-compatibility:%s", providerName)
|
||||||
|
id, token := idGen.next(idKind, base)
|
||||||
|
attrs := map[string]string{
|
||||||
|
"source": fmt.Sprintf("config:%s[%s]", providerName, token),
|
||||||
|
"base_url": base,
|
||||||
|
"compat_name": compat.Name,
|
||||||
|
"provider_key": providerName,
|
||||||
|
}
|
||||||
|
if hash := computeOpenAICompatModelsHash(compat.Models); hash != "" {
|
||||||
|
attrs["models_hash"] = hash
|
||||||
|
}
|
||||||
|
a := &coreauth.Auth{
|
||||||
|
ID: id,
|
||||||
|
Provider: providerName,
|
||||||
|
Label: compat.Name,
|
||||||
|
Status: coreauth.StatusActive,
|
||||||
|
Attributes: attrs,
|
||||||
|
CreatedAt: now,
|
||||||
|
UpdatedAt: now,
|
||||||
|
}
|
||||||
|
out = append(out, a)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Also synthesize auth entries directly from auth files (for OAuth/file-backed providers)
|
// Also synthesize auth entries directly from auth files (for OAuth/file-backed providers)
|
||||||
|
|||||||
Reference in New Issue
Block a user