Skip to content

Commit fba2edd

Browse files
committed
Merge upstream changes (v7.2.111) with local modifications
- Recover Home OAuth credentials after 401 without breaking fork scheduling - Add Kimi K3 256K model metadata and token-version fingerprints
2 parents ec7211a + 4a31513 commit fba2edd

17 files changed

Lines changed: 655 additions & 57 deletions

‎internal/home/client.go‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1327,7 +1327,7 @@ func isAmbiguousIssuedRPopAuthError(err error) bool {
13271327
return !errors.As(err, &redisErr)
13281328
}
13291329

1330-
func (c *Client) GetRefreshAuth(ctx context.Context, authIndex string) ([]byte, error) {
1330+
func (c *Client) GetRefreshAuth(ctx context.Context, authIndex string, lastRefreshedAt time.Time, accessTokenSHA256 string) ([]byte, error) {
13311331
cmd, errClient := c.commandClient()
13321332
if errClient != nil {
13331333
return nil, errClient
@@ -1340,6 +1340,10 @@ func (c *Client) GetRefreshAuth(ctx context.Context, authIndex string) ([]byte,
13401340
Type: "refresh",
13411341
AuthIndex: authIndex,
13421342
}
1343+
if !lastRefreshedAt.IsZero() {
1344+
req.LastRefreshedAt = lastRefreshedAt.UTC().Format(time.RFC3339Nano)
1345+
}
1346+
req.ObservedAccessTokenSHA256 = strings.TrimSpace(accessTokenSHA256)
13431347
keyBytes, err := json.Marshal(&req)
13441348
if err != nil {
13451349
return nil, err

‎internal/home/requests.go‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@ type modelsRequest struct {
1818
}
1919

2020
type refreshRequest struct {
21-
Type string `json:"type"`
22-
AuthIndex string `json:"auth_index"`
21+
Type string `json:"type"`
22+
AuthIndex string `json:"auth_index"`
23+
LastRefreshedAt string `json:"last_refreshed_at,omitempty"`
24+
ObservedAccessTokenSHA256 string `json:"access_token_sha256,omitempty"`
2325
}
2426

2527
type InFlightFrameKind string

‎internal/redisqueue/plugin.go‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ func (p *usageQueuePlugin) HandleUsage(ctx context.Context, record coreusage.Rec
9090
TTFTMs: record.TTFT.Milliseconds(),
9191
Source: record.Source,
9292
AuthIndex: record.AuthIndex,
93+
AccessTokenHash: record.AccessTokenSHA256,
9394
ClientIP: clientRequestMetadata.ClientIP,
9495
XForwardedFor: clientRequestMetadata.XForwardedFor,
9596
UserAgent: clientRequestMetadata.UserAgent,
@@ -145,6 +146,7 @@ type requestDetail struct {
145146
TTFTMs int64 `json:"ttft_ms"`
146147
Source string `json:"source"`
147148
AuthIndex string `json:"auth_index"`
149+
AccessTokenHash string `json:"access_token_sha256,omitempty"`
148150
ClientIP string `json:"client_ip"`
149151
XForwardedFor string `json:"x_forwarded_for"`
150152
UserAgent string `json:"user_agent"`

‎internal/registry/models/models.json‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2410,6 +2410,25 @@
24102410
"type": "kimi",
24112411
"display_name": "Kimi K3",
24122412
"description": "Kimi K3 - Moonshot AI's next-generation flagship model (~2.8T MoE) with multimodal input",
2413+
"context_length": 1048576,
2414+
"max_completion_tokens": 65536,
2415+
"thinking": {
2416+
"zero_allowed": false,
2417+
"levels": [
2418+
"low",
2419+
"high",
2420+
"max"
2421+
]
2422+
}
2423+
},
2424+
{
2425+
"id": "kimi-k3-256k",
2426+
"object": "model",
2427+
"created": 1785110400,
2428+
"owned_by": "moonshot",
2429+
"type": "kimi",
2430+
"display_name": "Kimi K3 256K",
2431+
"description": "Kimi K3 256K - 256K context version of Kimi K3 delivering the same results within 256K context at reduced quota consumption; supports image input only (no video)",
24132432
"context_length": 262144,
24142433
"max_completion_tokens": 65536,
24152434
"thinking": {

‎internal/runtime/executor/helps/home_refresh.go‎

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,14 @@ package helps
22

33
import (
44
"context"
5+
"crypto/sha256"
6+
"encoding/hex"
57
"encoding/json"
8+
"errors"
69
"fmt"
710
"net/http"
811
"strings"
12+
"time"
913

1014
"github.com/router-for-me/CLIProxyAPI/v7/internal/config"
1115
"github.com/router-for-me/CLIProxyAPI/v7/internal/home"
@@ -43,7 +47,7 @@ type homeErrorDetail struct {
4347

4448
type homeRefreshClient interface {
4549
HeartbeatOK() bool
46-
GetRefreshAuth(ctx context.Context, authIndex string) ([]byte, error)
50+
GetRefreshAuth(ctx context.Context, authIndex string, lastRefreshedAt time.Time, accessTokenSHA256 string) ([]byte, error)
4751
}
4852

4953
var currentHomeRefreshClient = func() homeRefreshClient {
@@ -77,8 +81,11 @@ func RefreshAuthViaHome(ctx context.Context, cfg *config.Config, auth *cliproxya
7781
return nil, true, homeStatusErr{code: http.StatusBadGateway, msg: "home refresh: auth_index is empty"}
7882
}
7983

80-
raw, err := client.GetRefreshAuth(ctx, authIndex)
84+
raw, err := client.GetRefreshAuth(ctx, authIndex, auth.LastRefreshedAt, authAccessTokenSHA256(auth))
8185
if err != nil {
86+
if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
87+
return nil, true, err
88+
}
8289
return nil, true, homeStatusErr{code: http.StatusBadGateway, msg: err.Error()}
8390
}
8491

@@ -107,6 +114,43 @@ func RefreshAuthViaHome(ctx context.Context, cfg *config.Config, auth *cliproxya
107114
return updated, true, nil
108115
}
109116

117+
func authAccessTokenSHA256(auth *cliproxyauth.Auth) string {
118+
accessToken := authAccessTokenForFingerprint(auth)
119+
if accessToken == "" {
120+
return ""
121+
}
122+
digest := sha256.Sum256([]byte(accessToken))
123+
return hex.EncodeToString(digest[:])
124+
}
125+
126+
func authAccessTokenForFingerprint(auth *cliproxyauth.Auth) string {
127+
if auth == nil || auth.Metadata == nil {
128+
return ""
129+
}
130+
for _, key := range []string{"access_token", "accessToken"} {
131+
if value, ok := auth.Metadata[key].(string); ok && strings.TrimSpace(value) != "" {
132+
return strings.TrimSpace(value)
133+
}
134+
}
135+
for _, key := range []string{"token", "Token"} {
136+
switch token := auth.Metadata[key].(type) {
137+
case map[string]any:
138+
for _, tokenKey := range []string{"access_token", "accessToken"} {
139+
if value, ok := token[tokenKey].(string); ok && strings.TrimSpace(value) != "" {
140+
return strings.TrimSpace(value)
141+
}
142+
}
143+
case map[string]string:
144+
for _, tokenKey := range []string{"access_token", "accessToken"} {
145+
if value := strings.TrimSpace(token[tokenKey]); value != "" {
146+
return value
147+
}
148+
}
149+
}
150+
}
151+
return ""
152+
}
153+
110154
func parseHomeRefreshAuth(raw []byte) (*cliproxyauth.Auth, string, error) {
111155
var rawObject map[string]json.RawMessage
112156
if errUnmarshal := json.Unmarshal(raw, &rawObject); errUnmarshal != nil {
@@ -132,6 +176,8 @@ func statusFromHomeErrorCode(code string) int {
132176
return http.StatusUnauthorized
133177
case "model_not_found":
134178
return http.StatusNotFound
179+
case "refresh_temporarily_unavailable", "home_unavailable":
180+
return http.StatusServiceUnavailable
135181
default:
136182
return http.StatusBadGateway
137183
}

‎internal/runtime/executor/helps/home_refresh_test.go‎

Lines changed: 57 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ package helps
33
import (
44
"context"
55
"encoding/json"
6+
"errors"
67
"net/http"
78
"sync/atomic"
89
"testing"
10+
"time"
911

1012
"github.com/router-for-me/CLIProxyAPI/v7/internal/config"
1113
cliproxyauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth"
@@ -18,22 +20,60 @@ func TestStatusFromHomeErrorCodeMapsAuthenticationErrorToUnauthorized(t *testing
1820
if got := statusFromHomeErrorCode("unauthorized"); got != http.StatusUnauthorized {
1921
t.Fatalf("statusFromHomeErrorCode(unauthorized) = %d, want %d", got, http.StatusUnauthorized)
2022
}
23+
if got := statusFromHomeErrorCode("refresh_temporarily_unavailable"); got != http.StatusServiceUnavailable {
24+
t.Fatalf("statusFromHomeErrorCode(refresh_temporarily_unavailable) = %d, want %d", got, http.StatusServiceUnavailable)
25+
}
2126
}
2227

2328
type fakeHomeRefreshClient struct {
24-
calls atomic.Int32
25-
authIndex string
26-
raw []byte
29+
calls atomic.Int32
30+
authIndex string
31+
lastRefreshedAt time.Time
32+
accessTokenHash string
33+
raw []byte
34+
err error
2735
}
2836

2937
func (c *fakeHomeRefreshClient) HeartbeatOK() bool {
3038
return true
3139
}
3240

33-
func (c *fakeHomeRefreshClient) GetRefreshAuth(_ context.Context, authIndex string) ([]byte, error) {
41+
func (c *fakeHomeRefreshClient) GetRefreshAuth(_ context.Context, authIndex string, lastRefreshedAt time.Time, accessTokenHash string) ([]byte, error) {
3442
c.calls.Add(1)
3543
c.authIndex = authIndex
36-
return c.raw, nil
44+
c.lastRefreshedAt = lastRefreshedAt
45+
c.accessTokenHash = accessTokenHash
46+
return c.raw, c.err
47+
}
48+
49+
func TestRefreshAuthViaHomePreservesContextErrors(t *testing.T) {
50+
client := &fakeHomeRefreshClient{err: context.DeadlineExceeded}
51+
oldCurrentHomeRefreshClient := currentHomeRefreshClient
52+
currentHomeRefreshClient = func() homeRefreshClient { return client }
53+
t.Cleanup(func() { currentHomeRefreshClient = oldCurrentHomeRefreshClient })
54+
55+
cfg := &config.Config{Home: config.HomeConfig{Enabled: true}}
56+
auth := &cliproxyauth.Auth{ID: "home-auth", Index: "home-auth", Provider: "codex"}
57+
_, handled, errRefresh := RefreshAuthViaHome(context.Background(), cfg, auth)
58+
if !handled || !errors.Is(errRefresh, context.DeadlineExceeded) {
59+
t.Fatalf("RefreshAuthViaHome() = handled %v err %v, want true/context.DeadlineExceeded", handled, errRefresh)
60+
}
61+
}
62+
63+
func TestAuthAccessTokenSHA256SupportsKnownMetadataShapes(t *testing.T) {
64+
want := authAccessTokenSHA256(&cliproxyauth.Auth{Metadata: map[string]any{"access_token": "same-token"}})
65+
cases := map[string]*cliproxyauth.Auth{
66+
"camel case": {Metadata: map[string]any{"accessToken": "same-token"}},
67+
"nested any map": {Metadata: map[string]any{"token": map[string]any{"access_token": "same-token"}}},
68+
"nested string map": {Metadata: map[string]any{"Token": map[string]string{"accessToken": "same-token"}}},
69+
}
70+
for name, auth := range cases {
71+
t.Run(name, func(t *testing.T) {
72+
if got := authAccessTokenSHA256(auth); got == "" || got != want {
73+
t.Fatalf("token hash = %q, want %q", got, want)
74+
}
75+
})
76+
}
3777
}
3878

3979
func TestRefreshAuthViaHomeAcceptsAuthEnvelope(t *testing.T) {
@@ -64,11 +104,14 @@ func TestRefreshAuthViaHomeAcceptsAuthEnvelope(t *testing.T) {
64104
})
65105

66106
cfg := &config.Config{Home: config.HomeConfig{Enabled: true}}
107+
observedRefreshAt := time.Now().UTC()
67108
auth := &cliproxyauth.Auth{
68-
ID: "home-auth-1",
69-
Provider: "antigravity",
70-
Index: "home-index-1",
109+
ID: "home-auth-1",
110+
Provider: "antigravity",
111+
Index: "home-index-1",
112+
LastRefreshedAt: observedRefreshAt,
71113
Metadata: map[string]any{
114+
"access_token": "old-access-token",
72115
"refresh_token": "refresh-token",
73116
},
74117
}
@@ -86,6 +129,12 @@ func TestRefreshAuthViaHomeAcceptsAuthEnvelope(t *testing.T) {
86129
if client.authIndex != "home-index-1" {
87130
t.Fatalf("home refresh auth_index = %q, want home-index-1", client.authIndex)
88131
}
132+
if !client.lastRefreshedAt.Equal(observedRefreshAt) {
133+
t.Fatalf("home refresh last_refreshed_at = %v, want %v", client.lastRefreshedAt, observedRefreshAt)
134+
}
135+
if client.accessTokenHash != authAccessTokenSHA256(auth) {
136+
t.Fatalf("home refresh access token hash = %q, want %q", client.accessTokenHash, authAccessTokenSHA256(auth))
137+
}
89138
if updated == nil {
90139
t.Fatal("updated auth = nil")
91140
}

‎internal/runtime/executor/helps/usage_helpers.go‎

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -23,25 +23,26 @@ import (
2323
)
2424

2525
type UsageReporter struct {
26-
provider string
27-
executorType string
28-
model string
29-
alias string
30-
authID string
31-
authIndex string
32-
authType string
33-
apiKey string
34-
source string
35-
reasoning string
36-
serviceTier string
37-
stream *bool
38-
generate bool
39-
requestedAt time.Time
40-
ttftMu sync.RWMutex
41-
ttft time.Duration
42-
ttftStart time.Time
43-
ttftSet bool
44-
once sync.Once
26+
provider string
27+
executorType string
28+
model string
29+
alias string
30+
authID string
31+
authIndex string
32+
accessTokenHash string
33+
authType string
34+
apiKey string
35+
source string
36+
reasoning string
37+
serviceTier string
38+
stream *bool
39+
generate bool
40+
requestedAt time.Time
41+
ttftMu sync.RWMutex
42+
ttft time.Duration
43+
ttftStart time.Time
44+
ttftSet bool
45+
once sync.Once
4546
}
4647

4748
type usageExecutor interface {
@@ -82,6 +83,7 @@ func NewUsageReporter(ctx context.Context, provider, model string, auth *cliprox
8283
if auth != nil {
8384
reporter.authID = auth.ID
8485
reporter.authIndex = auth.EnsureIndex()
86+
reporter.accessTokenHash = authAccessTokenSHA256(auth)
8587
}
8688
return reporter
8789
}
@@ -269,6 +271,7 @@ func (r *UsageReporter) buildRecordForModel(model string, detail usage.Detail, f
269271
APIKey: r.apiKey,
270272
AuthID: r.authID,
271273
AuthIndex: r.authIndex,
274+
AccessTokenSHA256: r.accessTokenHash,
272275
AuthType: r.authType,
273276
ReasoningEffort: r.reasoning,
274277
ServiceTier: r.serviceTier,

‎sdk/cliproxy/auth/conductor_execution.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -639,7 +639,7 @@ func (m *Manager) executeStreamMixedOnce(ctx context.Context, providers []string
639639
models = models[:1]
640640
pooled = false
641641
}
642-
streamResult, errStream := m.executeStreamWithModelPool(execCtx, executor, auth, provider, execReq, execOpts, routeModel, streamExecutionModel, models, pooled, aliasResult, routing, !homeMode, selection != nil)
642+
streamResult, errStream := m.executeStreamWithModelPool(execCtx, executor, auth, provider, execReq, execOpts, routeModel, streamExecutionModel, models, pooled, aliasResult, routing, true, selection != nil)
643643
if errStream != nil {
644644
if selection != nil {
645645
releaseAttempt()

0 commit comments

Comments
 (0)