Merge branch 'v7' into dev
This commit is contained in:
@@ -16,10 +16,10 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
. "github.com/router-for-me/CLIProxyAPI/v6/internal/constant"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/registry"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/sdk/api/handlers"
|
||||
. "github.com/router-for-me/CLIProxyAPI/v7/internal/constant"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/interfaces"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/registry"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/sdk/api/handlers"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
@@ -15,10 +15,10 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
. "github.com/router-for-me/CLIProxyAPI/v6/internal/constant"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/util"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/sdk/api/handlers"
|
||||
. "github.com/router-for-me/CLIProxyAPI/v7/internal/constant"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/interfaces"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/util"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/sdk/api/handlers"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
@@ -13,10 +13,10 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
. "github.com/router-for-me/CLIProxyAPI/v6/internal/constant"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/registry"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/sdk/api/handlers"
|
||||
. "github.com/router-for-me/CLIProxyAPI/v7/internal/constant"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/interfaces"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/registry"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/sdk/api/handlers"
|
||||
)
|
||||
|
||||
// GeminiAPIHandler contains the handlers for Gemini API endpoints.
|
||||
|
||||
@@ -14,14 +14,14 @@ 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/v6/internal/thinking"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/util"
|
||||
coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
|
||||
coreexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/sdk/config"
|
||||
sdktranslator "github.com/router-for-me/CLIProxyAPI/v6/sdk/translator"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/interfaces"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/logging"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/thinking"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/util"
|
||||
coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth"
|
||||
coreexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/sdk/config"
|
||||
sdktranslator "github.com/router-for-me/CLIProxyAPI/v7/sdk/translator"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
@@ -850,14 +850,22 @@ func (h *BaseAPIHandler) getRequestDetails(modelName string) (providers []string
|
||||
resolvedModelName := modelName
|
||||
initialSuffix := thinking.ParseSuffix(modelName)
|
||||
if initialSuffix.ModelName == "auto" {
|
||||
resolvedBase := util.ResolveAutoModel(initialSuffix.ModelName)
|
||||
if initialSuffix.HasSuffix {
|
||||
resolvedModelName = fmt.Sprintf("%s(%s)", resolvedBase, initialSuffix.RawSuffix)
|
||||
if h != nil && h.AuthManager != nil && h.AuthManager.HomeEnabled() {
|
||||
resolvedModelName = modelName
|
||||
} else {
|
||||
resolvedModelName = resolvedBase
|
||||
resolvedBase := util.ResolveAutoModel(initialSuffix.ModelName)
|
||||
if initialSuffix.HasSuffix {
|
||||
resolvedModelName = fmt.Sprintf("%s(%s)", resolvedBase, initialSuffix.RawSuffix)
|
||||
} else {
|
||||
resolvedModelName = resolvedBase
|
||||
}
|
||||
}
|
||||
} else {
|
||||
resolvedModelName = util.ResolveAutoModel(modelName)
|
||||
if h != nil && h.AuthManager != nil && h.AuthManager.HomeEnabled() {
|
||||
resolvedModelName = modelName
|
||||
} else {
|
||||
resolvedModelName = util.ResolveAutoModel(modelName)
|
||||
}
|
||||
}
|
||||
|
||||
parsed := thinking.ParseSuffix(resolvedModelName)
|
||||
@@ -870,6 +878,10 @@ func (h *BaseAPIHandler) getRequestDetails(modelName string) (providers []string
|
||||
}
|
||||
}
|
||||
|
||||
if h != nil && h.AuthManager != nil && h.AuthManager.HomeEnabled() {
|
||||
return []string{"home"}, resolvedModelName, nil
|
||||
}
|
||||
|
||||
providers = util.GetProviderName(baseModel)
|
||||
// Fallback: if baseModel has no provider but differs from resolvedModelName,
|
||||
// try using the full model name. This handles edge cases where custom models
|
||||
|
||||
@@ -9,9 +9,9 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces"
|
||||
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/interfaces"
|
||||
coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth"
|
||||
sdkconfig "github.com/router-for-me/CLIProxyAPI/v7/sdk/config"
|
||||
)
|
||||
|
||||
func TestWriteErrorResponse_AddonHeadersDisabledByDefault(t *testing.T) {
|
||||
|
||||
@@ -3,7 +3,7 @@ package handlers
|
||||
import (
|
||||
"testing"
|
||||
|
||||
coreexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor"
|
||||
coreexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
|
||||
@@ -7,9 +7,9 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/registry"
|
||||
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/registry"
|
||||
coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth"
|
||||
sdkconfig "github.com/router-for-me/CLIProxyAPI/v7/sdk/config"
|
||||
)
|
||||
|
||||
func TestGetRequestDetails_PreservesSuffix(t *testing.T) {
|
||||
|
||||
@@ -8,11 +8,11 @@ import (
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/registry"
|
||||
coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
|
||||
coreexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor"
|
||||
sdkconfig "github.com/router-for-me/CLIProxyAPI/v6/sdk/config"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/interfaces"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/registry"
|
||||
coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth"
|
||||
coreexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor"
|
||||
sdkconfig "github.com/router-for-me/CLIProxyAPI/v7/sdk/config"
|
||||
)
|
||||
|
||||
type failOnceStreamExecutor struct {
|
||||
|
||||
@@ -14,11 +14,11 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
. "github.com/router-for-me/CLIProxyAPI/v6/internal/constant"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/registry"
|
||||
responsesconverter "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/openai/openai/responses"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/sdk/api/handlers"
|
||||
. "github.com/router-for-me/CLIProxyAPI/v7/internal/constant"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/interfaces"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/registry"
|
||||
responsesconverter "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/openai/openai/responses"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/sdk/api/handlers"
|
||||
"github.com/tidwall/gjson"
|
||||
"github.com/tidwall/sjson"
|
||||
)
|
||||
|
||||
@@ -14,9 +14,9 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
internalconfig "github.com/router-for-me/CLIProxyAPI/v6/internal/config"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/sdk/api/handlers"
|
||||
internalconfig "github.com/router-for-me/CLIProxyAPI/v7/internal/config"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/interfaces"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/sdk/api/handlers"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/tidwall/gjson"
|
||||
"github.com/tidwall/sjson"
|
||||
|
||||
@@ -10,9 +10,9 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
internalconfig "github.com/router-for-me/CLIProxyAPI/v6/internal/config"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/sdk/api/handlers"
|
||||
sdkconfig "github.com/router-for-me/CLIProxyAPI/v6/sdk/config"
|
||||
internalconfig "github.com/router-for-me/CLIProxyAPI/v7/internal/config"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/sdk/api/handlers"
|
||||
sdkconfig "github.com/router-for-me/CLIProxyAPI/v7/sdk/config"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
|
||||
@@ -9,11 +9,11 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/registry"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/sdk/api/handlers"
|
||||
coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
|
||||
coreexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor"
|
||||
sdkconfig "github.com/router-for-me/CLIProxyAPI/v6/sdk/config"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/registry"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/sdk/api/handlers"
|
||||
coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth"
|
||||
coreexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor"
|
||||
sdkconfig "github.com/router-for-me/CLIProxyAPI/v7/sdk/config"
|
||||
)
|
||||
|
||||
type compactCaptureExecutor struct {
|
||||
|
||||
@@ -16,10 +16,10 @@ import (
|
||||
"sort"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
. "github.com/router-for-me/CLIProxyAPI/v6/internal/constant"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/registry"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/sdk/api/handlers"
|
||||
. "github.com/router-for-me/CLIProxyAPI/v7/internal/constant"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/interfaces"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/registry"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/sdk/api/handlers"
|
||||
"github.com/tidwall/gjson"
|
||||
"github.com/tidwall/sjson"
|
||||
)
|
||||
|
||||
@@ -8,9 +8,9 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/sdk/api/handlers"
|
||||
sdkconfig "github.com/router-for-me/CLIProxyAPI/v6/sdk/config"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/interfaces"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/sdk/api/handlers"
|
||||
sdkconfig "github.com/router-for-me/CLIProxyAPI/v7/sdk/config"
|
||||
)
|
||||
|
||||
func TestForwardResponsesStreamTerminalErrorUsesResponsesErrorChunk(t *testing.T) {
|
||||
|
||||
@@ -7,9 +7,9 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/sdk/api/handlers"
|
||||
sdkconfig "github.com/router-for-me/CLIProxyAPI/v6/sdk/config"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/interfaces"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/sdk/api/handlers"
|
||||
sdkconfig "github.com/router-for-me/CLIProxyAPI/v7/sdk/config"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
|
||||
@@ -13,13 +13,13 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/registry"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/thinking"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/util"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/sdk/api/handlers"
|
||||
coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
|
||||
cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/interfaces"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/registry"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/thinking"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/util"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/sdk/api/handlers"
|
||||
coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth"
|
||||
cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/tidwall/gjson"
|
||||
"github.com/tidwall/sjson"
|
||||
|
||||
@@ -14,12 +14,12 @@ import (
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/registry"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/sdk/api/handlers"
|
||||
coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
|
||||
coreexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor"
|
||||
sdkconfig "github.com/router-for-me/CLIProxyAPI/v6/sdk/config"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/interfaces"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/registry"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/sdk/api/handlers"
|
||||
coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth"
|
||||
coreexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor"
|
||||
sdkconfig "github.com/router-for-me/CLIProxyAPI/v7/sdk/config"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/interfaces"
|
||||
)
|
||||
|
||||
type StreamForwardOptions struct {
|
||||
|
||||
+82
-7
@@ -1,16 +1,21 @@
|
||||
// Package api exposes helpers for embedding CLIProxyAPI.
|
||||
//
|
||||
// It wraps internal management handler types so external projects can integrate
|
||||
// management endpoints without importing internal packages.
|
||||
// It wraps internal management handler types and helpers so external projects
|
||||
// can integrate management endpoints without importing internal packages.
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
internalmanagement "github.com/router-for-me/CLIProxyAPI/v6/internal/api/handlers/management"
|
||||
coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/sdk/config"
|
||||
internalmanagement "github.com/router-for-me/CLIProxyAPI/v7/internal/api/handlers/management"
|
||||
coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/sdk/config"
|
||||
)
|
||||
|
||||
// Handler re-exports the management handler used by the internal HTTP API.
|
||||
type Handler = internalmanagement.Handler
|
||||
|
||||
// ManagementTokenRequester exposes a limited subset of management endpoints for requesting tokens.
|
||||
type ManagementTokenRequester interface {
|
||||
RequestAnthropicToken(*gin.Context)
|
||||
@@ -23,13 +28,23 @@ type ManagementTokenRequester interface {
|
||||
}
|
||||
|
||||
type managementTokenRequester struct {
|
||||
handler *internalmanagement.Handler
|
||||
handler *Handler
|
||||
}
|
||||
|
||||
// NewHandler creates a management handler for SDK consumers.
|
||||
func NewHandler(cfg *config.Config, configFilePath string, manager *coreauth.Manager) *Handler {
|
||||
return internalmanagement.NewHandler(cfg, configFilePath, manager)
|
||||
}
|
||||
|
||||
// NewHandlerWithoutConfigFilePath creates a management handler that skips config file persistence.
|
||||
func NewHandlerWithoutConfigFilePath(cfg *config.Config, manager *coreauth.Manager) *Handler {
|
||||
return internalmanagement.NewHandlerWithoutConfigFilePath(cfg, manager)
|
||||
}
|
||||
|
||||
// NewManagementTokenRequester creates a limited management handler exposing only token request endpoints.
|
||||
func NewManagementTokenRequester(cfg *config.Config, manager *coreauth.Manager) ManagementTokenRequester {
|
||||
return &managementTokenRequester{
|
||||
handler: internalmanagement.NewHandlerWithoutConfigFilePath(cfg, manager),
|
||||
handler: NewHandlerWithoutConfigFilePath(cfg, manager),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,3 +75,63 @@ func (m *managementTokenRequester) GetAuthStatus(c *gin.Context) {
|
||||
func (m *managementTokenRequester) PostOAuthCallback(c *gin.Context) {
|
||||
m.handler.PostOAuthCallback(c)
|
||||
}
|
||||
|
||||
// WriteConfig persists management configuration to disk.
|
||||
func WriteConfig(path string, data []byte) error {
|
||||
return internalmanagement.WriteConfig(path, data)
|
||||
}
|
||||
|
||||
// RegisterOAuthSession records a pending OAuth callback state.
|
||||
func RegisterOAuthSession(state, provider string) {
|
||||
internalmanagement.RegisterOAuthSession(state, provider)
|
||||
}
|
||||
|
||||
// SetOAuthSessionError stores an OAuth session error message.
|
||||
func SetOAuthSessionError(state, message string) {
|
||||
internalmanagement.SetOAuthSessionError(state, message)
|
||||
}
|
||||
|
||||
// CompleteOAuthSession marks a single OAuth session as completed.
|
||||
func CompleteOAuthSession(state string) {
|
||||
internalmanagement.CompleteOAuthSession(state)
|
||||
}
|
||||
|
||||
// CompleteOAuthSessionsByProvider removes all pending OAuth sessions for a provider.
|
||||
func CompleteOAuthSessionsByProvider(provider string) int {
|
||||
return internalmanagement.CompleteOAuthSessionsByProvider(provider)
|
||||
}
|
||||
|
||||
// GetOAuthSession returns the current OAuth session state.
|
||||
func GetOAuthSession(state string) (provider string, status string, ok bool) {
|
||||
return internalmanagement.GetOAuthSession(state)
|
||||
}
|
||||
|
||||
// IsOAuthSessionPending reports whether a provider/state pair is still pending.
|
||||
func IsOAuthSessionPending(state, provider string) bool {
|
||||
return internalmanagement.IsOAuthSessionPending(state, provider)
|
||||
}
|
||||
|
||||
// ValidateOAuthState validates an OAuth state token.
|
||||
func ValidateOAuthState(state string) error {
|
||||
return internalmanagement.ValidateOAuthState(state)
|
||||
}
|
||||
|
||||
// NormalizeOAuthProvider normalizes a provider name to its canonical form.
|
||||
func NormalizeOAuthProvider(provider string) (string, error) {
|
||||
return internalmanagement.NormalizeOAuthProvider(provider)
|
||||
}
|
||||
|
||||
// WriteOAuthCallbackFile writes an OAuth callback payload to disk.
|
||||
func WriteOAuthCallbackFile(authDir, provider, state, code, errorMessage string) (string, error) {
|
||||
return internalmanagement.WriteOAuthCallbackFile(authDir, provider, state, code, errorMessage)
|
||||
}
|
||||
|
||||
// WriteOAuthCallbackFileForPendingSession writes an OAuth callback payload for a pending session.
|
||||
func WriteOAuthCallbackFileForPendingSession(authDir, provider, state, code, errorMessage string) (string, error) {
|
||||
return internalmanagement.WriteOAuthCallbackFileForPendingSession(authDir, provider, state, code, errorMessage)
|
||||
}
|
||||
|
||||
// PopulateAuthContext copies auth metadata from a Gin context into a request context.
|
||||
func PopulateAuthContext(ctx context.Context, c *gin.Context) context.Context {
|
||||
return internalmanagement.PopulateAuthContext(ctx, c)
|
||||
}
|
||||
|
||||
+4
-4
@@ -8,10 +8,10 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
internalapi "github.com/router-for-me/CLIProxyAPI/v6/internal/api"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/sdk/api/handlers"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/sdk/config"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/sdk/logging"
|
||||
internalapi "github.com/router-for-me/CLIProxyAPI/v7/internal/api"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/sdk/api/handlers"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/sdk/config"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/sdk/logging"
|
||||
)
|
||||
|
||||
// ServerOption customises HTTP server construction.
|
||||
|
||||
@@ -8,12 +8,12 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/auth/antigravity"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/browser"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/misc"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/util"
|
||||
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/browser"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/config"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/misc"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/util"
|
||||
coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
|
||||
+6
-6
@@ -7,13 +7,13 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/auth/claude"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/browser"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/auth/claude"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/browser"
|
||||
// legacy client removed
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/misc"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/util"
|
||||
coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/config"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/misc"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/util"
|
||||
coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
|
||||
+6
-6
@@ -7,13 +7,13 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/auth/codex"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/browser"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/auth/codex"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/browser"
|
||||
// legacy client removed
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/misc"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/util"
|
||||
coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/config"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/misc"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/util"
|
||||
coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
|
||||
@@ -13,11 +13,11 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/auth/codex"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/browser"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/util"
|
||||
coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/auth/codex"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/browser"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/config"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/util"
|
||||
coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ package auth
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/interfaces"
|
||||
)
|
||||
|
||||
// ProjectSelectionError indicates that the user must choose a specific project ID.
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
cliproxyauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
|
||||
cliproxyauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth"
|
||||
)
|
||||
|
||||
// FileTokenStore persists token records and auth metadata using the filesystem as backing storage.
|
||||
@@ -72,6 +72,10 @@ func (s *FileTokenStore) Save(ctx context.Context, auth *cliproxyauth.Auth) (str
|
||||
|
||||
switch {
|
||||
case auth.Storage != nil:
|
||||
if auth.Metadata == nil {
|
||||
auth.Metadata = make(map[string]any)
|
||||
}
|
||||
auth.Metadata["disabled"] = auth.Disabled
|
||||
if setter, ok := auth.Storage.(metadataSetter); ok {
|
||||
setter.SetMetadata(auth.Metadata)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
cliproxyauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth"
|
||||
)
|
||||
|
||||
type testTokenStorage struct {
|
||||
meta map[string]any
|
||||
}
|
||||
|
||||
func (s *testTokenStorage) SetMetadata(meta map[string]any) { s.meta = meta }
|
||||
|
||||
func (s *testTokenStorage) SaveTokenToFile(authFilePath string) error {
|
||||
raw, err := json.Marshal(s.meta)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(authFilePath, raw, 0o600)
|
||||
}
|
||||
|
||||
func TestFileTokenStore_Save_DisabledPersistsFlagForTokenStorage(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
baseDir := t.TempDir()
|
||||
path := filepath.Join(baseDir, "disabled.json")
|
||||
|
||||
if err := os.WriteFile(path, []byte(`{"type":"test","disabled":true}`), 0o600); err != nil {
|
||||
t.Fatalf("seed auth file: %v", err)
|
||||
}
|
||||
|
||||
store := NewFileTokenStore()
|
||||
store.SetBaseDir(baseDir)
|
||||
storage := &testTokenStorage{}
|
||||
|
||||
auth := &cliproxyauth.Auth{
|
||||
ID: "disabled.json",
|
||||
Provider: "test",
|
||||
FileName: "disabled.json",
|
||||
Disabled: true,
|
||||
Storage: storage,
|
||||
Metadata: map[string]any{"type": "test"},
|
||||
}
|
||||
|
||||
if _, err := store.Save(ctx, auth); err != nil {
|
||||
t.Fatalf("Save() error: %v", err)
|
||||
}
|
||||
|
||||
raw, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
t.Fatalf("read auth file: %v", err)
|
||||
}
|
||||
var meta map[string]any
|
||||
if err := json.Unmarshal(raw, &meta); err != nil {
|
||||
t.Fatalf("unmarshal auth file: %v", err)
|
||||
}
|
||||
if disabled, _ := meta["disabled"].(bool); !disabled {
|
||||
t.Fatalf("disabled=%v, want true (raw=%s)", meta["disabled"], string(raw))
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -5,10 +5,10 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/auth/gemini"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/auth/gemini"
|
||||
// legacy client removed
|
||||
"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"
|
||||
)
|
||||
|
||||
// GeminiAuthenticator implements the login flow for Google Gemini CLI accounts.
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
var ErrRefreshNotSupported = errors.New("cliproxy auth: refresh not supported")
|
||||
|
||||
+4
-4
@@ -6,10 +6,10 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/auth/kimi"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/browser"
|
||||
"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/auth/kimi"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/browser"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/config"
|
||||
coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
|
||||
+2
-2
@@ -4,8 +4,8 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
// Manager aggregates authenticators and coordinates persistence via a token store.
|
||||
|
||||
@@ -3,7 +3,7 @@ package auth
|
||||
import (
|
||||
"time"
|
||||
|
||||
cliproxyauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
|
||||
cliproxyauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
||||
@@ -3,7 +3,7 @@ package auth
|
||||
import (
|
||||
"sync"
|
||||
|
||||
coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
|
||||
coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth"
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
@@ -7,9 +7,9 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
internalconfig "github.com/router-for-me/CLIProxyAPI/v6/internal/config"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/registry"
|
||||
cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor"
|
||||
internalconfig "github.com/router-for-me/CLIProxyAPI/v7/internal/config"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/registry"
|
||||
cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor"
|
||||
)
|
||||
|
||||
type antigravityCreditsFallbackExecutor struct {
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
internalconfig "github.com/router-for-me/CLIProxyAPI/v6/internal/config"
|
||||
internalconfig "github.com/router-for-me/CLIProxyAPI/v7/internal/config"
|
||||
)
|
||||
|
||||
func TestLookupAPIKeyUpstreamModel(t *testing.T) {
|
||||
|
||||
@@ -336,7 +336,7 @@ func (l *authAutoRefreshLoop) remove(authID string) {
|
||||
}
|
||||
|
||||
func nextRefreshCheckAt(now time.Time, auth *Auth, interval time.Duration) (time.Time, bool) {
|
||||
if auth == nil || auth.Disabled {
|
||||
if auth == nil {
|
||||
return time.Time{}, false
|
||||
}
|
||||
|
||||
|
||||
@@ -34,9 +34,31 @@ func setRefreshLeadFactory(t *testing.T, provider string, factory func() *time.D
|
||||
|
||||
func TestNextRefreshCheckAt_DisabledUnschedule(t *testing.T) {
|
||||
now := time.Date(2026, 4, 12, 0, 0, 0, 0, time.UTC)
|
||||
auth := &Auth{ID: "a1", Provider: "test", Disabled: true}
|
||||
if _, ok := nextRefreshCheckAt(now, auth, 15*time.Minute); ok {
|
||||
t.Fatalf("nextRefreshCheckAt() ok = true, want false")
|
||||
expiry := now.Add(time.Hour)
|
||||
lead := 10 * time.Minute
|
||||
setRefreshLeadFactory(t, "disabled-schedule", func() *time.Duration {
|
||||
d := lead
|
||||
return &d
|
||||
})
|
||||
|
||||
auth := &Auth{
|
||||
ID: "a1",
|
||||
Provider: "disabled-schedule",
|
||||
Disabled: true,
|
||||
Status: StatusDisabled,
|
||||
Metadata: map[string]any{
|
||||
"email": "x@example.com",
|
||||
"expires_at": expiry.Format(time.RFC3339),
|
||||
},
|
||||
}
|
||||
|
||||
got, ok := nextRefreshCheckAt(now, auth, 15*time.Minute)
|
||||
if !ok {
|
||||
t.Fatalf("nextRefreshCheckAt() ok = false, want true")
|
||||
}
|
||||
want := expiry.Add(-lead)
|
||||
if !got.Equal(want) {
|
||||
t.Fatalf("nextRefreshCheckAt() = %s, want %s", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+285
-18
@@ -16,13 +16,14 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
internalconfig "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/registry"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/thinking"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/util"
|
||||
cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor"
|
||||
coreusage "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/usage"
|
||||
internalconfig "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/registry"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/thinking"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/util"
|
||||
cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor"
|
||||
coreusage "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/usage"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
@@ -50,6 +51,7 @@ type ExecutionSessionCloser interface {
|
||||
}
|
||||
|
||||
const (
|
||||
homeAuthCountMetadataKey = "__cliproxy_home_auth_count"
|
||||
// CloseAllExecutionSessionsID asks an executor to release all active execution sessions.
|
||||
// Executors that do not support this marker may ignore it.
|
||||
CloseAllExecutionSessionsID = "__all_execution_sessions__"
|
||||
@@ -377,6 +379,15 @@ func (m *Manager) SetConfig(cfg *internalconfig.Config) {
|
||||
m.rebuildAPIKeyModelAliasFromRuntimeConfig()
|
||||
}
|
||||
|
||||
// HomeEnabled reports whether the home control plane integration is enabled in the runtime config.
|
||||
func (m *Manager) HomeEnabled() bool {
|
||||
if m == nil {
|
||||
return false
|
||||
}
|
||||
cfg, _ := m.runtimeConfig.Load().(*internalconfig.Config)
|
||||
return cfg != nil && cfg.Home.Enabled
|
||||
}
|
||||
|
||||
func (m *Manager) lookupAPIKeyUpstreamModel(authID, requestedModel string) string {
|
||||
if m == nil {
|
||||
return ""
|
||||
@@ -522,6 +533,11 @@ func preserveRequestedModelSuffix(requestedModel, resolved string) string {
|
||||
}
|
||||
|
||||
func (m *Manager) executionModelCandidates(auth *Auth, routeModel string) []string {
|
||||
if auth != nil && auth.Attributes != nil {
|
||||
if homeModel := strings.TrimSpace(auth.Attributes[homeUpstreamModelAttributeKey]); homeModel != "" {
|
||||
return []string{homeModel}
|
||||
}
|
||||
}
|
||||
requestedModel := rewriteModelForAuth(routeModel, auth)
|
||||
requestedModel = m.applyOAuthModelAlias(auth, requestedModel)
|
||||
if pool := m.resolveOpenAICompatUpstreamModelPool(auth, requestedModel); len(pool) > 0 {
|
||||
@@ -555,6 +571,14 @@ func (m *Manager) selectionModelKeyForAuth(auth *Auth, routeModel string) string
|
||||
}
|
||||
|
||||
func (m *Manager) stateModelForExecution(auth *Auth, routeModel, upstreamModel string, pooled bool) string {
|
||||
if auth != nil && auth.Attributes != nil {
|
||||
if homeModel := strings.TrimSpace(auth.Attributes[homeUpstreamModelAttributeKey]); homeModel != "" {
|
||||
if resolved := strings.TrimSpace(upstreamModel); resolved != "" {
|
||||
return resolved
|
||||
}
|
||||
return homeModel
|
||||
}
|
||||
}
|
||||
stateModel := executionResultModel(routeModel, upstreamModel, pooled)
|
||||
selectionModel := m.selectionModelForAuth(auth, routeModel)
|
||||
if canonicalModelKey(selectionModel) == canonicalModelKey(upstreamModel) && strings.TrimSpace(selectionModel) != "" {
|
||||
@@ -1293,19 +1317,25 @@ func (m *Manager) executeMixedOnce(ctx context.Context, providers []string, req
|
||||
}
|
||||
routeModel := req.Model
|
||||
opts = ensureRequestedModelMetadata(opts, routeModel)
|
||||
homeMode := m.HomeEnabled()
|
||||
homeAuthCount := 1
|
||||
tried := make(map[string]struct{})
|
||||
attempted := make(map[string]struct{})
|
||||
var lastErr error
|
||||
for {
|
||||
if maxRetryCredentials > 0 && len(attempted) >= maxRetryCredentials {
|
||||
if !homeMode && maxRetryCredentials > 0 && len(attempted) >= maxRetryCredentials {
|
||||
if lastErr != nil {
|
||||
return cliproxyexecutor.Response{}, lastErr
|
||||
}
|
||||
return cliproxyexecutor.Response{}, &Error{Code: "auth_not_found", Message: "no auth available"}
|
||||
}
|
||||
auth, executor, provider, errPick := m.pickNextMixed(ctx, providers, routeModel, opts, tried)
|
||||
pickOpts := opts
|
||||
if homeMode {
|
||||
pickOpts = withHomeAuthCount(opts, homeAuthCount)
|
||||
}
|
||||
auth, executor, provider, errPick := m.pickNextMixed(ctx, providers, routeModel, pickOpts, tried)
|
||||
if errPick != nil {
|
||||
if lastErr != nil {
|
||||
if shouldReturnLastErrorOnPickFailure(homeMode, lastErr, errPick) {
|
||||
return cliproxyexecutor.Response{}, lastErr
|
||||
}
|
||||
return cliproxyexecutor.Response{}, errPick
|
||||
@@ -1361,6 +1391,9 @@ func (m *Manager) executeMixedOnce(ctx context.Context, providers []string, req
|
||||
return cliproxyexecutor.Response{}, authErr
|
||||
}
|
||||
lastErr = authErr
|
||||
if homeMode {
|
||||
homeAuthCount++
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
@@ -1372,19 +1405,25 @@ func (m *Manager) executeCountMixedOnce(ctx context.Context, providers []string,
|
||||
}
|
||||
routeModel := req.Model
|
||||
opts = ensureRequestedModelMetadata(opts, routeModel)
|
||||
homeMode := m.HomeEnabled()
|
||||
homeAuthCount := 1
|
||||
tried := make(map[string]struct{})
|
||||
attempted := make(map[string]struct{})
|
||||
var lastErr error
|
||||
for {
|
||||
if maxRetryCredentials > 0 && len(attempted) >= maxRetryCredentials {
|
||||
if !homeMode && maxRetryCredentials > 0 && len(attempted) >= maxRetryCredentials {
|
||||
if lastErr != nil {
|
||||
return cliproxyexecutor.Response{}, lastErr
|
||||
}
|
||||
return cliproxyexecutor.Response{}, &Error{Code: "auth_not_found", Message: "no auth available"}
|
||||
}
|
||||
auth, executor, provider, errPick := m.pickNextMixed(ctx, providers, routeModel, opts, tried)
|
||||
pickOpts := opts
|
||||
if homeMode {
|
||||
pickOpts = withHomeAuthCount(opts, homeAuthCount)
|
||||
}
|
||||
auth, executor, provider, errPick := m.pickNextMixed(ctx, providers, routeModel, pickOpts, tried)
|
||||
if errPick != nil {
|
||||
if lastErr != nil {
|
||||
if shouldReturnLastErrorOnPickFailure(homeMode, lastErr, errPick) {
|
||||
return cliproxyexecutor.Response{}, lastErr
|
||||
}
|
||||
return cliproxyexecutor.Response{}, errPick
|
||||
@@ -1440,6 +1479,9 @@ func (m *Manager) executeCountMixedOnce(ctx context.Context, providers []string,
|
||||
return cliproxyexecutor.Response{}, authErr
|
||||
}
|
||||
lastErr = authErr
|
||||
if homeMode {
|
||||
homeAuthCount++
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
@@ -1451,19 +1493,25 @@ func (m *Manager) executeStreamMixedOnce(ctx context.Context, providers []string
|
||||
}
|
||||
routeModel := req.Model
|
||||
opts = ensureRequestedModelMetadata(opts, routeModel)
|
||||
homeMode := m.HomeEnabled()
|
||||
homeAuthCount := 1
|
||||
tried := make(map[string]struct{})
|
||||
attempted := make(map[string]struct{})
|
||||
var lastErr error
|
||||
for {
|
||||
if maxRetryCredentials > 0 && len(attempted) >= maxRetryCredentials {
|
||||
if !homeMode && maxRetryCredentials > 0 && len(attempted) >= maxRetryCredentials {
|
||||
if lastErr != nil {
|
||||
return nil, lastErr
|
||||
}
|
||||
return nil, &Error{Code: "auth_not_found", Message: "no auth available"}
|
||||
}
|
||||
auth, executor, provider, errPick := m.pickNextMixed(ctx, providers, routeModel, opts, tried)
|
||||
pickOpts := opts
|
||||
if homeMode {
|
||||
pickOpts = withHomeAuthCount(opts, homeAuthCount)
|
||||
}
|
||||
auth, executor, provider, errPick := m.pickNextMixed(ctx, providers, routeModel, pickOpts, tried)
|
||||
if errPick != nil {
|
||||
if lastErr != nil {
|
||||
if shouldReturnLastErrorOnPickFailure(homeMode, lastErr, errPick) {
|
||||
return nil, lastErr
|
||||
}
|
||||
return nil, errPick
|
||||
@@ -1493,6 +1541,9 @@ func (m *Manager) executeStreamMixedOnce(ctx context.Context, providers []string
|
||||
return nil, errStream
|
||||
}
|
||||
lastErr = errStream
|
||||
if homeMode {
|
||||
homeAuthCount++
|
||||
}
|
||||
continue
|
||||
}
|
||||
return streamResult, nil
|
||||
@@ -1520,6 +1571,40 @@ func ensureRequestedModelMetadata(opts cliproxyexecutor.Options, requestedModel
|
||||
return opts
|
||||
}
|
||||
|
||||
func withHomeAuthCount(opts cliproxyexecutor.Options, count int) cliproxyexecutor.Options {
|
||||
if count <= 0 {
|
||||
count = 1
|
||||
}
|
||||
meta := make(map[string]any, len(opts.Metadata)+1)
|
||||
for k, v := range opts.Metadata {
|
||||
meta[k] = v
|
||||
}
|
||||
meta[homeAuthCountMetadataKey] = count
|
||||
opts.Metadata = meta
|
||||
return opts
|
||||
}
|
||||
|
||||
func homeAuthCountFromMetadata(meta map[string]any) int {
|
||||
if len(meta) == 0 {
|
||||
return 1
|
||||
}
|
||||
switch value := meta[homeAuthCountMetadataKey].(type) {
|
||||
case int:
|
||||
if value > 0 {
|
||||
return value
|
||||
}
|
||||
case int64:
|
||||
if value > 0 {
|
||||
return int(value)
|
||||
}
|
||||
case float64:
|
||||
if value > 0 {
|
||||
return int(value)
|
||||
}
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
func hasRequestedModelMetadata(meta map[string]any) bool {
|
||||
if len(meta) == 0 {
|
||||
return false
|
||||
@@ -2710,6 +2795,11 @@ func (m *Manager) routeAwareSelectionRequired(auth *Auth, routeModel string) boo
|
||||
}
|
||||
|
||||
func (m *Manager) pickNextLegacy(ctx context.Context, provider, model string, opts cliproxyexecutor.Options, tried map[string]struct{}) (*Auth, ProviderExecutor, error) {
|
||||
if m.HomeEnabled() {
|
||||
auth, exec, _, err := m.pickNextViaHome(ctx, model, opts)
|
||||
return auth, exec, err
|
||||
}
|
||||
|
||||
pinnedAuthID := pinnedAuthIDFromMetadata(opts.Metadata)
|
||||
disallowFreeAuth := disallowFreeAuthFromMetadata(opts.Metadata)
|
||||
|
||||
@@ -2779,6 +2869,11 @@ func (m *Manager) pickNextLegacy(ctx context.Context, provider, model string, op
|
||||
}
|
||||
|
||||
func (m *Manager) pickNext(ctx context.Context, provider, model string, opts cliproxyexecutor.Options, tried map[string]struct{}) (*Auth, ProviderExecutor, error) {
|
||||
if m.HomeEnabled() {
|
||||
auth, exec, _, err := m.pickNextViaHome(ctx, model, opts)
|
||||
return auth, exec, err
|
||||
}
|
||||
|
||||
if !m.useSchedulerFastPath() {
|
||||
return m.pickNextLegacy(ctx, provider, model, opts, tried)
|
||||
}
|
||||
@@ -2836,6 +2931,10 @@ func (m *Manager) pickNext(ctx context.Context, provider, model string, opts cli
|
||||
}
|
||||
|
||||
func (m *Manager) pickNextMixedLegacy(ctx context.Context, providers []string, model string, opts cliproxyexecutor.Options, tried map[string]struct{}) (*Auth, ProviderExecutor, string, error) {
|
||||
if m.HomeEnabled() {
|
||||
return m.pickNextViaHome(ctx, model, opts)
|
||||
}
|
||||
|
||||
pinnedAuthID := pinnedAuthIDFromMetadata(opts.Metadata)
|
||||
disallowFreeAuth := disallowFreeAuthFromMetadata(opts.Metadata)
|
||||
|
||||
@@ -2928,6 +3027,10 @@ func (m *Manager) pickNextMixedLegacy(ctx context.Context, providers []string, m
|
||||
}
|
||||
|
||||
func (m *Manager) pickNextMixed(ctx context.Context, providers []string, model string, opts cliproxyexecutor.Options, tried map[string]struct{}) (*Auth, ProviderExecutor, string, error) {
|
||||
if m.HomeEnabled() {
|
||||
return m.pickNextViaHome(ctx, model, opts)
|
||||
}
|
||||
|
||||
if !m.useSchedulerFastPath() {
|
||||
return m.pickNextMixedLegacy(ctx, providers, model, opts, tried)
|
||||
}
|
||||
@@ -3012,6 +3115,170 @@ func (m *Manager) pickNextMixed(ctx context.Context, providers []string, model s
|
||||
}
|
||||
}
|
||||
|
||||
type homeErrorEnvelope struct {
|
||||
Error *homeErrorDetail `json:"error"`
|
||||
}
|
||||
|
||||
type homeErrorDetail struct {
|
||||
Type string `json:"type"`
|
||||
Message string `json:"message"`
|
||||
Code string `json:"code,omitempty"`
|
||||
}
|
||||
|
||||
const (
|
||||
homeUpstreamModelAttributeKey = "home_upstream_model"
|
||||
homeRequestRetryExceededErrorCode = "request_retry_exceeded"
|
||||
)
|
||||
|
||||
func isHomeRequestRetryExceededError(err error) bool {
|
||||
var authErr *Error
|
||||
if !errors.As(err, &authErr) || authErr == nil {
|
||||
return false
|
||||
}
|
||||
return strings.EqualFold(strings.TrimSpace(authErr.Code), homeRequestRetryExceededErrorCode)
|
||||
}
|
||||
|
||||
func shouldReturnLastErrorOnPickFailure(homeMode bool, lastErr error, errPick error) bool {
|
||||
if lastErr == nil {
|
||||
return false
|
||||
}
|
||||
if !homeMode {
|
||||
return true
|
||||
}
|
||||
return isHomeRequestRetryExceededError(errPick)
|
||||
}
|
||||
|
||||
type homeAuthDispatchResponse struct {
|
||||
Model string `json:"model"`
|
||||
Provider string `json:"provider"`
|
||||
AuthIndex string `json:"auth_index"`
|
||||
UserAPIKey string `json:"user_api_key"`
|
||||
Auth Auth `json:"auth"`
|
||||
}
|
||||
|
||||
func setHomeUserAPIKeyOnGinContext(ctx context.Context, apiKey string) {
|
||||
apiKey = strings.TrimSpace(apiKey)
|
||||
if apiKey == "" || ctx == nil {
|
||||
return
|
||||
}
|
||||
ginCtx, ok := ctx.Value("gin").(interface{ Set(string, any) })
|
||||
if !ok || ginCtx == nil {
|
||||
return
|
||||
}
|
||||
ginCtx.Set("userApiKey", apiKey)
|
||||
}
|
||||
|
||||
func (m *Manager) pickNextViaHome(ctx context.Context, model string, opts cliproxyexecutor.Options) (*Auth, ProviderExecutor, string, error) {
|
||||
if m == nil {
|
||||
return nil, nil, "", &Error{Code: "auth_not_found", Message: "no auth available"}
|
||||
}
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
client := home.Current()
|
||||
if client == nil || !client.HeartbeatOK() {
|
||||
return nil, nil, "", &Error{Code: "home_unavailable", Message: "home control center unavailable", HTTPStatus: http.StatusServiceUnavailable}
|
||||
}
|
||||
|
||||
requestedModel := requestedModelFromMetadata(opts.Metadata, model)
|
||||
sessionID := ExtractSessionID(opts.Headers, opts.OriginalRequest, opts.Metadata)
|
||||
count := homeAuthCountFromMetadata(opts.Metadata)
|
||||
|
||||
raw, err := client.RPopAuth(ctx, requestedModel, sessionID, opts.Headers, count)
|
||||
if err != nil {
|
||||
return nil, nil, "", &Error{Code: "auth_not_found", Message: err.Error(), HTTPStatus: http.StatusServiceUnavailable}
|
||||
}
|
||||
|
||||
var env homeErrorEnvelope
|
||||
if errUnmarshal := json.Unmarshal(raw, &env); errUnmarshal == nil && env.Error != nil {
|
||||
code := strings.TrimSpace(env.Error.Type)
|
||||
if code == "" {
|
||||
code = strings.TrimSpace(env.Error.Code)
|
||||
}
|
||||
msg := strings.TrimSpace(env.Error.Message)
|
||||
if msg == "" {
|
||||
msg = "home returned error"
|
||||
}
|
||||
status := http.StatusBadGateway
|
||||
switch strings.ToLower(code) {
|
||||
case "model_not_found":
|
||||
status = http.StatusNotFound
|
||||
case "authentication_error", "unauthorized":
|
||||
status = http.StatusUnauthorized
|
||||
}
|
||||
return nil, nil, "", &Error{Code: code, Message: msg, HTTPStatus: status}
|
||||
}
|
||||
|
||||
var dispatch homeAuthDispatchResponse
|
||||
if errUnmarshal := json.Unmarshal(raw, &dispatch); errUnmarshal != nil {
|
||||
return nil, nil, "", &Error{Code: "invalid_auth", Message: "home returned invalid auth payload", HTTPStatus: http.StatusBadGateway}
|
||||
}
|
||||
setHomeUserAPIKeyOnGinContext(ctx, dispatch.UserAPIKey)
|
||||
auth := dispatch.Auth
|
||||
if strings.TrimSpace(auth.ID) == "" {
|
||||
// Backward compatibility: older home instances returned the auth directly.
|
||||
if errUnmarshal := json.Unmarshal(raw, &auth); errUnmarshal != nil {
|
||||
return nil, nil, "", &Error{Code: "invalid_auth", Message: "home returned invalid auth payload", HTTPStatus: http.StatusBadGateway}
|
||||
}
|
||||
}
|
||||
if upstreamModel := strings.TrimSpace(dispatch.Model); upstreamModel != "" {
|
||||
if auth.Attributes == nil {
|
||||
auth.Attributes = make(map[string]string, 1)
|
||||
}
|
||||
auth.Attributes[homeUpstreamModelAttributeKey] = upstreamModel
|
||||
}
|
||||
if strings.TrimSpace(auth.ID) == "" {
|
||||
return nil, nil, "", &Error{Code: "invalid_auth", Message: "home returned auth without id", HTTPStatus: http.StatusBadGateway}
|
||||
}
|
||||
providerKey := strings.ToLower(strings.TrimSpace(auth.Provider))
|
||||
if providerKey == "" {
|
||||
return nil, nil, "", &Error{Code: "invalid_auth", Message: "home returned auth without provider", HTTPStatus: http.StatusBadGateway}
|
||||
}
|
||||
|
||||
homeAuthIndex := strings.TrimSpace(dispatch.AuthIndex)
|
||||
if homeAuthIndex != "" {
|
||||
auth.Index = homeAuthIndex
|
||||
auth.indexAssigned = true
|
||||
} else {
|
||||
auth.EnsureIndex()
|
||||
}
|
||||
|
||||
executor, ok := m.Executor(providerKey)
|
||||
if !ok && auth.Attributes != nil && strings.TrimSpace(auth.Attributes["base_url"]) != "" {
|
||||
executor, ok = m.Executor("openai-compatibility")
|
||||
if ok {
|
||||
providerKey = "openai-compatibility"
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
return nil, nil, "", &Error{Code: "executor_not_found", Message: "executor not registered", HTTPStatus: http.StatusBadGateway}
|
||||
}
|
||||
|
||||
return auth.Clone(), executor, providerKey, nil
|
||||
}
|
||||
|
||||
func requestedModelFromMetadata(metadata map[string]any, fallback string) string {
|
||||
if metadata != nil {
|
||||
if v, ok := metadata[cliproxyexecutor.RequestedModelMetadataKey]; ok {
|
||||
switch typed := v.(type) {
|
||||
case string:
|
||||
if trimmed := strings.TrimSpace(typed); trimmed != "" {
|
||||
return trimmed
|
||||
}
|
||||
case []byte:
|
||||
if trimmed := strings.TrimSpace(string(typed)); trimmed != "" {
|
||||
return trimmed
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fallback = strings.TrimSpace(fallback)
|
||||
if fallback == "" {
|
||||
return "unknown"
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
func (m *Manager) findAllAntigravityCreditsCandidateAuths(routeModel string, opts cliproxyexecutor.Options) []creditsCandidateEntry {
|
||||
if m == nil {
|
||||
return nil
|
||||
@@ -3271,7 +3538,7 @@ func (m *Manager) queueRefreshReschedule(authID string) {
|
||||
}
|
||||
|
||||
func (m *Manager) shouldRefresh(a *Auth, now time.Time) bool {
|
||||
if a == nil || a.Disabled {
|
||||
if a == nil {
|
||||
return false
|
||||
}
|
||||
if !a.NextRefreshAfter.IsZero() && now.Before(a.NextRefreshAfter) {
|
||||
@@ -3478,7 +3745,7 @@ func lookupMetadataTime(meta map[string]any, keys ...string) (time.Time, bool) {
|
||||
func (m *Manager) markRefreshPending(id string, now time.Time) bool {
|
||||
m.mu.Lock()
|
||||
auth, ok := m.auths[id]
|
||||
if !ok || auth == nil || auth.Disabled {
|
||||
if !ok || auth == nil {
|
||||
m.mu.Unlock()
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor"
|
||||
cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor"
|
||||
)
|
||||
|
||||
func TestFindAllAntigravityCreditsCandidateAuths_PrefersKnownCreditsThenUnknown(t *testing.T) {
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor"
|
||||
cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor"
|
||||
)
|
||||
|
||||
type replaceAwareExecutor struct {
|
||||
|
||||
@@ -7,10 +7,10 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
internalconfig "github.com/router-for-me/CLIProxyAPI/v6/internal/config"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/registry"
|
||||
cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor"
|
||||
coreusage "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/usage"
|
||||
internalconfig "github.com/router-for-me/CLIProxyAPI/v7/internal/config"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/registry"
|
||||
cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor"
|
||||
coreusage "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/usage"
|
||||
)
|
||||
|
||||
type aliasRoutingExecutor struct {
|
||||
|
||||
@@ -8,9 +8,9 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
internalconfig "github.com/router-for-me/CLIProxyAPI/v6/internal/config"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/registry"
|
||||
cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor"
|
||||
internalconfig "github.com/router-for-me/CLIProxyAPI/v7/internal/config"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/registry"
|
||||
cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor"
|
||||
)
|
||||
|
||||
const requestScopedNotFoundMessage = "Item with id 'rs_0b5f3eb6f51f175c0169ca74e4a85881998539920821603a74' not found. Items are not persisted when `store` is set to false. Try again with `store` set to true, or remove this item from your input."
|
||||
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/registry"
|
||||
cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/registry"
|
||||
cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor"
|
||||
)
|
||||
|
||||
type schedulerProviderTestExecutor struct {
|
||||
|
||||
@@ -3,8 +3,8 @@ package auth
|
||||
import (
|
||||
"strings"
|
||||
|
||||
internalconfig "github.com/router-for-me/CLIProxyAPI/v6/internal/config"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/thinking"
|
||||
internalconfig "github.com/router-for-me/CLIProxyAPI/v7/internal/config"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/thinking"
|
||||
)
|
||||
|
||||
type modelAliasEntry interface {
|
||||
|
||||
@@ -3,7 +3,7 @@ package auth
|
||||
import (
|
||||
"testing"
|
||||
|
||||
internalconfig "github.com/router-for-me/CLIProxyAPI/v6/internal/config"
|
||||
internalconfig "github.com/router-for-me/CLIProxyAPI/v7/internal/config"
|
||||
)
|
||||
|
||||
func TestResolveOAuthUpstreamModel_SuffixPreservation(t *testing.T) {
|
||||
|
||||
@@ -7,9 +7,9 @@ import (
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
internalconfig "github.com/router-for-me/CLIProxyAPI/v6/internal/config"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/registry"
|
||||
cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor"
|
||||
internalconfig "github.com/router-for-me/CLIProxyAPI/v7/internal/config"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/registry"
|
||||
cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor"
|
||||
)
|
||||
|
||||
type openAICompatPoolExecutor struct {
|
||||
|
||||
@@ -7,8 +7,8 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/registry"
|
||||
cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/registry"
|
||||
cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor"
|
||||
)
|
||||
|
||||
// schedulerStrategy identifies which built-in routing semantics the scheduler should apply.
|
||||
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/registry"
|
||||
cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/registry"
|
||||
cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor"
|
||||
)
|
||||
|
||||
type schedulerBenchmarkExecutor struct {
|
||||
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/registry"
|
||||
cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/registry"
|
||||
cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor"
|
||||
)
|
||||
|
||||
type schedulerTestExecutor struct{}
|
||||
|
||||
@@ -18,9 +18,9 @@ import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/tidwall/gjson"
|
||||
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/logging"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/thinking"
|
||||
cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/logging"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/thinking"
|
||||
cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor"
|
||||
)
|
||||
|
||||
// RoundRobinSelector provides a simple provider scoped round-robin selection strategy.
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor"
|
||||
cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor"
|
||||
)
|
||||
|
||||
func TestFillFirstSelectorPick_Deterministic(t *testing.T) {
|
||||
|
||||
+58
-28
@@ -7,12 +7,13 @@ import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
baseauth "github.com/router-for-me/CLIProxyAPI/v6/internal/auth"
|
||||
baseauth "github.com/router-for-me/CLIProxyAPI/v7/internal/auth"
|
||||
)
|
||||
|
||||
// PostAuthHook defines a function that is called after an Auth record is created
|
||||
@@ -256,45 +257,65 @@ func (a *Auth) indexSeed() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
if fileName := strings.TrimSpace(a.FileName); fileName != "" {
|
||||
return "file:" + fileName
|
||||
}
|
||||
|
||||
providerKey := strings.ToLower(strings.TrimSpace(a.Provider))
|
||||
provider := strings.ToLower(strings.TrimSpace(a.Provider))
|
||||
compatName := ""
|
||||
baseURL := ""
|
||||
apiKey := ""
|
||||
source := ""
|
||||
filePath := ""
|
||||
if a.Attributes != nil {
|
||||
if value := strings.TrimSpace(a.Attributes["provider_key"]); value != "" {
|
||||
providerKey = strings.ToLower(value)
|
||||
}
|
||||
compatName = strings.ToLower(strings.TrimSpace(a.Attributes["compat_name"]))
|
||||
compatName = strings.TrimSpace(a.Attributes["compat_name"])
|
||||
baseURL = strings.TrimSpace(a.Attributes["base_url"])
|
||||
apiKey = strings.TrimSpace(a.Attributes["api_key"])
|
||||
source = strings.TrimSpace(a.Attributes["source"])
|
||||
filePath = strings.TrimSpace(a.Attributes["path"])
|
||||
if filePath == "" {
|
||||
filePath = strings.TrimSpace(a.Attributes["source"])
|
||||
}
|
||||
}
|
||||
|
||||
proxyURL := strings.TrimSpace(a.ProxyURL)
|
||||
hasCredentialIdentity := compatName != "" || baseURL != "" || proxyURL != "" || apiKey != "" || source != ""
|
||||
if providerKey != "" && hasCredentialIdentity {
|
||||
parts := []string{"provider=" + providerKey}
|
||||
if compatName != "" {
|
||||
parts = append(parts, "compat="+compatName)
|
||||
if filePath == "" {
|
||||
filePath = strings.TrimSpace(a.FileName)
|
||||
}
|
||||
if filePath == "" {
|
||||
filePath = strings.TrimSpace(a.ID)
|
||||
}
|
||||
|
||||
if filePath != "" && strings.HasSuffix(strings.ToLower(filePath), ".json") {
|
||||
abs, errAbs := filepath.Abs(filePath)
|
||||
if errAbs == nil && strings.TrimSpace(abs) != "" {
|
||||
filePath = abs
|
||||
}
|
||||
if baseURL != "" {
|
||||
parts = append(parts, "base="+baseURL)
|
||||
filePath = filepath.Clean(filePath)
|
||||
|
||||
authType := ""
|
||||
if a.Metadata != nil {
|
||||
if rawType, ok := a.Metadata["type"].(string); ok {
|
||||
authType = strings.TrimSpace(rawType)
|
||||
}
|
||||
}
|
||||
if proxyURL != "" {
|
||||
parts = append(parts, "proxy="+proxyURL)
|
||||
if authType == "" {
|
||||
authType = strings.TrimSpace(provider)
|
||||
}
|
||||
if apiKey != "" {
|
||||
parts = append(parts, "api_key="+apiKey)
|
||||
authType = strings.ToLower(strings.TrimSpace(authType))
|
||||
if authType != "" {
|
||||
return authType + ":" + filePath
|
||||
}
|
||||
if source != "" {
|
||||
parts = append(parts, "source="+source)
|
||||
}
|
||||
|
||||
apiPrefix := ""
|
||||
if apiKey != "" {
|
||||
switch {
|
||||
case compatName != "" || strings.EqualFold(provider, "openai-compatibility"):
|
||||
apiPrefix = "openai-compatibility"
|
||||
case strings.EqualFold(provider, "gemini"):
|
||||
apiPrefix = "gemini-api-key"
|
||||
case strings.EqualFold(provider, "codex"):
|
||||
apiPrefix = "codex-api-key"
|
||||
case strings.EqualFold(provider, "claude"):
|
||||
apiPrefix = "claude-api-key"
|
||||
}
|
||||
return "config:" + strings.Join(parts, "\x00")
|
||||
}
|
||||
if apiPrefix != "" {
|
||||
return apiPrefix + ":" + strings.TrimSpace(baseURL) + "+" + strings.TrimSpace(apiKey)
|
||||
}
|
||||
|
||||
if id := strings.TrimSpace(a.ID); id != "" {
|
||||
@@ -355,19 +376,28 @@ func (a *Auth) ProxyInfo() string {
|
||||
return "via proxy"
|
||||
}
|
||||
|
||||
// DisableCoolingOverride returns the auth-file scoped disable_cooling override when present.
|
||||
// DisableCoolingOverride returns the auth scoped disable_cooling override when present.
|
||||
// The value is read from metadata key "disable_cooling" (or legacy "disable-cooling").
|
||||
//
|
||||
// NOTE: This override is intentionally "true-only". When the metadata value is false, it is treated
|
||||
// as "not set" so the global disable-cooling flag can still take effect.
|
||||
func (a *Auth) DisableCoolingOverride() (bool, bool) {
|
||||
if a == nil || a.Metadata == nil {
|
||||
return false, false
|
||||
}
|
||||
if val, ok := a.Metadata["disable_cooling"]; ok {
|
||||
if parsed, okParse := parseBoolAny(val); okParse {
|
||||
if !parsed {
|
||||
return false, false
|
||||
}
|
||||
return parsed, true
|
||||
}
|
||||
}
|
||||
if val, ok := a.Metadata["disable-cooling"]; ok {
|
||||
if parsed, okParse := parseBoolAny(val); okParse {
|
||||
if !parsed {
|
||||
return false, false
|
||||
}
|
||||
return parsed, true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -96,8 +98,40 @@ func TestEnsureIndexUsesCredentialIdentity(t *testing.T) {
|
||||
if geminiIndex == altBaseIndex {
|
||||
t.Fatalf("same provider/key with different base_url produced duplicate auth_index %q", geminiIndex)
|
||||
}
|
||||
if geminiIndex == duplicateIndex {
|
||||
t.Fatalf("duplicate config entries should be separated by source-derived seed, got %q", geminiIndex)
|
||||
if geminiIndex != duplicateIndex {
|
||||
t.Fatalf("same provider/key with different source should share auth_index, got %q vs %q", geminiIndex, duplicateIndex)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnsureIndexUsesOAuthTypeAndAbsolutePath(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
wd, errWd := os.Getwd()
|
||||
if errWd != nil {
|
||||
t.Fatalf("os.Getwd returned error: %v", errWd)
|
||||
}
|
||||
|
||||
relPath := "test-oauth.json"
|
||||
absPath := filepath.Join(wd, relPath)
|
||||
expectedSeed := "gemini:" + filepath.Clean(absPath)
|
||||
expectedIndex := stableAuthIndex(expectedSeed)
|
||||
|
||||
a := &Auth{
|
||||
Provider: "gemini-cli",
|
||||
Attributes: map[string]string{
|
||||
"path": relPath,
|
||||
},
|
||||
Metadata: map[string]any{
|
||||
"type": "gemini",
|
||||
},
|
||||
}
|
||||
|
||||
got := a.EnsureIndex()
|
||||
if got == "" {
|
||||
t.Fatal("auth index should not be empty")
|
||||
}
|
||||
if got != expectedIndex {
|
||||
t.Fatalf("auth index = %q, want %q", got, expectedIndex)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,12 +8,12 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
configaccess "github.com/router-for-me/CLIProxyAPI/v6/internal/access/config_access"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/api"
|
||||
sdkaccess "github.com/router-for-me/CLIProxyAPI/v6/sdk/access"
|
||||
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/v6/sdk/config"
|
||||
configaccess "github.com/router-for-me/CLIProxyAPI/v7/internal/access/config_access"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/api"
|
||||
sdkaccess "github.com/router-for-me/CLIProxyAPI/v7/sdk/access"
|
||||
sdkAuth "github.com/router-for-me/CLIProxyAPI/v7/sdk/auth"
|
||||
coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/sdk/config"
|
||||
)
|
||||
|
||||
// Builder constructs a Service instance with customizable providers.
|
||||
@@ -214,7 +214,7 @@ func (b *Builder) Build() (*Service, error) {
|
||||
if b.cfg != nil {
|
||||
strategy = strings.ToLower(strings.TrimSpace(b.cfg.Routing.Strategy))
|
||||
// Support both legacy ClaudeCodeSessionAffinity and new universal SessionAffinity
|
||||
sessionAffinity = b.cfg.Routing.ClaudeCodeSessionAffinity || b.cfg.Routing.SessionAffinity
|
||||
sessionAffinity = b.cfg.Routing.SessionAffinity
|
||||
if ttlStr := strings.TrimSpace(b.cfg.Routing.SessionAffinityTTL); ttlStr != "" {
|
||||
if parsed, err := time.ParseDuration(ttlStr); err == nil && parsed > 0 {
|
||||
sessionAffinityTTL = parsed
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
sdktranslator "github.com/router-for-me/CLIProxyAPI/v6/sdk/translator"
|
||||
sdktranslator "github.com/router-for-me/CLIProxyAPI/v7/sdk/translator"
|
||||
)
|
||||
|
||||
// RequestedModelMetadataKey stores the client-requested model name in Options.Metadata.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package cliproxy
|
||||
|
||||
import "github.com/router-for-me/CLIProxyAPI/v6/internal/registry"
|
||||
import "github.com/router-for-me/CLIProxyAPI/v7/internal/registry"
|
||||
|
||||
// ModelInfo re-exports the registry model info structure.
|
||||
type ModelInfo = registry.ModelInfo
|
||||
|
||||
@@ -4,9 +4,9 @@ import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
cliproxyauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
|
||||
cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor"
|
||||
sdktranslator "github.com/router-for-me/CLIProxyAPI/v6/sdk/translator"
|
||||
cliproxyauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth"
|
||||
cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor"
|
||||
sdktranslator "github.com/router-for-me/CLIProxyAPI/v7/sdk/translator"
|
||||
)
|
||||
|
||||
// Context encapsulates execution state shared across middleware, translators, and executors.
|
||||
|
||||
@@ -9,7 +9,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"
|
||||
)
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@ package cliproxy
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/watcher"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/sdk/config"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/watcher"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/sdk/config"
|
||||
)
|
||||
|
||||
// NewFileTokenClientProvider returns the default token-backed client loader.
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/sdk/proxyutil"
|
||||
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"
|
||||
)
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
|
||||
coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth"
|
||||
)
|
||||
|
||||
func TestRoundTripperForDirectBypassesProxy(t *testing.T) {
|
||||
|
||||
+327
-124
@@ -12,17 +12,18 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/api"
|
||||
_ "github.com/router-for-me/CLIProxyAPI/v6/internal/redisqueue"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/registry"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/runtime/executor"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/watcher"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/wsrelay"
|
||||
sdkaccess "github.com/router-for-me/CLIProxyAPI/v6/sdk/access"
|
||||
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/v6/sdk/cliproxy/usage"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/sdk/config"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/api"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/home"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/redisqueue"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/registry"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/runtime/executor"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/watcher"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/wsrelay"
|
||||
sdkaccess "github.com/router-for-me/CLIProxyAPI/v7/sdk/access"
|
||||
sdkAuth "github.com/router-for-me/CLIProxyAPI/v7/sdk/auth"
|
||||
coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/usage"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/sdk/config"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
@@ -36,6 +37,9 @@ type Service struct {
|
||||
// cfgMu protects concurrent access to the configuration.
|
||||
cfgMu sync.RWMutex
|
||||
|
||||
// configUpdateMu serializes config updates across watcher + home.
|
||||
configUpdateMu sync.Mutex
|
||||
|
||||
// configPath is the path to the configuration file.
|
||||
configPath string
|
||||
|
||||
@@ -89,6 +93,9 @@ type Service struct {
|
||||
|
||||
// wsGateway manages websocket Gemini providers.
|
||||
wsGateway *wsrelay.Manager
|
||||
|
||||
homeClient *home.Client
|
||||
homeCancel context.CancelFunc
|
||||
}
|
||||
|
||||
// RegisterUsagePlugin registers a usage plugin on the global usage manager.
|
||||
@@ -462,6 +469,249 @@ func (s *Service) rebindExecutors() {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) applyConfigUpdate(newCfg *config.Config) {
|
||||
if s == nil {
|
||||
return
|
||||
}
|
||||
|
||||
s.configUpdateMu.Lock()
|
||||
defer s.configUpdateMu.Unlock()
|
||||
|
||||
previousStrategy := ""
|
||||
var previousSessionAffinity bool
|
||||
var previousSessionAffinityTTL string
|
||||
s.cfgMu.RLock()
|
||||
if s.cfg != nil {
|
||||
previousStrategy = strings.ToLower(strings.TrimSpace(s.cfg.Routing.Strategy))
|
||||
previousSessionAffinity = s.cfg.Routing.SessionAffinity
|
||||
previousSessionAffinityTTL = s.cfg.Routing.SessionAffinityTTL
|
||||
}
|
||||
s.cfgMu.RUnlock()
|
||||
|
||||
if newCfg == nil {
|
||||
s.cfgMu.RLock()
|
||||
newCfg = s.cfg
|
||||
s.cfgMu.RUnlock()
|
||||
}
|
||||
if newCfg == nil {
|
||||
return
|
||||
}
|
||||
|
||||
nextStrategy := strings.ToLower(strings.TrimSpace(newCfg.Routing.Strategy))
|
||||
normalizeStrategy := func(strategy string) string {
|
||||
switch strategy {
|
||||
case "fill-first", "fillfirst", "ff":
|
||||
return "fill-first"
|
||||
default:
|
||||
return "round-robin"
|
||||
}
|
||||
}
|
||||
previousStrategy = normalizeStrategy(previousStrategy)
|
||||
nextStrategy = normalizeStrategy(nextStrategy)
|
||||
|
||||
nextSessionAffinity := newCfg.Routing.SessionAffinity
|
||||
nextSessionAffinityTTL := newCfg.Routing.SessionAffinityTTL
|
||||
|
||||
selectorChanged := previousStrategy != nextStrategy ||
|
||||
previousSessionAffinity != nextSessionAffinity ||
|
||||
previousSessionAffinityTTL != nextSessionAffinityTTL
|
||||
|
||||
if s.coreManager != nil && selectorChanged {
|
||||
var selector coreauth.Selector
|
||||
switch nextStrategy {
|
||||
case "fill-first":
|
||||
selector = &coreauth.FillFirstSelector{}
|
||||
default:
|
||||
selector = &coreauth.RoundRobinSelector{}
|
||||
}
|
||||
|
||||
if nextSessionAffinity {
|
||||
ttl := time.Hour
|
||||
if ttlStr := strings.TrimSpace(nextSessionAffinityTTL); ttlStr != "" {
|
||||
if parsed, err := time.ParseDuration(ttlStr); err == nil && parsed > 0 {
|
||||
ttl = parsed
|
||||
}
|
||||
}
|
||||
selector = coreauth.NewSessionAffinitySelectorWithConfig(coreauth.SessionAffinityConfig{
|
||||
Fallback: selector,
|
||||
TTL: ttl,
|
||||
})
|
||||
}
|
||||
|
||||
s.coreManager.SetSelector(selector)
|
||||
}
|
||||
|
||||
s.applyRetryConfig(newCfg)
|
||||
s.applyPprofConfig(newCfg)
|
||||
if s.server != nil {
|
||||
s.server.UpdateClients(newCfg)
|
||||
}
|
||||
s.cfgMu.Lock()
|
||||
s.cfg = newCfg
|
||||
s.cfgMu.Unlock()
|
||||
if s.coreManager != nil {
|
||||
s.coreManager.SetConfig(newCfg)
|
||||
s.coreManager.SetOAuthModelAlias(newCfg.OAuthModelAlias)
|
||||
}
|
||||
s.rebindExecutors()
|
||||
}
|
||||
|
||||
func forceHomeRuntimeConfig(cfg *config.Config) {
|
||||
if cfg == nil {
|
||||
return
|
||||
}
|
||||
cfg.APIKeys = nil
|
||||
cfg.UsageStatisticsEnabled = true
|
||||
cfg.DisableCooling = true
|
||||
cfg.WebsocketAuth = false
|
||||
cfg.EnableGeminiCLIEndpoint = false
|
||||
cfg.RemoteManagement.AllowRemote = false
|
||||
cfg.RemoteManagement.DisableControlPanel = true
|
||||
}
|
||||
|
||||
func (s *Service) registerHomeExecutors() {
|
||||
if s == nil || s.coreManager == nil || s.cfg == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Register baseline executors so home-dispatched auth entries can execute without
|
||||
// requiring any local auth-dir credentials.
|
||||
s.coreManager.RegisterExecutor(executor.NewCodexAutoExecutor(s.cfg))
|
||||
s.coreManager.RegisterExecutor(executor.NewClaudeExecutor(s.cfg))
|
||||
s.coreManager.RegisterExecutor(executor.NewGeminiExecutor(s.cfg))
|
||||
s.coreManager.RegisterExecutor(executor.NewGeminiVertexExecutor(s.cfg))
|
||||
s.coreManager.RegisterExecutor(executor.NewGeminiCLIExecutor(s.cfg))
|
||||
s.coreManager.RegisterExecutor(executor.NewAIStudioExecutor(s.cfg, "", s.wsGateway))
|
||||
s.coreManager.RegisterExecutor(executor.NewAntigravityExecutor(s.cfg))
|
||||
s.coreManager.RegisterExecutor(executor.NewKimiExecutor(s.cfg))
|
||||
s.coreManager.RegisterExecutor(executor.NewOpenAICompatExecutor("openai-compatibility", s.cfg))
|
||||
}
|
||||
|
||||
func (s *Service) applyHomeOverlay(remoteCfg *config.Config) {
|
||||
if s == nil || remoteCfg == nil {
|
||||
return
|
||||
}
|
||||
|
||||
s.cfgMu.RLock()
|
||||
baseCfg := s.cfg
|
||||
s.cfgMu.RUnlock()
|
||||
if baseCfg == nil {
|
||||
return
|
||||
}
|
||||
|
||||
merged := *remoteCfg
|
||||
merged.Host = baseCfg.Host
|
||||
merged.Port = baseCfg.Port
|
||||
merged.TLS = baseCfg.TLS
|
||||
merged.Home = baseCfg.Home
|
||||
forceHomeRuntimeConfig(&merged)
|
||||
|
||||
s.applyConfigUpdate(&merged)
|
||||
}
|
||||
|
||||
func (s *Service) startHomeUsageForwarder(ctx context.Context, client *home.Client) {
|
||||
if s == nil || client == nil {
|
||||
return
|
||||
}
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
|
||||
sleep := func(d time.Duration) bool {
|
||||
if d <= 0 {
|
||||
return true
|
||||
}
|
||||
timer := time.NewTimer(d)
|
||||
defer timer.Stop()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return false
|
||||
case <-timer.C:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
if !client.HeartbeatOK() {
|
||||
if !sleep(time.Second) {
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
items := redisqueue.PopOldest(64)
|
||||
if len(items) == 0 {
|
||||
if !sleep(500 * time.Millisecond) {
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
for i := range items {
|
||||
if errPush := client.LPushUsage(ctx, items[i]); errPush != nil {
|
||||
for j := i; j < len(items); j++ {
|
||||
redisqueue.Enqueue(items[j])
|
||||
}
|
||||
if !sleep(time.Second) {
|
||||
return
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (s *Service) startHomeSubscriber(ctx context.Context) {
|
||||
if s == nil {
|
||||
return
|
||||
}
|
||||
s.cfgMu.RLock()
|
||||
cfg := s.cfg
|
||||
s.cfgMu.RUnlock()
|
||||
if cfg == nil || !cfg.Home.Enabled {
|
||||
return
|
||||
}
|
||||
|
||||
if s.homeCancel != nil {
|
||||
s.homeCancel()
|
||||
s.homeCancel = nil
|
||||
}
|
||||
if s.homeClient != nil {
|
||||
s.homeClient.Close()
|
||||
s.homeClient = nil
|
||||
}
|
||||
|
||||
homeCtx := ctx
|
||||
if homeCtx == nil {
|
||||
homeCtx = context.Background()
|
||||
}
|
||||
homeCtx, cancel := context.WithCancel(homeCtx)
|
||||
s.homeCancel = cancel
|
||||
|
||||
client := home.New(cfg.Home)
|
||||
s.homeClient = client
|
||||
home.SetCurrent(client)
|
||||
|
||||
go client.StartConfigSubscriber(homeCtx, func(raw []byte) error {
|
||||
parsed, err := config.ParseConfigBytes(raw)
|
||||
if err != nil {
|
||||
log.Warnf("failed to parse home config payload: %v", err)
|
||||
return err
|
||||
}
|
||||
s.applyHomeOverlay(parsed)
|
||||
return nil
|
||||
})
|
||||
s.startHomeUsageForwarder(homeCtx, client)
|
||||
}
|
||||
|
||||
// Run starts the service and blocks until the context is cancelled or the server stops.
|
||||
// It initializes all components including authentication, file watching, HTTP server,
|
||||
// and starts processing requests. The method blocks until the context is cancelled.
|
||||
@@ -480,6 +730,11 @@ func (s *Service) Run(ctx context.Context) error {
|
||||
}
|
||||
|
||||
usage.StartDefault(ctx)
|
||||
homeEnabled := s.cfg != nil && s.cfg.Home.Enabled
|
||||
if homeEnabled {
|
||||
forceHomeRuntimeConfig(s.cfg)
|
||||
redisqueue.SetUsageStatisticsEnabled(true)
|
||||
}
|
||||
|
||||
shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer shutdownCancel()
|
||||
@@ -489,32 +744,36 @@ func (s *Service) Run(ctx context.Context) error {
|
||||
}
|
||||
}()
|
||||
|
||||
if err := s.ensureAuthDir(); err != nil {
|
||||
return err
|
||||
if !homeEnabled {
|
||||
if errEnsureAuthDir := s.ensureAuthDir(); errEnsureAuthDir != nil {
|
||||
return errEnsureAuthDir
|
||||
}
|
||||
}
|
||||
|
||||
s.applyRetryConfig(s.cfg)
|
||||
|
||||
if s.coreManager != nil {
|
||||
if s.coreManager != nil && !homeEnabled {
|
||||
if errLoad := s.coreManager.Load(ctx); errLoad != nil {
|
||||
log.Warnf("failed to load auth store: %v", errLoad)
|
||||
}
|
||||
}
|
||||
|
||||
tokenResult, err := s.tokenProvider.Load(ctx, s.cfg)
|
||||
if err != nil && !errors.Is(err, context.Canceled) {
|
||||
return err
|
||||
}
|
||||
if tokenResult == nil {
|
||||
tokenResult = &TokenClientResult{}
|
||||
}
|
||||
if !homeEnabled {
|
||||
tokenResult, err := s.tokenProvider.Load(ctx, s.cfg)
|
||||
if err != nil && !errors.Is(err, context.Canceled) {
|
||||
return err
|
||||
}
|
||||
if tokenResult == nil {
|
||||
tokenResult = &TokenClientResult{}
|
||||
}
|
||||
|
||||
apiKeyResult, err := s.apiKeyProvider.Load(ctx, s.cfg)
|
||||
if err != nil && !errors.Is(err, context.Canceled) {
|
||||
return err
|
||||
}
|
||||
if apiKeyResult == nil {
|
||||
apiKeyResult = &APIKeyClientResult{}
|
||||
apiKeyResult, err := s.apiKeyProvider.Load(ctx, s.cfg)
|
||||
if err != nil && !errors.Is(err, context.Canceled) {
|
||||
return err
|
||||
}
|
||||
if apiKeyResult == nil {
|
||||
apiKeyResult = &APIKeyClientResult{}
|
||||
}
|
||||
}
|
||||
|
||||
// legacy clients removed; no caches to refresh
|
||||
@@ -526,6 +785,10 @@ func (s *Service) Run(ctx context.Context) error {
|
||||
s.authManager = newDefaultAuthManager()
|
||||
}
|
||||
|
||||
if homeEnabled {
|
||||
s.startHomeSubscriber(ctx)
|
||||
}
|
||||
|
||||
s.ensureWebsocketGateway()
|
||||
if s.server != nil && s.wsGateway != nil {
|
||||
s.server.AttachWebsocketRoute(s.wsGateway.Path(), s.wsGateway.Handler())
|
||||
@@ -547,6 +810,12 @@ func (s *Service) Run(ctx context.Context) error {
|
||||
})
|
||||
}
|
||||
|
||||
if homeEnabled {
|
||||
s.registerHomeExecutors()
|
||||
// Home mode does not expose in-process Redis RESP usage output; usage is forwarded to home instead.
|
||||
redisqueue.SetEnabled(true)
|
||||
}
|
||||
|
||||
if s.hooks.OnBeforeStart != nil {
|
||||
s.hooks.OnBeforeStart(s.cfg)
|
||||
}
|
||||
@@ -607,107 +876,31 @@ func (s *Service) Run(ctx context.Context) error {
|
||||
s.hooks.OnAfterStart(s)
|
||||
}
|
||||
|
||||
var watcherWrapper *WatcherWrapper
|
||||
reloadCallback := func(newCfg *config.Config) {
|
||||
previousStrategy := ""
|
||||
var previousSessionAffinity bool
|
||||
var previousSessionAffinityTTL string
|
||||
s.cfgMu.RLock()
|
||||
if s.cfg != nil {
|
||||
previousStrategy = strings.ToLower(strings.TrimSpace(s.cfg.Routing.Strategy))
|
||||
previousSessionAffinity = s.cfg.Routing.ClaudeCodeSessionAffinity || s.cfg.Routing.SessionAffinity
|
||||
previousSessionAffinityTTL = s.cfg.Routing.SessionAffinityTTL
|
||||
if !homeEnabled {
|
||||
var watcherWrapper *WatcherWrapper
|
||||
reloadCallback := func(newCfg *config.Config) { s.applyConfigUpdate(newCfg) }
|
||||
|
||||
watcherWrapper, errCreate := s.watcherFactory(s.configPath, s.cfg.AuthDir, reloadCallback)
|
||||
if errCreate != nil {
|
||||
return fmt.Errorf("cliproxy: failed to create watcher: %w", errCreate)
|
||||
}
|
||||
s.cfgMu.RUnlock()
|
||||
|
||||
if newCfg == nil {
|
||||
s.cfgMu.RLock()
|
||||
newCfg = s.cfg
|
||||
s.cfgMu.RUnlock()
|
||||
s.watcher = watcherWrapper
|
||||
s.ensureAuthUpdateQueue(ctx)
|
||||
if s.authUpdates != nil {
|
||||
watcherWrapper.SetAuthUpdateQueue(s.authUpdates)
|
||||
}
|
||||
if newCfg == nil {
|
||||
return
|
||||
watcherWrapper.SetConfig(s.cfg)
|
||||
|
||||
watcherCtx, watcherCancel := context.WithCancel(context.Background())
|
||||
s.watcherCancel = watcherCancel
|
||||
if errStart := watcherWrapper.Start(watcherCtx); errStart != nil {
|
||||
return fmt.Errorf("cliproxy: failed to start watcher: %w", errStart)
|
||||
}
|
||||
|
||||
nextStrategy := strings.ToLower(strings.TrimSpace(newCfg.Routing.Strategy))
|
||||
normalizeStrategy := func(strategy string) string {
|
||||
switch strategy {
|
||||
case "fill-first", "fillfirst", "ff":
|
||||
return "fill-first"
|
||||
default:
|
||||
return "round-robin"
|
||||
}
|
||||
}
|
||||
previousStrategy = normalizeStrategy(previousStrategy)
|
||||
nextStrategy = normalizeStrategy(nextStrategy)
|
||||
|
||||
nextSessionAffinity := newCfg.Routing.ClaudeCodeSessionAffinity || newCfg.Routing.SessionAffinity
|
||||
nextSessionAffinityTTL := newCfg.Routing.SessionAffinityTTL
|
||||
|
||||
selectorChanged := previousStrategy != nextStrategy ||
|
||||
previousSessionAffinity != nextSessionAffinity ||
|
||||
previousSessionAffinityTTL != nextSessionAffinityTTL
|
||||
|
||||
if s.coreManager != nil && selectorChanged {
|
||||
var selector coreauth.Selector
|
||||
switch nextStrategy {
|
||||
case "fill-first":
|
||||
selector = &coreauth.FillFirstSelector{}
|
||||
default:
|
||||
selector = &coreauth.RoundRobinSelector{}
|
||||
}
|
||||
|
||||
if nextSessionAffinity {
|
||||
ttl := time.Hour
|
||||
if ttlStr := strings.TrimSpace(nextSessionAffinityTTL); ttlStr != "" {
|
||||
if parsed, err := time.ParseDuration(ttlStr); err == nil && parsed > 0 {
|
||||
ttl = parsed
|
||||
}
|
||||
}
|
||||
selector = coreauth.NewSessionAffinitySelectorWithConfig(coreauth.SessionAffinityConfig{
|
||||
Fallback: selector,
|
||||
TTL: ttl,
|
||||
})
|
||||
}
|
||||
|
||||
s.coreManager.SetSelector(selector)
|
||||
}
|
||||
|
||||
s.applyRetryConfig(newCfg)
|
||||
s.applyPprofConfig(newCfg)
|
||||
if s.server != nil {
|
||||
s.server.UpdateClients(newCfg)
|
||||
}
|
||||
s.cfgMu.Lock()
|
||||
s.cfg = newCfg
|
||||
s.cfgMu.Unlock()
|
||||
if s.coreManager != nil {
|
||||
s.coreManager.SetConfig(newCfg)
|
||||
s.coreManager.SetOAuthModelAlias(newCfg.OAuthModelAlias)
|
||||
}
|
||||
s.rebindExecutors()
|
||||
log.Info("file watcher started for config and auth directory changes")
|
||||
}
|
||||
|
||||
watcherWrapper, err = s.watcherFactory(s.configPath, s.cfg.AuthDir, reloadCallback)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cliproxy: failed to create watcher: %w", err)
|
||||
}
|
||||
s.watcher = watcherWrapper
|
||||
s.ensureAuthUpdateQueue(ctx)
|
||||
if s.authUpdates != nil {
|
||||
watcherWrapper.SetAuthUpdateQueue(s.authUpdates)
|
||||
}
|
||||
watcherWrapper.SetConfig(s.cfg)
|
||||
|
||||
watcherCtx, watcherCancel := context.WithCancel(context.Background())
|
||||
s.watcherCancel = watcherCancel
|
||||
if err = watcherWrapper.Start(watcherCtx); err != nil {
|
||||
return fmt.Errorf("cliproxy: failed to start watcher: %w", err)
|
||||
}
|
||||
log.Info("file watcher started for config and auth directory changes")
|
||||
|
||||
// Prefer core auth manager auto refresh if available.
|
||||
if s.coreManager != nil {
|
||||
if s.coreManager != nil && !homeEnabled {
|
||||
interval := 15 * time.Minute
|
||||
s.coreManager.StartAutoRefresh(context.Background(), interval)
|
||||
log.Infof("core auth auto-refresh started (interval=%s)", interval)
|
||||
@@ -717,8 +910,8 @@ func (s *Service) Run(ctx context.Context) error {
|
||||
case <-ctx.Done():
|
||||
log.Debug("service context cancelled, shutting down...")
|
||||
return ctx.Err()
|
||||
case err = <-s.serverErr:
|
||||
return err
|
||||
case errServer := <-s.serverErr:
|
||||
return errServer
|
||||
}
|
||||
}
|
||||
|
||||
@@ -741,6 +934,16 @@ func (s *Service) Shutdown(ctx context.Context) error {
|
||||
ctx = context.Background()
|
||||
}
|
||||
|
||||
if s.homeCancel != nil {
|
||||
s.homeCancel()
|
||||
s.homeCancel = nil
|
||||
}
|
||||
if s.homeClient != nil {
|
||||
s.homeClient.Close()
|
||||
s.homeClient = nil
|
||||
}
|
||||
home.ClearCurrent()
|
||||
|
||||
// legacy refresh loop removed; only stopping core auth manager below
|
||||
|
||||
if s.watcherCancel != nil {
|
||||
|
||||
@@ -3,8 +3,8 @@ package cliproxy
|
||||
import (
|
||||
"testing"
|
||||
|
||||
coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/sdk/config"
|
||||
coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/sdk/config"
|
||||
)
|
||||
|
||||
func TestEnsureExecutorsForAuth_CodexDoesNotReplaceInNormalMode(t *testing.T) {
|
||||
|
||||
@@ -4,8 +4,8 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/sdk/config"
|
||||
coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/sdk/config"
|
||||
)
|
||||
|
||||
func TestRegisterModelsForAuth_UsesPreMergedExcludedModelsAttribute(t *testing.T) {
|
||||
|
||||
@@ -3,7 +3,7 @@ package cliproxy
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/sdk/config"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/sdk/config"
|
||||
)
|
||||
|
||||
func TestApplyOAuthModelAlias_Rename(t *testing.T) {
|
||||
|
||||
@@ -5,9 +5,9 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/registry"
|
||||
coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/sdk/config"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/registry"
|
||||
coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/sdk/config"
|
||||
)
|
||||
|
||||
func TestServiceApplyCoreAuthAddOrUpdate_DeleteReAddDoesNotInheritStaleRuntimeState(t *testing.T) {
|
||||
@@ -99,3 +99,32 @@ func TestServiceApplyCoreAuthAddOrUpdate_DeleteReAddDoesNotInheritStaleRuntimeSt
|
||||
t.Fatalf("expected re-added auth to re-register models in global registry")
|
||||
}
|
||||
}
|
||||
|
||||
func TestForceHomeRuntimeConfigEnablesUsageStatistics(t *testing.T) {
|
||||
cfg := &config.Config{
|
||||
UsageStatisticsEnabled: false,
|
||||
}
|
||||
|
||||
forceHomeRuntimeConfig(cfg)
|
||||
|
||||
if !cfg.UsageStatisticsEnabled {
|
||||
t.Fatal("expected home runtime config to force usage statistics enabled")
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyHomeOverlayForcesUsageStatisticsEnabled(t *testing.T) {
|
||||
baseCfg := &config.Config{}
|
||||
baseCfg.Home.Enabled = true
|
||||
service := &Service{cfg: baseCfg}
|
||||
|
||||
service.applyHomeOverlay(&config.Config{
|
||||
UsageStatisticsEnabled: false,
|
||||
})
|
||||
|
||||
if service.cfg == nil || !service.cfg.UsageStatisticsEnabled {
|
||||
t.Fatal("expected home overlay to force usage statistics enabled")
|
||||
}
|
||||
if !service.cfg.Home.Enabled {
|
||||
t.Fatal("expected home overlay to preserve local home settings")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,9 +6,9 @@ package cliproxy
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/watcher"
|
||||
coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/sdk/config"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/watcher"
|
||||
coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/sdk/config"
|
||||
)
|
||||
|
||||
// TokenClientProvider loads clients backed by stored authentication tokens.
|
||||
|
||||
@@ -22,9 +22,16 @@ type Record struct {
|
||||
RequestedAt time.Time
|
||||
Latency time.Duration
|
||||
Failed bool
|
||||
Fail Failure
|
||||
Detail Detail
|
||||
}
|
||||
|
||||
// Failure holds HTTP failure metadata for an upstream request attempt.
|
||||
type Failure struct {
|
||||
StatusCode int
|
||||
Body string
|
||||
}
|
||||
|
||||
// Detail holds the token usage breakdown.
|
||||
type Detail struct {
|
||||
InputTokens int64
|
||||
|
||||
@@ -3,9 +3,9 @@ package cliproxy
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/watcher"
|
||||
coreauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/sdk/config"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/internal/watcher"
|
||||
coreauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth"
|
||||
"github.com/router-for-me/CLIProxyAPI/v7/sdk/config"
|
||||
)
|
||||
|
||||
func defaultWatcherFactory(configPath, authDir string, reload func(*config.Config)) (*WatcherWrapper, error) {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// embed CLIProxyAPI without importing internal packages.
|
||||
package config
|
||||
|
||||
import internalconfig "github.com/router-for-me/CLIProxyAPI/v6/internal/config"
|
||||
import internalconfig "github.com/router-for-me/CLIProxyAPI/v7/internal/config"
|
||||
|
||||
type SDKConfig = internalconfig.SDKConfig
|
||||
|
||||
@@ -41,6 +41,8 @@ func LoadConfigOptional(configFile string, optional bool) (*Config, error) {
|
||||
return internalconfig.LoadConfigOptional(configFile, optional)
|
||||
}
|
||||
|
||||
func ParseConfigBytes(data []byte) (*Config, error) { return internalconfig.ParseConfigBytes(data) }
|
||||
|
||||
func SaveConfigPreserveComments(configFile string, cfg *Config) error {
|
||||
return internalconfig.SaveConfigPreserveComments(configFile, cfg)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Package logging re-exports request logging primitives for SDK consumers.
|
||||
package logging
|
||||
|
||||
import internallogging "github.com/router-for-me/CLIProxyAPI/v6/internal/logging"
|
||||
import internallogging "github.com/router-for-me/CLIProxyAPI/v7/internal/logging"
|
||||
|
||||
const defaultErrorLogsMaxFiles = 10
|
||||
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
package builtin
|
||||
|
||||
import (
|
||||
sdktranslator "github.com/router-for-me/CLIProxyAPI/v6/sdk/translator"
|
||||
sdktranslator "github.com/router-for-me/CLIProxyAPI/v7/sdk/translator"
|
||||
|
||||
_ "github.com/router-for-me/CLIProxyAPI/v6/internal/translator"
|
||||
_ "github.com/router-for-me/CLIProxyAPI/v7/internal/translator"
|
||||
)
|
||||
|
||||
// Registry exposes the default registry populated with all built-in translators.
|
||||
|
||||
Reference in New Issue
Block a user