diff --git a/go.mod b/go.mod index daf19453..adc4e720 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,8 @@ require ( github.com/google/uuid v1.6.0 github.com/gorilla/websocket v1.5.3 github.com/klauspost/compress v1.18.3 - github.com/tidwall/gjson v1.14.2 + github.com/router-for-me/CLIProxyAPI/v6 v6.7.53 + github.com/tidwall/gjson v1.18.0 github.com/tidwall/sjson v1.2.5 github.com/wailsapp/wails/v2 v2.11.0 golang.org/x/sync v0.19.0 @@ -21,60 +22,86 @@ require ( ) require ( + cloud.google.com/go/compute/metadata v0.3.0 // indirect filippo.io/edwards25519 v1.1.0 // indirect github.com/bep/debounce v1.2.1 // indirect github.com/bytedance/gopkg v0.1.3 // indirect github.com/bytedance/sonic/loader v0.4.0 // indirect github.com/cloudwego/base64x v0.1.6 // indirect + github.com/dlclark/regexp2 v1.11.5 // indirect github.com/dustin/go-humanize v1.0.1 // indirect + github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/getlantern/context v0.0.0-20190109183933-c447772a6520 // indirect github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7 // indirect github.com/getlantern/golog v0.0.0-20190830074920-4ef2e798c2d7 // indirect github.com/getlantern/hex v0.0.0-20190417191902-c6586a6fe0b7 // indirect github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55 // indirect github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/gin-gonic/gin v1.10.1 // indirect github.com/glebarez/go-sqlite v1.21.2 // indirect github.com/go-ole/go-ole v1.3.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.20.0 // indirect github.com/go-sql-driver/mysql v1.8.1 // indirect github.com/go-stack/stack v1.8.0 // indirect + github.com/goccy/go-json v0.10.2 // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect - github.com/jackc/pgx/v5 v5.6.0 // indirect + github.com/jackc/pgx/v5 v5.7.6 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect - github.com/klauspost/cpuid/v2 v2.2.9 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/labstack/echo/v4 v4.13.3 // indirect github.com/labstack/gommon v0.4.2 // indirect github.com/leaanthony/go-ansi-parser v1.6.1 // indirect github.com/leaanthony/gosod v1.0.4 // indirect github.com/leaanthony/slicer v1.6.0 // indirect github.com/leaanthony/u v1.1.1 // indirect + github.com/leodido/go-urn v1.4.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect + github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/refraction-networking/utls v1.8.2 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/rivo/uniseg v0.4.7 // indirect + github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/samber/lo v1.49.1 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.0 // indirect + github.com/tiktoken-go/tokenizer v0.7.0 // indirect github.com/tkrajina/go-reflector v0.5.8 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.12 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect github.com/wailsapp/go-webview2 v1.0.22 // indirect github.com/wailsapp/mimetype v1.4.1 // indirect - golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect + golang.org/x/arch v0.8.0 // indirect golang.org/x/crypto v0.45.0 // indirect golang.org/x/net v0.47.0 // indirect + golang.org/x/oauth2 v0.30.0 // indirect golang.org/x/sys v0.38.0 // indirect golang.org/x/text v0.33.0 // indirect + google.golang.org/protobuf v1.34.1 // indirect + gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect modernc.org/libc v1.22.5 // indirect modernc.org/mathutil v1.5.0 // indirect modernc.org/memory v1.5.0 // indirect modernc.org/sqlite v1.23.1 // indirect ) + +replace github.com/router-for-me/CLIProxyAPI/v6 => github.com/awsl-project/CLIProxyAPI/v6 v6.0.0-20260205175451-880f8f06a282 diff --git a/go.sum b/go.sum index 09c87205..f03043b2 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,11 @@ +cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc= +cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= +github.com/awsl-project/CLIProxyAPI/v6 v6.0.0-20260205175451-880f8f06a282 h1:uJjERHi5ZshMdhAVtHz3XsE7XdFkny75VZyH1GtbQmM= +github.com/awsl-project/CLIProxyAPI/v6 v6.0.0-20260205175451-880f8f06a282/go.mod h1:xXy27kWr355/aWdBF/7MGIm0BTArnE9CHMwxo87bnQk= github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY= github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0= github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M= @@ -15,8 +19,12 @@ github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gE github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ= +github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= +github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/getlantern/context v0.0.0-20190109183933-c447772a6520 h1:NRUJuo3v3WGC/g5YiyF790gut6oQr5f3FBI88Wv0dx4= github.com/getlantern/context v0.0.0-20190109183933-c447772a6520/go.mod h1:L+mq6/vvYHKjCX2oez0CgEAJmbq1fbb/oNJIWQkBybY= github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7 h1:6uJ+sZ/e03gkbqZ0kUG6mfKoqDb4XMAzMIwlajq19So= @@ -31,20 +39,37 @@ github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f h1:wrYrQttPS8FHIRSl github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f/go.mod h1:D5ao98qkA6pxftxoqzibIBBrLSUli+kYnJqrgBf9cIA= github.com/getlantern/systray v1.2.2 h1:dCEHtfmvkJG7HZ8lS/sLklTH4RKUcIsKrAD9sThoEBE= github.com/getlantern/systray v1.2.2/go.mod h1:pXFOI1wwqwYXEhLPm9ZGjS2u/vVELeIgNMY5HvhHhcE= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ= +github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo= github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k= github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw= github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8= +github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -55,8 +80,8 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY= -github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw= +github.com/jackc/pgx/v5 v5.7.6 h1:rWQc5FwZSPX58r1OQmkuaNicxdmExaEz5A2DO2hUuTk= +github.com/jackc/pgx/v5 v5.7.6/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e h1:Q3+PugElBCf4PFpxhErSzU3/PY5sFL5Z6rfv4AbGAck= @@ -65,10 +90,16 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/klauspost/compress v1.18.3 h1:9PJRvfbmTabkOX8moIpXPbMMbYN60bWImDDU7L+/6zw= github.com/klauspost/compress v1.18.3/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= -github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY= -github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8= +github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/labstack/echo/v4 v4.13.3 h1:pwhpCPrTl5qry5HRdM5FwdXnhXSLSY+WE+YQSeCaafY= github.com/labstack/echo/v4 v4.13.3/go.mod h1:o90YNEeQWjDozo584l7AwhJMHN0bOC4tAfg+Xox9q5g= github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= @@ -83,6 +114,8 @@ github.com/leaanthony/slicer v1.6.0 h1:1RFP5uiPJvT93TAHi+ipd3NACobkW53yUiBqZheE/ github.com/leaanthony/slicer v1.6.0/go.mod h1:o/Iz29g7LN0GqH3aMjWAe90381nyZlDNquK+mtH2Fj8= github.com/leaanthony/u v1.1.1 h1:TUFjwDGlNX+WuwVEzDqQwC2lOv0P4uhTQw7CMFdiK7M= github.com/leaanthony/u v1.1.1/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/lxn/walk v0.0.0-20210112085537-c389da54e794/go.mod h1:E23UucZGqpuUANJooIbHWCufXvOcT6E7Stq81gU+CSQ= github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk= github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= @@ -93,22 +126,36 @@ 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/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw= github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/refraction-networking/utls v1.8.2 h1:j4Q1gJj0xngdeH+Ox/qND11aEfhpgoEvV+S9iJ2IdQo= +github.com/refraction-networking/utls v1.8.2/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= 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/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew= github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA= github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= @@ -119,20 +166,26 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/tidwall/gjson v1.14.2 h1:6BBkirS0rAHjumnjHF6qgy5d2YAJ1TLIaFE2lzfOLqo= github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= +github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +github.com/tiktoken-go/tokenizer v0.7.0 h1:VMu6MPT0bXFDHr7UPh9uii7CNItVt3X9K90omxL54vw= +github.com/tiktoken-go/tokenizer v0.7.0/go.mod h1:6UCYI/DtOallbmL7sSy30p6YQv60qNyU/4aVigPOx6w= github.com/tkrajina/go-reflector v0.5.8 h1:yPADHrwmUbMq4RGEyaOUpz2H90sRsETNVpjzo3DLVQQ= github.com/tkrajina/go-reflector v0.5.8/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= +github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= @@ -145,19 +198,22 @@ github.com/wailsapp/wails/v2 v2.11.0 h1:seLacV8pqupq32IjS4Y7V8ucab0WZwtK6VvUVxSB github.com/wailsapp/wails/v2 v2.11.0/go.mod h1:jrf0ZaM6+GBc1wRmXsM8cIvzlg0karYin3erahI4+0k= github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= -golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU= -golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= +golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= +golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= +golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -168,8 +224,16 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= +google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/Knetic/govaluate.v3 v3.0.0/go.mod h1:csKLBORsPbafmSCGTEh3U7Ozmsuq8ZSIlKk1bcqph0E= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= +gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/adapter/provider/antigravity/adapter.go b/internal/adapter/provider/antigravity/adapter.go index 853602c9..0f610573 100644 --- a/internal/adapter/provider/antigravity/adapter.go +++ b/internal/adapter/provider/antigravity/adapter.go @@ -15,6 +15,7 @@ import ( "time" "github.com/awsl-project/maxx/internal/adapter/provider" + cliproxyapi "github.com/awsl-project/maxx/internal/adapter/provider/cliproxyapi_antigravity" ctxutil "github.com/awsl-project/maxx/internal/context" "github.com/awsl-project/maxx/internal/domain" "github.com/awsl-project/maxx/internal/usage" @@ -41,6 +42,27 @@ func NewAdapter(p *domain.Provider) (provider.ProviderAdapter, error) { if p.Config == nil || p.Config.Antigravity == nil { return nil, fmt.Errorf("provider %s missing antigravity config", p.Name) } + + // If UseCLIProxyAPI is enabled, directly return CLIProxyAPI adapter + if p.Config.Antigravity.UseCLIProxyAPI { + cliproxyapiProvider := &domain.Provider{ + ID: p.ID, + Name: p.Name, + Type: "cliproxyapi-antigravity", + SupportedClientTypes: p.SupportedClientTypes, + Config: &domain.ProviderConfig{ + CLIProxyAPIAntigravity: &domain.ProviderConfigCLIProxyAPIAntigravity{ + Email: p.Config.Antigravity.Email, + RefreshToken: p.Config.Antigravity.RefreshToken, + ProjectID: p.Config.Antigravity.ProjectID, + ModelMapping: p.Config.Antigravity.ModelMapping, + HaikuTarget: p.Config.Antigravity.HaikuTarget, + }, + }, + } + return cliproxyapi.NewAdapter(cliproxyapiProvider) + } + return &AntigravityAdapter{ provider: p, tokenCache: &TokenCache{}, diff --git a/internal/adapter/provider/cliproxyapi_antigravity/adapter.go b/internal/adapter/provider/cliproxyapi_antigravity/adapter.go new file mode 100644 index 00000000..9d80dcbd --- /dev/null +++ b/internal/adapter/provider/cliproxyapi_antigravity/adapter.go @@ -0,0 +1,273 @@ +package cliproxyapi_antigravity + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "log" + "net/http" + "strings" + "time" + + "github.com/awsl-project/maxx/internal/adapter/provider" + ctxutil "github.com/awsl-project/maxx/internal/context" + "github.com/awsl-project/maxx/internal/domain" + "github.com/awsl-project/maxx/internal/usage" + "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" + "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor" + "github.com/router-for-me/CLIProxyAPI/v6/sdk/exec" + "github.com/router-for-me/CLIProxyAPI/v6/sdk/translator" +) + +type CLIProxyAPIAntigravityAdapter struct { + provider *domain.Provider + authObj *auth.Auth + executor *exec.AntigravityExecutor +} + +func NewAdapter(p *domain.Provider) (provider.ProviderAdapter, error) { + if p.Config == nil || p.Config.CLIProxyAPIAntigravity == nil { + return nil, fmt.Errorf("provider %s missing cliproxyapi-antigravity config", p.Name) + } + + cfg := p.Config.CLIProxyAPIAntigravity + + // 创建 Auth 对象,executor 内部会自动处理 token 刷新 + authObj := &auth.Auth{ + Provider: "antigravity", + Metadata: map[string]any{ + "type": "antigravity", + "refresh_token": cfg.RefreshToken, + "project_id": cfg.ProjectID, + }, + } + + adapter := &CLIProxyAPIAntigravityAdapter{ + provider: p, + authObj: authObj, + executor: exec.NewAntigravityExecutor(), + } + + return adapter, nil +} + +func (a *CLIProxyAPIAntigravityAdapter) SupportedClientTypes() []domain.ClientType { + return []domain.ClientType{domain.ClientTypeClaude, domain.ClientTypeGemini} +} + +func (a *CLIProxyAPIAntigravityAdapter) Execute(ctx context.Context, w http.ResponseWriter, req *http.Request, p *domain.Provider) error { + clientType := ctxutil.GetClientType(ctx) + requestBody := ctxutil.GetRequestBody(ctx) + stream := ctxutil.GetIsStream(ctx) + requestModel := ctxutil.GetRequestModel(ctx) + model := ctxutil.GetMappedModel(ctx) // 全局映射后的模型名(已包含 ProviderType 条件) + + log.Printf("[CLIProxyAPI-Antigravity] requestModel=%s, mappedModel=%s, clientType=%s", requestModel, model, clientType) + + // 替换 body 中的 model 字段为映射后的模型名 + requestBody, err := updateModelInBody(requestBody, model) + if err != nil { + return domain.NewProxyErrorWithMessage(err, false, fmt.Sprintf("failed to update model in body: %v", err)) + } + + // 发送事件 + if eventChan := ctxutil.GetEventChan(ctx); eventChan != nil { + eventChan.SendRequestInfo(&domain.RequestInfo{ + Method: "POST", + URL: fmt.Sprintf("cliproxyapi://antigravity/%s", model), + Body: string(requestBody), + }) + } + + // 确定 source format + var sourceFormat translator.Format + switch clientType { + case domain.ClientTypeClaude: + sourceFormat = translator.FormatClaude + case domain.ClientTypeGemini: + sourceFormat = translator.FormatGemini + default: + return domain.NewProxyErrorWithMessage(nil, false, fmt.Sprintf("unsupported client type: %s", clientType)) + } + + // 直接透传原始请求给 executor,executor 内部处理格式转换 + execReq := executor.Request{ + Model: model, + Payload: requestBody, + Format: sourceFormat, + } + + execOpts := executor.Options{ + Stream: stream, + OriginalRequest: requestBody, + SourceFormat: sourceFormat, + } + + if stream { + return a.executeStream(ctx, w, execReq, execOpts) + } + return a.executeNonStream(ctx, w, execReq, execOpts) +} + +// updateModelInBody 替换 body 中的 model 字段 +func updateModelInBody(body []byte, model string) ([]byte, error) { + var req map[string]any + if err := json.Unmarshal(body, &req); err != nil { + return nil, err + } + req["model"] = model + return json.Marshal(req) +} + +func (a *CLIProxyAPIAntigravityAdapter) executeNonStream(ctx context.Context, w http.ResponseWriter, execReq executor.Request, execOpts executor.Options) error { + resp, err := a.executor.Execute(ctx, a.authObj, execReq, execOpts) + if err != nil { + log.Printf("[CLIProxyAPI-Antigravity] executeNonStream error: model=%s, err=%v", execReq.Model, err) + return domain.NewProxyErrorWithMessage(err, true, fmt.Sprintf("executor request failed: %v", err)) + } + + if eventChan := ctxutil.GetEventChan(ctx); eventChan != nil { + // Send response info + eventChan.SendResponseInfo(&domain.ResponseInfo{ + Status: http.StatusOK, + Body: string(resp.Payload), + }) + + // Extract and send token usage metrics + if metrics := usage.ExtractFromResponse(string(resp.Payload)); metrics != nil { + eventChan.SendMetrics(&domain.AdapterMetrics{ + InputTokens: metrics.InputTokens, + OutputTokens: metrics.OutputTokens, + CacheReadCount: metrics.CacheReadCount, + CacheCreationCount: metrics.CacheCreationCount, + Cache5mCreationCount: metrics.Cache5mCreationCount, + Cache1hCreationCount: metrics.Cache1hCreationCount, + }) + } + + // Extract and send response model + if model := extractModelFromResponse(resp.Payload); model != "" { + eventChan.SendResponseModel(model) + } + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + _, _ = w.Write(resp.Payload) + + return nil +} + +func (a *CLIProxyAPIAntigravityAdapter) executeStream(ctx context.Context, w http.ResponseWriter, execReq executor.Request, execOpts executor.Options) error { + flusher, ok := w.(http.Flusher) + if !ok { + return a.executeNonStream(ctx, w, execReq, execOpts) + } + + startTime := time.Now() + + stream, err := a.executor.ExecuteStream(ctx, a.authObj, execReq, execOpts) + if err != nil { + log.Printf("[CLIProxyAPI-Antigravity] executeStream error: model=%s, err=%v", execReq.Model, err) + return domain.NewProxyErrorWithMessage(err, true, fmt.Sprintf("executor stream request failed: %v", err)) + } + + // 设置 SSE 响应头 + w.Header().Set("Content-Type", "text/event-stream") + w.Header().Set("Cache-Control", "no-cache") + w.Header().Set("Connection", "keep-alive") + w.WriteHeader(http.StatusOK) + + eventChan := ctxutil.GetEventChan(ctx) + + // Collect SSE content for token extraction + var sseBuffer bytes.Buffer + var streamErr error + firstChunkSent := false + + for chunk := range stream { + if chunk.Err != nil { + log.Printf("[CLIProxyAPI-Antigravity] stream chunk error: %v", chunk.Err) + streamErr = chunk.Err + break + } + if len(chunk.Payload) > 0 { + // Payload from executor already includes SSE delimiters (\n\n) + sseBuffer.Write(chunk.Payload) + _, _ = w.Write(chunk.Payload) + flusher.Flush() + + // Report TTFT on first non-empty chunk + if !firstChunkSent && eventChan != nil { + eventChan.SendFirstToken(time.Since(startTime).Milliseconds()) + firstChunkSent = true + } + } + } + + // Send final events + if eventChan != nil && sseBuffer.Len() > 0 { + // Send response info + eventChan.SendResponseInfo(&domain.ResponseInfo{ + Status: http.StatusOK, + Body: sseBuffer.String(), + }) + + // Extract and send token usage metrics + if metrics := usage.ExtractFromStreamContent(sseBuffer.String()); metrics != nil { + eventChan.SendMetrics(&domain.AdapterMetrics{ + InputTokens: metrics.InputTokens, + OutputTokens: metrics.OutputTokens, + CacheReadCount: metrics.CacheReadCount, + CacheCreationCount: metrics.CacheCreationCount, + Cache5mCreationCount: metrics.Cache5mCreationCount, + Cache1hCreationCount: metrics.Cache1hCreationCount, + }) + } + + // Extract and send response model + if model := extractModelFromSSE(sseBuffer.String()); model != "" { + eventChan.SendResponseModel(model) + } + } + + // If error occurred before any data was sent, return error to caller + if streamErr != nil && sseBuffer.Len() == 0 { + return domain.NewProxyErrorWithMessage(streamErr, true, fmt.Sprintf("stream chunk error: %v", streamErr)) + } + + return nil +} + +// extractModelFromResponse extracts the model field from a JSON response body. +func extractModelFromResponse(body []byte) string { + var resp struct { + Model string `json:"model"` + } + if err := json.Unmarshal(body, &resp); err == nil && resp.Model != "" { + return resp.Model + } + return "" +} + +// extractModelFromSSE extracts the last model field from accumulated SSE content. +func extractModelFromSSE(sseContent string) string { + var lastModel string + for line := range strings.SplitSeq(sseContent, "\n") { + if !strings.HasPrefix(line, "data: ") { + continue + } + data := strings.TrimPrefix(line, "data: ") + if data == "[DONE]" { + continue + } + var chunk struct { + Model string `json:"model"` + } + if err := json.Unmarshal([]byte(data), &chunk); err == nil && chunk.Model != "" { + lastModel = chunk.Model + } + } + return lastModel +} diff --git a/internal/adapter/provider/cliproxyapi_codex/adapter.go b/internal/adapter/provider/cliproxyapi_codex/adapter.go new file mode 100644 index 00000000..bab635c3 --- /dev/null +++ b/internal/adapter/provider/cliproxyapi_codex/adapter.go @@ -0,0 +1,236 @@ +package cliproxyapi_codex + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "log" + "net/http" + "strings" + "time" + + "github.com/awsl-project/maxx/internal/adapter/provider" + ctxutil "github.com/awsl-project/maxx/internal/context" + "github.com/awsl-project/maxx/internal/domain" + "github.com/awsl-project/maxx/internal/usage" + "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" + "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor" + "github.com/router-for-me/CLIProxyAPI/v6/sdk/exec" + "github.com/router-for-me/CLIProxyAPI/v6/sdk/translator" +) + +type CLIProxyAPICodexAdapter struct { + provider *domain.Provider + authObj *auth.Auth + executor *exec.CodexExecutor +} + +func NewAdapter(p *domain.Provider) (provider.ProviderAdapter, error) { + if p.Config == nil || p.Config.CLIProxyAPICodex == nil { + return nil, fmt.Errorf("provider %s missing cliproxyapi-codex config", p.Name) + } + + // 创建 Auth 对象,executor 内部会自动处理 token 刷新 + authObj := &auth.Auth{ + Provider: "codex", + Metadata: map[string]any{ + "type": "codex", + "refresh_token": p.Config.CLIProxyAPICodex.RefreshToken, + }, + } + + adapter := &CLIProxyAPICodexAdapter{ + provider: p, + authObj: authObj, + executor: exec.NewCodexExecutor(), + } + + return adapter, nil +} + +func (a *CLIProxyAPICodexAdapter) SupportedClientTypes() []domain.ClientType { + return []domain.ClientType{domain.ClientTypeCodex} +} + +func (a *CLIProxyAPICodexAdapter) Execute(ctx context.Context, w http.ResponseWriter, req *http.Request, p *domain.Provider) error { + requestBody := ctxutil.GetRequestBody(ctx) + stream := ctxutil.GetIsStream(ctx) + model := ctxutil.GetMappedModel(ctx) + + // Codex CLI 使用 OpenAI Responses API 格式 + sourceFormat := translator.FormatCodex + + // 发送事件 + if eventChan := ctxutil.GetEventChan(ctx); eventChan != nil { + eventChan.SendRequestInfo(&domain.RequestInfo{ + Method: "POST", + URL: fmt.Sprintf("cliproxyapi://codex/%s", model), + Body: string(requestBody), + }) + } + + // 构建 executor 请求 + execReq := executor.Request{ + Model: model, + Payload: requestBody, + Format: sourceFormat, + } + + execOpts := executor.Options{ + Stream: stream, + OriginalRequest: requestBody, + SourceFormat: sourceFormat, + } + + if stream { + return a.executeStream(ctx, w, execReq, execOpts) + } + return a.executeNonStream(ctx, w, execReq, execOpts) +} + +func (a *CLIProxyAPICodexAdapter) executeNonStream(ctx context.Context, w http.ResponseWriter, execReq executor.Request, execOpts executor.Options) error { + resp, err := a.executor.Execute(ctx, a.authObj, execReq, execOpts) + if err != nil { + return domain.NewProxyErrorWithMessage(err, true, fmt.Sprintf("executor request failed: %v", err)) + } + + if eventChan := ctxutil.GetEventChan(ctx); eventChan != nil { + // Send response info + eventChan.SendResponseInfo(&domain.ResponseInfo{ + Status: http.StatusOK, + Body: string(resp.Payload), + }) + + // Extract and send token usage metrics + if metrics := usage.ExtractFromResponse(string(resp.Payload)); metrics != nil { + // Adjust for Codex: input_tokens includes cached_tokens + metrics = usage.AdjustForClientType(metrics, domain.ClientTypeCodex) + eventChan.SendMetrics(&domain.AdapterMetrics{ + InputTokens: metrics.InputTokens, + OutputTokens: metrics.OutputTokens, + }) + } + + // Extract and send response model + if model := extractModelFromResponse(resp.Payload); model != "" { + eventChan.SendResponseModel(model) + } + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + _, _ = w.Write(resp.Payload) + + return nil +} + +func (a *CLIProxyAPICodexAdapter) executeStream(ctx context.Context, w http.ResponseWriter, execReq executor.Request, execOpts executor.Options) error { + flusher, ok := w.(http.Flusher) + if !ok { + return a.executeNonStream(ctx, w, execReq, execOpts) + } + + startTime := time.Now() + + stream, err := a.executor.ExecuteStream(ctx, a.authObj, execReq, execOpts) + if err != nil { + return domain.NewProxyErrorWithMessage(err, true, fmt.Sprintf("executor stream request failed: %v", err)) + } + + // 设置 SSE 响应头 + w.Header().Set("Content-Type", "text/event-stream") + w.Header().Set("Cache-Control", "no-cache") + w.Header().Set("Connection", "keep-alive") + w.WriteHeader(http.StatusOK) + + eventChan := ctxutil.GetEventChan(ctx) + + // Collect SSE content for token extraction + var sseBuffer bytes.Buffer + var streamErr error + firstChunkSent := false + + for chunk := range stream { + if chunk.Err != nil { + log.Printf("[CLIProxyAPI-Codex] stream chunk error: %v", chunk.Err) + streamErr = chunk.Err + break + } + if len(chunk.Payload) > 0 { + // Payload from executor already includes SSE delimiters (\n\n) + sseBuffer.Write(chunk.Payload) + _, _ = w.Write(chunk.Payload) + flusher.Flush() + + // Report TTFT on first non-empty chunk + if !firstChunkSent && eventChan != nil { + eventChan.SendFirstToken(time.Since(startTime).Milliseconds()) + firstChunkSent = true + } + } + } + + // Send final events + if eventChan != nil && sseBuffer.Len() > 0 { + // Send response info + eventChan.SendResponseInfo(&domain.ResponseInfo{ + Status: http.StatusOK, + Body: sseBuffer.String(), + }) + + // Extract and send token usage metrics + if metrics := usage.ExtractFromStreamContent(sseBuffer.String()); metrics != nil { + // Adjust for Codex: input_tokens includes cached_tokens + metrics = usage.AdjustForClientType(metrics, domain.ClientTypeCodex) + eventChan.SendMetrics(&domain.AdapterMetrics{ + InputTokens: metrics.InputTokens, + OutputTokens: metrics.OutputTokens, + }) + } + + // Extract and send response model + if model := extractModelFromSSE(sseBuffer.String()); model != "" { + eventChan.SendResponseModel(model) + } + } + + // If error occurred before any data was sent, return error to caller + if streamErr != nil && sseBuffer.Len() == 0 { + return domain.NewProxyErrorWithMessage(streamErr, true, fmt.Sprintf("stream chunk error: %v", streamErr)) + } + + return nil +} + +// extractModelFromResponse extracts the model field from a JSON response body. +func extractModelFromResponse(body []byte) string { + var resp struct { + Model string `json:"model"` + } + if err := json.Unmarshal(body, &resp); err == nil && resp.Model != "" { + return resp.Model + } + return "" +} + +// extractModelFromSSE extracts the last model field from accumulated SSE content. +func extractModelFromSSE(sseContent string) string { + var lastModel string + for line := range strings.SplitSeq(sseContent, "\n") { + if !strings.HasPrefix(line, "data: ") { + continue + } + data := strings.TrimPrefix(line, "data: ") + if data == "[DONE]" { + continue + } + var chunk struct { + Model string `json:"model"` + } + if err := json.Unmarshal([]byte(data), &chunk); err == nil && chunk.Model != "" { + lastModel = chunk.Model + } + } + return lastModel +} diff --git a/internal/adapter/provider/codex/adapter.go b/internal/adapter/provider/codex/adapter.go index bab4ec44..74d90672 100644 --- a/internal/adapter/provider/codex/adapter.go +++ b/internal/adapter/provider/codex/adapter.go @@ -13,6 +13,7 @@ import ( "time" "github.com/awsl-project/maxx/internal/adapter/provider" + cliproxyapi "github.com/awsl-project/maxx/internal/adapter/provider/cliproxyapi_codex" ctxutil "github.com/awsl-project/maxx/internal/context" "github.com/awsl-project/maxx/internal/domain" "github.com/awsl-project/maxx/internal/usage" @@ -50,6 +51,26 @@ func NewAdapter(p *domain.Provider) (provider.ProviderAdapter, error) { return nil, fmt.Errorf("provider %s missing codex config", p.Name) } + config := p.Config.Codex + + // If UseCLIProxyAPI is enabled, directly return CLIProxyAPI adapter + if config.UseCLIProxyAPI { + cliproxyapiProvider := &domain.Provider{ + ID: p.ID, + Name: p.Name, + Type: "cliproxyapi-codex", + SupportedClientTypes: p.SupportedClientTypes, + Config: &domain.ProviderConfig{ + CLIProxyAPICodex: &domain.ProviderConfigCLIProxyAPICodex{ + Email: config.Email, + RefreshToken: config.RefreshToken, + ModelMapping: config.ModelMapping, + }, + }, + } + return cliproxyapi.NewAdapter(cliproxyapiProvider) + } + adapter := &CodexAdapter{ provider: p, tokenCache: &TokenCache{}, @@ -57,7 +78,6 @@ func NewAdapter(p *domain.Provider) (provider.ProviderAdapter, error) { } // Initialize token cache from persisted config if available - config := p.Config.Codex if config.AccessToken != "" && config.ExpiresAt != "" { expiresAt, err := time.Parse(time.RFC3339, config.ExpiresAt) if err == nil && time.Now().Before(expiresAt) { diff --git a/internal/domain/model.go b/internal/domain/model.go index 059c534e..f8c8c409 100644 --- a/internal/domain/model.go +++ b/internal/domain/model.go @@ -62,6 +62,9 @@ type ProviderConfigAntigravity struct { // Haiku 模型映射目标 (默认 "gemini-2.5-flash-lite" 省钱,可选 "claude-sonnet-4-5" 更强) // 空值使用默认 gemini-2.5-flash-lite HaikuTarget string `json:"haikuTarget,omitempty"` + + // 使用 CLIProxyAPI 转发 + UseCLIProxyAPI bool `json:"useCLIProxyAPI,omitempty"` } type ProviderConfigKiro struct { @@ -119,13 +122,51 @@ type ProviderConfigCodex struct { // Model 映射: RequestModel → MappedModel ModelMapping map[string]string `json:"modelMapping,omitempty"` + + // 使用 CLIProxyAPI 转发 + UseCLIProxyAPI bool `json:"useCLIProxyAPI,omitempty"` +} + +// ProviderConfigCLIProxyAPIAntigravity CLIProxyAPI Antigravity 内部配置 +// 用于 useCLIProxyAPI=true 时传递给 CLIProxyAPI adapter +type ProviderConfigCLIProxyAPIAntigravity struct { + // 邮箱(用于标识帐号) + Email string `json:"email"` + + // Google OAuth refresh_token + RefreshToken string `json:"refreshToken"` + + // Google Cloud Project ID + ProjectID string `json:"projectID,omitempty"` + + // Model 映射: RequestModel → MappedModel + ModelMapping map[string]string `json:"modelMapping,omitempty"` + + // Haiku 模型映射目标 (默认 "gemini-2.5-flash-lite" 省钱) + HaikuTarget string `json:"haikuTarget,omitempty"` +} + +// ProviderConfigCLIProxyAPICodex CLIProxyAPI Codex 内部配置 +// 用于 useCLIProxyAPI=true 时传递给 CLIProxyAPI adapter +type ProviderConfigCLIProxyAPICodex struct { + // 邮箱(用于标识帐号) + Email string `json:"email"` + + // OpenAI OAuth refresh_token + RefreshToken string `json:"refreshToken"` + + // Model 映射: RequestModel → MappedModel + ModelMapping map[string]string `json:"modelMapping,omitempty"` } type ProviderConfig struct { - Custom *ProviderConfigCustom `json:"custom,omitempty"` - Antigravity *ProviderConfigAntigravity `json:"antigravity,omitempty"` - Kiro *ProviderConfigKiro `json:"kiro,omitempty"` - Codex *ProviderConfigCodex `json:"codex,omitempty"` + Custom *ProviderConfigCustom `json:"custom,omitempty"` + Antigravity *ProviderConfigAntigravity `json:"antigravity,omitempty"` + Kiro *ProviderConfigKiro `json:"kiro,omitempty"` + Codex *ProviderConfigCodex `json:"codex,omitempty"` + // 内部运行时字段,仅用于 NewAdapter 委托,不序列化 + CLIProxyAPIAntigravity *ProviderConfigCLIProxyAPIAntigravity `json:"-"` + CLIProxyAPICodex *ProviderConfigCLIProxyAPICodex `json:"-"` } // Provider 供应商 diff --git a/web/src/lib/transport/types.ts b/web/src/lib/transport/types.ts index b1a870be..7f6b2150 100644 --- a/web/src/lib/transport/types.ts +++ b/web/src/lib/transport/types.ts @@ -28,6 +28,7 @@ export interface ProviderConfigAntigravity { projectID: string; endpoint: string; modelMapping?: Record; + useCLIProxyAPI?: boolean; } export interface ProviderConfigKiro { @@ -53,6 +54,7 @@ export interface ProviderConfigCodex { subscriptionStart?: string; subscriptionEnd?: string; modelMapping?: Record; + useCLIProxyAPI?: boolean; } export interface ProviderConfig { diff --git a/web/src/locales/en.json b/web/src/locales/en.json index f5edb82a..4a8c4ed9 100644 --- a/web/src/locales/en.json +++ b/web/src/locales/en.json @@ -55,7 +55,8 @@ "noActivity": "No Activity", "clear": "Clear", "unknown": "Unknown", - "back": "Back" + "back": "Back", + "useCLIProxyAPI": "Use CLIProxyAPI" }, "clientTypes": { "claude": "Claude", @@ -271,6 +272,7 @@ "enabled": "Enabled", "noClientsConfigured": "No clients configured", "refreshToken": "Refresh Token", + "cliProxyAPI": "CLIProxyAPI", "supportModels": { "title": "Supported Models", "desc": "Configure which models this provider supports. If empty, all models are supported. Supports wildcards like claude-* or gemini-*.", diff --git a/web/src/locales/zh.json b/web/src/locales/zh.json index 18a55646..4acfdaca 100644 --- a/web/src/locales/zh.json +++ b/web/src/locales/zh.json @@ -55,7 +55,8 @@ "noActivity": "暂无活动", "clear": "清除", "unknown": "未知", - "back": "返回" + "back": "返回", + "useCLIProxyAPI": "使用 CLIProxyAPI" }, "clientTypes": { "claude": "Claude", @@ -271,6 +272,7 @@ "enabled": "已启用", "noClientsConfigured": "未配置客户端", "refreshToken": "刷新令牌", + "cliProxyAPI": "CLIProxyAPI", "supportModels": { "title": "支持的模型", "desc": "配置此提供商支持的模型。如果为空,则支持所有模型。支持通配符,如 claude-* 或 gemini-*。", diff --git a/web/src/pages/providers/components/antigravity-provider-view.tsx b/web/src/pages/providers/components/antigravity-provider-view.tsx index fcb1d423..535995f4 100644 --- a/web/src/pages/providers/components/antigravity-provider-view.tsx +++ b/web/src/pages/providers/components/antigravity-provider-view.tsx @@ -24,6 +24,7 @@ import type { } from '@/lib/transport'; import { getTransport } from '@/lib/transport'; import { + useUpdateProvider, useModelMappings, useCreateModelMapping, useUpdateModelMapping, @@ -32,6 +33,7 @@ import { import { Button } from '@/components/ui'; import { ModelInput } from '@/components/ui/model-input'; import { ANTIGRAVITY_COLOR } from '../types'; +import { CLIProxyAPISwitch } from './cliproxyapi-switch'; interface AntigravityProviderViewProps { provider: Provider; @@ -273,6 +275,39 @@ export function AntigravityProviderView({ const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [tokenCopied, setTokenCopied] = useState(false); + const updateProvider = useUpdateProvider(); + + const [useCLIProxyAPI, setUseCLIProxyAPI] = useState( + () => provider.config?.antigravity?.useCLIProxyAPI ?? false, + ); + + useEffect(() => { + setUseCLIProxyAPI(provider.config?.antigravity?.useCLIProxyAPI ?? false); + }, [provider.config?.antigravity?.useCLIProxyAPI]); + + const handleToggleCLIProxyAPI = async (checked: boolean) => { + const antigravityConfig = provider.config?.antigravity; + if (!antigravityConfig) return; + const prev = useCLIProxyAPI; + setUseCLIProxyAPI(checked); + try { + await updateProvider.mutateAsync({ + id: provider.id, + data: { + ...provider, + config: { + ...provider.config, + antigravity: { + ...antigravityConfig, + useCLIProxyAPI: checked, + }, + }, + }, + }); + } catch { + setUseCLIProxyAPI(prev); + } + }; const handleCopyToken = async () => { const token = provider.config?.antigravity?.refreshToken; @@ -390,6 +425,16 @@ export function AntigravityProviderView({ )} +
+
+ {t('providers.cliProxyAPI')} +
+ +
diff --git a/web/src/pages/providers/components/antigravity-token-import.tsx b/web/src/pages/providers/components/antigravity-token-import.tsx index 24326f5d..42526e77 100644 --- a/web/src/pages/providers/components/antigravity-token-import.tsx +++ b/web/src/pages/providers/components/antigravity-token-import.tsx @@ -25,6 +25,7 @@ import { cn } from '@/lib/utils'; import { useProviderNavigation } from '../hooks/use-provider-navigation'; import { useCreateProvider } from '@/hooks/queries'; import { useTranslation } from 'react-i18next'; +import { CLIProxyAPISwitch } from './cliproxyapi-switch'; type ImportMode = 'oauth' | 'token'; type OAuthStatus = 'idle' | 'waiting' | 'success' | 'error'; @@ -43,6 +44,9 @@ export function AntigravityTokenImport() { ); const [error, setError] = useState(null); + // CLIProxyAPI 开关 + const [useCLIProxyAPI, setUseCLIProxyAPI] = useState(false); + // OAuth state const [oauthStatus, setOAuthStatus] = useState('idle'); const [oauthState, setOAuthState] = useState(null); @@ -178,6 +182,7 @@ export function AntigravityTokenImport() { endpoint: validationResult.projectID ? `https://us-central1-aiplatform.googleapis.com/v1/projects/${validationResult.projectID}/locations/us-central1` : '', + useCLIProxyAPI, }, }, }; @@ -216,6 +221,7 @@ export function AntigravityTokenImport() { endpoint: oauthResult.projectID ? `https://us-central1-aiplatform.googleapis.com/v1/projects/${oauthResult.projectID}/locations/us-central1` : '', + useCLIProxyAPI, }, }, }; @@ -253,6 +259,9 @@ export function AntigravityTokenImport() {

+ {/* CLIProxyAPI Switch */} + + {/* Mode Selector */}
+
+ ); +} diff --git a/web/src/pages/providers/components/codex-provider-view.tsx b/web/src/pages/providers/components/codex-provider-view.tsx index 5c0a4b78..07283e1b 100644 --- a/web/src/pages/providers/components/codex-provider-view.tsx +++ b/web/src/pages/providers/components/codex-provider-view.tsx @@ -1,4 +1,4 @@ -import { useState, useMemo } from 'react'; +import { useState, useEffect, useMemo } from 'react'; import { Code2, Mail, @@ -29,6 +29,7 @@ import type { } from '@/lib/transport'; import { getTransport } from '@/lib/transport'; import { + useUpdateProvider, useModelMappings, useCreateModelMapping, useUpdateModelMapping, @@ -38,6 +39,7 @@ import { Button } from '@/components/ui'; import { ModelInput } from '@/components/ui/model-input'; import { CODEX_COLOR } from '../types'; import { useCodexBatchQuotas } from '@/hooks/queries'; +import { CLIProxyAPISwitch } from './cliproxyapi-switch'; interface CodexProviderViewProps { provider: Provider; @@ -347,6 +349,38 @@ export function CodexProviderView({ provider, onDelete, onClose }: CodexProvider const [tokenCopied, setTokenCopied] = useState(false); const config = provider.config?.codex; + const updateProvider = useUpdateProvider(); + + const [useCLIProxyAPI, setUseCLIProxyAPI] = useState( + () => config?.useCLIProxyAPI ?? false, + ); + + useEffect(() => { + setUseCLIProxyAPI(config?.useCLIProxyAPI ?? false); + }, [config?.useCLIProxyAPI]); + + const handleToggleCLIProxyAPI = async (checked: boolean) => { + if (!config) return; + const prev = useCLIProxyAPI; + setUseCLIProxyAPI(checked); + try { + await updateProvider.mutateAsync({ + id: provider.id, + data: { + ...provider, + config: { + ...provider.config, + codex: { + ...config, + useCLIProxyAPI: checked, + }, + }, + }, + }); + } catch { + setUseCLIProxyAPI(prev); + } + }; const handleCopyToken = async () => { const token = config?.refreshToken; @@ -477,25 +511,37 @@ export function CodexProviderView({ provider, onDelete, onClose }: CodexProvider {config?.refreshToken && ( -
-
- {t('providers.refreshToken')} +
+
+
+ {t('providers.refreshToken')} +
+
+
+ {config.refreshToken.slice(0, 30)}... +
+ +
-
-
- {config.refreshToken.slice(0, 30)}... +
+
+ {t('providers.cliProxyAPI')}
- +
)} diff --git a/web/src/pages/providers/components/codex-token-import.tsx b/web/src/pages/providers/components/codex-token-import.tsx index 6b0b3577..8047cf6e 100644 --- a/web/src/pages/providers/components/codex-token-import.tsx +++ b/web/src/pages/providers/components/codex-token-import.tsx @@ -28,6 +28,7 @@ import { cn } from '@/lib/utils'; import { useProviderNavigation } from '../hooks/use-provider-navigation'; import { useCreateProvider } from '@/hooks/queries'; import { useTranslation } from 'react-i18next'; +import { CLIProxyAPISwitch } from './cliproxyapi-switch'; type ImportMode = 'oauth' | 'token'; type OAuthStatus = 'idle' | 'waiting' | 'success' | 'error'; @@ -44,6 +45,9 @@ export function CodexTokenImport() { const [validationResult, setValidationResult] = useState(null); const [error, setError] = useState(null); + // CLIProxyAPI 开关 + const [useCLIProxyAPI, setUseCLIProxyAPI] = useState(false); + // OAuth state const [oauthStatus, setOAuthStatus] = useState('idle'); const [oauthState, setOAuthState] = useState(null); @@ -245,6 +249,7 @@ export function CodexTokenImport() { planType: oauthResult.planType, subscriptionStart: oauthResult.subscriptionStart, subscriptionEnd: oauthResult.subscriptionEnd, + useCLIProxyAPI, }, }, }; @@ -287,6 +292,7 @@ export function CodexTokenImport() { planType: validationResult.planType, subscriptionStart: validationResult.subscriptionStart, subscriptionEnd: validationResult.subscriptionEnd, + useCLIProxyAPI, }, }, }; @@ -326,6 +332,9 @@ export function CodexTokenImport() {

+ {/* CLIProxyAPI Switch */} + + {/* Mode Tabs */}