Merge branch 'v7' into dev

This commit is contained in:
Luis Pater
2026-05-10 02:33:42 +08:00
324 changed files with 3634 additions and 1148 deletions
@@ -11,8 +11,8 @@ import (
"sync"
"time"
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
cliproxyauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
"github.com/router-for-me/CLIProxyAPI/v7/internal/config"
cliproxyauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth"
)
const (
@@ -0,0 +1,91 @@
package helps
import (
"context"
"encoding/json"
"fmt"
"net/http"
"strings"
"github.com/router-for-me/CLIProxyAPI/v7/internal/config"
"github.com/router-for-me/CLIProxyAPI/v7/internal/home"
cliproxyauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth"
)
type homeStatusErr struct {
code int
msg string
}
func (e homeStatusErr) Error() string {
if e.msg != "" {
return e.msg
}
return fmt.Sprintf("status %d", e.code)
}
func (e homeStatusErr) StatusCode() int { return e.code }
type homeErrorEnvelope struct {
Error *homeErrorDetail `json:"error"`
}
type homeErrorDetail struct {
Type string `json:"type"`
Message string `json:"message"`
Code string `json:"code,omitempty"`
}
// RefreshAuthViaHome replaces local refresh logic when home control plane integration is enabled.
// It returns (updatedAuth, true, nil) when home refresh succeeds; (nil, true, err) when home is
// enabled but refresh fails; and (nil, false, nil) when home is disabled.
func RefreshAuthViaHome(ctx context.Context, cfg *config.Config, auth *cliproxyauth.Auth) (*cliproxyauth.Auth, bool, error) {
if cfg == nil || !cfg.Home.Enabled {
return nil, false, nil
}
if ctx == nil {
ctx = context.Background()
}
if auth == nil {
return nil, true, homeStatusErr{code: http.StatusInternalServerError, msg: "home refresh: auth is nil"}
}
client := home.Current()
if client == nil || !client.HeartbeatOK() {
return nil, true, homeStatusErr{code: http.StatusServiceUnavailable, msg: "home control center unavailable"}
}
authIndex := strings.TrimSpace(auth.Index)
if authIndex == "" {
authIndex = strings.TrimSpace(auth.EnsureIndex())
}
if authIndex == "" {
return nil, true, homeStatusErr{code: http.StatusBadGateway, msg: "home refresh: auth_index is empty"}
}
raw, err := client.GetRefreshAuth(ctx, authIndex)
if err != nil {
return nil, true, homeStatusErr{code: http.StatusBadGateway, msg: err.Error()}
}
var env homeErrorEnvelope
if errUnmarshal := json.Unmarshal(raw, &env); errUnmarshal == nil && env.Error != nil {
code := strings.TrimSpace(env.Error.Type)
if code == "" {
code = strings.TrimSpace(env.Error.Code)
}
msg := strings.TrimSpace(env.Error.Message)
if msg == "" {
msg = "home returned error"
}
return nil, true, homeStatusErr{code: http.StatusBadGateway, msg: msg}
}
var updated cliproxyauth.Auth
if errUnmarshal := json.Unmarshal(raw, &updated); errUnmarshal != nil {
return nil, true, homeStatusErr{code: http.StatusBadGateway, msg: "home returned invalid auth payload"}
}
updated.Index = authIndex
updated.EnsureIndex()
return &updated, true, nil
}
@@ -12,9 +12,9 @@ import (
"time"
"github.com/gin-gonic/gin"
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
"github.com/router-for-me/CLIProxyAPI/v6/internal/logging"
"github.com/router-for-me/CLIProxyAPI/v6/internal/util"
"github.com/router-for-me/CLIProxyAPI/v7/internal/config"
"github.com/router-for-me/CLIProxyAPI/v7/internal/logging"
"github.com/router-for-me/CLIProxyAPI/v7/internal/util"
log "github.com/sirupsen/logrus"
"github.com/tidwall/gjson"
)
@@ -4,9 +4,9 @@ import (
"encoding/json"
"strings"
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
"github.com/router-for-me/CLIProxyAPI/v6/internal/thinking"
cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor"
"github.com/router-for-me/CLIProxyAPI/v7/internal/config"
"github.com/router-for-me/CLIProxyAPI/v7/internal/thinking"
cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor"
"github.com/tidwall/gjson"
"github.com/tidwall/sjson"
)
@@ -3,7 +3,7 @@ package helps
import (
"testing"
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
"github.com/router-for-me/CLIProxyAPI/v7/internal/config"
"github.com/tidwall/gjson"
)
@@ -6,9 +6,9 @@ import (
"strings"
"time"
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
cliproxyauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
"github.com/router-for-me/CLIProxyAPI/v6/sdk/proxyutil"
"github.com/router-for-me/CLIProxyAPI/v7/internal/config"
cliproxyauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth"
"github.com/router-for-me/CLIProxyAPI/v7/sdk/proxyutil"
log "github.com/sirupsen/logrus"
)
@@ -5,9 +5,9 @@ import (
"net/http"
"testing"
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
cliproxyauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
sdkconfig "github.com/router-for-me/CLIProxyAPI/v6/sdk/config"
"github.com/router-for-me/CLIProxyAPI/v7/internal/config"
cliproxyauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth"
sdkconfig "github.com/router-for-me/CLIProxyAPI/v7/sdk/config"
)
func TestNewProxyAwareHTTPClientDirectBypassesGlobalProxy(t *testing.T) {
@@ -1,11 +1,11 @@
package helps
import (
_ "github.com/router-for-me/CLIProxyAPI/v6/internal/thinking/provider/antigravity"
_ "github.com/router-for-me/CLIProxyAPI/v6/internal/thinking/provider/claude"
_ "github.com/router-for-me/CLIProxyAPI/v6/internal/thinking/provider/codex"
_ "github.com/router-for-me/CLIProxyAPI/v6/internal/thinking/provider/gemini"
_ "github.com/router-for-me/CLIProxyAPI/v6/internal/thinking/provider/geminicli"
_ "github.com/router-for-me/CLIProxyAPI/v6/internal/thinking/provider/kimi"
_ "github.com/router-for-me/CLIProxyAPI/v6/internal/thinking/provider/openai"
_ "github.com/router-for-me/CLIProxyAPI/v7/internal/thinking/provider/antigravity"
_ "github.com/router-for-me/CLIProxyAPI/v7/internal/thinking/provider/claude"
_ "github.com/router-for-me/CLIProxyAPI/v7/internal/thinking/provider/codex"
_ "github.com/router-for-me/CLIProxyAPI/v7/internal/thinking/provider/gemini"
_ "github.com/router-for-me/CLIProxyAPI/v7/internal/thinking/provider/geminicli"
_ "github.com/router-for-me/CLIProxyAPI/v7/internal/thinking/provider/kimi"
_ "github.com/router-for-me/CLIProxyAPI/v7/internal/thinking/provider/openai"
)
@@ -3,14 +3,15 @@ package helps
import (
"bytes"
"context"
"errors"
"fmt"
"strings"
"sync"
"time"
"github.com/gin-gonic/gin"
cliproxyauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
"github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/usage"
cliproxyauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth"
"github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/usage"
"github.com/tidwall/gjson"
"github.com/tidwall/sjson"
)
@@ -51,7 +52,7 @@ func NewUsageReporter(ctx context.Context, provider, model string, auth *cliprox
}
func (r *UsageReporter) Publish(ctx context.Context, detail usage.Detail) {
r.publishWithOutcome(ctx, detail, false)
r.publishWithOutcome(ctx, detail, false, usage.Failure{})
}
func (r *UsageReporter) PublishAdditionalModel(ctx context.Context, model string, detail usage.Detail) {
@@ -74,11 +75,11 @@ func (r *UsageReporter) buildAdditionalModelRecord(model string, detail usage.De
if !hasNonZeroTokenUsage(detail) {
return usage.Record{}, false
}
return r.buildRecordForModel(model, detail, false), true
return r.buildRecordForModel(model, detail, false, usage.Failure{}), true
}
func (r *UsageReporter) PublishFailure(ctx context.Context) {
r.publishWithOutcome(ctx, usage.Detail{}, true)
func (r *UsageReporter) PublishFailure(ctx context.Context, errs ...error) {
r.publishWithOutcome(ctx, usage.Detail{}, true, failFromErrors(errs...))
}
func (r *UsageReporter) TrackFailure(ctx context.Context, errPtr *error) {
@@ -86,17 +87,17 @@ func (r *UsageReporter) TrackFailure(ctx context.Context, errPtr *error) {
return
}
if *errPtr != nil {
r.PublishFailure(ctx)
r.PublishFailure(ctx, *errPtr)
}
}
func (r *UsageReporter) publishWithOutcome(ctx context.Context, detail usage.Detail, failed bool) {
func (r *UsageReporter) publishWithOutcome(ctx context.Context, detail usage.Detail, failed bool, fail usage.Failure) {
if r == nil {
return
}
detail = normalizeUsageDetailTotal(detail)
r.once.Do(func() {
usage.PublishRecord(ctx, r.buildRecord(detail, failed))
usage.PublishRecord(ctx, r.buildRecord(detail, failed, fail))
})
}
@@ -127,20 +128,24 @@ func (r *UsageReporter) EnsurePublished(ctx context.Context) {
return
}
r.once.Do(func() {
usage.PublishRecord(ctx, r.buildRecord(usage.Detail{}, false))
usage.PublishRecord(ctx, r.buildRecord(usage.Detail{}, false, usage.Failure{}))
})
}
func (r *UsageReporter) buildRecord(detail usage.Detail, failed bool) usage.Record {
if r == nil {
return usage.Record{Detail: detail, Failed: failed}
func (r *UsageReporter) buildRecord(detail usage.Detail, failed bool, failures ...usage.Failure) usage.Record {
var fail usage.Failure
if len(failures) > 0 {
fail = failures[0]
}
return r.buildRecordForModel(r.model, detail, failed)
if r == nil {
return usage.Record{Detail: detail, Failed: failed, Fail: fail}
}
return r.buildRecordForModel(r.model, detail, failed, fail)
}
func (r *UsageReporter) buildRecordForModel(model string, detail usage.Detail, failed bool) usage.Record {
func (r *UsageReporter) buildRecordForModel(model string, detail usage.Detail, failed bool, fail usage.Failure) usage.Record {
if r == nil {
return usage.Record{Model: model, Detail: detail, Failed: failed}
return usage.Record{Model: model, Detail: detail, Failed: failed, Fail: fail}
}
return usage.Record{
Provider: r.provider,
@@ -154,10 +159,28 @@ func (r *UsageReporter) buildRecordForModel(model string, detail usage.Detail, f
RequestedAt: r.requestedAt,
Latency: r.latency(),
Failed: failed,
Fail: fail,
Detail: detail,
}
}
func failFromErrors(errs ...error) usage.Failure {
for _, err := range errs {
if err == nil {
continue
}
fail := usage.Failure{
Body: strings.TrimSpace(err.Error()),
}
var se interface{ StatusCode() int }
if errors.As(err, &se) && se != nil {
fail.StatusCode = se.StatusCode()
}
return fail
}
return usage.Failure{}
}
func (r *UsageReporter) latency() time.Duration {
if r == nil || r.requestedAt.IsZero() {
return 0
@@ -177,7 +200,7 @@ func APIKeyFromContext(ctx context.Context) string {
if !ok || ginCtx == nil {
return ""
}
if v, exists := ginCtx.Get("apiKey"); exists {
if v, exists := ginCtx.Get("userApiKey"); exists {
switch value := v.(type) {
case string:
return value
@@ -5,7 +5,7 @@ import (
"testing"
"time"
"github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/usage"
"github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/usage"
)
func TestParseOpenAIUsageChatCompletions(t *testing.T) {
@@ -8,9 +8,9 @@ import (
"time"
tls "github.com/refraction-networking/utls"
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
cliproxyauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
"github.com/router-for-me/CLIProxyAPI/v6/sdk/proxyutil"
"github.com/router-for-me/CLIProxyAPI/v7/internal/config"
cliproxyauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth"
"github.com/router-for-me/CLIProxyAPI/v7/sdk/proxyutil"
log "github.com/sirupsen/logrus"
"golang.org/x/net/http2"
"golang.org/x/net/proxy"