**refactor(cliproxy, config): remove vertex-compat flow, streamline Vertex API key handling**

- Removed `vertex-compat` executor and related configuration.
- Consolidated Vertex compatibility checks into `vertex` handling with `apikey`-based model resolution.
- Streamlined model generation logic for Vertex API key entries.
This commit is contained in:
Luis Pater
2025-12-02 09:12:53 +08:00
parent 0ebb654019
commit 0fd2abbc3b
5 changed files with 97 additions and 54 deletions

View File

@@ -145,6 +145,19 @@ ws-auth: false
# - name: "moonshotai/kimi-k2:free" # The actual model name. # - name: "moonshotai/kimi-k2:free" # The actual model name.
# alias: "kimi-k2" # The alias used in the API. # alias: "kimi-k2" # The alias used in the API.
# Vertex API keys (Vertex-compatible endpoints, use API key + base URL)
#vertex-api-key:
# - api-key: "vk-123..." # x-goog-api-key header
# base-url: "https://example.com/api" # e.g. https://zenmux.ai/api
# proxy-url: "socks5://proxy.example.com:1080" # optional per-key proxy override
# headers:
# X-Custom-Header: "custom-value"
# models: # optional: map aliases to upstream model names
# - name: "gemini-2.0-flash" # upstream model name
# alias: "vertex-flash" # client-visible alias
# - name: "gemini-1.5-pro"
# alias: "vertex-pro"
#payload: # Optional payload configuration #payload: # Optional payload configuration
# default: # Default rules only set parameters when they are missing in the payload. # default: # Default rules only set parameters when they are missing in the payload.
# - models: # - models:

View File

@@ -55,7 +55,7 @@ func (cfg *Config) SanitizeVertexCompatKeys() {
} }
entry.BaseURL = strings.TrimSpace(entry.BaseURL) entry.BaseURL = strings.TrimSpace(entry.BaseURL)
if entry.BaseURL == "" { if entry.BaseURL == "" {
// BaseURL is required for vertex-compat keys // BaseURL is required for Vertex API key entries
continue continue
} }
entry.ProxyURL = strings.TrimSpace(entry.ProxyURL) entry.ProxyURL = strings.TrimSpace(entry.ProxyURL)

View File

