From 5dced4c0a68eb7cad1f72ef1173b2e3426899bf9 Mon Sep 17 00:00:00 2001 From: hkfires <10558748+hkfires@users.noreply.github.com> Date: Tue, 28 Oct 2025 19:00:25 +0800 Subject: [PATCH 1/3] feat(registry): unify Gemini models and add AI Studio set --- internal/api/server.go | 4 +- internal/registry/model_definitions.go | 138 ++++++++---------- .../runtime/executor/aistudio_executor.go | 40 ++--- sdk/cliproxy/auth/types.go | 9 -- sdk/cliproxy/service.go | 79 ++++------ 5 files changed, 106 insertions(+), 164 deletions(-) diff --git a/internal/api/server.go b/internal/api/server.go index afc19318..d834abc3 100644 --- a/internal/api/server.go +++ b/internal/api/server.go @@ -225,7 +225,7 @@ func NewServer(cfg *config.Config, authManager *auth.Manager, accessManager *sdk envManagementSecret := envAdminPasswordSet && envAdminPassword != "" // Create server instance - providerNames := make([]string, 0, len(cfg.OpenAICompatibility)) + providerNames := make([]string, 0, len(cfg.OpenAICompatibility)) for _, p := range cfg.OpenAICompatibility { 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 \ No newline at end of file diff --git a/internal/registry/model_definitions.go b/internal/registry/model_definitions.go index 7f525b15..ebc1a573 100644 --- a/internal/registry/model_definitions.go +++ b/internal/registry/model_definitions.go @@ -68,84 +68,8 @@ func GetClaudeModels() []*ModelInfo { } } -// GetGeminiModels returns the standard Gemini model definitions -func GetGeminiModels() []*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 { +// GeminiModels returns the shared base Gemini model set used by multiple providers. +func GeminiModels() []*ModelInfo { return []*ModelInfo{ { 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 func GetOpenAIModels() []*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-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: "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: "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"}, diff --git a/internal/runtime/executor/aistudio_executor.go b/internal/runtime/executor/aistudio_executor.go index 4e568cf6..a9452fd8 100644 --- a/internal/runtime/executor/aistudio_executor.go +++ b/internal/runtime/executor/aistudio_executor.go @@ -19,27 +19,27 @@ import ( "github.com/tidwall/sjson" ) -// AistudioExecutor routes AI Studio requests through a websocket-backed transport. -type AistudioExecutor struct { +// AIStudioExecutor routes AI Studio requests through a websocket-backed transport. +type AIStudioExecutor struct { provider string relay *wsrelay.Manager cfg *config.Config } -// NewAistudioExecutor constructs a websocket executor for the provider name. -func NewAistudioExecutor(cfg *config.Config, provider string, relay *wsrelay.Manager) *AistudioExecutor { - return &AistudioExecutor{provider: strings.ToLower(provider), relay: relay, cfg: cfg} +// NewAIStudioExecutor constructs a websocket executor for the provider name. +func NewAIStudioExecutor(cfg *config.Config, provider string, relay *wsrelay.Manager) *AIStudioExecutor { + return &AIStudioExecutor{provider: strings.ToLower(provider), relay: relay, cfg: cfg} } -// Identifier returns the provider key served by this executor. -func (e *AistudioExecutor) Identifier() string { return e.provider } +// Identifier returns the logical provider key for routing. +func (e *AIStudioExecutor) Identifier() string { return "aistudio" } // 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 } -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) defer reporter.trackFailure(ctx, &err) @@ -66,7 +66,7 @@ func (e *AistudioExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, Method: http.MethodPost, Headers: wsReq.Headers.Clone(), Body: bytes.Clone(body.payload), - Provider: e.provider, + Provider: e.Identifier(), AuthID: authID, AuthLabel: authLabel, AuthType: authType, @@ -92,7 +92,7 @@ func (e *AistudioExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, 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) defer reporter.trackFailure(ctx, &err) @@ -118,7 +118,7 @@ func (e *AistudioExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth Method: http.MethodPost, Headers: wsReq.Headers.Clone(), Body: bytes.Clone(body.payload), - Provider: e.provider, + Provider: e.Identifier(), AuthID: authID, AuthLabel: authLabel, AuthType: authType, @@ -151,7 +151,7 @@ func (e *AistudioExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth case wsrelay.MessageTypeStreamChunk: if len(event.Payload) > 0 { appendAPIResponseChunk(ctx, e.cfg, bytes.Clone(event.Payload)) - filtered := filterAistudioUsageMetadata(event.Payload) + filtered := filterAIStudioUsageMetadata(event.Payload) if detail, ok := parseGeminiStreamUsage(filtered); ok { reporter.publish(ctx, detail) } @@ -188,7 +188,7 @@ func (e *AistudioExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth 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) if err != nil { return cliproxyexecutor.Response{}, err @@ -215,7 +215,7 @@ func (e *AistudioExecutor) CountTokens(ctx context.Context, auth *cliproxyauth.A Method: http.MethodPost, Headers: wsReq.Headers.Clone(), Body: bytes.Clone(body.payload), - Provider: e.provider, + Provider: e.Identifier(), AuthID: authID, AuthLabel: authLabel, AuthType: authType, @@ -241,7 +241,7 @@ func (e *AistudioExecutor) CountTokens(ctx context.Context, auth *cliproxyauth.A 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 return auth, nil } @@ -252,7 +252,7 @@ type translatedPayload struct { 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 to := sdktranslator.FromString("gemini") 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 } -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) if action == "streamGenerateContent" { if alt == "" { @@ -289,9 +289,9 @@ func (e *AistudioExecutor) buildEndpoint(model, action, alt string) string { 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. -func filterAistudioUsageMetadata(payload []byte) []byte { +func filterAIStudioUsageMetadata(payload []byte) []byte { if len(payload) == 0 { return payload } diff --git a/sdk/cliproxy/auth/types.go b/sdk/cliproxy/auth/types.go index 2755383f..f9c71d56 100644 --- a/sdk/cliproxy/auth/types.go +++ b/sdk/cliproxy/auth/types.go @@ -157,15 +157,6 @@ func (a *Auth) AccountInfo() (string, string) { 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 v := a.Attributes["api_key"]; v != "" { return "api_key", v diff --git a/sdk/cliproxy/service.go b/sdk/cliproxy/service.go index 32c64837..1fa863ad 100644 --- a/sdk/cliproxy/service.go +++ b/sdk/cliproxy/service.go @@ -194,15 +194,15 @@ func (s *Service) ensureWebsocketGateway() { s.wsGateway = wsrelay.NewManager(opts) } -func (s *Service) wsOnConnected(provider string) { - if s == nil || provider == "" { +func (s *Service) wsOnConnected(channelID string) { + if s == nil || channelID == "" { return } - if !strings.HasPrefix(strings.ToLower(provider), "aistudio-") { + if !strings.HasPrefix(strings.ToLower(channelID), "aistudio-") { return } 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 { return } @@ -210,35 +210,35 @@ func (s *Service) wsOnConnected(provider string) { } now := time.Now().UTC() auth := &coreauth.Auth{ - ID: provider, - Provider: provider, - Label: provider, - Status: coreauth.StatusActive, - CreatedAt: now, - UpdatedAt: now, - Attributes: map[string]string{"ws_provider": "gemini"}, + ID: channelID, // keep channel identifier as ID + Provider: "aistudio", // logical provider for switch routing + Label: channelID, // display original channel id + Status: coreauth.StatusActive, + CreatedAt: now, + UpdatedAt: now, + 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) } -func (s *Service) wsOnDisconnected(provider string, reason error) { - if s == nil || provider == "" { +func (s *Service) wsOnDisconnected(channelID string, reason error) { + if s == nil || channelID == "" { return } if reason != nil { if strings.Contains(reason.Error(), "replaced by new connection") { - log.Infof("websocket provider replaced: %s", provider) + log.Infof("websocket provider replaced: %s", channelID) return } - log.Warnf("websocket provider disconnected: %s (%v)", provider, reason) + log.Warnf("websocket provider disconnected: %s (%v)", channelID, reason) } else { - log.Infof("websocket provider disconnected: %s", provider) + log.Infof("websocket provider disconnected: %s", channelID) } ctx := context.Background() - s.applyCoreAuthRemoval(ctx, provider) + s.applyCoreAuthRemoval(ctx, channelID) 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)) 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) { case "gemini": s.coreManager.RegisterExecutor(executor.NewGeminiExecutor(s.cfg)) case "gemini-cli": 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": s.coreManager.RegisterExecutor(executor.NewClaudeExecutor(s.cfg)) case "codex": @@ -609,13 +608,6 @@ func (s *Service) registerModelsForAuth(a *coreauth.Auth) { } provider := strings.ToLower(strings.TrimSpace(a.Provider)) 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 { provider = "openai-compatibility" } @@ -625,6 +617,8 @@ func (s *Service) registerModelsForAuth(a *coreauth.Auth) { models = registry.GetGeminiModels() case "gemini-cli": models = registry.GetGeminiCLIModels() + case "aistudio": + models = registry.GetAIStudioModels() case "claude": models = registry.GetClaudeModels() 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 { if auth == nil || s.cfg == nil { return nil From 663b9b35abf4f04ea65c7047acc6e11798d9b199 Mon Sep 17 00:00:00 2001 From: hkfires <10558748+hkfires@users.noreply.github.com> Date: Tue, 28 Oct 2025 19:28:26 +0800 Subject: [PATCH 2/3] fix(executor): pass authID to relay instead of provider --- internal/runtime/executor/aistudio_executor.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/runtime/executor/aistudio_executor.go b/internal/runtime/executor/aistudio_executor.go index a9452fd8..cfc86d0e 100644 --- a/internal/runtime/executor/aistudio_executor.go +++ b/internal/runtime/executor/aistudio_executor.go @@ -73,7 +73,7 @@ func (e *AIStudioExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, AuthValue: authValue, }) - wsResp, err := e.relay.NonStream(ctx, e.provider, wsReq) + wsResp, err := e.relay.NonStream(ctx, authID, wsReq) if err != nil { recordAPIResponseError(ctx, e.cfg, err) return resp, err @@ -124,7 +124,7 @@ func (e *AIStudioExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth AuthType: authType, AuthValue: authValue, }) - wsStream, err := e.relay.Stream(ctx, e.provider, wsReq) + wsStream, err := e.relay.Stream(ctx, authID, wsReq) if err != nil { recordAPIResponseError(ctx, e.cfg, err) return nil, err @@ -221,7 +221,7 @@ func (e *AIStudioExecutor) CountTokens(ctx context.Context, auth *cliproxyauth.A AuthType: authType, AuthValue: authValue, }) - resp, err := e.relay.NonStream(ctx, e.provider, wsReq) + resp, err := e.relay.NonStream(ctx, authID, wsReq) if err != nil { recordAPIResponseError(ctx, e.cfg, err) return cliproxyexecutor.Response{}, err From c99d0dfb337609767b7fd6cc176e545bd1de1f29 Mon Sep 17 00:00:00 2001 From: hkfires <10558748+hkfires@users.noreply.github.com> Date: Tue, 28 Oct 2025 19:51:05 +0800 Subject: [PATCH 3/3] fix(aistudio): remove no-op executor unregister on WS disconnect --- sdk/cliproxy/service.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/sdk/cliproxy/service.go b/sdk/cliproxy/service.go index 1fa863ad..eeccccab 100644 --- a/sdk/cliproxy/service.go +++ b/sdk/cliproxy/service.go @@ -237,9 +237,6 @@ func (s *Service) wsOnDisconnected(channelID string, reason error) { } ctx := context.Background() s.applyCoreAuthRemoval(ctx, channelID) - if s.coreManager != nil { - s.coreManager.UnregisterExecutor(channelID) - } } func (s *Service) applyCoreAuthAddOrUpdate(ctx context.Context, auth *coreauth.Auth) {