diff --git a/.github/workflows/backend-ci.yml b/.github/workflows/backend-ci.yml index f8b22ee70..15ff97fe0 100644 --- a/.github/workflows/backend-ci.yml +++ b/.github/workflows/backend-ci.yml @@ -20,7 +20,7 @@ jobs: cache-dependency-path: backend/go.sum - name: Verify Go version run: | - go version | grep -q 'go1.26.2' + go version | grep -q 'go1.26.3' - name: Unit tests working-directory: backend run: make test-unit @@ -60,7 +60,7 @@ jobs: cache-dependency-path: backend/go.sum - name: Verify Go version run: | - go version | grep -q 'go1.26.2' + go version | grep -q 'go1.26.3' - name: golangci-lint uses: golangci/golangci-lint-action@v9 with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 26ed85241..80bc9850d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -115,7 +115,7 @@ jobs: - name: Verify Go version run: | - go version | grep -q 'go1.26.2' + go version | grep -q 'go1.26.3' # Docker setup for GoReleaser - name: Set up QEMU diff --git a/.github/workflows/security-scan.yml b/.github/workflows/security-scan.yml index 600fd2fae..ef8e59e54 100644 --- a/.github/workflows/security-scan.yml +++ b/.github/workflows/security-scan.yml @@ -23,7 +23,7 @@ jobs: cache-dependency-path: backend/go.sum - name: Verify Go version run: | - go version | grep -q 'go1.26.2' + go version | grep -q 'go1.26.3' - name: Run govulncheck working-directory: backend run: | diff --git a/backend/.golangci.yml b/backend/.golangci.yml index 92ba39169..8f97c9454 100644 --- a/backend/.golangci.yml +++ b/backend/.golangci.yml @@ -9,7 +9,6 @@ linters: - govet - ineffassign - staticcheck - - unused settings: depguard: diff --git a/backend/Dockerfile b/backend/Dockerfile index 13f16df42..f153d6866 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.26.2-alpine +FROM golang:1.26.3-alpine WORKDIR /app diff --git a/backend/cmd/server/main.go b/backend/cmd/server/main.go index 22c76d472..a581b9263 100644 --- a/backend/cmd/server/main.go +++ b/backend/cmd/server/main.go @@ -9,7 +9,7 @@ import ( "flag" "log" "net/http" - _ "net/http/pprof" + _ "net/http/pprof" //nolint:gosec // Admin/debug profiling is intentionally exposed only when the server is started with that route mounted. "os" "os/signal" "strconv" @@ -27,7 +27,7 @@ import ( "github.com/gin-gonic/gin" "golang.org/x/net/http2" - "golang.org/x/net/http2/h2c" + "golang.org/x/net/http2/h2c" //nolint:staticcheck // Keep existing h2c behavior until the server moves fully to Go's Protocols API. ) //go:embed VERSION @@ -120,7 +120,7 @@ func runSetupServer() { server := &http.Server{ Addr: addr, - Handler: h2c.NewHandler(r, &http2.Server{}), + Handler: h2c.NewHandler(r, &http2.Server{}), //nolint:staticcheck // Keep existing h2c behavior until the server moves fully to Go's Protocols API. ReadHeaderTimeout: 30 * time.Second, IdleTimeout: 120 * time.Second, } diff --git a/backend/go.mod b/backend/go.mod index 982bf91b9..caf35ea4a 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -1,6 +1,6 @@ module github.com/Wei-Shaw/sub2api -go 1.26.2 +go 1.26.3 require ( entgo.io/ent v0.14.5 @@ -20,6 +20,7 @@ require ( github.com/google/wire v0.7.0 github.com/gorilla/websocket v1.5.3 github.com/imroc/req/v3 v3.57.0 + github.com/klauspost/compress v1.18.2 github.com/lib/pq v1.10.9 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/pquerna/otp v1.5.0 @@ -39,11 +40,11 @@ require ( github.com/wechatpay-apiv3/wechatpay-go v0.2.21 github.com/zeromicro/go-zero v1.9.4 go.uber.org/zap v1.24.0 - golang.org/x/crypto v0.49.0 + golang.org/x/crypto v0.51.0 golang.org/x/image v0.39.0 - golang.org/x/net v0.52.0 + golang.org/x/net v0.55.0 golang.org/x/sync v0.20.0 - golang.org/x/term v0.41.0 + golang.org/x/term v0.43.0 gopkg.in/natefinch/lumberjack.v2 v2.2.1 gopkg.in/yaml.v3 v3.0.1 modernc.org/sqlite v1.44.3 @@ -104,13 +105,11 @@ require ( github.com/goccy/go-json v0.10.2 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/go-querystring v1.1.0 // indirect - github.com/google/subcommands v1.2.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/hashicorp/hcl/v2 v2.18.1 // indirect github.com/icholy/digest v1.1.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/compress v1.18.2 // indirect github.com/klauspost/cpuid/v2 v2.2.4 // indirect github.com/leodido/go-urn v1.2.4 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect @@ -173,10 +172,10 @@ require ( go.uber.org/multierr v1.9.0 // indirect golang.org/x/arch v0.3.0 // indirect golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect - golang.org/x/mod v0.34.0 // indirect - golang.org/x/sys v0.42.0 // indirect - golang.org/x/text v0.36.0 // indirect - golang.org/x/tools v0.43.0 // indirect + golang.org/x/mod v0.35.0 // indirect + golang.org/x/sys v0.45.0 // indirect + golang.org/x/text v0.37.0 // indirect + golang.org/x/tools v0.44.0 // indirect google.golang.org/grpc v1.75.1 // indirect google.golang.org/protobuf v1.36.10 // indirect gopkg.in/ini.v1 v1.67.0 // indirect diff --git a/backend/go.sum b/backend/go.sum index 0f366ee10..29fab3969 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -162,8 +162,6 @@ github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= -github.com/google/subcommands v1.2.0 h1:vWQspBTo2nEqTUFita5/KeEWlUL8kQObDFbub/EN9oE= -github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/wire v0.7.0 h1:JxUKI6+CVBgCO2WToKy/nQk0sS+amI9z9EjVmdaocj4= @@ -183,8 +181,6 @@ github.com/icholy/digest v1.1.0 h1:HfGg9Irj7i+IX1o1QAmPfIBNu/Q5A5Tu3n/MED9k9H4= github.com/icholy/digest v1.1.0/go.mod h1:QNrsSGQ5v7v9cReDI0+eyjsXGUoRSUZQHeQ5C4XLa0Y= github.com/imroc/req/v3 v3.57.0 h1:LMTUjNRUybUkTPn8oJDq8Kg3JRBOBTcnDhKu7mzupKI= github.com/imroc/req/v3 v3.57.0/go.mod h1:JL62ey1nvSLq81HORNcosvlf7SxZStONNqOprg0Pz00= -github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= -github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= @@ -220,8 +216,6 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= -github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM= github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/mdelapenya/tlscert v0.2.0 h1:7H81W6Z/4weDvZBNOfQte5GpIMo0lGYEeWbkGp5LJHI= @@ -255,8 +249,6 @@ github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w= github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= -github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= -github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= @@ -286,8 +278,6 @@ github.com/refraction-networking/utls v1.8.2 h1:j4Q1gJj0xngdeH+Ox/qND11aEfhpgoEv github.com/refraction-networking/utls v1.8.2/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= -github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= -github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= @@ -320,8 +310,6 @@ github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= -github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= -github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= @@ -413,16 +401,16 @@ go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4= -golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA= +golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI= +golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8= golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 h1:mgKeJMpvi0yx/sU5GsxQ7p6s2wtOnGAHZWCHUM4KGzY= golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70= golang.org/x/image v0.39.0 h1:skVYidAEVKgn8lZ602XO75asgXBgLj9G/FE3RbuPFww= golang.org/x/image v0.39.0/go.mod h1:sIbmppfU+xFLPIG0FoVUTvyBMmgng1/XAMhQ2ft0hpA= -golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI= -golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY= -golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= -golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= +golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= +golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= +golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8= +golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -434,16 +422,16 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= -golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU= -golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A= -golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= -golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= +golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= +golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4= +golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk= +golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= +golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= -golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s= -golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0= +golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= +golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 h1:wpZ8pe2x1Q3f2KyT5f8oP/fa9rHAKgFPr/HZdNuS+PQ= google.golang.org/genproto/googleapis/api v0.0.0-20250929231259-57b25ae835d4 h1:8XJ4pajGwOlasW+L13MnEGA8W4115jJySQtVfS2/IBU= diff --git a/backend/internal/domain/constants.go b/backend/internal/domain/constants.go index 7ee3353dc..27431f2fb 100644 --- a/backend/internal/domain/constants.go +++ b/backend/internal/domain/constants.go @@ -103,12 +103,10 @@ var DefaultAntigravityModelMapping = map[string]string{ "claude-haiku-4-5": "claude-sonnet-4-6", "claude-haiku-4-5-20251001": "claude-sonnet-4-6", // Gemini 2.5 白名单 - "gemini-2.5-flash": "gemini-2.5-flash", - "gemini-2.5-flash-image": "gemini-2.5-flash-image", - "gemini-2.5-flash-image-preview": "gemini-2.5-flash-image", - "gemini-2.5-flash-lite": "gemini-2.5-flash-lite", - "gemini-2.5-flash-thinking": "gemini-2.5-flash-thinking", - "gemini-2.5-pro": "gemini-2.5-pro", + "gemini-2.5-flash": "gemini-2.5-flash", + "gemini-2.5-flash-lite": "gemini-2.5-flash-lite", + "gemini-2.5-flash-thinking": "gemini-2.5-flash-thinking", + "gemini-2.5-pro": "gemini-2.5-pro", // Gemini 3 白名单 "gemini-3-flash": "gemini-3-flash", "gemini-3-pro-high": "gemini-3-pro-high", @@ -121,13 +119,6 @@ var DefaultAntigravityModelMapping = map[string]string{ "gemini-3.1-pro-low": "gemini-3.1-pro-low", // Gemini 3.1 preview 映射 "gemini-3.1-pro-preview": "gemini-3.1-pro-high", - // Gemini 3.1 image 白名单 - "gemini-3.1-flash-image": "gemini-3.1-flash-image", - // Gemini 3.1 image preview 映射 - "gemini-3.1-flash-image-preview": "gemini-3.1-flash-image", - // Gemini 3 image 兼容映射(向 3.1 image 迁移) - "gemini-3-pro-image": "gemini-3.1-flash-image", - "gemini-3-pro-image-preview": "gemini-3.1-flash-image", // 其他官方模型 "gpt-oss-120b-medium": "gpt-oss-120b-medium", "tab_flash_lite_preview": "tab_flash_lite_preview", diff --git a/backend/internal/pkg/gemini/models.go b/backend/internal/pkg/gemini/models.go index fac79d187..f2c464166 100644 --- a/backend/internal/pkg/gemini/models.go +++ b/backend/internal/pkg/gemini/models.go @@ -20,13 +20,11 @@ func DefaultModels() []Model { return []Model{ {Name: "models/gemini-2.0-flash", SupportedGenerationMethods: methods}, {Name: "models/gemini-2.5-flash", SupportedGenerationMethods: methods}, - {Name: "models/gemini-2.5-flash-image", SupportedGenerationMethods: methods}, {Name: "models/gemini-2.5-pro", SupportedGenerationMethods: methods}, {Name: "models/gemini-3-flash-preview", SupportedGenerationMethods: methods}, {Name: "models/gemini-3-pro-preview", SupportedGenerationMethods: methods}, {Name: "models/gemini-3.1-pro-preview", SupportedGenerationMethods: methods}, {Name: "models/gemini-3.1-pro-preview-customtools", SupportedGenerationMethods: methods}, - {Name: "models/gemini-3.1-flash-image", SupportedGenerationMethods: methods}, } } diff --git a/backend/internal/pkg/geminicli/models.go b/backend/internal/pkg/geminicli/models.go index 195fb06f8..1fc4d983c 100644 --- a/backend/internal/pkg/geminicli/models.go +++ b/backend/internal/pkg/geminicli/models.go @@ -13,12 +13,10 @@ type Model struct { var DefaultModels = []Model{ {ID: "gemini-2.0-flash", Type: "model", DisplayName: "Gemini 2.0 Flash", CreatedAt: ""}, {ID: "gemini-2.5-flash", Type: "model", DisplayName: "Gemini 2.5 Flash", CreatedAt: ""}, - {ID: "gemini-2.5-flash-image", Type: "model", DisplayName: "Gemini 2.5 Flash Image", CreatedAt: ""}, {ID: "gemini-2.5-pro", Type: "model", DisplayName: "Gemini 2.5 Pro", CreatedAt: ""}, {ID: "gemini-3-flash-preview", Type: "model", DisplayName: "Gemini 3 Flash Preview", CreatedAt: ""}, {ID: "gemini-3-pro-preview", Type: "model", DisplayName: "Gemini 3 Pro Preview", CreatedAt: ""}, {ID: "gemini-3.1-pro-preview", Type: "model", DisplayName: "Gemini 3.1 Pro Preview", CreatedAt: ""}, - {ID: "gemini-3.1-flash-image", Type: "model", DisplayName: "Gemini 3.1 Flash Image", CreatedAt: ""}, } // DefaultTestModel is the default model to preselect in test flows. diff --git a/backend/internal/repository/account_share_policy_repo.go b/backend/internal/repository/account_share_policy_repo.go index fa264da8f..10a01efbe 100644 --- a/backend/internal/repository/account_share_policy_repo.go +++ b/backend/internal/repository/account_share_policy_repo.go @@ -218,8 +218,8 @@ func accountSharePolicyWhere(filters service.AccountSharePolicyFilters) (string, args := make([]any, 0, 3) add := func(condition string, arg any) { args = append(args, arg) - where.WriteString(" AND ") - where.WriteString(fmt.Sprintf(condition, len(args))) + _, _ = where.WriteString(" AND ") + _, _ = where.WriteString(fmt.Sprintf(condition, len(args))) } if scopeType := strings.TrimSpace(filters.ScopeType); scopeType != "" { add("scope_type = $%d", scopeType) diff --git a/backend/internal/repository/affiliate_repo.go b/backend/internal/repository/affiliate_repo.go index 54deca9d4..51c2a201a 100644 --- a/backend/internal/repository/affiliate_repo.go +++ b/backend/internal/repository/affiliate_repo.go @@ -582,6 +582,9 @@ func (r *affiliateRepository) withTx(ctx context.Context, fn func(txCtx context. tx, err := r.client.Tx(ctx) if err != nil { + if errors.Is(err, dbent.ErrTxStarted) { + return fn(ctx, r.client) + } return fmt.Errorf("begin affiliate transaction: %w", err) } defer func() { _ = tx.Rollback() }() @@ -984,7 +987,7 @@ func (r *affiliateRepository) ListUsersWithCustomSettings(ctx context.Context, f const baseFrom = ` FROM user_affiliates ua JOIN users u ON u.id = ua.user_id -WHERE ua.aff_code_custom = true +WHERE (ua.aff_code_custom = true OR ua.aff_rebate_rate_percent IS NOT NULL) AND (u.email ILIKE $1 OR u.username ILIKE $1)` client := clientFromContext(ctx, r.client) diff --git a/backend/internal/repository/api_key_repo.go b/backend/internal/repository/api_key_repo.go index ef758892d..74f9ac291 100644 --- a/backend/internal/repository/api_key_repo.go +++ b/backend/internal/repository/api_key_repo.go @@ -476,7 +476,7 @@ func (r *apiKeyRepository) ClearGroupIDByGroupID(ctx context.Context, groupID in if err != nil && !errors.Is(err, dbent.ErrTxStarted) { return 0, err } - client := r.client + var client *dbent.Client txCtx := ctx if err == nil { defer func() { _ = tx.Rollback() }() @@ -913,6 +913,9 @@ func (r *apiKeyRepository) withTx(ctx context.Context, fn func(txCtx context.Con } tx, err := r.client.Tx(ctx) if err != nil { + if errors.Is(err, dbent.ErrTxStarted) { + return fn(ctx, r.client) + } return fmt.Errorf("begin api key transaction: %w", err) } defer func() { _ = tx.Rollback() }() diff --git a/backend/internal/repository/integration_harness_test.go b/backend/internal/repository/integration_harness_test.go index 5857fbcb2..a904d628e 100644 --- a/backend/internal/repository/integration_harness_test.go +++ b/backend/internal/repository/integration_harness_test.go @@ -330,6 +330,7 @@ func (h prefixHook) prefixCmd(cmd redisclient.Cmder) { switch strings.ToLower(cmd.Name()) { case "get", "set", "setnx", "setex", "psetex", "incr", "decr", "incrby", "expire", "pexpire", "ttl", "pttl", "hgetall", "hget", "hset", "hdel", "hincrbyfloat", "exists", + "sadd", "scard", "smembers", "sismember", "srem", "zadd", "zcard", "zrange", "zrangebyscore", "zrem", "zremrangebyscore", "zrevrange", "zrevrangebyscore", "zscore": prefixOne(1) case "mget": diff --git a/backend/internal/repository/receipt_code_oss_store.go b/backend/internal/repository/receipt_code_oss_store.go index 03aac37ab..f263eed9d 100644 --- a/backend/internal/repository/receipt_code_oss_store.go +++ b/backend/internal/repository/receipt_code_oss_store.go @@ -104,4 +104,3 @@ func (s *receiptCodeOSSStore) PublicURL(key string) string { } return s.publicBaseURL + "/" + strings.TrimLeft(key, "/") } - diff --git a/backend/internal/repository/subsite_repo.go b/backend/internal/repository/subsite_repo.go index 782fff070..e24427dca 100644 --- a/backend/internal/repository/subsite_repo.go +++ b/backend/internal/repository/subsite_repo.go @@ -124,7 +124,7 @@ func (r *subsiteRepository) List(ctx context.Context, params pagination.Paginati if err != nil { return nil, nil, fmt.Errorf("list subsites: %w", err) } - defer rows.Close() + defer func() { _ = rows.Close() }() items := make([]service.Subsite, 0) for rows.Next() { subsite, err := scanSubsite(rows) @@ -459,7 +459,7 @@ func (r *accountLeaseRepository) ListActiveAccountIDsBySubsite(ctx context.Conte if err != nil { return nil, fmt.Errorf("list active lease account ids: %w", err) } - defer rows.Close() + defer func() { _ = rows.Close() }() accountIDs := make([]int64, 0) for rows.Next() { @@ -644,7 +644,7 @@ func (r *accountLeaseRepository) list(ctx context.Context, query string, args .. if err != nil { return nil, fmt.Errorf("list account leases: %w", err) } - defer rows.Close() + defer func() { _ = rows.Close() }() leases := make([]service.AccountLease, 0) for rows.Next() { lease, err := scanAccountLease(rows) diff --git a/backend/internal/repository/subsite_repo_integration_test.go b/backend/internal/repository/subsite_repo_integration_test.go index fe6ac09b7..d234d739c 100644 --- a/backend/internal/repository/subsite_repo_integration_test.go +++ b/backend/internal/repository/subsite_repo_integration_test.go @@ -132,6 +132,7 @@ func TestQuotaReservationRepositoryCreateEnforcesLeaseCapacity(t *testing.T) { AccountID: accountID, APIKeyID: apiKeyID, UserID: userID, + GroupID: &groupID, Platform: service.PlatformOpenAI, RequestedModel: "gpt-5.4", MappedModel: "gpt-5.4", diff --git a/backend/internal/repository/usage_log_repo.go b/backend/internal/repository/usage_log_repo.go index ff389ff42..83d051f71 100644 --- a/backend/internal/repository/usage_log_repo.go +++ b/backend/internal/repository/usage_log_repo.go @@ -1325,35 +1325,34 @@ func (r *usageLogRepository) bestEffortRecentKey(requestID string, apiKeyID int6 return usageLogBatchKey(requestID, apiKeyID), true } -func (r *usageLogRepository) GetByID(ctx context.Context, id int64) (log *service.UsageLog, err error) { +func (r *usageLogRepository) GetByID(ctx context.Context, id int64) (*service.UsageLog, error) { query := "SELECT " + usageLogSelectColumns + " FROM usage_logs WHERE id = $1" rows, err := r.sql.QueryContext(ctx, query, id) if err != nil { return nil, err } - defer func() { - // 保持主错误优先;仅在无错误时回传 Close 失败。 - // 同时清空返回值,避免误用不完整结果。 - if closeErr := rows.Close(); closeErr != nil && err == nil { - err = closeErr - log = nil - } - }() if !rows.Next() { - if err = rows.Err(); err != nil { + if err := rows.Err(); err != nil { + _ = rows.Close() return nil, err } + _ = rows.Close() return nil, service.ErrUsageLogNotFound } - log, err = scanUsageLog(rows) + log, err := scanUsageLog(rows) if err != nil { + _ = rows.Close() return nil, err } - if err = rows.Err(); err != nil { + if err := rows.Err(); err != nil { + _ = rows.Close() + return nil, err + } + if err := rows.Close(); err != nil { return nil, err } logs := []service.UsageLog{*log} - if err = r.hydrateUsageLogWalletDeductions(ctx, logs); err != nil { + if err := r.hydrateUsageLogWalletDeductions(ctx, logs); err != nil { return nil, err } *log = logs[0] @@ -4502,7 +4501,7 @@ func (r *usageLogRepository) hydrateUsageLogPointsDeductions(ctx context.Context if err != nil { return err } - defer rows.Close() + defer func() { _ = rows.Close() }() for rows.Next() { var id int64 var amount float64 @@ -4529,7 +4528,7 @@ func (r *usageLogRepository) hydrateUsageLogBalanceDeductions(ctx context.Contex if err != nil { return err } - defer rows.Close() + defer func() { _ = rows.Close() }() for rows.Next() { var id int64 var amount float64 diff --git a/backend/internal/server/api_contract_test.go b/backend/internal/server/api_contract_test.go index 0d5aa6259..ba657851d 100644 --- a/backend/internal/server/api_contract_test.go +++ b/backend/internal/server/api_contract_test.go @@ -829,6 +829,9 @@ func TestAPIContracts(t *testing.T) { "payment_product_name_suffix": "", "payment_announcement_text": "", "payment_recharge_center_items": [], + "payment_recharge_center_tab_enabled": false, + "payment_recharge_tab_enabled": false, + "payment_subscription_tab_enabled": false, "payment_receipt_code_oss_enabled": false, "payment_receipt_code_oss_endpoint": "", "payment_receipt_code_oss_region": "", @@ -1058,6 +1061,9 @@ func TestAPIContracts(t *testing.T) { "payment_product_name_suffix": "", "payment_announcement_text": "", "payment_recharge_center_items": [], + "payment_recharge_center_tab_enabled": false, + "payment_recharge_tab_enabled": false, + "payment_subscription_tab_enabled": false, "payment_receipt_code_oss_enabled": false, "payment_receipt_code_oss_endpoint": "", "payment_receipt_code_oss_region": "", diff --git a/backend/internal/server/http.go b/backend/internal/server/http.go index 023e40bb2..9883b176b 100644 --- a/backend/internal/server/http.go +++ b/backend/internal/server/http.go @@ -18,7 +18,7 @@ import ( "github.com/google/wire" "github.com/redis/go-redis/v9" "golang.org/x/net/http2" - "golang.org/x/net/http2/h2c" + "golang.org/x/net/http2/h2c" //nolint:staticcheck // Keep existing h2c behavior until the server moves fully to Go's Protocols API. ) // ProviderSet 提供服务器层的依赖 @@ -114,7 +114,7 @@ func ProvideHTTPServer(cfg *config.Config, router *gin.Engine) *http.Server { // 根据配置决定是否启用 H2C if cfg.Server.H2C.Enabled { h2cConfig := cfg.Server.H2C - httpHandler = h2c.NewHandler(router, &http2.Server{ + httpHandler = h2c.NewHandler(router, &http2.Server{ //nolint:staticcheck // Keep existing h2c behavior until the server moves fully to Go's Protocols API. MaxConcurrentStreams: h2cConfig.MaxConcurrentStreams, IdleTimeout: time.Duration(h2cConfig.IdleTimeout) * time.Second, MaxReadFrameSize: uint32(h2cConfig.MaxReadFrameSize), diff --git a/backend/internal/service/account_service_delete_test.go b/backend/internal/service/account_service_delete_test.go index 2892bdff2..6d9c17617 100644 --- a/backend/internal/service/account_service_delete_test.go +++ b/backend/internal/service/account_service_delete_test.go @@ -19,14 +19,20 @@ import ( // 用于隔离测试 AccountService.Delete 方法,避免依赖真实数据库。 // // 设计说明: +// - account/getErr: 模拟 GetByID 返回的账号和错误 // - exists: 模拟 ExistsByID 返回的存在性结果 // - existsErr: 模拟 ExistsByID 返回的错误 // - deleteErr: 模拟 Delete 返回的错误 +// - getIDs/existsIDs: 记录查询调用的账号 ID,用于断言验证 // - deletedIDs: 记录被调用删除的账号 ID,用于断言验证 type accountRepoStub struct { + account *Account + getErr error exists bool // ExistsByID 的返回值 existsErr error // ExistsByID 的错误返回值 deleteErr error // Delete 的错误返回值 + getIDs []int64 // 记录已查询的账号 ID 列表 + existsIDs []int64 // 记录已检查存在性的账号 ID 列表 deletedIDs []int64 // 记录已删除的账号 ID 列表 } @@ -37,7 +43,8 @@ func (s *accountRepoStub) Create(ctx context.Context, account *Account) error { } func (s *accountRepoStub) GetByID(ctx context.Context, id int64) (*Account, error) { - panic("unexpected GetByID call") + s.getIDs = append(s.getIDs, id) + return s.account, s.getErr } func (s *accountRepoStub) GetByIDs(ctx context.Context, ids []int64) ([]*Account, error) { @@ -45,8 +52,9 @@ func (s *accountRepoStub) GetByIDs(ctx context.Context, ids []int64) ([]*Account } // ExistsByID 返回预设的存在性检查结果。 -// 这是 Delete 方法调用的第一个仓储方法,用于验证账号是否存在。 +// Delete 方法会在 GetByID 失败时使用它作为兼容性兜底。 func (s *accountRepoStub) ExistsByID(ctx context.Context, id int64) (bool, error) { + s.existsIDs = append(s.existsIDs, id) return s.exists, s.existsErr } @@ -209,7 +217,7 @@ func (s *accountRepoStub) ResetQuotaUsed(ctx context.Context, id int64) error { // TestAccountService_Delete_NotFound 测试删除不存在的账号时返回正确的错误。 // 预期行为: -// - ExistsByID 返回 false(账号不存在) +// - GetByID 返回 nil(账号不存在) // - 返回 ErrAccountNotFound 错误 // - Delete 方法不被调用(deletedIDs 为空) func TestAccountService_Delete_NotFound(t *testing.T) { @@ -218,21 +226,29 @@ func TestAccountService_Delete_NotFound(t *testing.T) { err := svc.Delete(context.Background(), 55) require.ErrorIs(t, err, ErrAccountNotFound) + require.Equal(t, []int64{55}, repo.getIDs) + require.Empty(t, repo.existsIDs) require.Empty(t, repo.deletedIDs) // 验证删除操作未被调用 } // TestAccountService_Delete_CheckError 测试存在性检查失败时的错误处理。 // 预期行为: +// - GetByID 返回错误 // - ExistsByID 返回数据库错误 // - 返回包含 "check account" 的错误信息 // - Delete 方法不被调用 func TestAccountService_Delete_CheckError(t *testing.T) { - repo := &accountRepoStub{existsErr: errors.New("db down")} + repo := &accountRepoStub{ + getErr: errors.New("get failed"), + existsErr: errors.New("db down"), + } svc := &AccountService{accountRepo: repo} err := svc.Delete(context.Background(), 55) require.Error(t, err) require.ErrorContains(t, err, "check account") // 验证错误信息包含上下文 + require.Equal(t, []int64{55}, repo.getIDs) + require.Equal(t, []int64{55}, repo.existsIDs) require.Empty(t, repo.deletedIDs) } @@ -244,6 +260,7 @@ func TestAccountService_Delete_CheckError(t *testing.T) { // - deletedIDs 记录了尝试删除的 ID func TestAccountService_Delete_DeleteError(t *testing.T) { repo := &accountRepoStub{ + account: &Account{ID: 55}, exists: true, deleteErr: errors.New("delete failed"), } @@ -252,6 +269,8 @@ func TestAccountService_Delete_DeleteError(t *testing.T) { err := svc.Delete(context.Background(), 55) require.Error(t, err) require.ErrorContains(t, err, "delete account") + require.Equal(t, []int64{55}, repo.getIDs) + require.Empty(t, repo.existsIDs) require.Equal(t, []int64{55}, repo.deletedIDs) // 验证删除操作被调用 } @@ -262,10 +281,12 @@ func TestAccountService_Delete_DeleteError(t *testing.T) { // - 返回 nil 错误 // - deletedIDs 记录了被删除的 ID func TestAccountService_Delete_Success(t *testing.T) { - repo := &accountRepoStub{exists: true} + repo := &accountRepoStub{account: &Account{ID: 55}} svc := &AccountService{accountRepo: repo} err := svc.Delete(context.Background(), 55) require.NoError(t, err) + require.Equal(t, []int64{55}, repo.getIDs) + require.Empty(t, repo.existsIDs) require.Equal(t, []int64{55}, repo.deletedIDs) // 验证正确的 ID 被删除 } diff --git a/backend/internal/service/account_test_service.go b/backend/internal/service/account_test_service.go index d459c25f8..509b3ceb2 100644 --- a/backend/internal/service/account_test_service.go +++ b/backend/internal/service/account_test_service.go @@ -51,7 +51,6 @@ type TestEvent struct { const ( defaultGeminiTextTestPrompt = "hi" - defaultGeminiImageTestPrompt = "Generate a cute orange cat astronaut sticker on a clean pastel background." defaultOpenAIImageTestPrompt = "Generate a cute orange cat astronaut sticker on a clean pastel background." openAITestMaxOutputTokens = 16 ) @@ -1030,34 +1029,9 @@ func (s *AccountTestService) buildCodeAssistRequest(ctx context.Context, accessT return req, nil } -// createGeminiTestPayload creates a minimal test payload for Gemini API. -// Image models use the image-generation path so the frontend can preview the returned image. +// createGeminiTestPayload creates a minimal text-only test payload for Gemini API. func createGeminiTestPayload(modelID string, prompt string) []byte { - if isImageGenerationModel(modelID) { - imagePrompt := strings.TrimSpace(prompt) - if imagePrompt == "" { - imagePrompt = defaultGeminiImageTestPrompt - } - - payload := map[string]any{ - "contents": []map[string]any{ - { - "role": "user", - "parts": []map[string]any{ - {"text": imagePrompt}, - }, - }, - }, - "generationConfig": map[string]any{ - "responseModalities": []string{"TEXT", "IMAGE"}, - "imageConfig": map[string]any{ - "aspectRatio": "1:1", - }, - }, - } - bytes, _ := json.Marshal(payload) - return bytes - } + _ = modelID textPrompt := strings.TrimSpace(prompt) if textPrompt == "" { diff --git a/backend/internal/service/antigravity_gateway_service.go b/backend/internal/service/antigravity_gateway_service.go index 4e0356ab8..65f69e78a 100644 --- a/backend/internal/service/antigravity_gateway_service.go +++ b/backend/internal/service/antigravity_gateway_service.go @@ -1741,7 +1741,6 @@ func (s *AntigravityGatewayService) Forward(ctx context.Context, c *gin.Context, if resp != nil { resp.Request = nil } - body = nil // 客户端要求流式,直接透传转换 streamRes, err := s.handleClaudeStreamingResponse(c, resp, startTime, originalModel) if err != nil { @@ -2428,7 +2427,6 @@ handleSuccess: if resp != nil { resp.Request = nil } - body = nil // 客户端要求流式,直接透传 streamRes, err := s.handleGeminiStreamingResponse(c, resp, startTime) if err != nil { @@ -4297,7 +4295,6 @@ func (s *AntigravityGatewayService) ForwardUpstream(ctx context.Context, c *gin. if resp != nil { resp.Request = nil } - body = nil // 流式响应:透传 c.Header("Content-Type", "text/event-stream") c.Header("Cache-Control", "no-cache") diff --git a/backend/internal/service/auth_oauth_email_flow.go b/backend/internal/service/auth_oauth_email_flow.go index e3c8298c2..33371c821 100644 --- a/backend/internal/service/auth_oauth_email_flow.go +++ b/backend/internal/service/auth_oauth_email_flow.go @@ -18,7 +18,7 @@ func normalizeOAuthSignupSource(signupSource string) string { switch signupSource { case "", "email": return "email" - case "linuxdo", "wechat", "oidc", "github", "google": + case "linuxdo", "wechat", "oidc": return signupSource default: return "email" diff --git a/backend/internal/service/billing_service.go b/backend/internal/service/billing_service.go index 2ef531eb4..65f4a1d1f 100644 --- a/backend/internal/service/billing_service.go +++ b/backend/internal/service/billing_service.go @@ -247,6 +247,12 @@ func (s *BillingService) initFallbackPricing() { CacheReadPricePerToken: 7.5e-8, SupportsCacheBreakdown: false, } + s.fallbackPrices["gpt-5.4-nano"] = &ModelPricing{ + InputPricePerToken: 2e-7, + OutputPricePerToken: 1.25e-6, + CacheReadPricePerToken: 2e-8, + SupportsCacheBreakdown: false, + } // OpenAI GPT-5.2(本地兜底) s.fallbackPrices["gpt-5.2"] = &ModelPricing{ InputPricePerToken: 1.75e-6, @@ -314,6 +320,8 @@ func (s *BillingService) getFallbackPricing(model string) *ModelPricing { switch normalized { case "gpt-5.5": return s.fallbackPrices["gpt-5.5"] + case "gpt-5.4-nano": + return s.fallbackPrices["gpt-5.4-nano"] case "gpt-5.4-mini": return s.fallbackPrices["gpt-5.4-mini"] case "gpt-5.4": diff --git a/backend/internal/service/gateway_service.go b/backend/internal/service/gateway_service.go index 36f30ad81..44aa2dd9e 100644 --- a/backend/internal/service/gateway_service.go +++ b/backend/internal/service/gateway_service.go @@ -5205,7 +5205,6 @@ func (s *GatewayService) Forward(ctx context.Context, c *gin.Context, account *A if resp != nil { resp.Request = nil } - body = nil streamResult, err := s.handleStreamingResponse(ctx, resp, c, account, startTime, originalModel, reqModel, shouldMimicClaudeCode) if err != nil { if err.Error() == "have error in stream" { diff --git a/backend/internal/service/gemini_messages_compat_service.go b/backend/internal/service/gemini_messages_compat_service.go index d0a231a40..8d32a17f6 100644 --- a/backend/internal/service/gemini_messages_compat_service.go +++ b/backend/internal/service/gemini_messages_compat_service.go @@ -1082,7 +1082,6 @@ func (s *GeminiMessagesCompatService) Forward(ctx context.Context, c *gin.Contex if resp != nil { resp.Request = nil } - body = nil streamRes, err := s.handleStreamingResponse(c, resp, startTime, originalModel) if err != nil { return nil, err diff --git a/backend/internal/service/openai_gateway_service.go b/backend/internal/service/openai_gateway_service.go index 129e5b6af..ec80324bb 100644 --- a/backend/internal/service/openai_gateway_service.go +++ b/backend/internal/service/openai_gateway_service.go @@ -2554,9 +2554,6 @@ func (s *OpenAIGatewayService) Forward(ctx context.Context, c *gin.Context, acco } } openAIReqBody := reqBody - if !reqStream && wsDecision.Transport != OpenAIUpstreamTransportResponsesWebsocketV2 { - reqBody = nil - } // Get access token token, _, err := s.GetAccessToken(ctx, account) @@ -2780,9 +2777,6 @@ func (s *OpenAIGatewayService) Forward(ctx context.Context, c *gin.Context, acco s.writeOpenAIWSFallbackErrorResponse(c, account, wsErr) return nil, wsErr } - if reqStream { - reqBody = nil - } httpInvalidEncryptedContentRetryTried := false for { @@ -2884,15 +2878,13 @@ func (s *OpenAIGatewayService) Forward(ctx context.Context, c *gin.Context, acco if resp != nil { resp.Request = nil } - body = nil - openAIReqBody = nil streamResult, err := s.handleStreamingResponse(ctx, resp, c, account, startTime, originalModel, upstreamModel) if err != nil { return nil, err } - usage = streamResult.usage - firstTokenMs = streamResult.firstTokenMs if streamResult != nil { + usage = streamResult.usage + firstTokenMs = streamResult.firstTokenMs if responseServiceTier := extractOpenAIServiceTierFromResponses(streamResult.responseServiceTier); responseServiceTier != nil { serviceTier = responseServiceTier } @@ -3114,14 +3106,13 @@ func (s *OpenAIGatewayService) forwardOpenAIPassthrough( if resp != nil { resp.Request = nil } - body = nil result, err := s.handleStreamingResponsePassthrough(ctx, resp, c, account, startTime, reqModel, upstreamPassthroughModel) if err != nil { return nil, err } - usage = result.usage - firstTokenMs = result.firstTokenMs if result != nil { + usage = result.usage + firstTokenMs = result.firstTokenMs if responseServiceTier := extractOpenAIServiceTierFromResponses(result.responseServiceTier); responseServiceTier != nil { serviceTier = responseServiceTier } diff --git a/backend/internal/service/shop.go b/backend/internal/service/shop.go index 5acd588f3..d7c4a84b5 100644 --- a/backend/internal/service/shop.go +++ b/backend/internal/service/shop.go @@ -590,7 +590,7 @@ func (s *ShopService) createBalanceOrder(ctx context.Context, req ShopCreateOrde if err != nil { return nil, err } - delivered := []string{} + var delivered []string if drawReward != nil { order, err = tx.ShopOrder.UpdateOneID(order.ID). SetDrawRewardAmount(drawReward.Amount). @@ -697,7 +697,7 @@ func (s *ShopService) createPointsOrder(ctx context.Context, req ShopCreateOrder if err != nil { return nil, err } - delivered := []string{} + var delivered []string if drawReward != nil { order, err = tx.ShopOrder.UpdateOneID(order.ID). SetDrawRewardAmount(drawReward.Amount). diff --git a/backend/internal/service/shop_file_card.go b/backend/internal/service/shop_file_card.go index acfc1fc80..58d7608da 100644 --- a/backend/internal/service/shop_file_card.go +++ b/backend/internal/service/shop_file_card.go @@ -379,10 +379,10 @@ func (s *ShopService) writeOrderFileCardArchive(ctx context.Context, orderID int } name := uniqueArchiveFilename(sanitizeShopFilename(file.Filename), usedNames) header := &zip.FileHeader{ - Name: name, - Method: zip.Deflate, + Name: name, + Method: zip.Deflate, + Modified: time.Now(), } - header.SetModTime(time.Now()) writer, err := zw.CreateHeader(header) if err != nil { _ = body.Close() diff --git a/backend/internal/subsite/agent/proxy.go b/backend/internal/subsite/agent/proxy.go index aba49dab1..42c9dbe7d 100644 --- a/backend/internal/subsite/agent/proxy.go +++ b/backend/internal/subsite/agent/proxy.go @@ -76,7 +76,7 @@ func (s *Server) proxyAuthorizedRequest(c *gin.Context, authorization *service.A if err != nil { return nil, fmt.Errorf("call upstream: %w", err) } - defer resp.Body.Close() + defer func() { _ = resp.Body.Close() }() responseBody, wroteResponse, metrics, err := streamOrBufferResponse(c, resp) if err != nil { @@ -148,7 +148,7 @@ func (s *Server) proxyOpenAIChatCompletionsViaResponses(c *gin.Context, authoriz if err != nil { return nil, fmt.Errorf("call upstream: %w", err) } - defer resp.Body.Close() + defer func() { _ = resp.Body.Close() }() responseBody, wroteResponse, metrics, err := streamOrBufferResponse(c, resp) if err != nil { @@ -1240,7 +1240,7 @@ func convertResponsesSSEToChatCompletionsSSE(body []byte, originalModel string) if err != nil { return nil, fmt.Errorf("marshal chat chunk: %w", err) } - out.WriteString(sse) + _, _ = out.WriteString(sse) } } if err := scanner.Err(); err != nil { @@ -1251,8 +1251,8 @@ func convertResponsesSSEToChatCompletionsSSE(body []byte, originalModel string) if err != nil { return nil, fmt.Errorf("finalize chat chunk: %w", err) } - out.WriteString(sse) + _, _ = out.WriteString(sse) } - out.WriteString("data: [DONE]\n\n") + _, _ = out.WriteString("data: [DONE]\n\n") return out.Bytes(), nil } diff --git a/backend/internal/subsite/agent/proxy_test.go b/backend/internal/subsite/agent/proxy_test.go index de044c586..f3725079e 100644 --- a/backend/internal/subsite/agent/proxy_test.go +++ b/backend/internal/subsite/agent/proxy_test.go @@ -80,7 +80,7 @@ func TestSubsiteAgentProxy_AuthorizesForwardsAndQueuesUsage(t *testing.T) { usageQueue, err := queue.Open(filepath.Join(t.TempDir(), "usage.db")) require.NoError(t, err) - defer usageQueue.Close() + defer func() { _ = usageQueue.Close() }() server := NewServer(&Config{ ListenAddr: ":0", @@ -137,7 +137,7 @@ func TestSubsiteAgentProxy_WebSocketQueuesEachCompletedTurn(t *testing.T) { upstreamAuth = r.Header.Get("Authorization") conn, err := coderws.Accept(w, r, &coderws.AcceptOptions{InsecureSkipVerify: true}) require.NoError(t, err) - defer conn.CloseNow() + defer func() { _ = conn.CloseNow() }() for i := 1; i <= 2; i++ { readCtx, cancel := context.WithTimeout(r.Context(), time.Second) _, payload, err := conn.Read(readCtx) @@ -202,7 +202,7 @@ func TestSubsiteAgentProxy_WebSocketQueuesEachCompletedTurn(t *testing.T) { usageQueue, err := queue.Open(filepath.Join(t.TempDir(), "usage.db")) require.NoError(t, err) - defer usageQueue.Close() + defer func() { _ = usageQueue.Close() }() server := NewServer(&Config{ ListenAddr: ":0", @@ -223,7 +223,7 @@ func TestSubsiteAgentProxy_WebSocketQueuesEachCompletedTurn(t *testing.T) { }, }) require.NoError(t, err) - defer client.CloseNow() + defer func() { _ = client.CloseNow() }() writeCtx, cancel := context.WithTimeout(context.Background(), time.Second) require.NoError(t, client.Write(writeCtx, coderws.MessageText, []byte(`{"type":"response.create","model":"gpt-5.4","input":"one"}`))) @@ -306,7 +306,7 @@ func TestSubsiteAgentProxy_OpenAIOAuthResponsesUsesCodexUpstream(t *testing.T) { originalTransport := http.DefaultTransport http.DefaultTransport = &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, //nolint:gosec // Test transport connects only to httptest.NewTLSServer. DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { if addr == "chatgpt.com:443" { return (&net.Dialer{}).DialContext(ctx, network, strings.TrimPrefix(upstream.URL, "https://")) @@ -353,7 +353,7 @@ func TestSubsiteAgentProxy_OpenAIOAuthResponsesUsesCodexUpstream(t *testing.T) { usageQueue, err := queue.Open(filepath.Join(t.TempDir(), "usage.db")) require.NoError(t, err) - defer usageQueue.Close() + defer func() { _ = usageQueue.Close() }() server := NewServer(&Config{ ListenAddr: ":0", @@ -437,7 +437,7 @@ func TestSubsiteAgentProxy_OpenAIChatCompletionsViaResponses(t *testing.T) { usageQueue, err := queue.Open(filepath.Join(t.TempDir(), "usage.db")) require.NoError(t, err) - defer usageQueue.Close() + defer func() { _ = usageQueue.Close() }() server := NewServer(&Config{ ListenAddr: ":0", @@ -521,7 +521,7 @@ func TestSubsiteAgentProxy_OpenAIChatCompletionsViaResponsesCapturesLatencyAndRe usageQueue, err := queue.Open(filepath.Join(t.TempDir(), "usage.db")) require.NoError(t, err) - defer usageQueue.Close() + defer func() { _ = usageQueue.Close() }() server := NewServer(&Config{ ListenAddr: ":0", @@ -583,7 +583,7 @@ func TestSubsiteAgentProxy_OpenAIOAuthChatCompletionsUseCodexResponsesUpstream(t originalTransport := http.DefaultTransport http.DefaultTransport = &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, //nolint:gosec // Test transport connects only to httptest.NewTLSServer. DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { if addr == "chatgpt.com:443" { return (&net.Dialer{}).DialContext(ctx, network, strings.TrimPrefix(upstream.URL, "https://")) @@ -630,7 +630,7 @@ func TestSubsiteAgentProxy_OpenAIOAuthChatCompletionsUseCodexResponsesUpstream(t usageQueue, err := queue.Open(filepath.Join(t.TempDir(), "usage.db")) require.NoError(t, err) - defer usageQueue.Close() + defer func() { _ = usageQueue.Close() }() server := NewServer(&Config{ ListenAddr: ":0", @@ -755,7 +755,7 @@ func TestSubsiteAgentProxy_FailoverOnOpenAIResponsesScopeError(t *testing.T) { usageQueue, err := queue.Open(filepath.Join(t.TempDir(), "usage.db")) require.NoError(t, err) - defer usageQueue.Close() + defer func() { _ = usageQueue.Close() }() server := NewServer(&Config{ ListenAddr: ":0", @@ -869,7 +869,7 @@ func TestSubsiteAgentProxy_FailoverOnUpstreamServerError(t *testing.T) { usageQueue, err := queue.Open(filepath.Join(t.TempDir(), "usage.db")) require.NoError(t, err) - defer usageQueue.Close() + defer func() { _ = usageQueue.Close() }() server := NewServer(&Config{ ListenAddr: ":0", @@ -947,7 +947,7 @@ func TestSubsiteAgentProxy_ReturnsRetryAuthorizeError(t *testing.T) { usageQueue, err := queue.Open(filepath.Join(t.TempDir(), "usage.db")) require.NoError(t, err) - defer usageQueue.Close() + defer func() { _ = usageQueue.Close() }() server := NewServer(&Config{ ListenAddr: ":0", @@ -979,7 +979,7 @@ func TestSubsiteAgentProxy_WebSocketFailoverOnInitialDialError(t *testing.T) { upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { conn, err := coderws.Accept(w, r, &coderws.AcceptOptions{InsecureSkipVerify: true}) require.NoError(t, err) - defer conn.CloseNow() + defer func() { _ = conn.CloseNow() }() readCtx, cancel := context.WithTimeout(r.Context(), time.Second) _, payload, err := conn.Read(readCtx) cancel() @@ -1057,7 +1057,7 @@ func TestSubsiteAgentProxy_WebSocketFailoverOnInitialDialError(t *testing.T) { usageQueue, err := queue.Open(filepath.Join(t.TempDir(), "usage.db")) require.NoError(t, err) - defer usageQueue.Close() + defer func() { _ = usageQueue.Close() }() server := NewServer(&Config{ ListenAddr: ":0", @@ -1075,7 +1075,7 @@ func TestSubsiteAgentProxy_WebSocketFailoverOnInitialDialError(t *testing.T) { }, }) require.NoError(t, err) - defer client.CloseNow() + defer func() { _ = client.CloseNow() }() writeCtx, cancel := context.WithTimeout(context.Background(), time.Second) require.NoError(t, client.Write(writeCtx, coderws.MessageText, []byte(`{"type":"response.create","model":"gpt-5.4","input":"one"}`))) diff --git a/backend/internal/subsite/agent/server.go b/backend/internal/subsite/agent/server.go index fe0842bf5..ecd5c1993 100644 --- a/backend/internal/subsite/agent/server.go +++ b/backend/internal/subsite/agent/server.go @@ -571,7 +571,7 @@ func Run(ctx context.Context, cfg *Config) error { if err != nil { return err } - defer usageQueue.Close() + defer func() { _ = usageQueue.Close() }() server := NewServer(cfg, master, usageQueue) return server.Run(ctx) } diff --git a/backend/internal/subsite/client/master_client.go b/backend/internal/subsite/client/master_client.go index 75e63cf5b..f02824ef2 100644 --- a/backend/internal/subsite/client/master_client.go +++ b/backend/internal/subsite/client/master_client.go @@ -81,7 +81,7 @@ func (c *MasterClient) do(ctx context.Context, method, path string, body []byte, if err != nil { return fmt.Errorf("call master: %w", err) } - defer resp.Body.Close() + defer func() { _ = resp.Body.Close() }() payload, err := io.ReadAll(resp.Body) if err != nil { return fmt.Errorf("read master response: %w", err) diff --git a/backend/internal/subsite/queue/sqlite_queue.go b/backend/internal/subsite/queue/sqlite_queue.go index d78355a0d..eb15f408e 100644 --- a/backend/internal/subsite/queue/sqlite_queue.go +++ b/backend/internal/subsite/queue/sqlite_queue.go @@ -72,7 +72,7 @@ func (q *UsageQueue) DequeueBatch(ctx context.Context, limit int) ([]UsageQueueI if err != nil { return nil, fmt.Errorf("select usage queue: %w", err) } - defer rows.Close() + defer func() { _ = rows.Close() }() items := make([]UsageQueueItem, 0, limit) for rows.Next() { var item UsageQueueItem @@ -101,7 +101,7 @@ func (q *UsageQueue) Ack(ctx context.Context, ids []int64) error { if err != nil { return fmt.Errorf("prepare usage queue ack: %w", err) } - defer stmt.Close() + defer func() { _ = stmt.Close() }() for _, id := range ids { if _, err := stmt.ExecContext(ctx, id); err != nil { return fmt.Errorf("ack usage queue item: %w", err) diff --git a/frontend/package.json b/frontend/package.json index 61d58c10f..507d20cbe 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -57,5 +57,10 @@ "vite-plugin-checker": "^0.9.1", "vitest": "^2.1.9", "vue-tsc": "^2.2.0" + }, + "pnpm": { + "overrides": { + "js-cookie": "^3.0.8" + } } } diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 12fd84b55..06cd44bb2 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -4,6 +4,9 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false +overrides: + js-cookie: ^3.0.8 + importers: .: @@ -2879,9 +2882,8 @@ packages: engines: {node: '>=14'} hasBin: true - js-cookie@3.0.5: - resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==} - engines: {node: '>=14'} + js-cookie@3.0.8: + resolution: {integrity: sha512-yeJd4aNAdYZQjaon2bpD/Gb0B/omw7HQOsynXXcOiWVCacbBcPlgn8S/d1X6blFSaHao7ozqtW7NZW19xpCtIw==} js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -6362,7 +6364,7 @@ snapshots: '@types/js-cookie': 3.0.6 dayjs: 1.11.20 intersection-observer: 0.12.2 - js-cookie: 3.0.5 + js-cookie: 3.0.8 lodash: 4.18.1 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) @@ -7649,10 +7651,10 @@ snapshots: config-chain: 1.1.13 editorconfig: 1.0.4 glob: 10.5.0 - js-cookie: 3.0.5 + js-cookie: 3.0.8 nopt: 7.2.1 - js-cookie@3.0.5: {} + js-cookie@3.0.8: {} js-tokens@4.0.0: {} diff --git a/frontend/src/components/store/DeliveredFilesList.vue b/frontend/src/components/store/DeliveredFilesList.vue index b6ad51617..df8b3eb50 100644 --- a/frontend/src/components/store/DeliveredFilesList.vue +++ b/frontend/src/components/store/DeliveredFilesList.vue @@ -6,7 +6,7 @@ v-if="normalizedFiles.length > 1" type="button" class="btn btn-secondary btn-sm min-h-[2.5rem]" - @click="downloadAllFiles" + @click="handleDownloadAllFiles" > {{ t('store.downloadAllFiles') }} @@ -25,7 +25,7 @@