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 @@