diff --git a/service/ccm/service.go b/service/ccm/service.go index 8a9d8f17f..d3f76381f 100644 --- a/service/ccm/service.go +++ b/service/ccm/service.go @@ -5,6 +5,8 @@ import ( "encoding/json" "net/http" "strings" + "sync" + "time" "github.com/sagernet/sing-box/adapter" boxService "github.com/sagernet/sing-box/adapter/service" @@ -163,10 +165,29 @@ type Service struct { allCredentials []Credential userConfigMap map[string]*option.CCMUser + sessionModelAccess sync.Mutex + sessionModels map[sessionModelKey]time.Time + statusSubscriber *observable.Subscriber[struct{}] statusObserver *observable.Observer[struct{}] } +type sessionModelKey struct { + sessionID string + model string +} + +func (s *Service) cleanSessionModels() { + now := time.Now() + s.sessionModelAccess.Lock() + for key, createdAt := range s.sessionModels { + if now.Sub(createdAt) > sessionExpiry { + delete(s.sessionModels, key) + } + } + s.sessionModelAccess.Unlock() +} + func NewService(ctx context.Context, logger log.ContextLogger, tag string, options option.CCMServiceOptions) (adapter.Service, error) { initCCMUserAgent(logger) @@ -212,6 +233,7 @@ func NewService(ctx context.Context, logger log.ContextLogger, tag string, optio Listen: options.ListenOptions, }), userManager: userManager, + sessionModels: make(map[sessionModelKey]time.Time), statusSubscriber: statusSubscriber, statusObserver: observable.NewObserver[struct{}](statusSubscriber, 8), } diff --git a/service/ccm/service_handler.go b/service/ccm/service_handler.go index 9ce78db53..94af4e90e 100644 --- a/service/ccm/service_handler.go +++ b/service/ccm/service_handler.go @@ -218,6 +218,7 @@ func (s *Service) ServeHTTP(w http.ResponseWriter, r *http.Request) { } provider.pollIfStale() + s.cleanSessionModels() anthropicBetaHeader := r.Header.Get("anthropic-beta") if isFastModeRequest(anthropicBetaHeader) { @@ -235,7 +236,22 @@ func (s *Service) ServeHTTP(w http.ResponseWriter, r *http.Request) { writeNonRetryableCredentialError(w, r, unavailableCredentialMessage(provider, err.Error())) return } - if isNew { + modelDisplay := requestModel + if requestModel != "" && isExtendedContextRequest(anthropicBetaHeader) { + modelDisplay += "[1m]" + } + isNewModel := false + if sessionID != "" && modelDisplay != "" { + key := sessionModelKey{sessionID, modelDisplay} + s.sessionModelAccess.Lock() + _, exists := s.sessionModels[key] + if !exists { + s.sessionModels[key] = time.Now() + isNewModel = true + } + s.sessionModelAccess.Unlock() + } + if isNew || isNewModel { logParts := []any{"assigned credential ", selectedCredential.tagName()} if sessionID != "" { logParts = append(logParts, " for session ", sessionID) @@ -243,11 +259,7 @@ func (s *Service) ServeHTTP(w http.ResponseWriter, r *http.Request) { if username != "" { logParts = append(logParts, " by user ", username) } - if requestModel != "" { - modelDisplay := requestModel - if isExtendedContextRequest(anthropicBetaHeader) { - modelDisplay += "[1m]" - } + if modelDisplay != "" { logParts = append(logParts, ", model=", modelDisplay) } s.logger.DebugContext(ctx, logParts...)