Guard updateStateFromHeaders emission with value-change detection to avoid unnecessary computeAggregatedUtilization scans on every proxied response. Replace statusAggregateStateLocked two-value return with comparable statusSnapshot struct. Define statusPayload type for the status wire format, replacing anonymous structs and map literals.
194 lines
5.7 KiB
Go
194 lines
5.7 KiB
Go
package ccm
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/sagernet/sing-box/option"
|
|
)
|
|
|
|
type statusPayload struct {
|
|
FiveHourUtilization float64 `json:"five_hour_utilization"`
|
|
WeeklyUtilization float64 `json:"weekly_utilization"`
|
|
PlanWeight float64 `json:"plan_weight"`
|
|
}
|
|
|
|
func (s *Service) handleStatusEndpoint(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodGet {
|
|
writeJSONError(w, r, http.StatusMethodNotAllowed, "invalid_request_error", "method not allowed")
|
|
return
|
|
}
|
|
|
|
var provider credentialProvider
|
|
var userConfig *option.CCMUser
|
|
if len(s.options.Users) > 0 {
|
|
if r.Header.Get("X-Api-Key") != "" || r.Header.Get("Api-Key") != "" {
|
|
writeJSONError(w, r, http.StatusBadRequest, "invalid_request_error",
|
|
"API key authentication is not supported; use Authorization: Bearer with a CCM user token")
|
|
return
|
|
}
|
|
|
|
authHeader := r.Header.Get("Authorization")
|
|
if authHeader == "" {
|
|
writeJSONError(w, r, http.StatusUnauthorized, "authentication_error", "missing api key")
|
|
return
|
|
}
|
|
clientToken := strings.TrimPrefix(authHeader, "Bearer ")
|
|
if clientToken == authHeader {
|
|
writeJSONError(w, r, http.StatusUnauthorized, "authentication_error", "invalid api key format")
|
|
return
|
|
}
|
|
username, ok := s.userManager.Authenticate(clientToken)
|
|
if !ok {
|
|
writeJSONError(w, r, http.StatusUnauthorized, "authentication_error", "invalid api key")
|
|
return
|
|
}
|
|
|
|
userConfig = s.userConfigMap[username]
|
|
var err error
|
|
provider, err = credentialForUser(s.userConfigMap, s.providers, username)
|
|
if err != nil {
|
|
writeJSONError(w, r, http.StatusInternalServerError, "api_error", err.Error())
|
|
return
|
|
}
|
|
} else {
|
|
provider = s.providers[s.options.Credentials[0].Tag]
|
|
}
|
|
if provider == nil {
|
|
writeJSONError(w, r, http.StatusInternalServerError, "api_error", "no credential available")
|
|
return
|
|
}
|
|
|
|
if r.URL.Query().Get("watch") == "true" {
|
|
s.handleStatusStream(w, r, provider, userConfig)
|
|
return
|
|
}
|
|
|
|
provider.pollIfStale(r.Context())
|
|
avgFiveHour, avgWeekly, totalWeight := s.computeAggregatedUtilization(provider, userConfig)
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusOK)
|
|
json.NewEncoder(w).Encode(statusPayload{
|
|
FiveHourUtilization: avgFiveHour,
|
|
WeeklyUtilization: avgWeekly,
|
|
PlanWeight: totalWeight,
|
|
})
|
|
}
|
|
|
|
func (s *Service) handleStatusStream(w http.ResponseWriter, r *http.Request, provider credentialProvider, userConfig *option.CCMUser) {
|
|
flusher, ok := w.(http.Flusher)
|
|
if !ok {
|
|
writeJSONError(w, r, http.StatusInternalServerError, "api_error", "streaming not supported")
|
|
return
|
|
}
|
|
|
|
subscription, done, err := s.statusObserver.Subscribe()
|
|
if err != nil {
|
|
writeJSONError(w, r, http.StatusInternalServerError, "api_error", "service closing")
|
|
return
|
|
}
|
|
defer s.statusObserver.UnSubscribe(subscription)
|
|
|
|
provider.pollIfStale(r.Context())
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.Header().Set(statusStreamHeader, "true")
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
lastFiveHour, lastWeekly, lastWeight := s.computeAggregatedUtilization(provider, userConfig)
|
|
buf := &bytes.Buffer{}
|
|
json.NewEncoder(buf).Encode(statusPayload{
|
|
FiveHourUtilization: lastFiveHour,
|
|
WeeklyUtilization: lastWeekly,
|
|
PlanWeight: lastWeight,
|
|
})
|
|
_, writeErr := w.Write(buf.Bytes())
|
|
if writeErr != nil {
|
|
return
|
|
}
|
|
flusher.Flush()
|
|
|
|
for {
|
|
select {
|
|
case <-r.Context().Done():
|
|
return
|
|
case <-done:
|
|
return
|
|
case <-subscription:
|
|
for {
|
|
select {
|
|
case <-subscription:
|
|
default:
|
|
goto drained
|
|
}
|
|
}
|
|
drained:
|
|
fiveHour, weekly, weight := s.computeAggregatedUtilization(provider, userConfig)
|
|
if fiveHour == lastFiveHour && weekly == lastWeekly && weight == lastWeight {
|
|
continue
|
|
}
|
|
lastFiveHour = fiveHour
|
|
lastWeekly = weekly
|
|
lastWeight = weight
|
|
buf.Reset()
|
|
json.NewEncoder(buf).Encode(statusPayload{
|
|
FiveHourUtilization: fiveHour,
|
|
WeeklyUtilization: weekly,
|
|
PlanWeight: weight,
|
|
})
|
|
_, writeErr = w.Write(buf.Bytes())
|
|
if writeErr != nil {
|
|
return
|
|
}
|
|
flusher.Flush()
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *Service) computeAggregatedUtilization(provider credentialProvider, userConfig *option.CCMUser) (float64, float64, float64) {
|
|
var totalWeightedRemaining5h, totalWeightedRemainingWeekly, totalWeight float64
|
|
for _, credential := range provider.allCredentials() {
|
|
if !credential.isAvailable() {
|
|
continue
|
|
}
|
|
if userConfig != nil && userConfig.ExternalCredential != "" && credential.tagName() == userConfig.ExternalCredential {
|
|
continue
|
|
}
|
|
if userConfig != nil && !userConfig.AllowExternalUsage && credential.isExternal() {
|
|
continue
|
|
}
|
|
weight := credential.planWeight()
|
|
remaining5h := credential.fiveHourCap() - credential.fiveHourUtilization()
|
|
if remaining5h < 0 {
|
|
remaining5h = 0
|
|
}
|
|
remainingWeekly := credential.weeklyCap() - credential.weeklyUtilization()
|
|
if remainingWeekly < 0 {
|
|
remainingWeekly = 0
|
|
}
|
|
totalWeightedRemaining5h += remaining5h * weight
|
|
totalWeightedRemainingWeekly += remainingWeekly * weight
|
|
totalWeight += weight
|
|
}
|
|
if totalWeight == 0 {
|
|
return 100, 100, 0
|
|
}
|
|
return 100 - totalWeightedRemaining5h/totalWeight,
|
|
100 - totalWeightedRemainingWeekly/totalWeight,
|
|
totalWeight
|
|
}
|
|
|
|
func (s *Service) rewriteResponseHeadersForExternalUser(headers http.Header, provider credentialProvider, userConfig *option.CCMUser) {
|
|
avgFiveHour, avgWeekly, totalWeight := s.computeAggregatedUtilization(provider, userConfig)
|
|
|
|
headers.Set("anthropic-ratelimit-unified-5h-utilization", strconv.FormatFloat(avgFiveHour/100, 'f', 6, 64))
|
|
headers.Set("anthropic-ratelimit-unified-7d-utilization", strconv.FormatFloat(avgWeekly/100, 'f', 6, 64))
|
|
if totalWeight > 0 {
|
|
headers.Set("X-CCM-Plan-Weight", strconv.FormatFloat(totalWeight, 'f', -1, 64))
|
|
}
|
|
}
|