mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-02-02 20:40:52 +08:00
feat(registry): unify Gemini models and add AI Studio set
This commit is contained in:
@@ -225,7 +225,7 @@ func NewServer(cfg *config.Config, authManager *auth.Manager, accessManager *sdk
|
|||||||
envManagementSecret := envAdminPasswordSet && envAdminPassword != ""
|
envManagementSecret := envAdminPasswordSet && envAdminPassword != ""
|
||||||
|
|
||||||
// Create server instance
|
// Create server instance
|
||||||
providerNames := make([]string, 0, len(cfg.OpenAICompatibility))
|
providerNames := make([]string, 0, len(cfg.OpenAICompatibility))
|
||||||
for _, p := range cfg.OpenAICompatibility {
|
for _, p := range cfg.OpenAICompatibility {
|
||||||
providerNames = append(providerNames, p.Name)
|
providerNames = append(providerNames, p.Name)
|
||||||
}
|
}
|
||||||
@@ -914,5 +914,3 @@ func AuthMiddleware(manager *sdkaccess.Manager) gin.HandlerFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// legacy clientsToSlice removed; handlers no longer consume legacy client slices
|
|
||||||
@@ -68,84 +68,8 @@ func GetClaudeModels() []*ModelInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetGeminiModels returns the standard Gemini model definitions
|
// GeminiModels returns the shared base Gemini model set used by multiple providers.
|
||||||
func GetGeminiModels() []*ModelInfo {
|
func GeminiModels() []*ModelInfo {
|
||||||
return []*ModelInfo{
|
|
||||||
{
|
|
||||||
ID: "gemini-2.5-flash",
|
|
||||||
Object: "model",
|
|
||||||
Created: time.Now().Unix(),
|
|
||||||
OwnedBy: "google",
|
|
||||||
Type: "gemini",
|
|
||||||
Name: "models/gemini-2.5-flash",
|
|
||||||
Version: "001",
|
|
||||||
DisplayName: "Gemini 2.5 Flash",
|
|
||||||
Description: "Stable version of Gemini 2.5 Flash, our mid-size multimodal model that supports up to 1 million tokens, released in June of 2025.",
|
|
||||||
InputTokenLimit: 1048576,
|
|
||||||
OutputTokenLimit: 65536,
|
|
||||||
SupportedGenerationMethods: []string{"generateContent", "countTokens", "createCachedContent", "batchGenerateContent"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: "gemini-2.5-pro",
|
|
||||||
Object: "model",
|
|
||||||
Created: time.Now().Unix(),
|
|
||||||
OwnedBy: "google",
|
|
||||||
Type: "gemini",
|
|
||||||
Name: "models/gemini-2.5-pro",
|
|
||||||
Version: "2.5",
|
|
||||||
DisplayName: "Gemini 2.5 Pro",
|
|
||||||
Description: "Stable release (June 17th, 2025) of Gemini 2.5 Pro",
|
|
||||||
InputTokenLimit: 1048576,
|
|
||||||
OutputTokenLimit: 65536,
|
|
||||||
SupportedGenerationMethods: []string{"generateContent", "countTokens", "createCachedContent", "batchGenerateContent"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: "gemini-2.5-flash-lite",
|
|
||||||
Object: "model",
|
|
||||||
Created: time.Now().Unix(),
|
|
||||||
OwnedBy: "google",
|
|
||||||
Type: "gemini",
|
|
||||||
Name: "models/gemini-2.5-flash-lite",
|
|
||||||
Version: "2.5",
|
|
||||||
DisplayName: "Gemini 2.5 Flash Lite",
|
|
||||||
Description: "Stable release (June 17th, 2025) of Gemini 2.5 Flash Lite",
|
|
||||||
InputTokenLimit: 1048576,
|
|
||||||
OutputTokenLimit: 65536,
|
|
||||||
SupportedGenerationMethods: []string{"generateContent", "countTokens", "createCachedContent", "batchGenerateContent"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: "gemini-2.5-flash-image-preview",
|
|
||||||
Object: "model",
|
|
||||||
Created: time.Now().Unix(),
|
|
||||||
OwnedBy: "google",
|
|
||||||
Type: "gemini",
|
|
||||||
Name: "models/gemini-2.5-flash-image-preview",
|
|
||||||
Version: "2.5",
|
|
||||||
DisplayName: "Gemini 2.5 Flash Image Preview",
|
|
||||||
Description: "State-of-the-art image generation and editing model.",
|
|
||||||
InputTokenLimit: 1048576,
|
|
||||||
OutputTokenLimit: 8192,
|
|
||||||
SupportedGenerationMethods: []string{"generateContent", "countTokens", "createCachedContent", "batchGenerateContent"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: "gemini-2.5-flash-image",
|
|
||||||
Object: "model",
|
|
||||||
Created: time.Now().Unix(),
|
|
||||||
OwnedBy: "google",
|
|
||||||
Type: "gemini",
|
|
||||||
Name: "models/gemini-2.5-flash-image",
|
|
||||||
Version: "2.5",
|
|
||||||
DisplayName: "Gemini 2.5 Flash Image",
|
|
||||||
Description: "State-of-the-art image generation and editing model.",
|
|
||||||
InputTokenLimit: 1048576,
|
|
||||||
OutputTokenLimit: 8192,
|
|
||||||
SupportedGenerationMethods: []string{"generateContent", "countTokens", "createCachedContent", "batchGenerateContent"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetGeminiCLIModels returns the standard Gemini model definitions
|
|
||||||
func GetGeminiCLIModels() []*ModelInfo {
|
|
||||||
return []*ModelInfo{
|
return []*ModelInfo{
|
||||||
{
|
{
|
||||||
ID: "gemini-2.5-flash",
|
ID: "gemini-2.5-flash",
|
||||||
@@ -220,6 +144,63 @@ func GetGeminiCLIModels() []*ModelInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetGeminiModels returns the standard Gemini model definitions
|
||||||
|
func GetGeminiModels() []*ModelInfo { return GeminiModels() }
|
||||||
|
|
||||||
|
// GetGeminiCLIModels returns the standard Gemini model definitions
|
||||||
|
func GetGeminiCLIModels() []*ModelInfo { return GeminiModels() }
|
||||||
|
|
||||||
|
// GetAIStudioModels returns the Gemini model definitions for AI Studio integrations
|
||||||
|
func GetAIStudioModels() []*ModelInfo {
|
||||||
|
models := make([]*ModelInfo, 0, 8)
|
||||||
|
models = append(models, GeminiModels()...)
|
||||||
|
models = append(models,
|
||||||
|
&ModelInfo{
|
||||||
|
ID: "gemini-pro-latest",
|
||||||
|
Object: "model",
|
||||||
|
Created: time.Now().Unix(),
|
||||||
|
OwnedBy: "google",
|
||||||
|
Type: "gemini",
|
||||||
|
Name: "models/gemini-pro-latest",
|
||||||
|
Version: "2.5",
|
||||||
|
DisplayName: "Gemini Pro Latest",
|
||||||
|
Description: "Latest release of Gemini Pro",
|
||||||
|
InputTokenLimit: 1048576,
|
||||||
|
OutputTokenLimit: 65536,
|
||||||
|
SupportedGenerationMethods: []string{"generateContent", "countTokens", "createCachedContent", "batchGenerateContent"},
|
||||||
|
},
|
||||||
|
&ModelInfo{
|
||||||
|
ID: "gemini-flash-latest",
|
||||||
|
Object: "model",
|
||||||
|
Created: time.Now().Unix(),
|
||||||
|
OwnedBy: "google",
|
||||||
|
Type: "gemini",
|
||||||
|
Name: "models/gemini-flash-latest",
|
||||||
|
Version: "2.5",
|
||||||
|
DisplayName: "Gemini Flash Latest",
|
||||||
|
Description: "Latest release of Gemini Flash",
|
||||||
|
InputTokenLimit: 1048576,
|
||||||
|
OutputTokenLimit: 65536,
|
||||||
|
SupportedGenerationMethods: []string{"generateContent", "countTokens", "createCachedContent", "batchGenerateContent"},
|
||||||
|
},
|
||||||
|
&ModelInfo{
|
||||||
|
ID: "gemini-flash-lite-latest",
|
||||||
|
Object: "model",
|
||||||
|
Created: time.Now().Unix(),
|
||||||
|
OwnedBy: "google",
|
||||||
|
Type: "gemini",
|
||||||
|
Name: "models/gemini-flash-lite-latest",
|
||||||
|
Version: "2.5",
|
||||||
|
DisplayName: "Gemini Flash-Lite Latest",
|
||||||
|
Description: "Latest release of Gemini Flash-Lite",
|
||||||
|
InputTokenLimit: 1048576,
|
||||||
|
OutputTokenLimit: 65536,
|
||||||
|
SupportedGenerationMethods: []string{"generateContent", "countTokens", "createCachedContent", "batchGenerateContent"},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return models
|
||||||
|
}
|
||||||
|
|
||||||
// GetOpenAIModels returns the standard OpenAI model definitions
|
// GetOpenAIModels returns the standard OpenAI model definitions
|
||||||
func GetOpenAIModels() []*ModelInfo {
|
func GetOpenAIModels() []*ModelInfo {
|
||||||
return []*ModelInfo{
|
return []*ModelInfo{
|
||||||
@@ -417,7 +398,6 @@ func GetIFlowModels() []*ModelInfo {
|
|||||||
{ID: "qwen3-vl-plus", DisplayName: "Qwen3-VL-Plus", Description: "Qwen3 multimodal vision-language"},
|
{ID: "qwen3-vl-plus", DisplayName: "Qwen3-VL-Plus", Description: "Qwen3 multimodal vision-language"},
|
||||||
{ID: "qwen3-max-preview", DisplayName: "Qwen3-Max-Preview", Description: "Qwen3 Max preview build"},
|
{ID: "qwen3-max-preview", DisplayName: "Qwen3-Max-Preview", Description: "Qwen3 Max preview build"},
|
||||||
{ID: "kimi-k2-0905", DisplayName: "Kimi-K2-Instruct-0905", Description: "Moonshot Kimi K2 instruct 0905"},
|
{ID: "kimi-k2-0905", DisplayName: "Kimi-K2-Instruct-0905", Description: "Moonshot Kimi K2 instruct 0905"},
|
||||||
{ID: "glm-4.5", DisplayName: "GLM-4.5", Description: "Zhipu GLM 4.5 general model"},
|
|
||||||
{ID: "glm-4.6", DisplayName: "GLM-4.6", Description: "Zhipu GLM 4.6 general model"},
|
{ID: "glm-4.6", DisplayName: "GLM-4.6", Description: "Zhipu GLM 4.6 general model"},
|
||||||
{ID: "kimi-k2", DisplayName: "Kimi-K2", Description: "Moonshot Kimi K2 general model"},
|
{ID: "kimi-k2", DisplayName: "Kimi-K2", Description: "Moonshot Kimi K2 general model"},
|
||||||
{ID: "deepseek-v3.2", DisplayName: "DeepSeek-V3.2-Exp", Description: "DeepSeek V3.2 experimental"},
|
{ID: "deepseek-v3.2", DisplayName: "DeepSeek-V3.2-Exp", Description: "DeepSeek V3.2 experimental"},
|
||||||
|
|||||||
@@ -19,27 +19,27 @@ import (
|
|||||||
"github.com/tidwall/sjson"
|
"github.com/tidwall/sjson"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AistudioExecutor routes AI Studio requests through a websocket-backed transport.
|
// AIStudioExecutor routes AI Studio requests through a websocket-backed transport.
|
||||||
type AistudioExecutor struct {
|
type AIStudioExecutor struct {
|
||||||
provider string
|
provider string
|
||||||
relay *wsrelay.Manager
|
relay *wsrelay.Manager
|
||||||
cfg *config.Config
|
cfg *config.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAistudioExecutor constructs a websocket executor for the provider name.
|
// NewAIStudioExecutor constructs a websocket executor for the provider name.
|
||||||
func NewAistudioExecutor(cfg *config.Config, provider string, relay *wsrelay.Manager) *AistudioExecutor {
|
func NewAIStudioExecutor(cfg *config.Config, provider string, relay *wsrelay.Manager) *AIStudioExecutor {
|
||||||
return &AistudioExecutor{provider: strings.ToLower(provider), relay: relay, cfg: cfg}
|
return &AIStudioExecutor{provider: strings.ToLower(provider), relay: relay, cfg: cfg}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Identifier returns the provider key served by this executor.
|
// Identifier returns the logical provider key for routing.
|
||||||
func (e *AistudioExecutor) Identifier() string { return e.provider }
|
func (e *AIStudioExecutor) Identifier() string { return "aistudio" }
|
||||||
|
|
||||||
// PrepareRequest is a no-op because websocket transport already injects headers.
|
// PrepareRequest is a no-op because websocket transport already injects headers.
|
||||||
func (e *AistudioExecutor) PrepareRequest(_ *http.Request, _ *cliproxyauth.Auth) error {
|
func (e *AIStudioExecutor) PrepareRequest(_ *http.Request, _ *cliproxyauth.Auth) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *AistudioExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, req cliproxyexecutor.Request, opts cliproxyexecutor.Options) (resp cliproxyexecutor.Response, err error) {
|
func (e *AIStudioExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, req cliproxyexecutor.Request, opts cliproxyexecutor.Options) (resp cliproxyexecutor.Response, err error) {
|
||||||
reporter := newUsageReporter(ctx, e.Identifier(), req.Model, auth)
|
reporter := newUsageReporter(ctx, e.Identifier(), req.Model, auth)
|
||||||
defer reporter.trackFailure(ctx, &err)
|
defer reporter.trackFailure(ctx, &err)
|
||||||
|
|
||||||
@@ -66,7 +66,7 @@ func (e *AistudioExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth,
|
|||||||
Method: http.MethodPost,
|
Method: http.MethodPost,
|
||||||
Headers: wsReq.Headers.Clone(),
|
Headers: wsReq.Headers.Clone(),
|
||||||
Body: bytes.Clone(body.payload),
|
Body: bytes.Clone(body.payload),
|
||||||
Provider: e.provider,
|
Provider: e.Identifier(),
|
||||||
AuthID: authID,
|
AuthID: authID,
|
||||||
AuthLabel: authLabel,
|
AuthLabel: authLabel,
|
||||||
AuthType: authType,
|
AuthType: authType,
|
||||||
@@ -92,7 +92,7 @@ func (e *AistudioExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth,
|
|||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *AistudioExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.Auth, req cliproxyexecutor.Request, opts cliproxyexecutor.Options) (stream <-chan cliproxyexecutor.StreamChunk, err error) {
|
func (e *AIStudioExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.Auth, req cliproxyexecutor.Request, opts cliproxyexecutor.Options) (stream <-chan cliproxyexecutor.StreamChunk, err error) {
|
||||||
reporter := newUsageReporter(ctx, e.Identifier(), req.Model, auth)
|
reporter := newUsageReporter(ctx, e.Identifier(), req.Model, auth)
|
||||||
defer reporter.trackFailure(ctx, &err)
|
defer reporter.trackFailure(ctx, &err)
|
||||||
|
|
||||||
@@ -118,7 +118,7 @@ func (e *AistudioExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth
|
|||||||
Method: http.MethodPost,
|
Method: http.MethodPost,
|
||||||
Headers: wsReq.Headers.Clone(),
|
Headers: wsReq.Headers.Clone(),
|
||||||
Body: bytes.Clone(body.payload),
|
Body: bytes.Clone(body.payload),
|
||||||
Provider: e.provider,
|
Provider: e.Identifier(),
|
||||||
AuthID: authID,
|
AuthID: authID,
|
||||||
AuthLabel: authLabel,
|
AuthLabel: authLabel,
|
||||||
AuthType: authType,
|
AuthType: authType,
|
||||||
@@ -151,7 +151,7 @@ func (e *AistudioExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth
|
|||||||
case wsrelay.MessageTypeStreamChunk:
|
case wsrelay.MessageTypeStreamChunk:
|
||||||
if len(event.Payload) > 0 {
|
if len(event.Payload) > 0 {
|
||||||
appendAPIResponseChunk(ctx, e.cfg, bytes.Clone(event.Payload))
|
appendAPIResponseChunk(ctx, e.cfg, bytes.Clone(event.Payload))
|
||||||
filtered := filterAistudioUsageMetadata(event.Payload)
|
filtered := filterAIStudioUsageMetadata(event.Payload)
|
||||||
if detail, ok := parseGeminiStreamUsage(filtered); ok {
|
if detail, ok := parseGeminiStreamUsage(filtered); ok {
|
||||||
reporter.publish(ctx, detail)
|
reporter.publish(ctx, detail)
|
||||||
}
|
}
|
||||||
@@ -188,7 +188,7 @@ func (e *AistudioExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth
|
|||||||
return stream, nil
|
return stream, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *AistudioExecutor) CountTokens(ctx context.Context, auth *cliproxyauth.Auth, req cliproxyexecutor.Request, opts cliproxyexecutor.Options) (cliproxyexecutor.Response, error) {
|
func (e *AIStudioExecutor) CountTokens(ctx context.Context, auth *cliproxyauth.Auth, req cliproxyexecutor.Request, opts cliproxyexecutor.Options) (cliproxyexecutor.Response, error) {
|
||||||
_, body, err := e.translateRequest(req, opts, false)
|
_, body, err := e.translateRequest(req, opts, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cliproxyexecutor.Response{}, err
|
return cliproxyexecutor.Response{}, err
|
||||||
@@ -215,7 +215,7 @@ func (e *AistudioExecutor) CountTokens(ctx context.Context, auth *cliproxyauth.A
|
|||||||
Method: http.MethodPost,
|
Method: http.MethodPost,
|
||||||
Headers: wsReq.Headers.Clone(),
|
Headers: wsReq.Headers.Clone(),
|
||||||
Body: bytes.Clone(body.payload),
|
Body: bytes.Clone(body.payload),
|
||||||
Provider: e.provider,
|
Provider: e.Identifier(),
|
||||||
AuthID: authID,
|
AuthID: authID,
|
||||||
AuthLabel: authLabel,
|
AuthLabel: authLabel,
|
||||||
AuthType: authType,
|
AuthType: authType,
|
||||||
@@ -241,7 +241,7 @@ func (e *AistudioExecutor) CountTokens(ctx context.Context, auth *cliproxyauth.A
|
|||||||
return cliproxyexecutor.Response{Payload: []byte(translated)}, nil
|
return cliproxyexecutor.Response{Payload: []byte(translated)}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *AistudioExecutor) Refresh(ctx context.Context, auth *cliproxyauth.Auth) (*cliproxyauth.Auth, error) {
|
func (e *AIStudioExecutor) Refresh(ctx context.Context, auth *cliproxyauth.Auth) (*cliproxyauth.Auth, error) {
|
||||||
_ = ctx
|
_ = ctx
|
||||||
return auth, nil
|
return auth, nil
|
||||||
}
|
}
|
||||||
@@ -252,7 +252,7 @@ type translatedPayload struct {
|
|||||||
toFormat sdktranslator.Format
|
toFormat sdktranslator.Format
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *AistudioExecutor) translateRequest(req cliproxyexecutor.Request, opts cliproxyexecutor.Options, stream bool) ([]byte, translatedPayload, error) {
|
func (e *AIStudioExecutor) translateRequest(req cliproxyexecutor.Request, opts cliproxyexecutor.Options, stream bool) ([]byte, translatedPayload, error) {
|
||||||
from := opts.SourceFormat
|
from := opts.SourceFormat
|
||||||
to := sdktranslator.FromString("gemini")
|
to := sdktranslator.FromString("gemini")
|
||||||
payload := sdktranslator.TranslateRequest(from, to, req.Model, bytes.Clone(req.Payload), stream)
|
payload := sdktranslator.TranslateRequest(from, to, req.Model, bytes.Clone(req.Payload), stream)
|
||||||
@@ -275,7 +275,7 @@ func (e *AistudioExecutor) translateRequest(req cliproxyexecutor.Request, opts c
|
|||||||
return payload, translatedPayload{payload: payload, action: action, toFormat: to}, nil
|
return payload, translatedPayload{payload: payload, action: action, toFormat: to}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *AistudioExecutor) buildEndpoint(model, action, alt string) string {
|
func (e *AIStudioExecutor) buildEndpoint(model, action, alt string) string {
|
||||||
base := fmt.Sprintf("%s/%s/models/%s:%s", glEndpoint, glAPIVersion, model, action)
|
base := fmt.Sprintf("%s/%s/models/%s:%s", glEndpoint, glAPIVersion, model, action)
|
||||||
if action == "streamGenerateContent" {
|
if action == "streamGenerateContent" {
|
||||||
if alt == "" {
|
if alt == "" {
|
||||||
@@ -289,9 +289,9 @@ func (e *AistudioExecutor) buildEndpoint(model, action, alt string) string {
|
|||||||
return base
|
return base
|
||||||
}
|
}
|
||||||
|
|
||||||
// filterAistudioUsageMetadata removes usageMetadata from intermediate SSE events so that
|
// filterAIStudioUsageMetadata removes usageMetadata from intermediate SSE events so that
|
||||||
// only the terminal chunk retains token statistics.
|
// only the terminal chunk retains token statistics.
|
||||||
func filterAistudioUsageMetadata(payload []byte) []byte {
|
func filterAIStudioUsageMetadata(payload []byte) []byte {
|
||||||
if len(payload) == 0 {
|
if len(payload) == 0 {
|
||||||
return payload
|
return payload
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -157,15 +157,6 @@ func (a *Auth) AccountInfo() (string, string) {
|
|||||||
return "oauth", v
|
return "oauth", v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if strings.HasPrefix(strings.ToLower(strings.TrimSpace(a.Provider)), "aistudio-") {
|
|
||||||
if label := strings.TrimSpace(a.Label); label != "" {
|
|
||||||
return "oauth", label
|
|
||||||
}
|
|
||||||
if id := strings.TrimSpace(a.ID); id != "" {
|
|
||||||
return "oauth", id
|
|
||||||
}
|
|
||||||
return "oauth", "aistudio"
|
|
||||||
}
|
|
||||||
if a.Attributes != nil {
|
if a.Attributes != nil {
|
||||||
if v := a.Attributes["api_key"]; v != "" {
|
if v := a.Attributes["api_key"]; v != "" {
|
||||||
return "api_key", v
|
return "api_key", v
|
||||||
|
|||||||
@@ -194,15 +194,15 @@ func (s *Service) ensureWebsocketGateway() {
|
|||||||
s.wsGateway = wsrelay.NewManager(opts)
|
s.wsGateway = wsrelay.NewManager(opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) wsOnConnected(provider string) {
|
func (s *Service) wsOnConnected(channelID string) {
|
||||||
if s == nil || provider == "" {
|
if s == nil || channelID == "" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !strings.HasPrefix(strings.ToLower(provider), "aistudio-") {
|
if !strings.HasPrefix(strings.ToLower(channelID), "aistudio-") {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if s.coreManager != nil {
|
if s.coreManager != nil {
|
||||||
if existing, ok := s.coreManager.GetByID(provider); ok && existing != nil {
|
if existing, ok := s.coreManager.GetByID(channelID); ok && existing != nil {
|
||||||
if !existing.Disabled && existing.Status == coreauth.StatusActive {
|
if !existing.Disabled && existing.Status == coreauth.StatusActive {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -210,35 +210,35 @@ func (s *Service) wsOnConnected(provider string) {
|
|||||||
}
|
}
|
||||||
now := time.Now().UTC()
|
now := time.Now().UTC()
|
||||||
auth := &coreauth.Auth{
|
auth := &coreauth.Auth{
|
||||||
ID: provider,
|
ID: channelID, // keep channel identifier as ID
|
||||||
Provider: provider,
|
Provider: "aistudio", // logical provider for switch routing
|
||||||
Label: provider,
|
Label: channelID, // display original channel id
|
||||||
Status: coreauth.StatusActive,
|
Status: coreauth.StatusActive,
|
||||||
CreatedAt: now,
|
CreatedAt: now,
|
||||||
UpdatedAt: now,
|
UpdatedAt: now,
|
||||||
Attributes: map[string]string{"ws_provider": "gemini"},
|
Metadata: map[string]any{"email": channelID}, // inject email inline
|
||||||
}
|
}
|
||||||
log.Infof("websocket provider connected: %s", provider)
|
log.Infof("websocket provider connected: %s", channelID)
|
||||||
s.applyCoreAuthAddOrUpdate(context.Background(), auth)
|
s.applyCoreAuthAddOrUpdate(context.Background(), auth)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) wsOnDisconnected(provider string, reason error) {
|
func (s *Service) wsOnDisconnected(channelID string, reason error) {
|
||||||
if s == nil || provider == "" {
|
if s == nil || channelID == "" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if reason != nil {
|
if reason != nil {
|
||||||
if strings.Contains(reason.Error(), "replaced by new connection") {
|
if strings.Contains(reason.Error(), "replaced by new connection") {
|
||||||
log.Infof("websocket provider replaced: %s", provider)
|
log.Infof("websocket provider replaced: %s", channelID)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Warnf("websocket provider disconnected: %s (%v)", provider, reason)
|
log.Warnf("websocket provider disconnected: %s (%v)", channelID, reason)
|
||||||
} else {
|
} else {
|
||||||
log.Infof("websocket provider disconnected: %s", provider)
|
log.Infof("websocket provider disconnected: %s", channelID)
|
||||||
}
|
}
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
s.applyCoreAuthRemoval(ctx, provider)
|
s.applyCoreAuthRemoval(ctx, channelID)
|
||||||
if s.coreManager != nil {
|
if s.coreManager != nil {
|
||||||
s.coreManager.UnregisterExecutor(provider)
|
s.coreManager.UnregisterExecutor(channelID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -317,17 +317,16 @@ func (s *Service) ensureExecutorsForAuth(a *coreauth.Auth) {
|
|||||||
s.coreManager.RegisterExecutor(executor.NewOpenAICompatExecutor(compatProviderKey, s.cfg))
|
s.coreManager.RegisterExecutor(executor.NewOpenAICompatExecutor(compatProviderKey, s.cfg))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if strings.HasPrefix(strings.ToLower(strings.TrimSpace(a.Provider)), "aistudio-") {
|
|
||||||
if s.wsGateway != nil {
|
|
||||||
s.coreManager.RegisterExecutor(executor.NewAistudioExecutor(s.cfg, a.Provider, s.wsGateway))
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
switch strings.ToLower(a.Provider) {
|
switch strings.ToLower(a.Provider) {
|
||||||
case "gemini":
|
case "gemini":
|
||||||
s.coreManager.RegisterExecutor(executor.NewGeminiExecutor(s.cfg))
|
s.coreManager.RegisterExecutor(executor.NewGeminiExecutor(s.cfg))
|
||||||
case "gemini-cli":
|
case "gemini-cli":
|
||||||
s.coreManager.RegisterExecutor(executor.NewGeminiCLIExecutor(s.cfg))
|
s.coreManager.RegisterExecutor(executor.NewGeminiCLIExecutor(s.cfg))
|
||||||
|
case "aistudio":
|
||||||
|
if s.wsGateway != nil {
|
||||||
|
s.coreManager.RegisterExecutor(executor.NewAIStudioExecutor(s.cfg, a.ID, s.wsGateway))
|
||||||
|
}
|
||||||
|
return
|
||||||
case "claude":
|
case "claude":
|
||||||
s.coreManager.RegisterExecutor(executor.NewClaudeExecutor(s.cfg))
|
s.coreManager.RegisterExecutor(executor.NewClaudeExecutor(s.cfg))
|
||||||
case "codex":
|
case "codex":
|
||||||
@@ -609,13 +608,6 @@ func (s *Service) registerModelsForAuth(a *coreauth.Auth) {
|
|||||||
}
|
}
|
||||||
provider := strings.ToLower(strings.TrimSpace(a.Provider))
|
provider := strings.ToLower(strings.TrimSpace(a.Provider))
|
||||||
compatProviderKey, compatDisplayName, compatDetected := openAICompatInfoFromAuth(a)
|
compatProviderKey, compatDisplayName, compatDetected := openAICompatInfoFromAuth(a)
|
||||||
if a.Attributes != nil {
|
|
||||||
if strings.EqualFold(a.Attributes["ws_provider"], "gemini") {
|
|
||||||
models := mergeGeminiModels()
|
|
||||||
GlobalModelRegistry().RegisterClient(a.ID, provider, models)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if compatDetected {
|
if compatDetected {
|
||||||
provider = "openai-compatibility"
|
provider = "openai-compatibility"
|
||||||
}
|
}
|
||||||
@@ -625,6 +617,8 @@ func (s *Service) registerModelsForAuth(a *coreauth.Auth) {
|
|||||||
models = registry.GetGeminiModels()
|
models = registry.GetGeminiModels()
|
||||||
case "gemini-cli":
|
case "gemini-cli":
|
||||||
models = registry.GetGeminiCLIModels()
|
models = registry.GetGeminiCLIModels()
|
||||||
|
case "aistudio":
|
||||||
|
models = registry.GetAIStudioModels()
|
||||||
case "claude":
|
case "claude":
|
||||||
models = registry.GetClaudeModels()
|
models = registry.GetClaudeModels()
|
||||||
if entry := s.resolveConfigClaudeKey(a); entry != nil && len(entry.Models) > 0 {
|
if entry := s.resolveConfigClaudeKey(a); entry != nil && len(entry.Models) > 0 {
|
||||||
@@ -726,27 +720,6 @@ func (s *Service) registerModelsForAuth(a *coreauth.Auth) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func mergeGeminiModels() []*ModelInfo {
|
|
||||||
models := make([]*ModelInfo, 0, 16)
|
|
||||||
seen := make(map[string]struct{})
|
|
||||||
appendModels := func(items []*ModelInfo) {
|
|
||||||
for i := range items {
|
|
||||||
m := items[i]
|
|
||||||
if m == nil || m.ID == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if _, ok := seen[m.ID]; ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
seen[m.ID] = struct{}{}
|
|
||||||
models = append(models, m)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
appendModels(registry.GetGeminiModels())
|
|
||||||
appendModels(registry.GetGeminiCLIModels())
|
|
||||||
return models
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Service) resolveConfigClaudeKey(auth *coreauth.Auth) *config.ClaudeKey {
|
func (s *Service) resolveConfigClaudeKey(auth *coreauth.Auth) *config.ClaudeKey {
|
||||||
if auth == nil || s.cfg == nil {
|
if auth == nil || s.cfg == nil {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
Reference in New Issue
Block a user