Skip to content

Commit ac4017e

Browse files
committed
Merge branch 'dev' of github.com:router-for-me/CLIProxyAPI into dev
2 parents 9985976 + 030c4a9 commit ac4017e

5 files changed

Lines changed: 285 additions & 91 deletions

File tree

internal/managementasset/updater.go

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -81,16 +81,8 @@ func runAutoUpdater(ctx context.Context) {
8181

8282
runOnce := func() {
8383
cfg := currentConfigPtr.Load()
84-
if cfg == nil {
85-
log.Debug("management asset auto-updater skipped: config not yet available")
86-
return
87-
}
88-
if cfg.RemoteManagement.DisableControlPanel {
89-
log.Debug("management asset auto-updater skipped: control panel disabled")
90-
return
91-
}
92-
if cfg.RemoteManagement.DisableAutoUpdatePanel {
93-
log.Debug("management asset auto-updater skipped: disable-auto-update-panel is enabled")
84+
if reason, skip := autoUpdateSkipReason(cfg); skip {
85+
log.Debugf("management asset auto-updater skipped: %s", reason)
9486
return
9587
}
9688

@@ -111,6 +103,22 @@ func runAutoUpdater(ctx context.Context) {
111103
}
112104
}
113105

106+
func autoUpdateSkipReason(cfg *config.Config) (string, bool) {
107+
if cfg == nil {
108+
return "config not yet available", true
109+
}
110+
if cfg.Home.Enabled {
111+
return "cluster mode enabled", true
112+
}
113+
if cfg.RemoteManagement.DisableControlPanel {
114+
return "control panel disabled", true
115+
}
116+
if cfg.RemoteManagement.DisableAutoUpdatePanel {
117+
return "disable-auto-update-panel is enabled", true
118+
}
119+
return "", false
120+
}
121+
114122
func newHTTPClient(proxyURL string) *http.Client {
115123
client := &http.Client{Timeout: 15 * time.Second}
116124

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package managementasset
2+
3+
import (
4+
"testing"
5+
6+
"github.com/router-for-me/CLIProxyAPI/v7/internal/config"
7+
)
8+
9+
func TestAutoUpdateSkipReason(t *testing.T) {
10+
tests := []struct {
11+
name string
12+
cfg *config.Config
13+
wantReason string
14+
wantSkip bool
15+
}{
16+
{
17+
name: "nil config",
18+
cfg: nil,
19+
wantReason: "config not yet available",
20+
wantSkip: true,
21+
},
22+
{
23+
name: "cluster mode",
24+
cfg: &config.Config{
25+
Home: config.HomeConfig{Enabled: true},
26+
},
27+
wantReason: "cluster mode enabled",
28+
wantSkip: true,
29+
},
30+
{
31+
name: "control panel disabled",
32+
cfg: &config.Config{
33+
RemoteManagement: config.RemoteManagement{DisableControlPanel: true},
34+
},
35+
wantReason: "control panel disabled",
36+
wantSkip: true,
37+
},
38+
{
39+
name: "auto update disabled",
40+
cfg: &config.Config{
41+
RemoteManagement: config.RemoteManagement{DisableAutoUpdatePanel: true},
42+
},
43+
wantReason: "disable-auto-update-panel is enabled",
44+
wantSkip: true,
45+
},
46+
{
47+
name: "enabled",
48+
cfg: &config.Config{},
49+
wantReason: "",
50+
wantSkip: false,
51+
},
52+
}
53+
54+
for _, tt := range tests {
55+
t.Run(tt.name, func(t *testing.T) {
56+
gotReason, gotSkip := autoUpdateSkipReason(tt.cfg)
57+
if gotReason != tt.wantReason || gotSkip != tt.wantSkip {
58+
t.Fatalf("autoUpdateSkipReason() = (%q, %t), want (%q, %t)", gotReason, gotSkip, tt.wantReason, tt.wantSkip)
59+
}
60+
})
61+
}
62+
}

sdk/cliproxy/service.go

Lines changed: 104 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,20 @@ type modelRegistrationTask struct {
121121
run func()
122122
}
123123

124+
type executorRegistrationOptions struct {
125+
includeBaseline bool
126+
includePlugins bool
127+
forceReplaceAuths bool
128+
auths []*coreauth.Auth
129+
}
130+
131+
var registerPluginExecutors = func(host *pluginhost.Host, manager *coreauth.Manager) {
132+
if host == nil || manager == nil {
133+
return
134+
}
135+
host.RegisterExecutors(manager, registry.GetGlobalRegistry())
136+
}
137+
124138
// RegisterUsagePlugin registers a usage plugin on the global usage manager.
125139
// This allows external code to monitor API usage and token consumption.
126140
//
@@ -191,8 +205,12 @@ func (s *Service) syncPluginModelRuntime(ctx context.Context) {
191205
ctx = context.Background()
192206
}
193207
s.pluginHost.RegisterModels(ctx, registry.GetGlobalRegistry())
194-
s.rebindExecutors()
195-
s.pluginHost.RegisterExecutors(s.coreManager, registry.GetGlobalRegistry())
208+
s.registerAvailableExecutors(ctx, executorRegistrationOptions{
209+
includeBaseline: s.cfg != nil && s.cfg.Home.Enabled,
210+
includePlugins: true,
211+
forceReplaceAuths: true,
212+
auths: s.coreManager.List(),
213+
})
196214
s.refreshPluginModelRegistrations(ctx)
197215
s.coreManager.RefreshSchedulerAll()
198216
}
@@ -809,6 +827,76 @@ func (s *Service) ensureExecutorsForAuth(a *coreauth.Auth) {
809827
}
810828

811829
func (s *Service) ensureExecutorsForAuthWithMode(a *coreauth.Auth, forceReplace bool) {
830+
if a == nil {
831+
return
832+
}
833+
s.registerAvailableExecutors(context.Background(), executorRegistrationOptions{
834+
auths: []*coreauth.Auth{a},
835+
forceReplaceAuths: forceReplace,
836+
})
837+
}
838+
839+
func (s *Service) registerAvailableExecutors(ctx context.Context, opts executorRegistrationOptions) {
840+
if s == nil || s.coreManager == nil {
841+
return
842+
}
843+
if ctx == nil {
844+
ctx = context.Background()
845+
}
846+
// Keep all Service-owned executor registration paths here so native, Home,
847+
// auth-derived, and plugin executors stay in the same binding order.
848+
if opts.includeBaseline {
849+
s.registerExecutorsForAuths(baselineExecutorAuths(), true)
850+
}
851+
if len(opts.auths) > 0 {
852+
s.registerExecutorsForAuths(opts.auths, opts.forceReplaceAuths)
853+
}
854+
if opts.includePlugins && s.pluginHost != nil {
855+
registerPluginExecutors(s.pluginHost, s.coreManager)
856+
}
857+
}
858+
859+
func baselineExecutorAuths() []*coreauth.Auth {
860+
providers := []string{
861+
"codex",
862+
"claude",
863+
"gemini",
864+
"vertex",
865+
"gemini-cli",
866+
"aistudio",
867+
"antigravity",
868+
"kimi",
869+
"xai",
870+
"openai-compatibility",
871+
}
872+
auths := make([]*coreauth.Auth, 0, len(providers))
873+
for _, provider := range providers {
874+
auth := &coreauth.Auth{
875+
ID: provider,
876+
Provider: provider,
877+
}
878+
if provider == "openai-compatibility" {
879+
auth.Attributes = map[string]string{"compat_name": "openai-compatibility"}
880+
}
881+
auths = append(auths, auth)
882+
}
883+
return auths
884+
}
885+
886+
func (s *Service) registerExecutorsForAuths(auths []*coreauth.Auth, forceReplace bool) {
887+
reboundCodex := false
888+
for _, auth := range auths {
889+
if auth != nil && strings.EqualFold(strings.TrimSpace(auth.Provider), "codex") {
890+
if reboundCodex && forceReplace {
891+
continue
892+
}
893+
reboundCodex = true
894+
}
895+
s.registerExecutorForAuth(auth, forceReplace)
896+
}
897+
}
898+
899+
func (s *Service) registerExecutorForAuth(a *coreauth.Auth, forceReplace bool) {
812900
if s == nil || s.coreManager == nil || a == nil {
813901
return
814902
}
@@ -1015,24 +1103,6 @@ func (s *Service) tryRegisterPluginModelsForAuth(ctx context.Context, a *coreaut
10151103
return true
10161104
}
10171105

1018-
// rebindExecutors refreshes provider executors so they observe the latest configuration.
1019-
func (s *Service) rebindExecutors() {
1020-
if s == nil || s.coreManager == nil {
1021-
return
1022-
}
1023-
auths := s.coreManager.List()
1024-
reboundCodex := false
1025-
for _, auth := range auths {
1026-
if auth != nil && strings.EqualFold(strings.TrimSpace(auth.Provider), "codex") {
1027-
if reboundCodex {
1028-
continue
1029-
}
1030-
reboundCodex = true
1031-
}
1032-
s.ensureExecutorsForAuthWithMode(auth, true)
1033-
}
1034-
}
1035-
10361106
func (s *Service) applyConfigUpdate(newCfg *config.Config) {
10371107
if s == nil {
10381108
return
@@ -1117,10 +1187,15 @@ func (s *Service) applyConfigUpdate(newCfg *config.Config) {
11171187
s.coreManager.SetConfig(newCfg)
11181188
s.coreManager.SetOAuthModelAlias(newCfg.OAuthModelAlias)
11191189
}
1120-
if newCfg.Home.Enabled {
1121-
s.registerHomeExecutors()
1190+
var auths []*coreauth.Auth
1191+
if s.coreManager != nil {
1192+
auths = s.coreManager.List()
11221193
}
1123-
s.rebindExecutors()
1194+
s.registerAvailableExecutors(context.Background(), executorRegistrationOptions{
1195+
includeBaseline: newCfg.Home.Enabled,
1196+
forceReplaceAuths: true,
1197+
auths: auths,
1198+
})
11241199
ctx := context.Background()
11251200
s.registerConfigAPIKeyAuths(ctx, newCfg)
11261201
s.syncPluginRuntime(ctx)
@@ -1178,24 +1253,6 @@ func forceHomeRuntimeConfig(cfg *config.Config) {
11781253
cfg.RemoteManagement.DisableControlPanel = true
11791254
}
11801255

1181-
func (s *Service) registerHomeExecutors() {
1182-
if s == nil || s.coreManager == nil || s.cfg == nil {
1183-
return
1184-
}
1185-
1186-
// Register baseline executors so home-dispatched auth entries can execute without
1187-
// requiring any local auth-dir credentials.
1188-
s.coreManager.RegisterExecutor(executor.NewCodexAutoExecutor(s.cfg))
1189-
s.coreManager.RegisterExecutor(executor.NewClaudeExecutor(s.cfg))
1190-
s.coreManager.RegisterExecutor(executor.NewGeminiExecutor(s.cfg))
1191-
s.coreManager.RegisterExecutor(executor.NewGeminiVertexExecutor(s.cfg))
1192-
s.coreManager.RegisterExecutor(executor.NewGeminiCLIExecutor(s.cfg))
1193-
s.coreManager.RegisterExecutor(executor.NewAIStudioExecutor(s.cfg, "", s.wsGateway))
1194-
s.coreManager.RegisterExecutor(executor.NewAntigravityExecutor(s.cfg))
1195-
s.coreManager.RegisterExecutor(executor.NewKimiExecutor(s.cfg))
1196-
s.coreManager.RegisterExecutor(executor.NewOpenAICompatExecutor("openai-compatibility", s.cfg))
1197-
}
1198-
11991256
func (s *Service) applyHomeOverlay(remoteCfg *config.Config) {
12001257
if s == nil || remoteCfg == nil {
12011258
return
@@ -1416,7 +1473,9 @@ func (s *Service) Run(ctx context.Context) error {
14161473

14171474
s.ensureWebsocketGateway()
14181475
if homeEnabled {
1419-
s.registerHomeExecutors()
1476+
s.registerAvailableExecutors(ctx, executorRegistrationOptions{
1477+
includeBaseline: true,
1478+
})
14201479
// Home mode does not expose in-process Redis RESP usage output; usage is forwarded to home instead.
14211480
redisqueue.SetEnabled(true)
14221481
}
@@ -1609,9 +1668,9 @@ func (s *Service) Shutdown(ctx context.Context) error {
16091668
}
16101669
s.pluginHost.ApplyConfig(ctx, &config.Config{})
16111670
s.pluginHost.RegisterModels(ctx, registry.GetGlobalRegistry())
1612-
if s.coreManager != nil {
1613-
s.pluginHost.RegisterExecutors(s.coreManager, registry.GetGlobalRegistry())
1614-
}
1671+
s.registerAvailableExecutors(ctx, executorRegistrationOptions{
1672+
includePlugins: true,
1673+
})
16151674
s.pluginHost.RegisterFrontendAuthProviders()
16161675
s.pluginHost.ShutdownAll()
16171676
if s.accessManager != nil {

0 commit comments

Comments
 (0)