Merge branch 'dev' into fix/antigravity-adaptive-effort
This commit is contained in:
@@ -10,7 +10,7 @@ So you can use local or multi-account CLI access with OpenAI(include Responses)/
|
|||||||
|
|
||||||
## Sponsor
|
## Sponsor
|
||||||
|
|
||||||
[](https://z.ai/subscribe?ic=8JVLJQFSKB)
|
[](https://z.ai/subscribe?ic=8JVLJQFSKB)
|
||||||
|
|
||||||
This project is sponsored by Z.ai, supporting us with their GLM CODING PLAN.
|
This project is sponsored by Z.ai, supporting us with their GLM CODING PLAN.
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
## 赞助商
|
## 赞助商
|
||||||
|
|
||||||
[](https://www.bigmodel.cn/claude-code?ic=RRVJPB5SII)
|
[](https://www.bigmodel.cn/claude-code?ic=RRVJPB5SII)
|
||||||
|
|
||||||
本项目由 Z智谱 提供赞助, 他们通过 GLM CODING PLAN 对本项目提供技术支持。
|
本项目由 Z智谱 提供赞助, 他们通过 GLM CODING PLAN 对本项目提供技术支持。
|
||||||
|
|
||||||
|
|||||||
@@ -201,6 +201,9 @@ nonstream-keepalive-interval: 0
|
|||||||
# alias: "vertex-flash" # client-visible alias
|
# alias: "vertex-flash" # client-visible alias
|
||||||
# - name: "gemini-2.5-pro"
|
# - name: "gemini-2.5-pro"
|
||||||
# alias: "vertex-pro"
|
# alias: "vertex-pro"
|
||||||
|
# excluded-models: # optional: models to exclude from listing
|
||||||
|
# - "imagen-3.0-generate-002"
|
||||||
|
# - "imagen-*"
|
||||||
|
|
||||||
# Amp Integration
|
# Amp Integration
|
||||||
# ampcode:
|
# ampcode:
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -692,17 +693,20 @@ func (h *Handler) authIDForPath(path string) string {
|
|||||||
if path == "" {
|
if path == "" {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
if h == nil || h.cfg == nil {
|
id := path
|
||||||
return path
|
if h != nil && h.cfg != nil {
|
||||||
}
|
|
||||||
authDir := strings.TrimSpace(h.cfg.AuthDir)
|
authDir := strings.TrimSpace(h.cfg.AuthDir)
|
||||||
if authDir == "" {
|
if authDir != "" {
|
||||||
return path
|
if rel, errRel := filepath.Rel(authDir, path); errRel == nil && rel != "" {
|
||||||
|
id = rel
|
||||||
}
|
}
|
||||||
if rel, err := filepath.Rel(authDir, path); err == nil && rel != "" {
|
|
||||||
return rel
|
|
||||||
}
|
}
|
||||||
return path
|
}
|
||||||
|
// On Windows, normalize ID casing to avoid duplicate auth entries caused by case-insensitive paths.
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
id = strings.ToLower(id)
|
||||||
|
}
|
||||||
|
return id
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) registerAuthFromFile(ctx context.Context, path string, data []byte) error {
|
func (h *Handler) registerAuthFromFile(ctx context.Context, path string, data []byte) error {
|
||||||
|
|||||||
@@ -522,6 +522,7 @@ func (h *Handler) PatchVertexCompatKey(c *gin.Context) {
|
|||||||
ProxyURL *string `json:"proxy-url"`
|
ProxyURL *string `json:"proxy-url"`
|
||||||
Headers *map[string]string `json:"headers"`
|
Headers *map[string]string `json:"headers"`
|
||||||
Models *[]config.VertexCompatModel `json:"models"`
|
Models *[]config.VertexCompatModel `json:"models"`
|
||||||
|
ExcludedModels *[]string `json:"excluded-models"`
|
||||||
}
|
}
|
||||||
var body struct {
|
var body struct {
|
||||||
Index *int `json:"index"`
|
Index *int `json:"index"`
|
||||||
@@ -585,6 +586,9 @@ func (h *Handler) PatchVertexCompatKey(c *gin.Context) {
|
|||||||
if body.Value.Models != nil {
|
if body.Value.Models != nil {
|
||||||
entry.Models = append([]config.VertexCompatModel(nil), (*body.Value.Models)...)
|
entry.Models = append([]config.VertexCompatModel(nil), (*body.Value.Models)...)
|
||||||
}
|
}
|
||||||
|
if body.Value.ExcludedModels != nil {
|
||||||
|
entry.ExcludedModels = config.NormalizeExcludedModels(*body.Value.ExcludedModels)
|
||||||
|
}
|
||||||
normalizeVertexCompatKey(&entry)
|
normalizeVertexCompatKey(&entry)
|
||||||
h.cfg.VertexCompatAPIKey[targetIndex] = entry
|
h.cfg.VertexCompatAPIKey[targetIndex] = entry
|
||||||
h.cfg.SanitizeVertexCompatKeys()
|
h.cfg.SanitizeVertexCompatKeys()
|
||||||
@@ -1025,6 +1029,7 @@ func normalizeVertexCompatKey(entry *config.VertexCompatKey) {
|
|||||||
entry.BaseURL = strings.TrimSpace(entry.BaseURL)
|
entry.BaseURL = strings.TrimSpace(entry.BaseURL)
|
||||||
entry.ProxyURL = strings.TrimSpace(entry.ProxyURL)
|
entry.ProxyURL = strings.TrimSpace(entry.ProxyURL)
|
||||||
entry.Headers = config.NormalizeHeaders(entry.Headers)
|
entry.Headers = config.NormalizeHeaders(entry.Headers)
|
||||||
|
entry.ExcludedModels = config.NormalizeExcludedModels(entry.ExcludedModels)
|
||||||
if len(entry.Models) == 0 {
|
if len(entry.Models) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,9 @@ type VertexCompatKey struct {
|
|||||||
|
|
||||||
// Models defines the model configurations including aliases for routing.
|
// Models defines the model configurations including aliases for routing.
|
||||||
Models []VertexCompatModel `yaml:"models,omitempty" json:"models,omitempty"`
|
Models []VertexCompatModel `yaml:"models,omitempty" json:"models,omitempty"`
|
||||||
|
|
||||||
|
// ExcludedModels lists model IDs that should be excluded for this provider.
|
||||||
|
ExcludedModels []string `yaml:"excluded-models,omitempty" json:"excluded-models,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k VertexCompatKey) GetAPIKey() string { return k.APIKey }
|
func (k VertexCompatKey) GetAPIKey() string { return k.APIKey }
|
||||||
@@ -74,6 +77,7 @@ func (cfg *Config) SanitizeVertexCompatKeys() {
|
|||||||
}
|
}
|
||||||
entry.ProxyURL = strings.TrimSpace(entry.ProxyURL)
|
entry.ProxyURL = strings.TrimSpace(entry.ProxyURL)
|
||||||
entry.Headers = NormalizeHeaders(entry.Headers)
|
entry.Headers = NormalizeHeaders(entry.Headers)
|
||||||
|
entry.ExcludedModels = NormalizeExcludedModels(entry.ExcludedModels)
|
||||||
|
|
||||||
// Sanitize models: remove entries without valid alias
|
// Sanitize models: remove entries without valid alias
|
||||||
sanitizedModels := make([]VertexCompatModel, 0, len(entry.Models))
|
sanitizedModels := make([]VertexCompatModel, 0, len(entry.Models))
|
||||||
|
|||||||
@@ -304,6 +304,11 @@ func BuildConfigChangeDetails(oldCfg, newCfg *config.Config) []string {
|
|||||||
if oldModels.hash != newModels.hash {
|
if oldModels.hash != newModels.hash {
|
||||||
changes = append(changes, fmt.Sprintf("vertex[%d].models: updated (%d -> %d entries)", i, oldModels.count, newModels.count))
|
changes = append(changes, fmt.Sprintf("vertex[%d].models: updated (%d -> %d entries)", i, oldModels.count, newModels.count))
|
||||||
}
|
}
|
||||||
|
oldExcluded := SummarizeExcludedModels(o.ExcludedModels)
|
||||||
|
newExcluded := SummarizeExcludedModels(n.ExcludedModels)
|
||||||
|
if oldExcluded.hash != newExcluded.hash {
|
||||||
|
changes = append(changes, fmt.Sprintf("vertex[%d].excluded-models: updated (%d -> %d entries)", i, oldExcluded.count, newExcluded.count))
|
||||||
|
}
|
||||||
if !equalStringMap(o.Headers, n.Headers) {
|
if !equalStringMap(o.Headers, n.Headers) {
|
||||||
changes = append(changes, fmt.Sprintf("vertex[%d].headers: updated", i))
|
changes = append(changes, fmt.Sprintf("vertex[%d].headers: updated", i))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -315,7 +315,7 @@ func (s *ConfigSynthesizer) synthesizeVertexCompat(ctx *SynthesisContext) []*cor
|
|||||||
CreatedAt: now,
|
CreatedAt: now,
|
||||||
UpdatedAt: now,
|
UpdatedAt: now,
|
||||||
}
|
}
|
||||||
ApplyAuthExcludedModelsMeta(a, cfg, nil, "apikey")
|
ApplyAuthExcludedModelsMeta(a, cfg, compat.ExcludedModels, "apikey")
|
||||||
out = append(out, a)
|
out = append(out, a)
|
||||||
}
|
}
|
||||||
return out
|
return out
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -72,6 +73,10 @@ func (s *FileSynthesizer) Synthesize(ctx *SynthesisContext) ([]*coreauth.Auth, e
|
|||||||
if rel, errRel := filepath.Rel(ctx.AuthDir, full); errRel == nil && rel != "" {
|
if rel, errRel := filepath.Rel(ctx.AuthDir, full); errRel == nil && rel != "" {
|
||||||
id = rel
|
id = rel
|
||||||
}
|
}
|
||||||
|
// On Windows, normalize ID casing to avoid duplicate auth entries caused by case-insensitive paths.
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
id = strings.ToLower(id)
|
||||||
|
}
|
||||||
|
|
||||||
proxyURL := ""
|
proxyURL := ""
|
||||||
if p, ok := metadata["proxy_url"].(string); ok {
|
if p, ok := metadata["proxy_url"].(string); ok {
|
||||||
|
|||||||
+10
-6
@@ -10,6 +10,7 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@@ -257,14 +258,17 @@ func (s *FileTokenStore) readAuthFile(path, baseDir string) (*cliproxyauth.Auth,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *FileTokenStore) idFor(path, baseDir string) string {
|
func (s *FileTokenStore) idFor(path, baseDir string) string {
|
||||||
if baseDir == "" {
|
id := path
|
||||||
return path
|
if baseDir != "" {
|
||||||
|
if rel, errRel := filepath.Rel(baseDir, path); errRel == nil && rel != "" {
|
||||||
|
id = rel
|
||||||
}
|
}
|
||||||
rel, err := filepath.Rel(baseDir, path)
|
|
||||||
if err != nil {
|
|
||||||
return path
|
|
||||||
}
|
}
|
||||||
return rel
|
// On Windows, normalize ID casing to avoid duplicate auth entries caused by case-insensitive paths.
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
id = strings.ToLower(id)
|
||||||
|
}
|
||||||
|
return id
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *FileTokenStore) resolveAuthPath(auth *cliproxyauth.Auth) (string, error) {
|
func (s *FileTokenStore) resolveAuthPath(auth *cliproxyauth.Auth) (string, error) {
|
||||||
|
|||||||
@@ -463,10 +463,15 @@ func (m *Manager) Update(ctx context.Context, auth *Auth) (*Auth, error) {
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
m.mu.Lock()
|
m.mu.Lock()
|
||||||
if existing, ok := m.auths[auth.ID]; ok && existing != nil && !auth.indexAssigned && auth.Index == "" {
|
if existing, ok := m.auths[auth.ID]; ok && existing != nil {
|
||||||
|
if !auth.indexAssigned && auth.Index == "" {
|
||||||
auth.Index = existing.Index
|
auth.Index = existing.Index
|
||||||
auth.indexAssigned = existing.indexAssigned
|
auth.indexAssigned = existing.indexAssigned
|
||||||
}
|
}
|
||||||
|
if len(auth.ModelStates) == 0 && len(existing.ModelStates) > 0 {
|
||||||
|
auth.ModelStates = existing.ModelStates
|
||||||
|
}
|
||||||
|
}
|
||||||
auth.EnsureIndex()
|
auth.EnsureIndex()
|
||||||
m.auths[auth.ID] = auth.Clone()
|
m.auths[auth.ID] = auth.Clone()
|
||||||
m.mu.Unlock()
|
m.mu.Unlock()
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestManager_Update_PreservesModelStates(t *testing.T) {
|
||||||
|
m := NewManager(nil, nil, nil)
|
||||||
|
|
||||||
|
model := "test-model"
|
||||||
|
backoffLevel := 7
|
||||||
|
|
||||||
|
if _, errRegister := m.Register(context.Background(), &Auth{
|
||||||
|
ID: "auth-1",
|
||||||
|
Provider: "claude",
|
||||||
|
Metadata: map[string]any{"k": "v"},
|
||||||
|
ModelStates: map[string]*ModelState{
|
||||||
|
model: {
|
||||||
|
Quota: QuotaState{BackoffLevel: backoffLevel},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}); errRegister != nil {
|
||||||
|
t.Fatalf("register auth: %v", errRegister)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, errUpdate := m.Update(context.Background(), &Auth{
|
||||||
|
ID: "auth-1",
|
||||||
|
Provider: "claude",
|
||||||
|
Metadata: map[string]any{"k": "v2"},
|
||||||
|
}); errUpdate != nil {
|
||||||
|
t.Fatalf("update auth: %v", errUpdate)
|
||||||
|
}
|
||||||
|
|
||||||
|
updated, ok := m.GetByID("auth-1")
|
||||||
|
if !ok || updated == nil {
|
||||||
|
t.Fatalf("expected auth to be present")
|
||||||
|
}
|
||||||
|
if len(updated.ModelStates) == 0 {
|
||||||
|
t.Fatalf("expected ModelStates to be preserved")
|
||||||
|
}
|
||||||
|
state := updated.ModelStates[model]
|
||||||
|
if state == nil {
|
||||||
|
t.Fatalf("expected model state to be present")
|
||||||
|
}
|
||||||
|
if state.Quota.BackoffLevel != backoffLevel {
|
||||||
|
t.Fatalf("expected BackoffLevel to be %d, got %d", backoffLevel, state.Quota.BackoffLevel)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -290,6 +290,9 @@ func (s *Service) applyCoreAuthAddOrUpdate(ctx context.Context, auth *coreauth.A
|
|||||||
auth.CreatedAt = existing.CreatedAt
|
auth.CreatedAt = existing.CreatedAt
|
||||||
auth.LastRefreshedAt = existing.LastRefreshedAt
|
auth.LastRefreshedAt = existing.LastRefreshedAt
|
||||||
auth.NextRefreshAfter = existing.NextRefreshAfter
|
auth.NextRefreshAfter = existing.NextRefreshAfter
|
||||||
|
if len(auth.ModelStates) == 0 && len(existing.ModelStates) > 0 {
|
||||||
|
auth.ModelStates = existing.ModelStates
|
||||||
|
}
|
||||||
op = "update"
|
op = "update"
|
||||||
_, err = s.coreManager.Update(ctx, auth)
|
_, err = s.coreManager.Update(ctx, auth)
|
||||||
} else {
|
} else {
|
||||||
@@ -788,10 +791,13 @@ 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()
|
||||||
if authKind == "apikey" {
|
if entry := s.resolveConfigVertexCompatKey(a); entry != nil {
|
||||||
if entry := s.resolveConfigVertexCompatKey(a); entry != nil && len(entry.Models) > 0 {
|
if len(entry.Models) > 0 {
|
||||||
models = buildVertexCompatConfigModels(entry)
|
models = buildVertexCompatConfigModels(entry)
|
||||||
}
|
}
|
||||||
|
if authKind == "apikey" {
|
||||||
|
excluded = entry.ExcludedModels
|
||||||
|
}
|
||||||
}
|
}
|
||||||
models = applyExcludedModels(models, excluded)
|
models = applyExcludedModels(models, excluded)
|
||||||
case "gemini-cli":
|
case "gemini-cli":
|
||||||
|
|||||||
Reference in New Issue
Block a user