@@ -44,22 +44,6 @@ func NewGeminiVertexExecutor(cfg *config.Config) *GeminiVertexExecutor {
// Identifier returns provider key for manager routing. // Identifier returns provider key for manager routing.
func (e *GeminiVertexExecutor) Identifier() string { return "vertex" } func (e *GeminiVertexExecutor) Identifier() string { return "vertex" }
// GeminiVertexCompatExecutor is a thin wrapper around GeminiVertexExecutor
// that provides the correct identifier for vertex-compat routing.
type GeminiVertexCompatExecutor struct {
*GeminiVertexExecutor
}
// NewGeminiVertexCompatExecutor constructs the Vertex-compatible executor.
func NewGeminiVertexCompatExecutor(cfg *config.Config) *GeminiVertexCompatExecutor {
return &GeminiVertexCompatExecutor{
GeminiVertexExecutor: NewGeminiVertexExecutor(cfg),
}
}
// Identifier returns provider key for manager routing.
func (e *GeminiVertexCompatExecutor) Identifier() string { return "vertex-compat" }
// PrepareRequest is a no-op for Vertex. // PrepareRequest is a no-op for Vertex.
func (e *GeminiVertexExecutor) PrepareRequest(_ *http.Request, _ *cliproxyauth.Auth) error { func (e *GeminiVertexExecutor) PrepareRequest(_ *http.Request, _ *cliproxyauth.Auth) error {
return nil return nil
@@ -393,7 +377,6 @@ func (e *GeminiVertexExecutor) executeWithServiceAccount(ctx context.Context, au
} }
// executeWithAPIKey handles authentication using API key credentials. // executeWithAPIKey handles authentication using API key credentials.
// This method follows the vertex-compat pattern for API key authentication.
func (e *GeminiVertexExecutor) executeWithAPIKey(ctx context.Context, auth *cliproxyauth.Auth, req cliproxyexecutor.Request, opts cliproxyexecutor.Options, apiKey, baseURL string) (resp cliproxyexecutor.Response, err error) { func (e *GeminiVertexExecutor) executeWithAPIKey(ctx context.Context, auth *cliproxyauth.Auth, req cliproxyexecutor.Request, opts cliproxyexecutor.Options, apiKey, baseURL string) (resp cliproxyexecutor.Response, err error) {
reporter := newUsageReporter(ctx, e.Identifier(), req.Model, auth) reporter := newUsageReporter(ctx, e.Identifier(), req.Model, auth)
defer reporter.trackFailure(ctx, &err) defer reporter.trackFailure(ctx, &err)

View File

@@ -986,7 +986,7 @@ func (w *Watcher) reloadClients(rescanAuth bool, affectedOAuthProviders []string
w.refreshAuthState() w.refreshAuthState()
log.Infof("full client load complete - %d clients (%d auth files + %d Gemini API keys + %d Vertex-compat keys + %d Claude API keys + %d Codex keys + %d OpenAI-compat)", log.Infof("full client load complete - %d clients (%d auth files + %d Gemini API keys + %d Vertex API keys + %d Claude API keys + %d Codex keys + %d OpenAI-compat)",
totalNewClients, totalNewClients,
authFileCount, authFileCount,
geminiAPIKeyCount, geminiAPIKeyCount,
@@ -1273,18 +1273,18 @@ func (w *Watcher) SnapshotCoreAuths() []*coreauth.Auth {
} }
} }
// Process Vertex compatibility providers // Process Vertex API key providers (Vertex-compatible endpoints)
for i := range cfg.VertexCompatAPIKey { for i := range cfg.VertexCompatAPIKey {
compat := &cfg.VertexCompatAPIKey[i] compat := &cfg.VertexCompatAPIKey[i]
providerName := "vertex-compat" providerName := "vertex"
base := strings.TrimSpace(compat.BaseURL) base := strings.TrimSpace(compat.BaseURL)
key := strings.TrimSpace(compat.APIKey) key := strings.TrimSpace(compat.APIKey)
proxyURL := strings.TrimSpace(compat.ProxyURL) proxyURL := strings.TrimSpace(compat.ProxyURL)
idKind := fmt.Sprintf("vertex-compatibility:%s", base) idKind := fmt.Sprintf("vertex:apikey:%s", base)
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:vertex-compatibility[%s]", token), "source": fmt.Sprintf("config:vertex-apikey[%s]", token),
"base_url": base, "base_url": base,
"provider_key": providerName, "provider_key": providerName,
} }
@@ -1298,13 +1298,14 @@ func (w *Watcher) SnapshotCoreAuths() []*coreauth.Auth {
a := &coreauth.Auth{ a := &coreauth.Auth{
ID: id, ID: id,
Provider: providerName, Provider: providerName,
Label: "Vertex Compatibility", Label: "vertex-apikey",
Status: coreauth.StatusActive, Status: coreauth.StatusActive,
ProxyURL: proxyURL, ProxyURL: proxyURL,
Attributes: attrs, Attributes: attrs,
CreatedAt: now, CreatedAt: now,
UpdatedAt: now, UpdatedAt: now,
} }
applyAuthExcludedModelsMeta(a, cfg, nil, "apikey")
out = append(out, a) out = append(out, a)
} }

View File

@@ -362,8 +362,6 @@ func (s *Service) ensureExecutorsForAuth(a *coreauth.Auth) {
s.coreManager.RegisterExecutor(executor.NewGeminiExecutor(s.cfg)) s.coreManager.RegisterExecutor(executor.NewGeminiExecutor(s.cfg))
case "vertex": case "vertex":
s.coreManager.RegisterExecutor(executor.NewGeminiVertexExecutor(s.cfg)) s.coreManager.RegisterExecutor(executor.NewGeminiVertexExecutor(s.cfg))
case "vertex-compat":
s.coreManager.RegisterExecutor(executor.NewGeminiVertexCompatExecutor(s.cfg))
case "gemini-cli": case "gemini-cli":
s.coreManager.RegisterExecutor(executor.NewGeminiCLIExecutor(s.cfg)) s.coreManager.RegisterExecutor(executor.NewGeminiCLIExecutor(s.cfg))
case "aistudio": case "aistudio":
@@ -681,36 +679,12 @@ func (s *Service) registerModelsForAuth(a *coreauth.Auth) {
case "vertex": case "vertex":
// Vertex AI Gemini supports the same model identifiers as Gemini. // Vertex AI Gemini supports the same model identifiers as Gemini.
models = registry.GetGeminiVertexModels() models = registry.GetGeminiVertexModels()
models = applyExcludedModels(models, excluded) if authKind == "apikey" {
case "vertex-compat": if entry := s.resolveConfigVertexCompatKey(a); entry != nil && len(entry.Models) > 0 {
// Handle Vertex AI compatibility providers with custom model definitions models = buildVertexCompatConfigModels(entry)
if s.cfg != nil && len(s.cfg.VertexCompatAPIKey) > 0 {
// Create models for all Vertex compatibility providers
allModels := make([]*ModelInfo, 0)
for i := range s.cfg.VertexCompatAPIKey {
compat := &s.cfg.VertexCompatAPIKey[i]
for j := range compat.Models {
m := compat.Models[j]
// Use alias as model ID, fallback to name if alias is empty
modelID := m.Alias
if modelID == "" {
modelID = m.Name
}
if modelID != "" {
allModels = append(allModels, &ModelInfo{
ID: modelID,
Object: "model",
Created: time.Now().Unix(),
OwnedBy: "vertex-compat",
Type: "vertex-compat",
DisplayName: m.Name,
})
}
}
} }
models = allModels
} }
models = applyExcludedModels(models, excluded)
case "gemini-cli": case "gemini-cli":
models = registry.GetGeminiCLIModels() models = registry.GetGeminiCLIModels()
models = applyExcludedModels(models, excluded) models = applyExcludedModels(models, excluded)
@@ -905,6 +879,40 @@ func (s *Service) resolveConfigGeminiKey(auth *coreauth.Auth) *config.GeminiKey
return nil return nil
} }
func (s *Service) resolveConfigVertexCompatKey(auth *coreauth.Auth) *config.VertexCompatKey {
if auth == nil || s.cfg == nil {
return nil
}
var attrKey, attrBase string
if auth.Attributes != nil {
attrKey = strings.TrimSpace(auth.Attributes["api_key"])
attrBase = strings.TrimSpace(auth.Attributes["base_url"])
}
for i := range s.cfg.VertexCompatAPIKey {
entry := &s.cfg.VertexCompatAPIKey[i]
cfgKey := strings.TrimSpace(entry.APIKey)
cfgBase := strings.TrimSpace(entry.BaseURL)
if attrKey != "" && strings.EqualFold(cfgKey, attrKey) {
if cfgBase == "" || strings.EqualFold(cfgBase, attrBase) {
return entry
}
continue
}
if attrKey == "" && attrBase != "" && strings.EqualFold(cfgBase, attrBase) {
return entry
}
}
if attrKey != "" {
for i := range s.cfg.VertexCompatAPIKey {
entry := &s.cfg.VertexCompatAPIKey[i]
if strings.EqualFold(strings.TrimSpace(entry.APIKey), attrKey) {
return entry
}
}
}
return nil
}
func (s *Service) resolveConfigCodexKey(auth *coreauth.Auth) *config.CodexKey { func (s *Service) resolveConfigCodexKey(auth *coreauth.Auth) *config.CodexKey {
if auth == nil || s.cfg == nil { if auth == nil || s.cfg == nil {
return nil return nil
@@ -1023,6 +1031,44 @@ func matchWildcard(pattern, value string) bool {
return true return true
} }
func buildVertexCompatConfigModels(entry *config.VertexCompatKey) []*ModelInfo {
if entry == nil || len(entry.Models) == 0 {
return nil
}
now := time.Now().Unix()
out := make([]*ModelInfo, 0, len(entry.Models))
seen := make(map[string]struct{}, len(entry.Models))
for i := range entry.Models {
model := entry.Models[i]
name := strings.TrimSpace(model.Name)
alias := strings.TrimSpace(model.Alias)
if alias == "" {
alias = name
}
if alias == "" {
continue
}
key := strings.ToLower(alias)
if _, exists := seen[key]; exists {
continue
}
seen[key] = struct{}{}
display := name
if display == "" {
display = alias
}
out = append(out, &ModelInfo{
ID: alias,
Object: "model",
Created: now,
OwnedBy: "vertex",
Type: "vertex",
DisplayName: display,
})
}
return out
}
func buildClaudeConfigModels(entry *config.ClaudeKey) []*ModelInfo { func buildClaudeConfigModels(entry *config.ClaudeKey) []*ModelInfo {
if entry == nil || len(entry.Models) == 0 { if entry == nil || len(entry.Models) == 0 {
return nil return nil