@@ -3,17 +3,23 @@ package ccm
33import (
44 "bytes"
55 "context"
6+ stdTLS "crypto/tls"
67 "encoding/json"
78 "io"
9+ "net"
810 "net/http"
911 "strconv"
1012 "strings"
1113 "sync"
1214 "time"
1315
16+ "github.com/sagernet/sing-box/adapter"
17+ "github.com/sagernet/sing-box/common/dialer"
1418 "github.com/sagernet/sing-box/log"
1519 "github.com/sagernet/sing-box/option"
1620 E "github.com/sagernet/sing/common/exceptions"
21+ M "github.com/sagernet/sing/common/metadata"
22+ "github.com/sagernet/sing/common/ntp"
1723)
1824
1925const defaultPollInterval = 60 * time .Second
@@ -44,7 +50,29 @@ type defaultCredential struct {
4450 logger log.ContextLogger
4551}
4652
47- func newDefaultCredential (tag string , options option.CCMDefaultCredentialOptions , httpClient * http.Client , logger log.ContextLogger ) * defaultCredential {
53+ func newDefaultCredential (ctx context.Context , tag string , options option.CCMDefaultCredentialOptions , logger log.ContextLogger ) (* defaultCredential , error ) {
54+ credentialDialer , err := dialer .NewWithOptions (dialer.Options {
55+ Context : ctx ,
56+ Options : option.DialerOptions {
57+ Detour : options .Detour ,
58+ },
59+ RemoteIsDomain : true ,
60+ })
61+ if err != nil {
62+ return nil , E .Cause (err , "create dialer for credential " , tag )
63+ }
64+ httpClient := & http.Client {
65+ Transport : & http.Transport {
66+ ForceAttemptHTTP2 : true ,
67+ TLSClientConfig : & stdTLS.Config {
68+ RootCAs : adapter .RootPoolFromContext (ctx ),
69+ Time : ntp .TimeFuncFromContext (ctx ),
70+ },
71+ DialContext : func (ctx context.Context , network , addr string ) (net.Conn , error ) {
72+ return credentialDialer .DialContext (ctx , network , M .ParseSocksaddr (addr ))
73+ },
74+ },
75+ }
4876 credential := & defaultCredential {
4977 tag : tag ,
5078 credentialPath : options .CredentialPath ,
@@ -61,7 +89,7 @@ func newDefaultCredential(tag string, options option.CCMDefaultCredentialOptions
6189 logger : logger ,
6290 }
6391 }
64- return credential
92+ return credential , nil
6593}
6694
6795func (c * defaultCredential ) start () error {
@@ -244,6 +272,7 @@ func (c *defaultCredential) pollUsage(ctx context.Context) {
244272 }
245273 request .Header .Set ("Authorization" , "Bearer " + accessToken )
246274 request .Header .Set ("Content-Type" , "application/json" )
275+ request .Header .Set ("anthropic-beta" , anthropicBetaOAuthValue )
247276
248277 httpClient := & http.Client {
249278 Transport : c .httpClient .Transport ,
@@ -264,12 +293,12 @@ func (c *defaultCredential) pollUsage(ctx context.Context) {
264293
265294 var usageResponse struct {
266295 FiveHour struct {
267- Utilization float64 `json:"utilization"`
268- ResetsAt int64 `json:"resets_at"`
296+ Utilization float64 `json:"utilization"`
297+ ResetsAt json. Number `json:"resets_at"`
269298 } `json:"five_hour"`
270299 SevenDay struct {
271- Utilization float64 `json:"utilization"`
272- ResetsAt int64 `json:"resets_at"`
300+ Utilization float64 `json:"utilization"`
301+ ResetsAt json. Number `json:"resets_at"`
273302 } `json:"seven_day"`
274303 }
275304 err = json .NewDecoder (response .Body ).Decode (& usageResponse )
@@ -281,12 +310,14 @@ func (c *defaultCredential) pollUsage(ctx context.Context) {
281310 c .stateMutex .Lock ()
282311 defer c .stateMutex .Unlock ()
283312 c .state .fiveHourUtilization = usageResponse .FiveHour .Utilization * 100
284- if usageResponse .FiveHour .ResetsAt > 0 {
285- c .state .fiveHourReset = time .Unix (usageResponse .FiveHour .ResetsAt , 0 )
313+ fiveHourReset , _ := usageResponse .FiveHour .ResetsAt .Int64 ()
314+ if fiveHourReset > 0 {
315+ c .state .fiveHourReset = time .Unix (fiveHourReset , 0 )
286316 }
287317 c .state .weeklyUtilization = usageResponse .SevenDay .Utilization * 100
288- if usageResponse .SevenDay .ResetsAt > 0 {
289- c .state .weeklyReset = time .Unix (usageResponse .SevenDay .ResetsAt , 0 )
318+ weeklyReset , _ := usageResponse .SevenDay .ResetsAt .Int64 ()
319+ if weeklyReset > 0 {
320+ c .state .weeklyReset = time .Unix (weeklyReset , 0 )
290321 }
291322 if c .state .hardRateLimited && time .Now ().After (c .state .rateLimitResetAt ) {
292323 c .state .hardRateLimited = false
@@ -548,8 +579,8 @@ func extractCCMSessionID(bodyBytes []byte) string {
548579}
549580
550581func buildCredentialProviders (
582+ ctx context.Context ,
551583 options option.CCMServiceOptions ,
552- httpClient * http.Client ,
553584 logger log.ContextLogger ,
554585) (map [string ]credentialProvider , []* defaultCredential , error ) {
555586 defaultCredentials := make (map [string ]* defaultCredential )
@@ -559,7 +590,10 @@ func buildCredentialProviders(
559590 for _ , credOpt := range options .Credentials {
560591 switch credOpt .Type {
561592 case "default" :
562- credential := newDefaultCredential (credOpt .Tag , credOpt .DefaultOptions , httpClient , logger )
593+ credential , err := newDefaultCredential (ctx , credOpt .Tag , credOpt .DefaultOptions , logger )
594+ if err != nil {
595+ return nil , nil , err
596+ }
563597 defaultCredentials [credOpt .Tag ] = credential
564598 allDefaults = append (allDefaults , credential )
565599 providers [credOpt .Tag ] = & singleCredentialProvider {credential : credential }
@@ -645,13 +679,17 @@ func validateCCMOptions(options option.CCMServiceOptions) error {
645679 hasCredentials := len (options .Credentials ) > 0
646680 hasLegacyPath := options .CredentialPath != ""
647681 hasLegacyUsages := options .UsagesPath != ""
682+ hasLegacyDetour := options .Detour != ""
648683
649684 if hasCredentials && hasLegacyPath {
650685 return E .New ("credential_path and credentials are mutually exclusive" )
651686 }
652687 if hasCredentials && hasLegacyUsages {
653688 return E .New ("usages_path and credentials are mutually exclusive; use usages_path on individual credentials" )
654689 }
690+ if hasCredentials && hasLegacyDetour {
691+ return E .New ("detour and credentials are mutually exclusive; use detour on individual credentials" )
692+ }
655693
656694 if hasCredentials {
657695 tags := make (map [string ]bool )
@@ -685,7 +723,6 @@ func validateCCMOptions(options option.CCMServiceOptions) error {
685723
686724// retryRequestWithBody re-sends a buffered request body using a different credential.
687725func retryRequestWithBody (
688- httpClient * http.Client ,
689726 originalRequest * http.Request ,
690727 bodyBytes []byte ,
691728 credential * defaultCredential ,
@@ -726,7 +763,7 @@ func retryRequestWithBody(
726763 }
727764 retryRequest .Header .Set ("Authorization" , "Bearer " + accessToken )
728765
729- return httpClient .Do (retryRequest )
766+ return credential . httpClient .Do (retryRequest )
730767}
731768
732769// credentialForUser finds the credential provider for a user.
0 commit comments