chore: upgrade CLIProxyAPI dependency to v7 across the project

- Updated all references from v6 to v7 for `github.com/router-for-me/CLIProxyAPI`.
- Ensured consistency in imports within core libraries, tests, and integration tests.
- Added missing tests for new features in Redis Protocol integration.
This commit is contained in:
Luis Pater
2026-05-08 11:46:46 +08:00
parent 785b00c312
commit e50cabac4b
317 changed files with 2415 additions and 1035 deletions
@@ -6,7 +6,7 @@ import (
"time"
"github.com/gin-gonic/gin"
coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth"
)
type apiKeyUsageEntry struct {
@@ -8,8 +8,8 @@ import (
"testing"
"github.com/gin-gonic/gin"
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
"github.com/router-for-me/CLIProxyAPI/v7/internal/config"
coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth"
)
func sumRecentRequestBuckets(buckets []coreauth.RecentRequestBucket) (int64, int64) {
@@ -11,10 +11,10 @@ import (
"time"
"github.com/gin-gonic/gin"
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
"github.com/router-for-me/CLIProxyAPI/v6/internal/runtime/geminicli"
coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
"github.com/router-for-me/CLIProxyAPI/v6/sdk/proxyutil"
"github.com/router-for-me/CLIProxyAPI/v7/internal/config"
"github.com/router-for-me/CLIProxyAPI/v7/internal/runtime/geminicli"
coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth"
"github.com/router-for-me/CLIProxyAPI/v7/sdk/proxyutil"
log "github.com/sirupsen/logrus"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
@@ -5,9 +5,9 @@ import (
"net/http"
"testing"
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
sdkconfig "github.com/router-for-me/CLIProxyAPI/v6/sdk/config"
"github.com/router-for-me/CLIProxyAPI/v7/internal/config"
coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth"
sdkconfig "github.com/router-for-me/CLIProxyAPI/v7/sdk/config"
)
func TestAPICallTransportDirectBypassesGlobalProxy(t *testing.T) {
+11 -11
View File
@@ -22,17 +22,17 @@ import (
"time"
"github.com/gin-gonic/gin"
"github.com/router-for-me/CLIProxyAPI/v6/internal/auth/antigravity"
"github.com/router-for-me/CLIProxyAPI/v6/internal/auth/claude"
"github.com/router-for-me/CLIProxyAPI/v6/internal/auth/codex"
geminiAuth "github.com/router-for-me/CLIProxyAPI/v6/internal/auth/gemini"
"github.com/router-for-me/CLIProxyAPI/v6/internal/auth/kimi"
"github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces"
"github.com/router-for-me/CLIProxyAPI/v6/internal/misc"
"github.com/router-for-me/CLIProxyAPI/v6/internal/registry"
"github.com/router-for-me/CLIProxyAPI/v6/internal/util"
sdkAuth "github.com/router-for-me/CLIProxyAPI/v6/sdk/auth"
coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
"github.com/router-for-me/CLIProxyAPI/v7/internal/auth/antigravity"
"github.com/router-for-me/CLIProxyAPI/v7/internal/auth/claude"
"github.com/router-for-me/CLIProxyAPI/v7/internal/auth/codex"
geminiAuth "github.com/router-for-me/CLIProxyAPI/v7/internal/auth/gemini"
"github.com/router-for-me/CLIProxyAPI/v7/internal/auth/kimi"
"github.com/router-for-me/CLIProxyAPI/v7/internal/interfaces"
"github.com/router-for-me/CLIProxyAPI/v7/internal/misc"
"github.com/router-for-me/CLIProxyAPI/v7/internal/registry"
"github.com/router-for-me/CLIProxyAPI/v7/internal/util"
sdkAuth "github.com/router-for-me/CLIProxyAPI/v7/sdk/auth"
coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth"
log "github.com/sirupsen/logrus"
"github.com/tidwall/gjson"
"golang.org/x/oauth2"
@@ -12,8 +12,8 @@ import (
"testing"
"github.com/gin-gonic/gin"
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
"github.com/router-for-me/CLIProxyAPI/v7/internal/config"
coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth"
)
func TestUploadAuthFile_BatchMultipart(t *testing.T) {
@@ -11,8 +11,8 @@ import (
"testing"
"github.com/gin-gonic/gin"
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
"github.com/router-for-me/CLIProxyAPI/v7/internal/config"
coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth"
)
func TestDeleteAuthFile_UsesAuthPathFromManager(t *testing.T) {
@@ -9,7 +9,7 @@ import (
"testing"
"github.com/gin-gonic/gin"
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
"github.com/router-for-me/CLIProxyAPI/v7/internal/config"
)
func TestDownloadAuthFile_ReturnsFile(t *testing.T) {
@@ -11,7 +11,7 @@ import (
"testing"
"github.com/gin-gonic/gin"
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
"github.com/router-for-me/CLIProxyAPI/v7/internal/config"
)
func TestDownloadAuthFile_PreventsWindowsSlashTraversal(t *testing.T) {
@@ -9,8 +9,8 @@ import (
"testing"
"github.com/gin-gonic/gin"
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
"github.com/router-for-me/CLIProxyAPI/v7/internal/config"
coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth"
)
func TestPatchAuthFileFields_MergeHeadersAndDeleteEmptyValues(t *testing.T) {
@@ -8,8 +8,8 @@ import (
"testing"
"github.com/gin-gonic/gin"
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
"github.com/router-for-me/CLIProxyAPI/v7/internal/config"
coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth"
)
func TestListAuthFiles_IncludesRecentRequestsBuckets(t *testing.T) {
@@ -4,8 +4,8 @@ import (
"fmt"
"strings"
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
"github.com/router-for-me/CLIProxyAPI/v6/internal/watcher/synthesizer"
"github.com/router-for-me/CLIProxyAPI/v7/internal/config"
"github.com/router-for-me/CLIProxyAPI/v7/internal/watcher/synthesizer"
)
type geminiKeyWithAuthIndex struct {
@@ -11,9 +11,9 @@ import (
"time"
"github.com/gin-gonic/gin"
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
"github.com/router-for-me/CLIProxyAPI/v6/internal/util"
sdkconfig "github.com/router-for-me/CLIProxyAPI/v6/sdk/config"
"github.com/router-for-me/CLIProxyAPI/v7/internal/config"
"github.com/router-for-me/CLIProxyAPI/v7/internal/util"
sdkconfig "github.com/router-for-me/CLIProxyAPI/v7/sdk/config"
log "github.com/sirupsen/logrus"
"gopkg.in/yaml.v3"
)
@@ -6,7 +6,7 @@ import (
"strings"
"github.com/gin-gonic/gin"
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
"github.com/router-for-me/CLIProxyAPI/v7/internal/config"
)
// Generic helpers for list[string]
@@ -8,7 +8,7 @@ import (
"testing"
"github.com/gin-gonic/gin"
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
"github.com/router-for-me/CLIProxyAPI/v7/internal/config"
)
func writeTestConfigFile(t *testing.T) string {
+4 -4
View File
@@ -13,10 +13,10 @@ import (
"time"
"github.com/gin-gonic/gin"
"github.com/router-for-me/CLIProxyAPI/v6/internal/buildinfo"
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
sdkAuth "github.com/router-for-me/CLIProxyAPI/v6/sdk/auth"
coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
"github.com/router-for-me/CLIProxyAPI/v7/internal/buildinfo"
"github.com/router-for-me/CLIProxyAPI/v7/internal/config"
sdkAuth "github.com/router-for-me/CLIProxyAPI/v7/sdk/auth"
coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth"
"golang.org/x/crypto/bcrypt"
)
@@ -5,7 +5,7 @@ import (
"strings"
"testing"
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
"github.com/router-for-me/CLIProxyAPI/v7/internal/config"
)
func TestAuthenticateManagementKey_LocalhostIPBan_BlocksCorrectKeyDuringBan(t *testing.T) {
+1 -1
View File
@@ -13,7 +13,7 @@ import (
"time"
"github.com/gin-gonic/gin"
"github.com/router-for-me/CLIProxyAPI/v6/internal/logging"
"github.com/router-for-me/CLIProxyAPI/v7/internal/logging"
)
const (
@@ -5,7 +5,7 @@ import (
"strings"
"github.com/gin-gonic/gin"
"github.com/router-for-me/CLIProxyAPI/v6/internal/registry"
"github.com/router-for-me/CLIProxyAPI/v7/internal/registry"
)
// GetStaticModelDefinitions returns static model metadata for a given channel.
@@ -4,7 +4,7 @@ import (
"context"
"sync"
coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth"
)
type memoryAuthStore struct {
+1 -1
View File
@@ -8,7 +8,7 @@ import (
"strings"
"github.com/gin-gonic/gin"
"github.com/router-for-me/CLIProxyAPI/v6/internal/redisqueue"
"github.com/router-for-me/CLIProxyAPI/v7/internal/redisqueue"
)
type usageQueueRecord []byte
@@ -7,7 +7,7 @@ import (
"testing"
"github.com/gin-gonic/gin"
"github.com/router-for-me/CLIProxyAPI/v6/internal/redisqueue"
"github.com/router-for-me/CLIProxyAPI/v7/internal/redisqueue"
)
func TestGetUsageQueuePopsRequestedRecords(t *testing.T) {
@@ -9,8 +9,8 @@ import (
"strings"
"github.com/gin-gonic/gin"
"github.com/router-for-me/CLIProxyAPI/v6/internal/auth/vertex"
coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
"github.com/router-for-me/CLIProxyAPI/v7/internal/auth/vertex"
coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth"
)
// ImportVertexCredential handles uploading a Vertex service account JSON and saving it as an auth record.
+2 -2
View File
@@ -11,8 +11,8 @@ import (
"time"
"github.com/gin-gonic/gin"
"github.com/router-for-me/CLIProxyAPI/v6/internal/logging"
"github.com/router-for-me/CLIProxyAPI/v6/internal/util"
"github.com/router-for-me/CLIProxyAPI/v7/internal/logging"
"github.com/router-for-me/CLIProxyAPI/v7/internal/util"
)
const maxErrorOnlyCapturedRequestBodyBytes int64 = 1 << 20 // 1 MiB
+2 -2
View File
@@ -10,8 +10,8 @@ import (
"time"
"github.com/gin-gonic/gin"
"github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces"
"github.com/router-for-me/CLIProxyAPI/v6/internal/logging"
"github.com/router-for-me/CLIProxyAPI/v7/internal/interfaces"
"github.com/router-for-me/CLIProxyAPI/v7/internal/logging"
)
const requestBodyOverrideContextKey = "REQUEST_BODY_OVERRIDE"
@@ -7,8 +7,8 @@ import (
"time"
"github.com/gin-gonic/gin"
"github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces"
"github.com/router-for-me/CLIProxyAPI/v6/internal/logging"
"github.com/router-for-me/CLIProxyAPI/v7/internal/interfaces"
"github.com/router-for-me/CLIProxyAPI/v7/internal/logging"
)
func TestExtractRequestBodyPrefersOverride(t *testing.T) {
+3 -3
View File
@@ -9,9 +9,9 @@ import (
"sync"
"github.com/gin-gonic/gin"
"github.com/router-for-me/CLIProxyAPI/v6/internal/api/modules"
"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/v7/internal/api/modules"
"github.com/router-for-me/CLIProxyAPI/v7/internal/config"
sdkaccess "github.com/router-for-me/CLIProxyAPI/v7/sdk/access"
log "github.com/sirupsen/logrus"
)
+4 -4
View File
@@ -9,10 +9,10 @@ import (
"time"
"github.com/gin-gonic/gin"
"github.com/router-for-me/CLIProxyAPI/v6/internal/api/modules"
"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/api/handlers"
"github.com/router-for-me/CLIProxyAPI/v7/internal/api/modules"
"github.com/router-for-me/CLIProxyAPI/v7/internal/config"
sdkaccess "github.com/router-for-me/CLIProxyAPI/v7/sdk/access"
"github.com/router-for-me/CLIProxyAPI/v7/sdk/api/handlers"
)
func TestAmpModule_Name(t *testing.T) {
@@ -8,8 +8,8 @@ import (
"time"
"github.com/gin-gonic/gin"
"github.com/router-for-me/CLIProxyAPI/v6/internal/thinking"
"github.com/router-for-me/CLIProxyAPI/v6/internal/util"
"github.com/router-for-me/CLIProxyAPI/v7/internal/thinking"
"github.com/router-for-me/CLIProxyAPI/v7/internal/util"
log "github.com/sirupsen/logrus"
"github.com/tidwall/gjson"
"github.com/tidwall/sjson"
@@ -9,8 +9,8 @@ import (
"testing"
"github.com/gin-gonic/gin"
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
"github.com/router-for-me/CLIProxyAPI/v6/internal/registry"
"github.com/router-for-me/CLIProxyAPI/v7/internal/config"
"github.com/router-for-me/CLIProxyAPI/v7/internal/registry"
)
func TestFallbackHandler_ModelMapping_PreservesThinkingSuffixAndRewritesResponse(t *testing.T) {
+3 -3
View File
@@ -7,9 +7,9 @@ import (
"strings"
"sync"
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
"github.com/router-for-me/CLIProxyAPI/v6/internal/thinking"
"github.com/router-for-me/CLIProxyAPI/v6/internal/util"
"github.com/router-for-me/CLIProxyAPI/v7/internal/config"
"github.com/router-for-me/CLIProxyAPI/v7/internal/thinking"
"github.com/router-for-me/CLIProxyAPI/v7/internal/util"
log "github.com/sirupsen/logrus"
)
@@ -3,8 +3,8 @@ package amp
import (
"testing"
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
"github.com/router-for-me/CLIProxyAPI/v6/internal/registry"
"github.com/router-for-me/CLIProxyAPI/v7/internal/config"
"github.com/router-for-me/CLIProxyAPI/v7/internal/registry"
)
func TestNewModelMapper(t *testing.T) {
+1 -1
View File
@@ -14,7 +14,7 @@ import (
"strings"
"github.com/gin-gonic/gin"
"github.com/router-for-me/CLIProxyAPI/v6/internal/misc"
"github.com/router-for-me/CLIProxyAPI/v7/internal/misc"
log "github.com/sirupsen/logrus"
)
+1 -1
View File
@@ -11,7 +11,7 @@ import (
"strings"
"testing"
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
"github.com/router-for-me/CLIProxyAPI/v7/internal/config"
)
// Helper: compress data with gzip
+7 -7
View File
@@ -9,11 +9,11 @@ import (
"strings"
"github.com/gin-gonic/gin"
"github.com/router-for-me/CLIProxyAPI/v6/internal/logging"
"github.com/router-for-me/CLIProxyAPI/v6/sdk/api/handlers"
"github.com/router-for-me/CLIProxyAPI/v6/sdk/api/handlers/claude"
"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/v7/internal/logging"
"github.com/router-for-me/CLIProxyAPI/v7/sdk/api/handlers"
"github.com/router-for-me/CLIProxyAPI/v7/sdk/api/handlers/claude"
"github.com/router-for-me/CLIProxyAPI/v7/sdk/api/handlers/gemini"
"github.com/router-for-me/CLIProxyAPI/v7/sdk/api/handlers/openai"
log "github.com/sirupsen/logrus"
)
@@ -21,12 +21,12 @@ import (
// from gin.Context to the request context for SecretSource lookup.
type clientAPIKeyContextKey struct{}
// clientAPIKeyMiddleware injects the authenticated client API key from gin.Context["apiKey"]
// clientAPIKeyMiddleware injects the authenticated client API key from gin.Context["userApiKey"]
// into the request context so that SecretSource can look it up for per-client upstream routing.
func clientAPIKeyMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// Extract the client API key from gin context (set by AuthMiddleware)
if apiKey, exists := c.Get("apiKey"); exists {
if apiKey, exists := c.Get("userApiKey"); exists {
if keyStr, ok := apiKey.(string); ok && keyStr != "" {
// Inject into request context for SecretSource.Get(ctx) to read
ctx := context.WithValue(c.Request.Context(), clientAPIKeyContextKey{}, keyStr)
+1 -1
View File
@@ -6,7 +6,7 @@ import (
"testing"
"github.com/gin-gonic/gin"
"github.com/router-for-me/CLIProxyAPI/v6/sdk/api/handlers"
"github.com/router-for-me/CLIProxyAPI/v7/sdk/api/handlers"
)
func TestRegisterManagementRoutes(t *testing.T) {
+1 -1
View File
@@ -10,7 +10,7 @@ import (
"sync"
"time"
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
"github.com/router-for-me/CLIProxyAPI/v7/internal/config"
log "github.com/sirupsen/logrus"
)
+1 -1
View File
@@ -9,7 +9,7 @@ import (
"testing"
"time"
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
"github.com/router-for-me/CLIProxyAPI/v7/internal/config"
log "github.com/sirupsen/logrus"
"github.com/sirupsen/logrus/hooks/test"
)
+2 -2
View File
@@ -6,8 +6,8 @@ import (
"fmt"
"github.com/gin-gonic/gin"
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
"github.com/router-for-me/CLIProxyAPI/v6/sdk/api/handlers"
"github.com/router-for-me/CLIProxyAPI/v7/internal/config"
"github.com/router-for-me/CLIProxyAPI/v7/sdk/api/handlers"
)
// Context encapsulates the dependencies exposed to routing modules during
+6
View File
@@ -83,6 +83,12 @@ func (s *Server) acceptMuxConnections(listener net.Listener, httpListener *muxLi
}
if isRedisRESPPrefix(prefix[0]) {
if s.cfg != nil && s.cfg.Home.Enabled {
if errClose := conn.Close(); errClose != nil {
log.Errorf("failed to close redis connection while home mode is enabled: %v", errClose)
}
continue
}
if !s.managementRoutesEnabled.Load() {
if errClose := conn.Close(); errClose != nil {
log.Errorf("failed to close redis connection while management is disabled: %v", errClose)
+7 -1
View File
@@ -10,7 +10,7 @@ import (
"strconv"
"strings"
"github.com/router-for-me/CLIProxyAPI/v6/internal/redisqueue"
"github.com/router-for-me/CLIProxyAPI/v7/internal/redisqueue"
log "github.com/sirupsen/logrus"
)
@@ -45,6 +45,12 @@ func (s *Server) handleRedisConnection(conn net.Conn, reader *bufio.Reader) {
return true
}
if s.cfg != nil && s.cfg.Home.Enabled {
_ = writeRedisError(writer, "ERR redis usage output disabled in home mode")
_ = writer.Flush()
return
}
for {
if !s.managementRoutesEnabled.Load() {
return
@@ -12,7 +12,7 @@ import (
"testing"
"time"
"github.com/router-for-me/CLIProxyAPI/v6/internal/redisqueue"
"github.com/router-for-me/CLIProxyAPI/v7/internal/redisqueue"
)
type remoteAddrConn struct {
@@ -204,6 +204,43 @@ func TestRedisProtocol_ManagementDisabled_RejectsConnection(t *testing.T) {
}
}
func TestRedisProtocol_HomeEnabled_DisablesConnection(t *testing.T) {
t.Setenv("MANAGEMENT_PASSWORD", "test-management-password")
redisqueue.SetEnabled(false)
t.Cleanup(func() { redisqueue.SetEnabled(false) })
server := newTestServer(t)
if !server.managementRoutesEnabled.Load() {
t.Fatalf("expected managementRoutesEnabled to be true")
}
if server.cfg == nil {
t.Fatalf("expected server cfg to be non-nil")
}
server.cfg.Home.Enabled = true
redisqueue.SetEnabled(true)
addr, stop := startRedisMuxListener(t, server)
t.Cleanup(stop)
conn, errDial := net.DialTimeout("tcp", addr, time.Second)
if errDial != nil {
t.Fatalf("failed to dial redis listener: %v", errDial)
}
t.Cleanup(func() { _ = conn.Close() })
_ = conn.SetDeadline(time.Now().Add(2 * time.Second))
_ = writeTestRESPCommand(conn, "PING")
buf := make([]byte, 1)
_, errRead := conn.Read(buf)
if errRead == nil {
t.Fatalf("expected connection to be closed when home mode is enabled")
}
if ne, ok := errRead.(net.Error); ok && ne.Timeout() {
t.Fatalf("expected connection to be closed when home mode is enabled, got timeout: %v", errRead)
}
}
func TestRedisProtocol_AUTH_And_PopContracts(t *testing.T) {
const managementPassword = "test-management-password"
+235 -26
View File
@@ -8,6 +8,7 @@ import (
"context"
"crypto/subtle"
"crypto/tls"
"encoding/json"
"errors"
"fmt"
"net"
@@ -15,30 +16,32 @@ import (
"os"
"path/filepath"
"reflect"
"sort"
"strings"
"sync"
"sync/atomic"
"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/api/modules"
ampmodule "github.com/router-for-me/CLIProxyAPI/v6/internal/api/modules/amp"
"github.com/router-for-me/CLIProxyAPI/v6/internal/cache"
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
"github.com/router-for-me/CLIProxyAPI/v6/internal/logging"
"github.com/router-for-me/CLIProxyAPI/v6/internal/managementasset"
"github.com/router-for-me/CLIProxyAPI/v6/internal/redisqueue"
"github.com/router-for-me/CLIProxyAPI/v6/internal/util"
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"
"github.com/router-for-me/CLIProxyAPI/v6/sdk/api/handlers/gemini"
"github.com/router-for-me/CLIProxyAPI/v6/sdk/api/handlers/openai"
sdkAuth "github.com/router-for-me/CLIProxyAPI/v6/sdk/auth"
"github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
"github.com/router-for-me/CLIProxyAPI/v7/internal/access"
managementHandlers "github.com/router-for-me/CLIProxyAPI/v7/internal/api/handlers/management"
"github.com/router-for-me/CLIProxyAPI/v7/internal/api/middleware"
"github.com/router-for-me/CLIProxyAPI/v7/internal/api/modules"
ampmodule "github.com/router-for-me/CLIProxyAPI/v7/internal/api/modules/amp"
"github.com/router-for-me/CLIProxyAPI/v7/internal/cache"
"github.com/router-for-me/CLIProxyAPI/v7/internal/config"
"github.com/router-for-me/CLIProxyAPI/v7/internal/home"
"github.com/router-for-me/CLIProxyAPI/v7/internal/logging"
"github.com/router-for-me/CLIProxyAPI/v7/internal/managementasset"
"github.com/router-for-me/CLIProxyAPI/v7/internal/redisqueue"
"github.com/router-for-me/CLIProxyAPI/v7/internal/util"
sdkaccess "github.com/router-for-me/CLIProxyAPI/v7/sdk/access"
"github.com/router-for-me/CLIProxyAPI/v7/sdk/api/handlers"
"github.com/router-for-me/CLIProxyAPI/v7/sdk/api/handlers/claude"
"github.com/router-for-me/CLIProxyAPI/v7/sdk/api/handlers/gemini"
"github.com/router-for-me/CLIProxyAPI/v7/sdk/api/handlers/openai"
sdkAuth "github.com/router-for-me/CLIProxyAPI/v7/sdk/auth"
"github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth"
log "github.com/sirupsen/logrus"
"golang.org/x/net/http2"
"gopkg.in/yaml.v3"
@@ -284,6 +287,10 @@ func NewServer(cfg *config.Config, authManager *auth.Manager, accessManager *sdk
}
s.localPassword = optionState.localPassword
// Home heartbeat gate: when home is enabled, block all endpoints with 503 until the
// subscribe-config heartbeat connection is healthy.
engine.Use(s.homeHeartbeatMiddleware())
// Setup routes
s.setupRoutes()
@@ -308,7 +315,7 @@ func NewServer(cfg *config.Config, authManager *auth.Manager, accessManager *sdk
// or when a local management password is provided (e.g. TUI mode).
hasManagementSecret := cfg.RemoteManagement.SecretKey != "" || envManagementSecret || s.localPassword != ""
s.managementRoutesEnabled.Store(hasManagementSecret)
redisqueue.SetEnabled(hasManagementSecret)
redisqueue.SetEnabled(hasManagementSecret || (cfg != nil && cfg.Home.Enabled))
if hasManagementSecret {
s.registerManagementRoutes()
}
@@ -326,6 +333,28 @@ func NewServer(cfg *config.Config, authManager *auth.Manager, accessManager *sdk
return s
}
func (s *Server) homeHeartbeatMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
if s == nil || s.cfg == nil || !s.cfg.Home.Enabled {
c.Next()
return
}
if c != nil && c.Request != nil {
path := c.Request.URL.Path
if strings.HasPrefix(path, "/v0/management/") || path == "/v0/management" || path == "/management.html" {
c.Next()
return
}
}
client := home.Current()
if client == nil || !client.HeartbeatOK() {
c.AbortWithStatus(http.StatusServiceUnavailable)
return
}
c.Next()
}
}
// setupRoutes configures the API routes for the server.
// It defines the endpoints and associates them with their respective handlers.
func (s *Server) setupRoutes() {
@@ -661,6 +690,14 @@ func (s *Server) registerManagementRoutes() {
func (s *Server) managementAvailabilityMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
if s == nil || s.cfg == nil {
c.AbortWithStatus(http.StatusNotFound)
return
}
if s.cfg.Home.Enabled {
c.AbortWithStatus(http.StatusNotFound)
return
}
if !s.managementRoutesEnabled.Load() {
c.AbortWithStatus(http.StatusNotFound)
return
@@ -671,7 +708,7 @@ func (s *Server) managementAvailabilityMiddleware() gin.HandlerFunc {
func (s *Server) serveManagementControlPanel(c *gin.Context) {
cfg := s.cfg
if cfg == nil || cfg.RemoteManagement.DisableControlPanel {
if cfg == nil || cfg.Home.Enabled || cfg.RemoteManagement.DisableControlPanel {
c.AbortWithStatus(http.StatusNotFound)
return
}
@@ -783,6 +820,11 @@ func (s *Server) watchKeepAlive() {
// otherwise it routes to OpenAI handler.
func (s *Server) unifiedModelsHandler(openaiHandler *openai.OpenAIAPIHandler, claudeHandler *claude.ClaudeCodeAPIHandler) gin.HandlerFunc {
return func(c *gin.Context) {
if s != nil && s.cfg != nil && s.cfg.Home.Enabled {
s.handleHomeModels(c)
return
}
userAgent := c.GetHeader("User-Agent")
// Route to Claude handler if User-Agent starts with "claude-cli"
@@ -796,6 +838,170 @@ func (s *Server) unifiedModelsHandler(openaiHandler *openai.OpenAIAPIHandler, cl
}
}
type homeModelEntry struct {
id string
created int64
ownedBy string
displayName string
}
func (s *Server) handleHomeModels(c *gin.Context) {
if s == nil || c == nil || c.Request == nil {
return
}
client := home.Current()
if client == nil {
c.JSON(http.StatusServiceUnavailable, handlers.ErrorResponse{
Error: handlers.ErrorDetail{
Message: "home control center unavailable",
Type: "server_error",
},
})
return
}
raw, errGet := client.GetModels(c.Request.Context())
if errGet != nil {
c.JSON(http.StatusBadGateway, handlers.ErrorResponse{
Error: handlers.ErrorDetail{
Message: errGet.Error(),
Type: "server_error",
},
})
return
}
entries, errDecode := decodeHomeModels(raw)
if errDecode != nil {
c.JSON(http.StatusBadGateway, handlers.ErrorResponse{
Error: handlers.ErrorDetail{
Message: errDecode.Error(),
Type: "server_error",
},
})
return
}
userAgent := c.GetHeader("User-Agent")
isClaude := strings.HasPrefix(userAgent, "claude-cli")
if isClaude {
out := make([]map[string]any, 0, len(entries))
for _, entry := range entries {
model := map[string]any{
"id": entry.id,
"object": "model",
"owned_by": entry.ownedBy,
}
if entry.created > 0 {
model["created_at"] = entry.created
}
if entry.displayName != "" {
model["display_name"] = entry.displayName
}
out = append(out, model)
}
firstID := ""
lastID := ""
if len(out) > 0 {
if id, ok := out[0]["id"].(string); ok {
firstID = id
}
if id, ok := out[len(out)-1]["id"].(string); ok {
lastID = id
}
}
c.JSON(http.StatusOK, gin.H{
"data": out,
"has_more": false,
"first_id": firstID,
"last_id": lastID,
})
return
}
filtered := make([]map[string]any, 0, len(entries))
for _, entry := range entries {
model := map[string]any{
"id": entry.id,
"object": "model",
}
if entry.created > 0 {
model["created"] = entry.created
}
if entry.ownedBy != "" {
model["owned_by"] = entry.ownedBy
}
filtered = append(filtered, model)
}
c.JSON(http.StatusOK, gin.H{
"object": "list",
"data": filtered,
})
}
func decodeHomeModels(raw []byte) ([]homeModelEntry, error) {
if len(raw) == 0 {
return nil, fmt.Errorf("home models payload is empty")
}
var bySection map[string][]map[string]any
if err := json.Unmarshal(raw, &bySection); err != nil {
return nil, fmt.Errorf("parse home models payload: %w", err)
}
if len(bySection) == 0 {
return nil, fmt.Errorf("home models payload has no sections")
}
seen := make(map[string]struct{})
out := make([]homeModelEntry, 0, 256)
for _, models := range bySection {
for _, model := range models {
id, _ := model["id"].(string)
id = strings.TrimSpace(id)
if id == "" {
continue
}
if _, ok := seen[id]; ok {
continue
}
seen[id] = struct{}{}
created := int64(0)
switch v := model["created"].(type) {
case float64:
created = int64(v)
case int64:
created = v
case int:
created = int64(v)
case json.Number:
if n, err := v.Int64(); err == nil {
created = n
}
}
ownedBy, _ := model["owned_by"].(string)
ownedBy = strings.TrimSpace(ownedBy)
displayName, _ := model["display_name"].(string)
displayName = strings.TrimSpace(displayName)
out = append(out, homeModelEntry{
id: id,
created: created,
ownedBy: ownedBy,
displayName: displayName,
})
}
}
sort.Slice(out, func(i, j int) bool { return out[i].id < out[j].id })
if len(out) == 0 {
return nil, fmt.Errorf("home models payload contains no models")
}
return out, nil
}
// Start begins listening for and serving HTTP or HTTPS requests.
// It's a blocking call and will only return on an unrecoverable error.
//
@@ -1061,7 +1267,7 @@ func (s *Server) UpdateClients(cfg *config.Config) {
s.managementRoutesEnabled.Store(!newSecretEmpty)
}
}
redisqueue.SetEnabled(s.managementRoutesEnabled.Load())
redisqueue.SetEnabled(s.managementRoutesEnabled.Load() || (cfg != nil && cfg.Home.Enabled))
s.applyAccessConfig(oldCfg, cfg)
s.cfg = cfg
@@ -1094,11 +1300,14 @@ func (s *Server) UpdateClients(cfg *config.Config) {
}
// Count client sources from configuration and auth store.
tokenStore := sdkAuth.GetTokenStore()
if dirSetter, ok := tokenStore.(interface{ SetBaseDir(string) }); ok {
dirSetter.SetBaseDir(cfg.AuthDir)
authEntries := 0
if cfg != nil && !cfg.Home.Enabled {
tokenStore := sdkAuth.GetTokenStore()
if dirSetter, ok := tokenStore.(interface{ SetBaseDir(string) }); ok {
dirSetter.SetBaseDir(cfg.AuthDir)
}
authEntries = util.CountAuthFiles(context.Background(), tokenStore)
}
authEntries := util.CountAuthFiles(context.Background(), tokenStore)
geminiAPIKeyCount := len(cfg.GeminiKey)
claudeAPIKeyCount := len(cfg.ClaudeKey)
codexAPIKeyCount := len(cfg.CodexKey)
@@ -1146,7 +1355,7 @@ func AuthMiddleware(manager *sdkaccess.Manager) gin.HandlerFunc {
result, err := manager.Authenticate(c.Request.Context(), c.Request)
if err == nil {
if result != nil {
c.Set("apiKey", result.Principal)
c.Set("userApiKey", result.Principal)
c.Set("accessProvider", result.Provider)
if len(result.Metadata) > 0 {
c.Set("accessMetadata", result.Metadata)
+32 -6
View File
@@ -11,12 +11,12 @@ import (
"time"
gin "github.com/gin-gonic/gin"
proxyconfig "github.com/router-for-me/CLIProxyAPI/v6/internal/config"
internallogging "github.com/router-for-me/CLIProxyAPI/v6/internal/logging"
"github.com/router-for-me/CLIProxyAPI/v6/internal/redisqueue"
sdkaccess "github.com/router-for-me/CLIProxyAPI/v6/sdk/access"
"github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
sdkconfig "github.com/router-for-me/CLIProxyAPI/v6/sdk/config"
proxyconfig "github.com/router-for-me/CLIProxyAPI/v7/internal/config"
internallogging "github.com/router-for-me/CLIProxyAPI/v7/internal/logging"
"github.com/router-for-me/CLIProxyAPI/v7/internal/redisqueue"
sdkaccess "github.com/router-for-me/CLIProxyAPI/v7/sdk/access"
"github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth"
sdkconfig "github.com/router-for-me/CLIProxyAPI/v7/sdk/config"
)
func newTestServer(t *testing.T) *Server {
@@ -147,6 +147,32 @@ func TestManagementUsageRequiresManagementAuthAndPopsArray(t *testing.T) {
}
}
func TestHomeEnabledHidesManagementEndpointsAndControlPanel(t *testing.T) {
t.Setenv("MANAGEMENT_PASSWORD", "test-management-key")
server := newTestServer(t)
server.cfg.Home.Enabled = true
t.Run("management endpoints return 404", func(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/v0/management/config", nil)
req.Header.Set("Authorization", "Bearer test-management-key")
rr := httptest.NewRecorder()
server.engine.ServeHTTP(rr, req)
if rr.Code != http.StatusNotFound {
t.Fatalf("status = %d, want %d body=%s", rr.Code, http.StatusNotFound, rr.Body.String())
}
})
t.Run("management control panel returns 404", func(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/management.html", nil)
rr := httptest.NewRecorder()
server.engine.ServeHTTP(rr, req)
if rr.Code != http.StatusNotFound {
t.Fatalf("status = %d, want %d body=%s", rr.Code, http.StatusNotFound, rr.Body.String())
}
})
}
func TestAmpProviderModelRoutes(t *testing.T) {
testCases := []struct {
name string