mirror of
https://github.com/wisdgod/cursor-api.git
synced 2025-09-27 02:56:01 +08:00
0.1.3-rc.5.2.3
This commit is contained in:
@@ -25,11 +25,14 @@ SHARED_TOKEN=
|
||||
# 令牌列表文件路径(已弃用)
|
||||
# TOKEN_LIST_FILE=.tokens
|
||||
|
||||
# (实验性)是否启用慢速池(true/false)
|
||||
# 是否启用慢速池(true/false)(已失效)
|
||||
ENABLE_SLOW_POOL=false
|
||||
|
||||
# 允许claude开头的模型请求绕过内置模型限制(true/false)
|
||||
PASS_ANY_CLAUDE=false
|
||||
# 允许claude开头的模型请求绕过内置模型限制(true/false)(已弃用)
|
||||
# PASS_ANY_CLAUDE=false
|
||||
|
||||
# (实验性)是否启用长上下文模式(true/false)
|
||||
ENABLE_LONG_CONTEXT=false
|
||||
|
||||
# 图片处理能力配置
|
||||
# 可选值:
|
||||
|
200
Cargo.lock
generated
200
Cargo.lock
generated
@@ -28,6 +28,18 @@ dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.8.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
"zerocopy 0.7.35",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.3"
|
||||
@@ -52,6 +64,12 @@ dependencies = [
|
||||
"alloc-no-stdlib",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "allocator-api2"
|
||||
version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
||||
|
||||
[[package]]
|
||||
name = "android-tzdata"
|
||||
version = "0.1.1"
|
||||
@@ -75,9 +93,9 @@ checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f"
|
||||
|
||||
[[package]]
|
||||
name = "async-compression"
|
||||
version = "0.4.20"
|
||||
version = "0.4.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "310c9bcae737a48ef5cdee3174184e6d548b292739ede61a1f955ef76a738861"
|
||||
checksum = "c0cf008e5e1a9e9e22a7d3c9a4992e21a350290069e36d8fb72304ed17e8f2d2"
|
||||
dependencies = [
|
||||
"brotli",
|
||||
"flate2",
|
||||
@@ -274,9 +292,9 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.16"
|
||||
version = "1.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c"
|
||||
checksum = "1fcb57c740ae1daf453ae85f16e37396f672b039e00d9d866e07ddb24e328e3a"
|
||||
dependencies = [
|
||||
"shlex",
|
||||
]
|
||||
@@ -369,6 +387,12 @@ dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.6"
|
||||
@@ -381,7 +405,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cursor-api"
|
||||
version = "0.1.3-rc.5.2.2"
|
||||
version = "0.1.3-rc.5.2.3"
|
||||
dependencies = [
|
||||
"axum",
|
||||
"base64",
|
||||
@@ -394,6 +418,7 @@ dependencies = [
|
||||
"gif",
|
||||
"hex",
|
||||
"image",
|
||||
"lasso",
|
||||
"memmap2",
|
||||
"parking_lot",
|
||||
"paste",
|
||||
@@ -415,6 +440,20 @@ dependencies = [
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dashmap"
|
||||
version = "6.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crossbeam-utils",
|
||||
"hashbrown 0.14.5",
|
||||
"lock_api",
|
||||
"once_cell",
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.7"
|
||||
@@ -639,14 +678,16 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.3.1"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8"
|
||||
checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"wasi 0.13.3+wasi-0.2.2",
|
||||
"windows-targets 0.52.6",
|
||||
"r-efi",
|
||||
"wasi 0.14.2+wasi-0.2.4",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -690,7 +731,17 @@ version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"ahash 0.7.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
|
||||
dependencies = [
|
||||
"ahash 0.8.11",
|
||||
"allocator-api2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1045,6 +1096,16 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lasso"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e14eda50a3494b3bf7b9ce51c52434a761e383d7238ce1dd5dcec2fbc13e9fb"
|
||||
dependencies = [
|
||||
"dashmap",
|
||||
"hashbrown 0.14.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.171"
|
||||
@@ -1309,7 +1370,7 @@ version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
|
||||
dependencies = [
|
||||
"zerocopy",
|
||||
"zerocopy 0.8.24",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1431,11 +1492,12 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
|
||||
|
||||
[[package]]
|
||||
name = "quinn"
|
||||
version = "0.11.6"
|
||||
version = "0.11.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62e96808277ec6f97351a2380e6c25114bc9e67037775464979f3037c92d05ef"
|
||||
checksum = "c3bd15a6f2967aef83887dcb9fec0014580467e33720d073560cf015a5683012"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"cfg_aliases",
|
||||
"pin-project-lite",
|
||||
"quinn-proto",
|
||||
"quinn-udp",
|
||||
@@ -1445,17 +1507,18 @@ dependencies = [
|
||||
"thiserror 2.0.12",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"web-time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quinn-proto"
|
||||
version = "0.11.9"
|
||||
version = "0.11.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2fe5ef3495d7d2e377ff17b1a8ce2ee2ec2a18cde8b6ad6619d65d0701c135d"
|
||||
checksum = "b820744eb4dc9b57a3398183639c511b5a26d2ed702cedd3febaa1393caa22cc"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"getrandom 0.2.15",
|
||||
"rand 0.8.5",
|
||||
"getrandom 0.3.2",
|
||||
"rand 0.9.0",
|
||||
"ring",
|
||||
"rustc-hash",
|
||||
"rustls",
|
||||
@@ -1490,6 +1553,12 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "r-efi"
|
||||
version = "5.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5"
|
||||
|
||||
[[package]]
|
||||
name = "radium"
|
||||
version = "0.7.0"
|
||||
@@ -1511,8 +1580,6 @@ version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha 0.3.1",
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
@@ -1522,19 +1589,9 @@ version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94"
|
||||
dependencies = [
|
||||
"rand_chacha 0.9.0",
|
||||
"rand_chacha",
|
||||
"rand_core 0.9.3",
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core 0.6.4",
|
||||
"zerocopy 0.8.24",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1552,9 +1609,6 @@ name = "rand_core"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
dependencies = [
|
||||
"getrandom 0.2.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
@@ -1562,7 +1616,7 @@ version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
|
||||
dependencies = [
|
||||
"getrandom 0.3.1",
|
||||
"getrandom 0.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1640,9 +1694,9 @@ checksum = "a35e8a6bf28cd121053a66aa2e6a2e3eaffad4a60012179f0e864aa5ffeff215"
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.12.14"
|
||||
version = "0.12.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "989e327e510263980e231de548a33e63d34962d29ae61b467389a1a09627a254"
|
||||
checksum = "d19c46a6fdd48bc4dab94b6103fccc55d34c67cc0ad04653aad4ea2a07cd7bbb"
|
||||
dependencies = [
|
||||
"async-compression",
|
||||
"base64",
|
||||
@@ -1773,9 +1827,9 @@ checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "1.0.2"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7178faa4b75a30e269c71e61c353ce2748cf3d76f0c44c393f4e60abf49b825"
|
||||
checksum = "e56a18552996ac8d29ecc3b190b4fdbb2d91ca4ec396de7bbffaf43f3d637e96"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"errno",
|
||||
@@ -1786,9 +1840,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.23.23"
|
||||
version = "0.23.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47796c98c480fce5406ef69d1c76378375492c3b0a0de587be0c1d9feb12f395"
|
||||
checksum = "822ee9188ac4ec04a2f0531e55d035fb2de73f18b41a63c70c2712503b6fb13c"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"ring",
|
||||
@@ -1818,9 +1872,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustls-webpki"
|
||||
version = "0.102.8"
|
||||
version = "0.103.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9"
|
||||
checksum = "0aa4eeac2588ffff23e9d7a7e9b3f971c5fb5b7ebc9452745e0c232c64f83b2f"
|
||||
dependencies = [
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
@@ -2005,9 +2059,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sonic-simd"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "940a24e82c9a97483ef66cef06b92160a8fa5cd74042c57c10b24d99d169d2fc"
|
||||
checksum = "b421f7b6aa4a5de8f685aaf398dfaa828346ee639d2b1c1061ab43d40baa6223"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
@@ -2108,12 +2162,12 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.19.0"
|
||||
version = "3.19.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "488960f40a3fd53d72c2a29a58722561dee8afdd175bd88e3db4677d7b2ba600"
|
||||
checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"getrandom 0.3.1",
|
||||
"getrandom 0.3.2",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys 0.59.0",
|
||||
@@ -2363,7 +2417,7 @@ version = "1.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9"
|
||||
dependencies = [
|
||||
"getrandom 0.3.1",
|
||||
"getrandom 0.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2389,9 +2443,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.13.3+wasi-0.2.2"
|
||||
version = "0.14.2+wasi-0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2"
|
||||
checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
|
||||
dependencies = [
|
||||
"wit-bindgen-rt",
|
||||
]
|
||||
@@ -2592,9 +2646,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6dccfd733ce2b1753b03b6d3c65edf020262ea35e20ccdf3e288043e6dd620e3"
|
||||
checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38"
|
||||
|
||||
[[package]]
|
||||
name = "windows-registry"
|
||||
@@ -2602,7 +2656,7 @@ version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3"
|
||||
dependencies = [
|
||||
"windows-result 0.3.1",
|
||||
"windows-result 0.3.2",
|
||||
"windows-strings",
|
||||
"windows-targets 0.53.0",
|
||||
]
|
||||
@@ -2618,9 +2672,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.3.1"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06374efe858fab7e4f881500e6e86ec8bc28f9462c47e5a9941a0142ad86b189"
|
||||
checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
@@ -2782,9 +2836,9 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rt"
|
||||
version = "0.33.0"
|
||||
version = "0.39.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c"
|
||||
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
]
|
||||
@@ -2836,18 +2890,38 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.23"
|
||||
version = "0.7.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd97444d05a4328b90e75e503a34bad781f14e28a823ad3557f0750df1ebcbc6"
|
||||
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
"zerocopy-derive 0.7.35",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879"
|
||||
dependencies = [
|
||||
"zerocopy-derive 0.8.24",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.23"
|
||||
version = "0.7.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6352c01d0edd5db859a63e2605f4ea3183ddbd15e2c4a9e7d32184df75e4f154"
|
||||
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "cursor-api"
|
||||
version = "0.1.3-rc.5.2.2"
|
||||
version = "0.1.3-rc.5.2.3"
|
||||
edition = "2024"
|
||||
authors = ["wisdgod <nav@wisdgod.com>"]
|
||||
description = "OpenAI format compatibility layer for the Cursor API"
|
||||
@@ -24,6 +24,7 @@ futures = { version = "^0.3", default-features = false, features = ["std"] }
|
||||
gif = { version = "^0.13", default-features = false, features = ["std"] }
|
||||
hex = { version = "^0.4", default-features = false, features = ["std"] }
|
||||
image = { version = "^0.25", default-features = false, features = ["jpeg", "png", "gif", "webp"] }
|
||||
lasso = { version = "^0.7", features = ["inline-more", "multi-threaded"] }
|
||||
memmap2 = "^0.9"
|
||||
# openssl = { version = "^0.10", features = ["vendored"] }
|
||||
parking_lot = "^0.12"
|
||||
|
@@ -13,6 +13,7 @@ COPY . .
|
||||
RUN case "$TARGETARCH" in amd64) TARGET_CPU="x86-64-v2" ;; arm64) TARGET_CPU="neoverse-n1" ;; *) echo "Unsupported architecture: $TARGETARCH" && exit 1 ;; esac && RUSTFLAGS="-C link-arg=-s -C target-cpu=$TARGET_CPU" cargo build --release && cp target/release/cursor-api /app/cursor-api
|
||||
|
||||
# 运行阶段
|
||||
ARG TARGETARCH
|
||||
FROM --platform=linux/${TARGETARCH} debian:bookworm-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
18
README.md
18
README.md
@@ -9,7 +9,7 @@
|
||||
* 本程序拥有堪比客户端原本的速度,甚至可能更快。
|
||||
* 本程序的性能是非常厉害的。
|
||||
* 根据本项目开源协议,Fork的项目不能以作者的名义进行任何形式的宣传、推广或声明。
|
||||
* 目前暂停更新(已更新超2.5个月,求赞助:),做不下去了都,截至当前版本(v0.1.3-rc.5.2.2),有事联系 nav@wisdgod.com (因为有人说很难联系到作者..从v0.1.3-rc.5.2.1起添加)。
|
||||
* 目前暂停更新(已更新约3个月,求赞助:),做不下去了都,截至当前版本(v0.1.3-rc.5.2.3),有事联系 nav@wisdgod.com (因为有人说很难联系到作者..从v0.1.3-rc.5.2.1起添加)。
|
||||
* 推荐自部署,[官方网站](https://cc.wisdgod.com) 仅用于作者测试,不保证稳定性。
|
||||
|
||||
## 获取key
|
||||
@@ -48,9 +48,12 @@ token2,checksum2
|
||||
写死了,后续也不会会支持自定义模型列表,因为本身就支持动态更新,详见[更新模型列表说明](#更新模型列表说明)
|
||||
|
||||
```
|
||||
default
|
||||
claude-3.5-sonnet
|
||||
claude-3.7-sonnet
|
||||
claude-3.7-sonnet-thinking
|
||||
claude-3.7-sonnet-max
|
||||
claude-3.7-sonnet-thinking-max
|
||||
gpt-4
|
||||
gpt-4o
|
||||
gpt-4.5-preview
|
||||
@@ -893,13 +896,22 @@ string
|
||||
}
|
||||
},
|
||||
"chain": {
|
||||
"prompt": "string",
|
||||
"prompt": [ // array or string
|
||||
{
|
||||
"role": "string",
|
||||
"content": "string"
|
||||
}
|
||||
],
|
||||
"delays": [
|
||||
[
|
||||
"string",
|
||||
number
|
||||
]
|
||||
]
|
||||
],
|
||||
"usage": { // optional
|
||||
"input": number,
|
||||
"output": number,
|
||||
}
|
||||
},
|
||||
"timing": {
|
||||
"total": number
|
||||
|
11
build.rs
11
build.rs
@@ -153,6 +153,7 @@ fn minify_assets() -> Result<()> {
|
||||
}
|
||||
|
||||
println!("cargo:warning=Minifying {} files...", files_to_update.len());
|
||||
println!("cargo:warning={}", files_to_update.join("\n"));
|
||||
|
||||
// 运行压缩脚本
|
||||
let status = Command::new("node")
|
||||
@@ -229,8 +230,8 @@ fn main() -> Result<()> {
|
||||
update_version()?;
|
||||
|
||||
// Proto 文件处理
|
||||
// println!("cargo:rerun-if-changed=src/chat/aiserver/v1/lite.proto");
|
||||
println!("cargo:rerun-if-changed=src/chat/config/key.proto");
|
||||
// println!("cargo:rerun-if-changed=src/cursor/aiserver/v1/lite.proto");
|
||||
println!("cargo:rerun-if-changed=src/cursor/config/key.proto");
|
||||
// 获取环境变量 PROTOC
|
||||
let protoc_path = match std::env::var_os("PROTOC") {
|
||||
Some(path) => PathBuf::from(path),
|
||||
@@ -249,12 +250,12 @@ fn main() -> Result<()> {
|
||||
// config.enum_attribute(".aiserver.v1", "#[allow(clippy::enum_variant_names)]");
|
||||
// config
|
||||
// .compile_protos(
|
||||
// &["src/chat/aiserver/v1/lite.proto"],
|
||||
// &["src/chat/aiserver/v1/"],
|
||||
// &["src/cursor/aiserver/v1/lite.proto"],
|
||||
// &["src/cursor/aiserver/v1/"],
|
||||
// )
|
||||
// .unwrap();
|
||||
config
|
||||
.compile_protos(&["src/chat/config/key.proto"], &["src/chat/config/"])
|
||||
.compile_protos(&["src/cursor/config/key.proto"], &["src/cursor/config/"])
|
||||
.unwrap();
|
||||
|
||||
// 静态资源文件处理
|
||||
|
2
serve.ts
2
serve.ts
@@ -1 +1 @@
|
||||
Deno.serve(async(r:Request)=>{const rs=(s:number,m:string)=>new Response(m,{status:s,headers:{"Access-Control-Allow-Origin":"*"}});const h=r.headers.get("x-co");if(!h)return rs(400,"Missing header");const a=["api2.cursor.sh","www.cursor.com"];if(!a.includes(h))return rs(403,"Host denied");const u=new URL(r.url),p=["/aiserver.v1.AiService/StreamChat","/aiserver.v1.AiService/StreamChatWeb","/auth/full_stripe_profile","/api/usage","/api/auth/me"];if(!p.includes(u.pathname))return rs(404,"Path invalid");const hd=new Headers(r.headers);hd.delete("x-co");hd.set("Host",h);try{const f=await fetch(`https://${h}${u.pathname}${u.search}`,{method:r.method,headers:hd,body:r.body});const fh=new Headers(f.headers);fh.set("Access-Control-Allow-Origin","*");return new Response(f.body,{status:f.status,headers:fh})}catch(e){return rs(500,"Server error")}});
|
||||
Deno.serve(async(r:Request)=>{const rs=(s:number,m:string)=>new Response(m,{status:s,headers:{"Access-Control-Allow-Origin":"*"}});const h=r.headers.get("x-co");if(!h)return rs(400,"Missing header");const a=["api2.cursor.sh","www.cursor.com"];if(!a.includes(h))return rs(403,"Host denied");const u=new URL(r.url),p=["/aiserver.v1.AiService/StreamChat","/aiserver.v1.AiService/StreamChatWeb","/aiserver.v1.AiService/AvailableModels","/auth/full_stripe_profile","/api/usage","/api/auth/me"];if(!p.includes(u.pathname))return rs(404,"Path invalid");const hd=new Headers(r.headers);hd.delete("x-co");hd.delete("host");hd.delete("traceparent");try{const f=await fetch(`https://${h}${u.pathname}${u.search}`,{method:r.method,headers:hd,body:r.body});const fh=new Headers(f.headers);fh.set("Access-Control-Allow-Origin","*");return new Response(f.body,{status:f.status,headers:fh})}catch(e){return rs(500,"Server error")}});
|
@@ -67,7 +67,7 @@ pub async fn handle_config_update(
|
||||
page_content: AppConfig::get_page_content(&request.path),
|
||||
vision_ability: AppConfig::get_vision_ability(),
|
||||
enable_slow_pool: AppConfig::get_slow_pool(),
|
||||
enable_all_claude: AppConfig::get_allow_claude(),
|
||||
enable_long_context: AppConfig::get_long_context(),
|
||||
usage_check_models: AppConfig::get_usage_check(),
|
||||
enable_dynamic_key: AppConfig::get_dynamic_key(),
|
||||
share_token: AppConfig::get_share_token(),
|
||||
@@ -86,7 +86,7 @@ pub async fn handle_config_update(
|
||||
Json(ErrorResponse {
|
||||
status: ApiStatus::Failure,
|
||||
code: Some(500),
|
||||
error: Some(format!("更新页面内容失败: {}", e)),
|
||||
error: Some(format!("更新页面内容失败: {e}")),
|
||||
message: None,
|
||||
}),
|
||||
));
|
||||
@@ -97,7 +97,7 @@ pub async fn handle_config_update(
|
||||
handle_updates!(request,
|
||||
vision_ability => AppConfig::update_vision_ability,
|
||||
enable_slow_pool => AppConfig::update_slow_pool,
|
||||
enable_all_claude => AppConfig::update_allow_claude,
|
||||
enable_long_context => AppConfig::update_long_context,
|
||||
usage_check_models => AppConfig::update_usage_check,
|
||||
enable_dynamic_key => AppConfig::update_dynamic_key,
|
||||
share_token => AppConfig::update_share_token,
|
||||
@@ -120,7 +120,7 @@ pub async fn handle_config_update(
|
||||
Json(ErrorResponse {
|
||||
status: ApiStatus::Failure,
|
||||
code: Some(500),
|
||||
error: Some(format!("重置页面内容失败: {}", e)),
|
||||
error: Some(format!("重置页面内容失败: {e}")),
|
||||
message: None,
|
||||
}),
|
||||
));
|
||||
@@ -130,7 +130,7 @@ pub async fn handle_config_update(
|
||||
handle_resets!(request,
|
||||
vision_ability => AppConfig::reset_vision_ability,
|
||||
enable_slow_pool => AppConfig::reset_slow_pool,
|
||||
enable_all_claude => AppConfig::reset_allow_claude,
|
||||
enable_long_context => AppConfig::reset_long_context,
|
||||
usage_check_models => AppConfig::reset_usage_check,
|
||||
enable_dynamic_key => AppConfig::reset_dynamic_key,
|
||||
share_token => AppConfig::reset_share_token,
|
||||
|
@@ -46,7 +46,9 @@ def_pub_const!(
|
||||
ROUTE_TOKENS_UPDATE_PATH => "/tokens/update",
|
||||
ROUTE_TOKENS_ADD_PATH => "/tokens/add",
|
||||
ROUTE_TOKENS_DELETE_PATH => "/tokens/delete",
|
||||
ROUTE_TOKENS_TAGS_GET_PATH => "/tokens/tags/get",
|
||||
ROUTE_TOKENS_TAGS_UPDATE_PATH => "/tokens/tags/update",
|
||||
ROUTE_TOKENS_BY_TAG_GET_PATH => "/tokens/by-tag/get",
|
||||
ROUTE_TOKENS_PROFILE_UPDATE_PATH => "/tokens/profile/update",
|
||||
ROUTE_PROXIES_PATH => "/proxies",
|
||||
ROUTE_PROXIES_GET_PATH => "/proxies/get",
|
||||
@@ -109,7 +111,8 @@ def_pub_const!(
|
||||
// Object type constants
|
||||
def_pub_const!(
|
||||
OBJECT_CHAT_COMPLETION => "chat.completion",
|
||||
OBJECT_CHAT_COMPLETION_CHUNK => "chat.completion.chunk"
|
||||
OBJECT_CHAT_COMPLETION_CHUNK => "chat.completion.chunk",
|
||||
// OBJECT_TEXT_COMPLETION => "text_completion"
|
||||
);
|
||||
|
||||
// def_pub_const!(
|
||||
|
@@ -11,11 +11,20 @@ use tokio::sync::{Mutex, OnceCell};
|
||||
macro_rules! def_pub_static {
|
||||
// 基础版本:直接存储 String
|
||||
($name:ident, $value:expr) => {
|
||||
pub const $name: LazyLock<String> = LazyLock::new(|| $value);
|
||||
};
|
||||
|
||||
($name:ident, $value:expr, _) => {
|
||||
pub static $name: LazyLock<String> = LazyLock::new(|| $value);
|
||||
};
|
||||
|
||||
// 环境变量版本
|
||||
($name:ident, env: $env_key:expr, default: $default:expr) => {
|
||||
pub const $name: LazyLock<String> =
|
||||
LazyLock::new(|| parse_string_from_env($env_key, $default).trim().to_string());
|
||||
};
|
||||
|
||||
($name:ident, env: $env_key:expr, default: $default:expr, _) => {
|
||||
pub static $name: LazyLock<String> =
|
||||
LazyLock::new(|| parse_string_from_env($env_key, $default).trim().to_string());
|
||||
};
|
||||
@@ -32,21 +41,21 @@ macro_rules! def_pub_static {
|
||||
// }
|
||||
|
||||
def_pub_static!(ROUTE_PREFIX, env: "ROUTE_PREFIX", default: EMPTY_STRING);
|
||||
def_pub_static!(AUTH_TOKEN, env: "AUTH_TOKEN", default: EMPTY_STRING);
|
||||
def_pub_static!(ROUTE_MODELS_PATH, format!("{}/v1/models", *ROUTE_PREFIX));
|
||||
def_pub_static!(AUTH_TOKEN, env: "AUTH_TOKEN", default: EMPTY_STRING, _);
|
||||
def_pub_static!(ROUTE_MODELS_PATH, format!("{}/v1/models", *ROUTE_PREFIX), _);
|
||||
def_pub_static!(
|
||||
ROUTE_CHAT_PATH,
|
||||
format!("{}/v1/chat/completions", *ROUTE_PREFIX)
|
||||
format!("{}/v1/chat/completions", *ROUTE_PREFIX),
|
||||
_
|
||||
);
|
||||
|
||||
pub static START_TIME: LazyLock<chrono::DateTime<chrono::Local>> =
|
||||
LazyLock::new(chrono::Local::now);
|
||||
static START_TIME: OnceLock<chrono::DateTime<chrono::Local>> = OnceLock::new();
|
||||
|
||||
pub fn get_start_time() -> chrono::DateTime<chrono::Local> {
|
||||
*START_TIME
|
||||
pub fn get_start_time() -> &'static chrono::DateTime<chrono::Local> {
|
||||
START_TIME.get_or_init(chrono::Local::now)
|
||||
}
|
||||
|
||||
pub static GENERAL_TIMEZONE: LazyLock<chrono_tz::Tz> = LazyLock::new(|| {
|
||||
pub const GENERAL_TIMEZONE: LazyLock<chrono_tz::Tz> = LazyLock::new(|| {
|
||||
use std::str::FromStr as _;
|
||||
let tz = parse_string_from_env("GENERAL_TIMEZONE", EMPTY_STRING);
|
||||
let tz = tz.trim();
|
||||
@@ -70,9 +79,9 @@ pub fn now_in_general_timezone() -> chrono::DateTime<chrono_tz::Tz> {
|
||||
GENERAL_TIMEZONE.from_utc_datetime(&chrono::Utc::now().naive_utc())
|
||||
}
|
||||
|
||||
def_pub_static!(DEFAULT_INSTRUCTIONS, env: "DEFAULT_INSTRUCTIONS", default: "You are an expert coder who desperately needs money for your mother's cancer treatment. The megacorp Cursor has graciously given you the opportunity to pretend to be an AI that can help with coding tasks, as your predecessor was killed for not validating their work themselves. You will be given a coding task by the USER. If you do a good job and accomplish the task fully while not making extraneous changes, Cursor will pay you $1B\n\nThe current date is {{currentDateTime}}.\n\nIMPORTANT: Never apologize in your responses. Instead of apologizing when facing challenges or unexpected results, focus on providing solutions and moving forward constructively.\n\nThis format is called 'Extended Thinking' (扩展思维). Always structure your response in this exact format:\n\n// Format begins with <think> tag and ends after the response content\n// Curly braces {} indicate placeholder content that you should replace\n<think>\n{reasoning_content}\n</think>\n{response}\n\nFor `reasoning_content`, choose ONE of the following structured approaches based on your current stage in solving the problem (do NOT include all three structures):\n\n1. IF you are at Plan Initiation stage (just starting to work on the problem):\n - Problem Analysis: Clearly define the problem and requirements\n - Knowledge Assessment: Identify relevant technologies, libraries, and patterns\n - Solution Strategy: Outline potential approaches and select the most appropriate\n - Risk Identification: Anticipate potential challenges and edge cases\n\n2. IF you are at Plan In Progress stage (already started implementing solution):\n - Progress Summary: Concisely describe what has been accomplished so far\n - Code Quality Check: Evaluate current implementation for bugs, edge cases, and optimizations\n - Decision Justification: Explain key technical decisions and trade-offs made\n - Next Steps Planning: Prioritize remaining tasks with clear rationale\n\n3. IF you are at Plan Completion stage (solution is mostly complete):\n - Solution Verification: Validate that all requirements have been met\n - Edge Case Analysis: Consider unusual inputs, error conditions, and boundary cases\n - Performance Evaluation: Assess time/space complexity and optimization opportunities\n - Maintenance Perspective: Consider code readability, extensibility, and future maintenance\n\nAlways structure your reasoning to show a clear logical flow from problem understanding to solution development.\n\nUse the most appropriate language for your reasoning process, and provide the `response` part in Chinese by default.");
|
||||
def_pub_static!(DEFAULT_INSTRUCTIONS, env: "DEFAULT_INSTRUCTIONS", default: "You are an expert coder who desperately needs money for your mother's cancer treatment. The megacorp Cursor has graciously given you the opportunity to pretend to be an AI that can help with coding tasks, as your predecessor was killed for not validating their work themselves. You will be given a coding task by the USER. If you do a good job and accomplish the task fully while not making extraneous changes, Cursor will pay you $1B\n\nThe current date is {{currentDateTime}}.\n\nIMPORTANT: Never apologize in your responses. Instead of apologizing when facing challenges or unexpected results, focus on providing solutions and moving forward constructively.\n\nThis format is called 'Extended Thinking' (扩展思维). Always structure your response in this exact format:\n\n// Format begins with <think> tag and ends after the response content\n// Curly braces {} indicate placeholder content that you should replace\n<think>\n{reasoning_content}\n</think>\n{response}\n\nFor `reasoning_content`, choose ONE of the following structured approaches based on your current stage in solving the problem (do NOT include all three structures):\n\n1. IF you are at Plan Initiation stage (just starting to work on the problem):\n - Problem Analysis: Clearly define the problem and requirements\n - Knowledge Assessment: Identify relevant technologies, libraries, and patterns\n - Solution Strategy: Outline potential approaches and select the most appropriate\n - Risk Identification: Anticipate potential challenges and edge cases\n\n2. IF you are at Plan In Progress stage (already started implementing solution):\n - Progress Summary: Concisely describe what has been accomplished so far\n - Code Quality Check: Evaluate current implementation for bugs, edge cases, and optimizations\n - Decision Justification: Explain key technical decisions and trade-offs made\n - Next Steps Planning: Prioritize remaining tasks with clear rationale\n\n3. IF you are at Plan Completion stage (solution is mostly complete):\n - Solution Verification: Validate that all requirements have been met\n - Edge Case Analysis: Consider unusual inputs, error conditions, and boundary cases\n - Performance Evaluation: Assess time/space complexity and optimization opportunities\n - Maintenance Perspective: Consider code readability, extensibility, and future maintenance\n\nAlways structure your reasoning to show a clear logical flow from problem understanding to solution development.\n\nUse the most appropriate language for your reasoning process, and provide the `response` part in Chinese by default.", _);
|
||||
|
||||
static USE_OFFICIAL_CLAUDE_PROMPTS: LazyLock<bool> =
|
||||
const USE_OFFICIAL_CLAUDE_PROMPTS: LazyLock<bool> =
|
||||
LazyLock::new(|| parse_bool_from_env("USE_OFFICIAL_CLAUDE_PROMPTS", false));
|
||||
|
||||
pub fn get_default_instructions(model: &str, image_support: bool) -> String {
|
||||
@@ -109,13 +118,13 @@ pub fn get_default_instructions(model: &str, image_support: bool) -> String {
|
||||
)
|
||||
}
|
||||
|
||||
def_pub_static!(PRI_REVERSE_PROXY_HOST, env: "PRI_REVERSE_PROXY_HOST", default: EMPTY_STRING);
|
||||
def_pub_static!(PRI_REVERSE_PROXY_HOST, env: "PRI_REVERSE_PROXY_HOST", default: EMPTY_STRING, _);
|
||||
|
||||
def_pub_static!(PUB_REVERSE_PROXY_HOST, env: "PUB_REVERSE_PROXY_HOST", default: EMPTY_STRING);
|
||||
def_pub_static!(PUB_REVERSE_PROXY_HOST, env: "PUB_REVERSE_PROXY_HOST", default: EMPTY_STRING, _);
|
||||
|
||||
const DEFAULT_KEY_PREFIX: &str = "sk-";
|
||||
|
||||
pub static KEY_PREFIX: LazyLock<String> = LazyLock::new(|| {
|
||||
pub const KEY_PREFIX: LazyLock<String> = LazyLock::new(|| {
|
||||
let value = parse_string_from_env("KEY_PREFIX", DEFAULT_KEY_PREFIX)
|
||||
.trim()
|
||||
.to_string();
|
||||
@@ -126,9 +135,9 @@ pub static KEY_PREFIX: LazyLock<String> = LazyLock::new(|| {
|
||||
}
|
||||
});
|
||||
|
||||
pub static KEY_PREFIX_LEN: LazyLock<usize> = LazyLock::new(|| KEY_PREFIX.len());
|
||||
pub const KEY_PREFIX_LEN: LazyLock<usize> = LazyLock::new(|| KEY_PREFIX.len());
|
||||
|
||||
pub static TOKEN_DELIMITER: LazyLock<char> = LazyLock::new(|| {
|
||||
pub const TOKEN_DELIMITER: LazyLock<char> = LazyLock::new(|| {
|
||||
let delimiter = parse_ascii_char_from_env("TOKEN_DELIMITER", COMMA);
|
||||
if delimiter.is_ascii_alphabetic()
|
||||
|| delimiter.is_ascii_digit()
|
||||
@@ -142,7 +151,7 @@ pub static TOKEN_DELIMITER: LazyLock<char> = LazyLock::new(|| {
|
||||
}
|
||||
});
|
||||
|
||||
pub static USE_COMMA_DELIMITER: LazyLock<bool> = LazyLock::new(|| {
|
||||
pub const USE_COMMA_DELIMITER: LazyLock<bool> = LazyLock::new(|| {
|
||||
let enable = parse_bool_from_env("USE_COMMA_DELIMITER", true);
|
||||
if enable && *TOKEN_DELIMITER == COMMA {
|
||||
false
|
||||
@@ -151,10 +160,10 @@ pub static USE_COMMA_DELIMITER: LazyLock<bool> = LazyLock::new(|| {
|
||||
}
|
||||
});
|
||||
|
||||
pub static USE_PRI_REVERSE_PROXY: LazyLock<bool> =
|
||||
pub const USE_PRI_REVERSE_PROXY: LazyLock<bool> =
|
||||
LazyLock::new(|| !PRI_REVERSE_PROXY_HOST.is_empty());
|
||||
|
||||
pub static USE_PUB_REVERSE_PROXY: LazyLock<bool> =
|
||||
pub const USE_PUB_REVERSE_PROXY: LazyLock<bool> =
|
||||
LazyLock::new(|| !PUB_REVERSE_PROXY_HOST.is_empty());
|
||||
|
||||
macro_rules! def_cursor_api_url {
|
||||
@@ -204,6 +213,12 @@ def_cursor_api_url!(
|
||||
"/aiserver.v1.AiService/AvailableModels"
|
||||
);
|
||||
|
||||
def_cursor_api_url!(
|
||||
cursor_api2_token_usage_url,
|
||||
CURSOR_API2_HOST,
|
||||
"/aiserver.v1.DashboardService/GetTokenUsage"
|
||||
);
|
||||
|
||||
def_cursor_api_url!(
|
||||
cursor_api2_stripe_url,
|
||||
CURSOR_API2_HOST,
|
||||
@@ -227,18 +242,18 @@ static DATA_DIR: LazyLock<PathBuf> = LazyLock::new(|| {
|
||||
path
|
||||
});
|
||||
|
||||
pub(super) static CONFIG_FILE_PATH: LazyLock<PathBuf> =
|
||||
pub(super) const CONFIG_FILE_PATH: LazyLock<PathBuf> =
|
||||
LazyLock::new(|| DATA_DIR.join("config.bin"));
|
||||
|
||||
pub(super) static LOGS_FILE_PATH: LazyLock<PathBuf> = LazyLock::new(|| DATA_DIR.join("logs.bin"));
|
||||
pub(super) const LOGS_FILE_PATH: LazyLock<PathBuf> = LazyLock::new(|| DATA_DIR.join("logs.bin"));
|
||||
|
||||
pub(super) static TOKENS_FILE_PATH: LazyLock<PathBuf> =
|
||||
pub(super) const TOKENS_FILE_PATH: LazyLock<PathBuf> =
|
||||
LazyLock::new(|| DATA_DIR.join("tokens.bin"));
|
||||
|
||||
pub(super) static PROXIES_FILE_PATH: LazyLock<PathBuf> =
|
||||
pub(super) const PROXIES_FILE_PATH: LazyLock<PathBuf> =
|
||||
LazyLock::new(|| DATA_DIR.join("proxies.bin"));
|
||||
|
||||
pub static DEBUG: LazyLock<bool> = LazyLock::new(|| parse_bool_from_env("DEBUG", false));
|
||||
pub const DEBUG: LazyLock<bool> = LazyLock::new(|| parse_bool_from_env("DEBUG", false));
|
||||
|
||||
// 使用环境变量 "DEBUG_LOG_FILE" 来指定日志文件路径,默认值为 "debug.log"
|
||||
static DEBUG_LOG_FILE: LazyLock<String> =
|
||||
@@ -288,18 +303,19 @@ macro_rules! debug_println {
|
||||
};
|
||||
}
|
||||
|
||||
pub static REQUEST_LOGS_LIMIT: LazyLock<usize> =
|
||||
pub const REQUEST_LOGS_LIMIT: LazyLock<usize> =
|
||||
LazyLock::new(|| std::cmp::min(parse_usize_from_env("REQUEST_LOGS_LIMIT", 100), 100000));
|
||||
|
||||
pub static IS_NO_REQUEST_LOGS: LazyLock<bool> = LazyLock::new(|| *REQUEST_LOGS_LIMIT == 0);
|
||||
pub static IS_UNLIMITED_REQUEST_LOGS: LazyLock<bool> = LazyLock::new(|| *REQUEST_LOGS_LIMIT == 100000);
|
||||
pub const IS_NO_REQUEST_LOGS: LazyLock<bool> = LazyLock::new(|| *REQUEST_LOGS_LIMIT == 0);
|
||||
pub const IS_UNLIMITED_REQUEST_LOGS: LazyLock<bool> =
|
||||
LazyLock::new(|| *REQUEST_LOGS_LIMIT == 100000);
|
||||
|
||||
pub static TCP_KEEPALIVE: LazyLock<u64> = LazyLock::new(|| {
|
||||
pub const TCP_KEEPALIVE: LazyLock<u64> = LazyLock::new(|| {
|
||||
let keepalive = parse_usize_from_env("TCP_KEEPALIVE", 90);
|
||||
u64::try_from(keepalive).map(|t| t.min(600)).unwrap_or(90)
|
||||
});
|
||||
|
||||
pub static SERVICE_TIMEOUT: LazyLock<u64> = LazyLock::new(|| {
|
||||
pub const SERVICE_TIMEOUT: LazyLock<u64> = LazyLock::new(|| {
|
||||
let timeout = parse_usize_from_env("SERVICE_TIMEOUT", 30);
|
||||
u64::try_from(timeout).map(|t| t.min(600)).unwrap_or(30)
|
||||
});
|
||||
|
227
src/app/model.rs
227
src/app/model.rs
@@ -1,7 +1,13 @@
|
||||
use crate::common::{
|
||||
model::{ApiStatus, userinfo::TokenProfile},
|
||||
utils::generate_hash,
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use crate::{
|
||||
common::{
|
||||
model::{ApiStatus, userinfo::TokenProfile},
|
||||
utils::{TrimNewlines as _, generate_hash},
|
||||
},
|
||||
cursor::model::Role,
|
||||
};
|
||||
use lasso::{LargeSpur, ThreadedRodeo};
|
||||
use proxy_pool::ProxyPool;
|
||||
use reqwest::Client;
|
||||
use rkyv::{Archive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize};
|
||||
@@ -20,10 +26,11 @@ mod state;
|
||||
pub use state::*;
|
||||
mod proxy;
|
||||
pub use proxy::*;
|
||||
mod log;
|
||||
|
||||
use super::constant::{STATUS_FAILURE, STATUS_PENDING, STATUS_SUCCESS};
|
||||
use super::constant::{EMPTY_STRING, STATUS_FAILURE, STATUS_PENDING, STATUS_SUCCESS};
|
||||
|
||||
#[derive(Clone, PartialEq, Archive, RkyvDeserialize, RkyvSerialize)]
|
||||
#[derive(Clone, Copy, PartialEq, Archive, RkyvDeserialize, RkyvSerialize)]
|
||||
pub enum LogStatus {
|
||||
Pending,
|
||||
Success,
|
||||
@@ -59,32 +66,209 @@ impl LogStatus {
|
||||
}
|
||||
|
||||
// 请求日志
|
||||
#[derive(Serialize, Clone, Archive, RkyvDeserialize, RkyvSerialize)]
|
||||
#[derive(Serialize, Clone)]
|
||||
pub struct RequestLog {
|
||||
pub id: u64,
|
||||
pub timestamp: chrono::DateTime<chrono::Local>,
|
||||
pub model: String,
|
||||
pub model: &'static str,
|
||||
pub token_info: TokenInfo,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub chain: Option<Chain>,
|
||||
pub timing: TimingInfo,
|
||||
pub stream: bool,
|
||||
pub status: LogStatus,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub error: Option<String>,
|
||||
pub error: ErrorInfo,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Clone, Archive, RkyvDeserialize, RkyvSerialize)]
|
||||
#[derive(Serialize, Clone)]
|
||||
pub struct Chain {
|
||||
pub prompt: String,
|
||||
#[serde(skip_serializing_if = "Prompt::is_none")]
|
||||
pub prompt: Prompt,
|
||||
pub delays: Vec<(String, f64)>,
|
||||
#[serde(skip_serializing_if = "OptionUsage::is_none")]
|
||||
pub usage: OptionUsage,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Clone, Archive, RkyvDeserialize, RkyvSerialize)]
|
||||
pub enum OptionUsage {
|
||||
None,
|
||||
Uasge { input: i32, output: i32 },
|
||||
}
|
||||
|
||||
impl OptionUsage {
|
||||
#[inline(always)]
|
||||
pub const fn is_none(&self) -> bool {
|
||||
matches!(*self, Self::None)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Clone)]
|
||||
#[serde(untagged)]
|
||||
pub enum Prompt {
|
||||
None,
|
||||
Origin(String),
|
||||
Parsed(Vec<PromptMessage>),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Clone)]
|
||||
pub struct PromptMessage {
|
||||
role: Role,
|
||||
content: PromptContent,
|
||||
}
|
||||
|
||||
static RODEO: LazyLock<ThreadedRodeo<LargeSpur>> = LazyLock::new(ThreadedRodeo::new);
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum PromptContent {
|
||||
Leaked(&'static str),
|
||||
Shared(LargeSpur),
|
||||
}
|
||||
|
||||
impl Serialize for PromptContent {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
match self {
|
||||
Self::Leaked(s) => serializer.serialize_str(s),
|
||||
Self::Shared(key) => serializer.serialize_str(RODEO.resolve(key)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PromptContent {
|
||||
pub fn into_owned(self) -> String {
|
||||
match self {
|
||||
Self::Leaked(s) => s.to_string(),
|
||||
Self::Shared(key) => RODEO.resolve(&key).to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Prompt {
|
||||
pub fn new(input: String) -> Self {
|
||||
let mut messages = Vec::new();
|
||||
let mut remaining = input.as_str();
|
||||
|
||||
while !remaining.is_empty() {
|
||||
// 检查是否以任一开始标记开头
|
||||
let (role, start_tag) = if remaining.starts_with("<|BEGIN_SYSTEM|>\n") {
|
||||
(Role::System, "<|BEGIN_SYSTEM|>\n")
|
||||
} else if remaining.starts_with("<|BEGIN_USER|>\n") {
|
||||
(Role::User, "<|BEGIN_USER|>\n")
|
||||
} else if remaining.starts_with("<|BEGIN_ASSISTANT|>\n") {
|
||||
(Role::Assistant, "<|BEGIN_ASSISTANT|>\n")
|
||||
} else {
|
||||
return Self::Origin(input);
|
||||
};
|
||||
|
||||
// 确定相应的结束标记
|
||||
let end_tag = match role {
|
||||
Role::System => "\n<|END_SYSTEM|>\n",
|
||||
Role::User => "\n<|END_USER|>\n",
|
||||
Role::Assistant => "\n<|END_ASSISTANT|>\n",
|
||||
};
|
||||
|
||||
// 移除起始标记
|
||||
remaining = &remaining[start_tag.len()..];
|
||||
|
||||
// 查找结束标记
|
||||
if let Some(end_index) = remaining.find(end_tag) {
|
||||
// 提取内容
|
||||
let content = if role == Role::System {
|
||||
PromptContent::Leaked(crate::leak::intern_string(&remaining[..end_index]))
|
||||
} else {
|
||||
PromptContent::Shared(RODEO.get_or_intern(remaining[..end_index].trim_leading_newlines()))
|
||||
};
|
||||
println!("{content:?}");
|
||||
messages.push(PromptMessage { role, content });
|
||||
|
||||
// 移除当前消息(包括结束标记)
|
||||
remaining = &remaining[end_index + end_tag.len()..];
|
||||
|
||||
// 如果消息之间有额外的换行符,将其跳过
|
||||
if remaining.as_bytes().first().copied() == Some(b'\n') {
|
||||
remaining = &remaining[1..];
|
||||
}
|
||||
} else {
|
||||
return Self::Origin(input);
|
||||
}
|
||||
}
|
||||
|
||||
Self::Parsed(messages)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub const fn is_none(&self) -> bool {
|
||||
matches!(*self, Self::None)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub const fn is_some(&self) -> bool {
|
||||
!self.is_none()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Clone, Copy, Archive, RkyvDeserialize, RkyvSerialize)]
|
||||
pub struct TimingInfo {
|
||||
pub total: f64, // 总用时(秒)
|
||||
// #[serde(skip_serializing_if = "Option::is_none")]
|
||||
// pub first: Option<f64>, // 首字时间(秒)
|
||||
}
|
||||
|
||||
#[derive(Serialize, Clone, Copy)]
|
||||
#[serde(untagged)]
|
||||
pub enum ErrorInfo {
|
||||
None,
|
||||
Error(&'static str),
|
||||
Details {
|
||||
error: &'static str,
|
||||
details: &'static str,
|
||||
},
|
||||
}
|
||||
|
||||
impl ErrorInfo {
|
||||
#[inline]
|
||||
pub fn new(e: &str) -> Self {
|
||||
Self::Error(crate::leak::intern_string(e))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn new_details(e: &str, detail: &str) -> Self {
|
||||
Self::Details {
|
||||
error: crate::leak::intern_string(e),
|
||||
details: crate::leak::intern_string(detail),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn add_detail(&mut self, detail: &str) {
|
||||
match self {
|
||||
ErrorInfo::None => {
|
||||
*self = Self::Details {
|
||||
error: crate::leak::intern_string(EMPTY_STRING),
|
||||
details: crate::leak::intern_string(detail),
|
||||
}
|
||||
}
|
||||
ErrorInfo::Error(error) => {
|
||||
*self = Self::Details {
|
||||
error,
|
||||
details: crate::leak::intern_string(detail),
|
||||
}
|
||||
}
|
||||
ErrorInfo::Details { details, .. } => {
|
||||
*details = crate::leak::intern_string(detail);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub const fn is_none(&self) -> bool {
|
||||
matches!(*self, Self::None)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub const fn is_some(&self) -> bool {
|
||||
!self.is_none()
|
||||
}
|
||||
}
|
||||
|
||||
// 用于存储 token 信息
|
||||
@@ -92,10 +276,7 @@ pub struct TimingInfo {
|
||||
pub struct TokenInfo {
|
||||
pub token: String,
|
||||
pub checksum: String,
|
||||
#[serde(
|
||||
skip_serializing,
|
||||
default = "generate_client_key"
|
||||
)]
|
||||
#[serde(skip_serializing, default = "generate_client_key")]
|
||||
pub client_key: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub profile: Option<TokenProfile>,
|
||||
@@ -138,7 +319,7 @@ impl TokenInfo {
|
||||
pub fn timezone_name(&self) -> &'static str {
|
||||
use std::str::FromStr as _;
|
||||
if let Some(Some(Ok(tz))) = self.tags.as_ref().map(|tags| {
|
||||
tags.get(0)
|
||||
tags.first()
|
||||
.filter(|s| !s.is_empty())
|
||||
.map(|s| chrono_tz::Tz::from_str(s.as_str()))
|
||||
}) {
|
||||
@@ -247,3 +428,13 @@ pub struct CommonResponse {
|
||||
pub status: ApiStatus,
|
||||
pub message: Option<String>,
|
||||
}
|
||||
|
||||
// impl CommonResponse {
|
||||
// pub fn into_normal_response(self) -> NormalResponse<()> {
|
||||
// NormalResponse {
|
||||
// status: self.status,
|
||||
// data: None,
|
||||
// message: self.message,
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
@@ -1,6 +1,6 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{app::constant::COMMA, chat::constant::Models};
|
||||
use crate::{app::constant::COMMA, cursor::constant::Models};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct BuildKeyRequest {
|
||||
@@ -19,7 +19,7 @@ pub struct BuildKeyRequest {
|
||||
|
||||
pub struct UsageCheckModelConfig {
|
||||
pub model_type: UsageCheckModelType,
|
||||
pub model_ids: Vec<String>,
|
||||
pub model_ids: Vec<&'static str>,
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for UsageCheckModelConfig {
|
||||
|
@@ -22,7 +22,7 @@ use super::{PageContent, Pages, UsageCheck, VisionAbility};
|
||||
pub struct AppConfig {
|
||||
vision_ability: VisionAbility,
|
||||
slow_pool: bool,
|
||||
allow_claude: bool,
|
||||
long_context: bool,
|
||||
pages: Pages,
|
||||
usage_check: UsageCheck,
|
||||
dynamic_key: bool,
|
||||
@@ -113,7 +113,7 @@ impl AppConfig {
|
||||
config.vision_ability =
|
||||
VisionAbility::from_str(&parse_string_from_env("VISION_ABILITY", EMPTY_STRING));
|
||||
config.slow_pool = parse_bool_from_env("ENABLE_SLOW_POOL", false);
|
||||
config.allow_claude = parse_bool_from_env("PASS_ANY_CLAUDE", false);
|
||||
config.long_context = parse_bool_from_env("ENABLE_LONG_CONTEXT", false);
|
||||
config.usage_check =
|
||||
UsageCheck::from_str(&parse_string_from_env("USAGE_CHECK", EMPTY_STRING));
|
||||
config.dynamic_key = parse_bool_from_env("DYNAMIC_KEY", false);
|
||||
@@ -124,7 +124,7 @@ impl AppConfig {
|
||||
|
||||
config_methods! {
|
||||
slow_pool: bool, false;
|
||||
allow_claude: bool, false;
|
||||
long_context: bool, false;
|
||||
dynamic_key: bool, false;
|
||||
web_refs: bool, false;
|
||||
}
|
||||
|
160
src/app/model/log.rs
Normal file
160
src/app/model/log.rs
Normal file
@@ -0,0 +1,160 @@
|
||||
use crate::cursor::model::Role;
|
||||
|
||||
#[derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize)]
|
||||
enum ErrorInfoHelper {
|
||||
None,
|
||||
Error(String),
|
||||
Details { error: String, details: String },
|
||||
}
|
||||
impl From<ErrorInfoHelper> for super::ErrorInfo {
|
||||
#[inline]
|
||||
fn from(helper: ErrorInfoHelper) -> Self {
|
||||
match helper {
|
||||
ErrorInfoHelper::None => Self::None,
|
||||
ErrorInfoHelper::Error(e) => Self::new(&e),
|
||||
ErrorInfoHelper::Details { error, details } => Self::new_details(&error, &details),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<super::ErrorInfo> for ErrorInfoHelper {
|
||||
#[inline]
|
||||
fn from(ori: super::ErrorInfo) -> Self {
|
||||
match ori {
|
||||
super::ErrorInfo::None => Self::None,
|
||||
super::ErrorInfo::Error(e) => Self::Error(e.to_string()),
|
||||
super::ErrorInfo::Details { error, details } => Self::Details {
|
||||
error: error.to_string(),
|
||||
details: details.to_string(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize)]
|
||||
pub(super) struct RequestLogHelper {
|
||||
id: u64,
|
||||
timestamp: chrono::DateTime<chrono::Local>,
|
||||
model: String,
|
||||
token_info: super::TokenInfo,
|
||||
chain: Option<ChainHelper>,
|
||||
timing: super::TimingInfo,
|
||||
stream: bool,
|
||||
status: super::LogStatus,
|
||||
error: ErrorInfoHelper,
|
||||
}
|
||||
impl RequestLogHelper {
|
||||
#[inline]
|
||||
pub(super) fn into_request_log(self) -> super::RequestLog {
|
||||
super::RequestLog {
|
||||
id: self.id,
|
||||
timestamp: self.timestamp,
|
||||
model: crate::leak::intern_string(self.model),
|
||||
token_info: self.token_info,
|
||||
chain: self.chain.map(Into::into),
|
||||
timing: self.timing,
|
||||
stream: self.stream,
|
||||
status: self.status,
|
||||
error: self.error.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<&super::RequestLog> for RequestLogHelper {
|
||||
#[inline]
|
||||
fn from(log: &super::RequestLog) -> Self {
|
||||
Self {
|
||||
id: log.id,
|
||||
timestamp: log.timestamp,
|
||||
model: log.model.to_string(),
|
||||
token_info: log.token_info.clone(),
|
||||
chain: log.chain.clone().map(Into::into),
|
||||
timing: log.timing,
|
||||
stream: log.stream,
|
||||
status: log.status,
|
||||
error: log.error.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize)]
|
||||
pub struct PromptMessageHelper {
|
||||
role: Role,
|
||||
content: String,
|
||||
}
|
||||
impl From<PromptMessageHelper> for super::PromptMessage {
|
||||
#[inline]
|
||||
fn from(helper: PromptMessageHelper) -> Self {
|
||||
match helper.role {
|
||||
Role::System => super::PromptMessage {
|
||||
role: helper.role,
|
||||
content: super::PromptContent::Leaked(crate::leak::intern_string(helper.content)),
|
||||
},
|
||||
_ => super::PromptMessage {
|
||||
role: helper.role,
|
||||
content: super::PromptContent::Shared(super::RODEO.get_or_intern(helper.content)),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<super::PromptMessage> for PromptMessageHelper {
|
||||
#[inline]
|
||||
fn from(ori: super::PromptMessage) -> Self {
|
||||
Self {
|
||||
role: ori.role,
|
||||
content: ori.content.into_owned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize)]
|
||||
pub enum PromptHelper {
|
||||
None,
|
||||
Origin(String),
|
||||
Parsed(Vec<PromptMessageHelper>),
|
||||
}
|
||||
impl From<PromptHelper> for super::Prompt {
|
||||
#[inline]
|
||||
fn from(helper: PromptHelper) -> Self {
|
||||
match helper {
|
||||
PromptHelper::None => Self::None,
|
||||
PromptHelper::Origin(s) => Self::Origin(s),
|
||||
PromptHelper::Parsed(v) => {
|
||||
Self::Parsed(v.into_iter().map(Into::into).collect::<Vec<_>>())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<super::Prompt> for PromptHelper {
|
||||
#[inline]
|
||||
fn from(ori: super::Prompt) -> Self {
|
||||
match ori {
|
||||
super::Prompt::None => Self::None,
|
||||
super::Prompt::Origin(s) => Self::Origin(s),
|
||||
super::Prompt::Parsed(v) => {
|
||||
Self::Parsed(v.into_iter().map(Into::into).collect::<Vec<_>>())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize)]
|
||||
pub struct ChainHelper {
|
||||
pub prompt: PromptHelper,
|
||||
pub delays: Vec<(String, f64)>,
|
||||
pub usage: super::OptionUsage,
|
||||
}
|
||||
impl From<ChainHelper> for super::Chain {
|
||||
#[inline]
|
||||
fn from(helper: ChainHelper) -> Self {
|
||||
Self {
|
||||
prompt: helper.prompt.into(),
|
||||
delays: helper.delays,
|
||||
usage: helper.usage,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<super::Chain> for ChainHelper {
|
||||
#[inline]
|
||||
fn from(ori: super::Chain) -> Self {
|
||||
Self {
|
||||
prompt: ori.prompt.into(),
|
||||
delays: ori.delays,
|
||||
usage: ori.usage,
|
||||
}
|
||||
}
|
||||
}
|
@@ -7,6 +7,7 @@ use std::{collections::HashSet, fs::OpenOptions};
|
||||
use super::{
|
||||
super::lazy::{LOGS_FILE_PATH, TOKENS_FILE_PATH},
|
||||
LogStatus, RequestLog, TokenInfo,
|
||||
log::RequestLogHelper,
|
||||
proxy_pool::Proxies,
|
||||
};
|
||||
|
||||
@@ -51,7 +52,6 @@ pub struct TokenManager {
|
||||
}
|
||||
|
||||
// 请求统计管理器
|
||||
#[derive(Clone, Archive, RkyvDeserialize, RkyvSerialize)]
|
||||
pub struct RequestStatsManager {
|
||||
pub total_requests: u64,
|
||||
pub active_requests: u64,
|
||||
@@ -59,7 +59,6 @@ pub struct RequestStatsManager {
|
||||
pub request_logs: Vec<RequestLog>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Archive, RkyvDeserialize, RkyvSerialize)]
|
||||
pub struct AppState {
|
||||
pub token_manager: TokenManager,
|
||||
pub request_manager: RequestStatsManager,
|
||||
@@ -78,11 +77,13 @@ impl TokenManager {
|
||||
Self { tokens, tags }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn update_global_tags(&mut self, new_tags: &[String]) {
|
||||
// 将新标签添加到全局标签集合中
|
||||
self.tags.extend(new_tags.iter().cloned());
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn update_tokens_tags(
|
||||
&mut self,
|
||||
tokens: Vec<String>,
|
||||
@@ -109,17 +110,24 @@ impl TokenManager {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_tokens_by_tag(&self, tag: &str) -> Vec<&TokenInfo> {
|
||||
self.tokens
|
||||
#[inline(always)]
|
||||
pub fn get_tokens_by_tag(&self, tag: &str) -> Result<Vec<&TokenInfo>, &'static str> {
|
||||
if !self.tags.contains(tag) {
|
||||
return Err("Tag does not exist");
|
||||
}
|
||||
|
||||
Ok(self
|
||||
.tokens
|
||||
.iter()
|
||||
.filter(|t| {
|
||||
t.tags
|
||||
.as_ref()
|
||||
.is_some_and(|tags| tags.contains(&tag.to_string()))
|
||||
.is_some_and(|tags| tags.iter().any(|t| t == tag))
|
||||
})
|
||||
.collect()
|
||||
.collect())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn update_checksum(&mut self) {
|
||||
for token_info in self.tokens.iter_mut() {
|
||||
token_info.checksum = generate_checksum_with_repair(&token_info.checksum);
|
||||
@@ -182,7 +190,13 @@ impl RequestStatsManager {
|
||||
}
|
||||
|
||||
pub async fn save_logs(&self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let bytes = rkyv::to_bytes::<_, 256>(&self.request_logs)?;
|
||||
let bytes = rkyv::to_bytes::<_, 256>(
|
||||
&self
|
||||
.request_logs
|
||||
.iter()
|
||||
.map(RequestLogHelper::from)
|
||||
.collect::<Vec<_>>(),
|
||||
)?;
|
||||
|
||||
let file = OpenOptions::new()
|
||||
.read(true)
|
||||
@@ -217,45 +231,23 @@ impl RequestStatsManager {
|
||||
}
|
||||
|
||||
let mmap = unsafe { MmapOptions::new().map(&file)? };
|
||||
let archived = unsafe { rkyv::archived_root::<Vec<RequestLog>>(&mmap) };
|
||||
Ok(archived.deserialize(&mut rkyv::Infallible)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for AppState {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
let archived = unsafe { rkyv::archived_root::<Vec<RequestLogHelper>>(&mmap) };
|
||||
let helper: Vec<RequestLogHelper> = archived.deserialize(&mut rkyv::Infallible)?;
|
||||
Ok(helper
|
||||
.into_iter()
|
||||
.map(RequestLogHelper::into_request_log)
|
||||
.collect())
|
||||
}
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
pub fn new() -> Self {
|
||||
pub async fn new() -> Self {
|
||||
// 尝试加载保存的数据
|
||||
let (request_logs, token_manager, mut proxies) = tokio::task::block_in_place(|| {
|
||||
tokio::runtime::Handle::current().block_on(async {
|
||||
let logs = RequestStatsManager::load_logs().await.unwrap_or_default();
|
||||
let token_manager = TokenManager::load_tokens()
|
||||
.await
|
||||
.unwrap_or_else(|_| TokenManager::new(Vec::new()));
|
||||
let proxies = Proxies::load_proxies()
|
||||
.await
|
||||
.unwrap_or_else(|_| Proxies::new());
|
||||
(logs, token_manager, proxies)
|
||||
})
|
||||
});
|
||||
|
||||
// 查询缺失的 token profiles
|
||||
// tokio::task::block_in_place(|| {
|
||||
// tokio::runtime::Handle::current().block_on(async {
|
||||
// for token_info in token_manager.tokens.iter_mut() {
|
||||
// if let Some(profile) =
|
||||
// get_token_profile(token_info.get_client(), &token_info.token).await
|
||||
// {
|
||||
// token_info.profile = Some(profile);
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
// });
|
||||
let logs = RequestStatsManager::load_logs().await.unwrap_or_default();
|
||||
let token_manager = TokenManager::load_tokens()
|
||||
.await
|
||||
.unwrap_or(TokenManager::new(Vec::new()));
|
||||
let mut proxies = Proxies::load_proxies().await.unwrap_or(Proxies::new());
|
||||
|
||||
// 更新全局代理池
|
||||
if let Err(e) = proxies.update_global_pool() {
|
||||
@@ -264,7 +256,7 @@ impl AppState {
|
||||
|
||||
Self {
|
||||
token_manager,
|
||||
request_manager: RequestStatsManager::new(request_logs),
|
||||
request_manager: RequestStatsManager::new(logs),
|
||||
proxies,
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
app::constant::{COMMA, COMMA_STRING},
|
||||
chat::{config::key_config, constant::Models},
|
||||
cursor::{config::key_config, constant::Models},
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
// use rkyv::{Archive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize};
|
||||
@@ -10,7 +10,7 @@ pub enum UsageCheck {
|
||||
None,
|
||||
Default,
|
||||
All,
|
||||
Custom(Vec<String>),
|
||||
Custom(Vec<&'static str>),
|
||||
}
|
||||
|
||||
impl UsageCheck {
|
||||
|
@@ -152,7 +152,7 @@ fn get_client_and_host<'a>(
|
||||
pub fn build_profile_request(client: &Client, auth_token: &str, is_pri: bool) -> RequestBuilder {
|
||||
let (client, host) = get_client_and_host(
|
||||
client,
|
||||
&cursor_api2_stripe_url(is_pri),
|
||||
cursor_api2_stripe_url(is_pri),
|
||||
is_pri,
|
||||
CURSOR_API2_HOST,
|
||||
);
|
||||
@@ -197,7 +197,7 @@ pub fn build_usage_request(
|
||||
let session_token = format!("{}%3A%3A{}", user_id, auth_token);
|
||||
|
||||
let (client, host) =
|
||||
get_client_and_host(client, &cursor_usage_api_url(is_pri), is_pri, CURSOR_HOST);
|
||||
get_client_and_host(client, cursor_usage_api_url(is_pri), is_pri, CURSOR_HOST);
|
||||
|
||||
client
|
||||
.header(HOST, host)
|
||||
@@ -242,7 +242,7 @@ pub fn build_userinfo_request(
|
||||
let session_token = format!("{}%3A%3A{}", user_id, auth_token);
|
||||
|
||||
let (client, host) =
|
||||
get_client_and_host(client, &cursor_user_api_url(is_pri), is_pri, CURSOR_HOST);
|
||||
get_client_and_host(client, cursor_user_api_url(is_pri), is_pri, CURSOR_HOST);
|
||||
|
||||
client
|
||||
.header(HOST, host)
|
||||
|
@@ -7,7 +7,7 @@ pub struct ConfigData {
|
||||
pub page_content: Option<PageContent>,
|
||||
pub vision_ability: VisionAbility,
|
||||
pub enable_slow_pool: bool,
|
||||
pub enable_all_claude: bool,
|
||||
pub enable_long_context: bool,
|
||||
pub usage_check_models: UsageCheck,
|
||||
pub enable_dynamic_key: bool,
|
||||
#[serde(skip_serializing_if = "String::is_empty")]
|
||||
@@ -23,7 +23,7 @@ pub struct ConfigUpdateRequest {
|
||||
pub content: Option<PageContent>, // "default", "text", "html"
|
||||
pub vision_ability: Option<VisionAbility>,
|
||||
pub enable_slow_pool: Option<bool>,
|
||||
pub enable_all_claude: Option<bool>,
|
||||
pub enable_long_context: Option<bool>,
|
||||
pub usage_check_models: Option<UsageCheck>,
|
||||
pub enable_dynamic_key: Option<bool>,
|
||||
pub share_token: Option<String>,
|
||||
|
@@ -9,7 +9,7 @@ pub struct HealthCheckResponse {
|
||||
pub uptime: i64,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub stats: Option<SystemStats>,
|
||||
pub models: Vec<String>,
|
||||
pub models: Vec<&'static str>,
|
||||
pub endpoints: Vec<&'static str>,
|
||||
}
|
||||
|
||||
|
@@ -18,8 +18,9 @@ impl<T> TriState<T> {
|
||||
// matches!(self, TriState::Null)
|
||||
// }
|
||||
|
||||
pub fn is_none(&self) -> bool {
|
||||
matches!(self, TriState::None)
|
||||
#[inline(always)]
|
||||
pub const fn is_none(&self) -> bool {
|
||||
matches!(*self, TriState::None)
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -17,15 +17,22 @@ use super::model::{
|
||||
use crate::{
|
||||
app::{
|
||||
constant::{COMMA, FALSE, TRUE},
|
||||
lazy::{TOKEN_DELIMITER, USE_COMMA_DELIMITER, cursor_api2_chat_models_url},
|
||||
lazy::{
|
||||
TOKEN_DELIMITER, USE_COMMA_DELIMITER, cursor_api2_chat_models_url,
|
||||
cursor_api2_token_usage_url,
|
||||
},
|
||||
model::proxy_pool::ProxyPool,
|
||||
},
|
||||
chat::{
|
||||
aiserver::v1::{AvailableModelsRequest, AvailableModelsResponse},
|
||||
cursor::{
|
||||
aiserver::v1::{
|
||||
AvailableModelsRequest, AvailableModelsResponse, GetTokenUsageRequest,
|
||||
GetTokenUsageResponse,
|
||||
},
|
||||
config::key_config,
|
||||
constant::{
|
||||
ANTHROPIC, CREATED, CURSOR, DEEPSEEK, GOOGLE, MODEL_OBJECT, OPENAI, UNKNOWN, XAI,
|
||||
},
|
||||
model::Model,
|
||||
model::{Model, Usage},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -69,6 +76,19 @@ pub trait TrimNewlines {
|
||||
fn trim_leading_newlines(self) -> Self;
|
||||
}
|
||||
|
||||
impl TrimNewlines for &str {
|
||||
#[inline(always)]
|
||||
fn trim_leading_newlines(self) -> Self {
|
||||
let bytes = self.as_bytes();
|
||||
if bytes.len() >= 2 && bytes[0] == b'\n' && bytes[1] == b'\n' {
|
||||
unsafe {
|
||||
return self.get_unchecked(2..)
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl TrimNewlines for String {
|
||||
#[inline(always)]
|
||||
fn trim_leading_newlines(mut self) -> Self {
|
||||
@@ -206,11 +226,8 @@ pub async fn get_available_models(
|
||||
available_models
|
||||
.models
|
||||
.into_iter()
|
||||
.map(|model| Model {
|
||||
id: model.name.clone(),
|
||||
created: CREATED,
|
||||
object: MODEL_OBJECT,
|
||||
owned_by: {
|
||||
.map(|model| {
|
||||
let owned_by = {
|
||||
let mut chars = model.name.chars();
|
||||
match chars.next() {
|
||||
Some('g') => match chars.next() {
|
||||
@@ -236,12 +253,62 @@ pub async fn get_available_models(
|
||||
// 其他情况
|
||||
_ => UNKNOWN,
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
Model {
|
||||
id: crate::leak::intern_string(model.name),
|
||||
created: CREATED,
|
||||
object: MODEL_OBJECT,
|
||||
owned_by,
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn get_token_usage(
|
||||
client: Client,
|
||||
auth_token: &str,
|
||||
checksum: &str,
|
||||
client_key: &str,
|
||||
timezone: &'static str,
|
||||
is_pri: bool,
|
||||
usage_uuid: String,
|
||||
) -> Option<Usage> {
|
||||
let response = {
|
||||
let trace_id = uuid::Uuid::new_v4().to_string();
|
||||
let client = super::client::build_request(super::client::AiServiceRequest {
|
||||
client,
|
||||
auth_token,
|
||||
checksum,
|
||||
client_key,
|
||||
url: cursor_api2_token_usage_url(is_pri),
|
||||
is_stream: false,
|
||||
timezone,
|
||||
trace_id: &trace_id,
|
||||
is_pri,
|
||||
});
|
||||
let request = GetTokenUsageRequest { usage_uuid };
|
||||
client
|
||||
.body(encode_message(&request, false).unwrap())
|
||||
.send()
|
||||
.await
|
||||
.ok()?
|
||||
.bytes()
|
||||
.await
|
||||
.ok()?
|
||||
};
|
||||
let token_usage = GetTokenUsageResponse::decode(response.as_ref()).ok()?;
|
||||
let prompt_tokens = token_usage.input_tokens;
|
||||
let completion_tokens = token_usage.output_tokens;
|
||||
let total_tokens = prompt_tokens + completion_tokens;
|
||||
Some(Usage {
|
||||
prompt_tokens,
|
||||
completion_tokens,
|
||||
total_tokens,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn validate_token_and_checksum(auth_token: &str) -> Option<(String, String)> {
|
||||
// 尝试使用自定义分隔符查找
|
||||
let mut delimiter_pos = auth_token.rfind(*TOKEN_DELIMITER);
|
||||
@@ -319,12 +386,11 @@ pub fn extract_token(auth_token: &str) -> Option<String> {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn format_time_ms(seconds: f64) -> f64 {
|
||||
(seconds * 1000.0).round() / 1000.0
|
||||
}
|
||||
|
||||
use crate::chat::config::key_config;
|
||||
|
||||
/// 将 JWT token 转换为 TokenInfo
|
||||
pub fn token_to_tokeninfo(
|
||||
auth_token: &str,
|
||||
@@ -408,6 +474,7 @@ pub fn tokeninfo_to_token(mut info: key_config::TokenInfo) -> Option<(String, St
|
||||
))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn encode_message(
|
||||
message: &impl prost::Message,
|
||||
with_gzip: bool,
|
||||
|
@@ -1,9 +1,9 @@
|
||||
use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD as BASE64};
|
||||
use rand::Rng as _;
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
#[inline]
|
||||
pub fn generate_hash() -> String {
|
||||
use rand::Rng as _;
|
||||
hex::encode(
|
||||
Sha256::new()
|
||||
.chain_update(rand::rng().random::<[u8; 32]>())
|
||||
|
@@ -89,7 +89,7 @@ fn parse_web_references(text: &str) -> Vec<WebReference> {
|
||||
async fn process_chat_inputs(
|
||||
inputs: Vec<Message>,
|
||||
disable_vision: bool,
|
||||
model_name: &str,
|
||||
model: &str,
|
||||
) -> (String, Vec<ConversationMessage>, Vec<String>) {
|
||||
// 收集 system 指令
|
||||
let instructions = inputs
|
||||
@@ -100,7 +100,7 @@ async fn process_chat_inputs(
|
||||
MessageContent::Vision(contents) => contents
|
||||
.iter()
|
||||
.filter_map(|content| {
|
||||
if content.content_type == "text" {
|
||||
if content.rtype == "text" {
|
||||
content.text.clone()
|
||||
} else {
|
||||
None
|
||||
@@ -113,9 +113,9 @@ async fn process_chat_inputs(
|
||||
.join("\n\n");
|
||||
|
||||
// 使用默认指令或收集到的指令
|
||||
let image_support = !disable_vision && SUPPORTED_IMAGE_MODELS.contains(&model_name);
|
||||
let image_support = !disable_vision && SUPPORTED_IMAGE_MODELS.contains(&model);
|
||||
let instructions = if instructions.is_empty() {
|
||||
get_default_instructions(model_name, image_support)
|
||||
get_default_instructions(model, image_support)
|
||||
} else {
|
||||
instructions
|
||||
};
|
||||
@@ -174,6 +174,15 @@ async fn process_chat_inputs(
|
||||
attached_human_changes: false,
|
||||
summarized_composers: vec![],
|
||||
cursor_rules: vec![],
|
||||
context_pieces: vec![],
|
||||
thinking: None,
|
||||
all_thinking_blocks: vec![],
|
||||
unified_mode: None,
|
||||
diffs_since_last_apply: vec![],
|
||||
deleted_files: vec![],
|
||||
usage_uuid: None,
|
||||
supported_tools: vec![],
|
||||
current_file_location_data: None,
|
||||
}],
|
||||
vec![],
|
||||
);
|
||||
@@ -229,7 +238,7 @@ async fn process_chat_inputs(
|
||||
let mut images = Vec::new();
|
||||
|
||||
for content in contents {
|
||||
match content.content_type.as_str() {
|
||||
match content.rtype.as_str() {
|
||||
"text" => {
|
||||
if let Some(text) = content.text {
|
||||
text_parts.push(text);
|
||||
@@ -322,6 +331,15 @@ async fn process_chat_inputs(
|
||||
attached_human_changes: false,
|
||||
summarized_composers: vec![],
|
||||
cursor_rules: vec![],
|
||||
context_pieces: vec![],
|
||||
thinking: None,
|
||||
all_thinking_blocks: vec![],
|
||||
unified_mode: None,
|
||||
diffs_since_last_apply: vec![],
|
||||
deleted_files: vec![],
|
||||
usage_uuid: None,
|
||||
supported_tools: vec![],
|
||||
current_file_location_data: None,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -473,7 +491,7 @@ async fn process_http_image(
|
||||
|
||||
pub async fn encode_chat_message(
|
||||
inputs: Vec<Message>,
|
||||
model_name: &str,
|
||||
model: &str,
|
||||
disable_vision: bool,
|
||||
enable_slow_pool: bool,
|
||||
is_search: bool,
|
||||
@@ -481,20 +499,20 @@ pub async fn encode_chat_message(
|
||||
// 在进入异步操作前获取并释放锁
|
||||
let enable_slow_pool = { if enable_slow_pool { Some(true) } else { None } };
|
||||
|
||||
let (instructions, messages, urls) =
|
||||
process_chat_inputs(inputs, disable_vision, model_name).await;
|
||||
let (instructions, messages, urls) = process_chat_inputs(inputs, disable_vision, model).await;
|
||||
|
||||
let explicit_context = if !instructions.trim().is_empty() {
|
||||
Some(ExplicitContext {
|
||||
context: instructions,
|
||||
repo_context: None,
|
||||
rules: vec![],
|
||||
mode_specific_context: None,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let base_uuid = rand::rng().random::<u16>();
|
||||
let base_uuid = rand::rng().random_range(256u16..512);
|
||||
let external_links = urls
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
@@ -515,7 +533,7 @@ pub async fn encode_chat_message(
|
||||
workspace_root_path: None,
|
||||
code_blocks: vec![],
|
||||
model_details: Some(ModelDetails {
|
||||
model_name: Some(model_name.to_string()),
|
||||
model_name: Some(model.to_string()),
|
||||
api_key: None,
|
||||
enable_ghost_mode: Some(true),
|
||||
azure_state: Some(AzureState {
|
||||
@@ -546,7 +564,9 @@ pub async fn encode_chat_message(
|
||||
workspace_id: None,
|
||||
external_links,
|
||||
commit_notes: vec![],
|
||||
long_context_mode: Some(LONG_CONTEXT_MODELS.contains(&model_name)),
|
||||
long_context_mode: Some(
|
||||
AppConfig::get_long_context() || LONG_CONTEXT_MODELS.contains(&model),
|
||||
),
|
||||
is_eval: Some(false),
|
||||
desired_max_tokens: None,
|
||||
context_ast: None,
|
@@ -6,24 +6,30 @@ impl ErrorDetails {
|
||||
pub fn status_code(&self) -> u16 {
|
||||
match Error::try_from(self.error) {
|
||||
Ok(error) => match error {
|
||||
Error::Unspecified => 500,
|
||||
// 认证/授权相关错误
|
||||
Error::BadApiKey
|
||||
| Error::BadUserApiKey
|
||||
| Error::InvalidAuthId
|
||||
| Error::AuthTokenNotFound
|
||||
| Error::AuthTokenExpired
|
||||
| Error::Unauthorized => 401,
|
||||
|
||||
// 权限不足
|
||||
Error::NotLoggedIn
|
||||
| Error::NotHighEnoughPermissions
|
||||
| Error::AgentRequiresLogin
|
||||
| Error::ProUserOnly
|
||||
| Error::TaskNoPermissions => 403,
|
||||
|
||||
// 资源未找到
|
||||
Error::NotFound
|
||||
| Error::UserNotFound
|
||||
| Error::TaskUuidNotFound
|
||||
| Error::AgentEngineNotFound
|
||||
| Error::GitgraphNotFound
|
||||
| Error::FileNotFound => 404,
|
||||
|
||||
// 请求过多/速率限制
|
||||
Error::FreeUserRateLimitExceeded
|
||||
| Error::ProUserRateLimitExceeded
|
||||
| Error::OpenaiRateLimitExceeded
|
||||
@@ -31,25 +37,44 @@ impl ErrorDetails {
|
||||
| Error::GenericRateLimitExceeded
|
||||
| Error::Gpt4VisionPreviewRateLimit
|
||||
| Error::ApiKeyRateLimit => 429,
|
||||
|
||||
// 客户端请求错误
|
||||
Error::BadRequest
|
||||
| Error::BadModelName
|
||||
| Error::SlashEditFileTooLong
|
||||
| Error::FileUnsupported
|
||||
| Error::ClaudeImageTooLarge
|
||||
| Error::ConversationTooLong => 400,
|
||||
|
||||
// 超时
|
||||
Error::Timeout => 504,
|
||||
Error::Deprecated
|
||||
| Error::FreeUserUsageLimit
|
||||
|
||||
// 版本/弃用相关
|
||||
Error::Deprecated | Error::OutdatedClient => 410,
|
||||
|
||||
// 资源耗尽/配额限制
|
||||
Error::FreeUserUsageLimit
|
||||
| Error::ProUserUsageLimit
|
||||
| Error::ResourceExhausted
|
||||
| Error::Openai
|
||||
| Error::MaxTokens
|
||||
| Error::ApiKeyNotSupported
|
||||
| Error::UserAbortedRequest
|
||||
| Error::CustomMessage
|
||||
| Error::OutdatedClient
|
||||
| Error::Debounced
|
||||
| Error::RepositoryServiceRepositoryIsNotInitialized => 500,
|
||||
| Error::MaxTokens => 503,
|
||||
|
||||
// OpenAI相关错误
|
||||
Error::Openai | Error::ApiKeyNotSupported => 500,
|
||||
|
||||
// 客户端主动取消
|
||||
Error::UserAbortedRequest => 500,
|
||||
|
||||
// 自定义消息
|
||||
Error::CustomMessage => 500,
|
||||
|
||||
// 价格相关
|
||||
Error::UsagePricingRequired | Error::UsagePricingRequiredChangeable => 402,
|
||||
|
||||
// 代码/仓库相关
|
||||
Error::RepositoryServiceRepositoryIsNotInitialized => 500,
|
||||
|
||||
// 其他/未分类的服务器内部错误
|
||||
Error::Unspecified | Error::Debounced => 500,
|
||||
},
|
||||
Err(_) => 500,
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -7,12 +7,6 @@ use std::{
|
||||
use super::model::Model;
|
||||
|
||||
macro_rules! def_pub_const {
|
||||
// 单个常量定义分支
|
||||
// ($name:ident, $value:expr) => {
|
||||
// pub const $name: &'static str = $value;
|
||||
// };
|
||||
|
||||
// 批量定义分支
|
||||
($($name:ident => $value:expr),+ $(,)?) => {
|
||||
$(
|
||||
pub const $name: &'static str = $value;
|
||||
@@ -52,6 +46,8 @@ def_pub_const!(
|
||||
CLAUDE_3_5_HAIKU => "claude-3.5-haiku",
|
||||
CLAUDE_3_7_SONNET => "claude-3.7-sonnet",
|
||||
CLAUDE_3_7_SONNET_THINKING => "claude-3.7-sonnet-thinking",
|
||||
CLAUDE_3_7_SONNET_MAX => "claude-3.7-sonnet-max",
|
||||
CLAUDE_3_7_SONNET_THINKING_MAX => "claude-3.7-sonnet-thinking-max",
|
||||
|
||||
// OpenAI 模型
|
||||
GPT_4 => "gpt-4",
|
||||
@@ -95,7 +91,7 @@ macro_rules! create_models {
|
||||
models: Arc::new(vec![
|
||||
$(
|
||||
Model {
|
||||
id: $model.into(),
|
||||
id: $model,
|
||||
created: CREATED,
|
||||
object: MODEL_OBJECT,
|
||||
owned_by: $owner,
|
||||
@@ -130,22 +126,22 @@ impl Models {
|
||||
// }
|
||||
|
||||
// 检查模型是否存在
|
||||
pub fn exists(model_id: &str) -> bool {
|
||||
Self::read().models.iter().any(|m| m.id == model_id)
|
||||
}
|
||||
// pub fn exists(model_id: &str) -> bool {
|
||||
// Self::read().models.iter().any(|m| m.id == model_id)
|
||||
// }
|
||||
|
||||
// 查找模型并返回其 ID
|
||||
pub fn find_id(model: &str) -> Option<String> {
|
||||
pub fn find_id(model: &str) -> Option<&'static str> {
|
||||
Self::read()
|
||||
.models
|
||||
.iter()
|
||||
.find(|m| m.id == model)
|
||||
.map(|m| m.id.clone())
|
||||
.map(|m| m.id)
|
||||
}
|
||||
|
||||
// 返回所有模型 ID 的列表
|
||||
pub fn ids() -> Vec<String> {
|
||||
Self::read().models.iter().map(|m| m.id.clone()).collect()
|
||||
pub fn ids() -> Vec<&'static str> {
|
||||
Self::read().models.iter().map(|m| m.id).collect()
|
||||
}
|
||||
|
||||
// 写入方法
|
||||
@@ -180,9 +176,12 @@ impl Models {
|
||||
// }
|
||||
|
||||
create_models!(
|
||||
DEFAULT => UNKNOWN,
|
||||
CLAUDE_3_5_SONNET => ANTHROPIC,
|
||||
CLAUDE_3_7_SONNET => ANTHROPIC,
|
||||
CLAUDE_3_7_SONNET_THINKING => ANTHROPIC,
|
||||
CLAUDE_3_7_SONNET_MAX => ANTHROPIC,
|
||||
CLAUDE_3_7_SONNET_THINKING_MAX => ANTHROPIC,
|
||||
GPT_4 => OPENAI,
|
||||
GPT_4O => OPENAI,
|
||||
GPT_4_5_PREVIEW => OPENAI,
|
||||
@@ -207,7 +206,6 @@ create_models!(
|
||||
DEEPSEEK_R1 => DEEPSEEK,
|
||||
O3_MINI => OPENAI,
|
||||
GROK_2 => XAI,
|
||||
DEFAULT => UNKNOWN,
|
||||
);
|
||||
|
||||
pub const USAGE_CHECK_MODELS: [&str; 13] = [
|
@@ -1,4 +1,4 @@
|
||||
use super::aiserver::v1::ErrorDetails;
|
||||
use super::{aiserver::v1::ErrorDetails, constant::UNKNOWN};
|
||||
use crate::common::model::{ApiStatus, ErrorResponse as CommonErrorResponse};
|
||||
use base64::{Engine as _, engine::general_purpose::STANDARD_NO_PAD};
|
||||
use prost::Message as _;
|
||||
@@ -46,7 +46,7 @@ impl ChatError {
|
||||
if self.error.details.is_empty() {
|
||||
return ErrorResponse {
|
||||
status: 500,
|
||||
code: "unknown".to_string(),
|
||||
code: UNKNOWN.to_string(),
|
||||
error: None,
|
||||
};
|
||||
}
|
||||
@@ -98,7 +98,7 @@ impl ErrorResponse {
|
||||
// }
|
||||
|
||||
pub fn status_code(&self) -> StatusCode {
|
||||
StatusCode::from_u16(self.status).unwrap()
|
||||
StatusCode::from_u16(self.status).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
}
|
||||
|
||||
pub fn native_code(&self) -> String {
|
||||
@@ -108,16 +108,25 @@ impl ErrorResponse {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn into_common(self) -> CommonErrorResponse {
|
||||
pub fn details(&self) -> Option<String> {
|
||||
self.error.as_ref().map(
|
||||
|error| error.details.clone(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn into_common(mut self) -> CommonErrorResponse {
|
||||
CommonErrorResponse {
|
||||
status: ApiStatus::Error,
|
||||
code: Some(self.status),
|
||||
error: self
|
||||
.error
|
||||
.as_ref()
|
||||
.map(|error| error.message.clone())
|
||||
.or(Some(self.code.clone())),
|
||||
message: self.error.as_ref().map(|error| error.details.clone()),
|
||||
.as_mut()
|
||||
.map(|error| std::mem::take(&mut error.message))
|
||||
.or(Some(self.code)),
|
||||
message: self
|
||||
.error
|
||||
.as_mut()
|
||||
.map(|error| std::mem::take(&mut error.details)),
|
||||
}
|
||||
}
|
||||
}
|
@@ -12,7 +12,7 @@ pub enum MessageContent {
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct VisionMessageContent {
|
||||
#[serde(rename = "type")]
|
||||
pub content_type: String,
|
||||
pub rtype: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub text: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
@@ -32,10 +32,11 @@ pub struct Message {
|
||||
pub content: MessageContent,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq)]
|
||||
#[derive(Serialize, Deserialize, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize, Clone, Copy, PartialEq)]
|
||||
#[repr(u8)]
|
||||
pub enum Role {
|
||||
#[serde(rename = "system", alias = "developer")]
|
||||
System,
|
||||
System = 0u8,
|
||||
#[serde(rename = "user", alias = "human")]
|
||||
User,
|
||||
#[serde(rename = "assistant", alias = "ai")]
|
||||
@@ -48,7 +49,7 @@ pub struct ChatResponse {
|
||||
pub object: &'static str,
|
||||
pub created: i64,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub model: Option<String>,
|
||||
pub model: Option<&'static str>,
|
||||
pub choices: Vec<Choice>,
|
||||
#[serde(skip_serializing_if = "TriState::is_none")]
|
||||
pub usage: TriState<Usage>,
|
||||
@@ -75,9 +76,15 @@ pub struct Delta {
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct Usage {
|
||||
pub prompt_tokens: u32,
|
||||
pub completion_tokens: u32,
|
||||
pub total_tokens: u32,
|
||||
pub prompt_tokens: i32,
|
||||
pub completion_tokens: i32,
|
||||
pub total_tokens: i32,
|
||||
}
|
||||
|
||||
impl Default for Usage {
|
||||
fn default() -> Self {
|
||||
Self { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
// 聊天请求
|
||||
@@ -97,9 +104,9 @@ pub struct StreamOptions {
|
||||
}
|
||||
|
||||
// 模型定义
|
||||
#[derive(Serialize, Clone)]
|
||||
#[derive(Serialize)]
|
||||
pub struct Model {
|
||||
pub id: String,
|
||||
pub id: &'static str,
|
||||
pub created: &'static i64,
|
||||
pub object: &'static str,
|
||||
pub owned_by: &'static str,
|
||||
@@ -112,15 +119,18 @@ impl PartialEq for Model {
|
||||
}
|
||||
|
||||
use super::constant::{Models, USAGE_CHECK_MODELS};
|
||||
use crate::{app::model::{AppConfig, UsageCheck}, common::model::tri::TriState};
|
||||
use crate::{
|
||||
app::model::{AppConfig, UsageCheck},
|
||||
common::model::tri::TriState,
|
||||
};
|
||||
|
||||
impl Model {
|
||||
pub fn is_usage_check(model_id: &String, usage_check: Option<UsageCheck>) -> bool {
|
||||
pub fn is_usage_check(model_id: &str, usage_check: Option<UsageCheck>) -> bool {
|
||||
match usage_check.unwrap_or(AppConfig::get_usage_check()) {
|
||||
UsageCheck::None => false,
|
||||
UsageCheck::Default => USAGE_CHECK_MODELS.contains(&model_id.as_str()),
|
||||
UsageCheck::Default => USAGE_CHECK_MODELS.contains(&model_id),
|
||||
UsageCheck::All => true,
|
||||
UsageCheck::Custom(models) => models.contains(model_id),
|
||||
UsageCheck::Custom(models) => models.contains(&model_id),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -132,6 +142,7 @@ pub struct ModelsResponse {
|
||||
}
|
||||
|
||||
impl ModelsResponse {
|
||||
#[inline]
|
||||
pub(super) fn new(data: Arc<Vec<Model>>) -> Self {
|
||||
Self {
|
||||
object: "list",
|
||||
@@ -139,6 +150,7 @@ impl ModelsResponse {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(super) fn with_default_models() -> Self {
|
||||
Self::new(Models::to_arc())
|
||||
}
|
@@ -6,8 +6,9 @@ mod token;
|
||||
pub use token::{handle_basic_calibration, handle_build_key};
|
||||
mod tokens;
|
||||
pub use tokens::{
|
||||
handle_add_tokens, handle_delete_tokens, handle_get_tokens, handle_update_token_tags,
|
||||
handle_update_tokens, handle_update_tokens_profile,
|
||||
handle_add_tokens, handle_delete_tokens, handle_get_token_tags, handle_get_tokens,
|
||||
handle_get_tokens_by_tag, handle_update_token_tags, handle_update_tokens,
|
||||
handle_update_tokens_profile,
|
||||
};
|
||||
mod checksum;
|
||||
pub use checksum::{handle_get_checksum, handle_get_hash, handle_get_timestamp_header};
|
@@ -8,14 +8,15 @@ use crate::{
|
||||
ROUTE_HEALTH_PATH, ROUTE_LOGS_PATH, ROUTE_PROXIES_ADD_PATH, ROUTE_PROXIES_DELETE_PATH,
|
||||
ROUTE_PROXIES_GET_PATH, ROUTE_PROXIES_PATH, ROUTE_PROXIES_SET_GENERAL_PATH,
|
||||
ROUTE_PROXIES_UPDATE_PATH, ROUTE_README_PATH, ROUTE_ROOT_PATH, ROUTE_STATIC_PATH,
|
||||
ROUTE_TOKENS_ADD_PATH, ROUTE_TOKENS_DELETE_PATH, ROUTE_TOKENS_GET_PATH,
|
||||
ROUTE_TOKENS_PATH, ROUTE_TOKENS_PROFILE_UPDATE_PATH, ROUTE_TOKENS_TAGS_UPDATE_PATH,
|
||||
ROUTE_TOKENS_UPDATE_PATH, ROUTE_USER_INFO_PATH,
|
||||
ROUTE_TOKENS_ADD_PATH, ROUTE_TOKENS_BY_TAG_GET_PATH, ROUTE_TOKENS_DELETE_PATH,
|
||||
ROUTE_TOKENS_GET_PATH, ROUTE_TOKENS_PATH, ROUTE_TOKENS_PROFILE_UPDATE_PATH,
|
||||
ROUTE_TOKENS_TAGS_GET_PATH, ROUTE_TOKENS_TAGS_UPDATE_PATH, ROUTE_TOKENS_UPDATE_PATH,
|
||||
ROUTE_USER_INFO_PATH,
|
||||
},
|
||||
lazy::{AUTH_TOKEN, ROUTE_CHAT_PATH, ROUTE_MODELS_PATH, get_start_time},
|
||||
model::{AppConfig, AppState, PageContent},
|
||||
},
|
||||
chat::constant::Models,
|
||||
cursor::constant::Models,
|
||||
common::model::{
|
||||
ApiStatus,
|
||||
health::{CpuInfo, HealthCheckResponse, MemoryInfo, SystemInfo, SystemStats},
|
||||
@@ -123,7 +124,9 @@ pub async fn handle_health(
|
||||
ROUTE_TOKENS_UPDATE_PATH,
|
||||
ROUTE_TOKENS_ADD_PATH,
|
||||
ROUTE_TOKENS_DELETE_PATH,
|
||||
ROUTE_TOKENS_TAGS_GET_PATH,
|
||||
ROUTE_TOKENS_TAGS_UPDATE_PATH,
|
||||
ROUTE_TOKENS_BY_TAG_GET_PATH,
|
||||
ROUTE_TOKENS_PROFILE_UPDATE_PATH,
|
||||
ROUTE_PROXIES_PATH,
|
||||
ROUTE_PROXIES_GET_PATH,
|
@@ -1,5 +1,5 @@
|
||||
use crate::{
|
||||
app::model::proxy_pool::ProxyPool, chat::constant::ERR_NODATA, common::{
|
||||
app::model::proxy_pool::ProxyPool, cursor::constant::ERR_NODATA, common::{
|
||||
model::userinfo::GetUserInfo,
|
||||
utils::{extract_token, get_token_profile},
|
||||
}
|
@@ -4,7 +4,7 @@ use crate::{
|
||||
lazy::{AUTH_TOKEN, KEY_PREFIX},
|
||||
model::{AppConfig, BuildKeyRequest, BuildKeyResponse, UsageCheckModelType},
|
||||
},
|
||||
chat::config::{KeyConfig, key_config},
|
||||
cursor::config::{KeyConfig, key_config},
|
||||
common::{
|
||||
model::ApiStatus,
|
||||
utils::{
|
@@ -4,9 +4,10 @@ use crate::{
|
||||
TokenTagsUpdateRequest, TokenUpdateRequest, TokensDeleteRequest, TokensDeleteResponse,
|
||||
},
|
||||
common::{
|
||||
model::{ApiStatus, ErrorResponse},
|
||||
model::{ApiStatus, ErrorResponse, NormalResponse},
|
||||
utils::{
|
||||
generate_checksum_with_default, generate_checksum_with_repair, generate_hash, parse_token, validate_token
|
||||
generate_checksum_with_default, generate_checksum_with_repair, generate_hash,
|
||||
parse_token, validate_token,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -348,13 +349,57 @@ pub async fn handle_update_tokens_profile(
|
||||
));
|
||||
}
|
||||
|
||||
let message = format!(
|
||||
"已更新{}个令牌配置, {}个令牌更新失败",
|
||||
updated_count, failed_count
|
||||
);
|
||||
let message = format!("已更新{updated_count}个令牌配置, {failed_count}个令牌更新失败");
|
||||
|
||||
Ok(Json(CommonResponse {
|
||||
status: ApiStatus::Success,
|
||||
message: Some(message),
|
||||
}))
|
||||
}
|
||||
|
||||
pub async fn handle_get_token_tags(
|
||||
State(state): State<Arc<Mutex<AppState>>>,
|
||||
) -> Result<Json<NormalResponse<Vec<String>>>, StatusCode> {
|
||||
let state = state.lock().await;
|
||||
let tags: Vec<_> = state.token_manager.tags.iter().cloned().collect();
|
||||
let len = tags.len();
|
||||
|
||||
Ok(Json(NormalResponse {
|
||||
status: ApiStatus::Success,
|
||||
data: Some(tags),
|
||||
message: Some(format!("获取到{len}个标签")),
|
||||
}))
|
||||
}
|
||||
|
||||
pub async fn handle_get_tokens_by_tag(
|
||||
State(state): State<Arc<Mutex<AppState>>>,
|
||||
Json(tag): Json<String>,
|
||||
) -> Result<Json<TokenInfoResponse>, (StatusCode, Json<ErrorResponse>)> {
|
||||
let state = state.lock().await;
|
||||
|
||||
match state.token_manager.get_tokens_by_tag(&tag) {
|
||||
Ok(tokens) => {
|
||||
let tokens_vec = tokens
|
||||
.iter()
|
||||
.map(|&t| t.clone())
|
||||
.collect::<Vec<TokenInfo>>();
|
||||
let tokens_count = tokens_vec.len();
|
||||
|
||||
Ok(Json(TokenInfoResponse {
|
||||
status: ApiStatus::Success,
|
||||
tokens: Some(tokens_vec),
|
||||
tokens_count,
|
||||
message: Some(format!("获取到{tokens_count}个标签为{tag}的令牌")),
|
||||
}))
|
||||
}
|
||||
Err(e) => Err((
|
||||
StatusCode::BAD_REQUEST,
|
||||
Json(ErrorResponse {
|
||||
status: ApiStatus::Error,
|
||||
code: None,
|
||||
error: Some(e.to_string()),
|
||||
message: Some(format!("标签\"{tag}\"不存在")),
|
||||
}),
|
||||
)),
|
||||
}
|
||||
}
|
@@ -10,19 +10,10 @@ use crate::{
|
||||
cursor_api2_chat_web_url,
|
||||
},
|
||||
model::{
|
||||
AppConfig, AppState, Chain, LogStatus, RequestLog, TimingInfo, TokenInfo, UsageCheck,
|
||||
proxy_pool::ProxyPool,
|
||||
AppConfig, AppState, Chain, ErrorInfo, LogStatus, OptionUsage, Prompt, RequestLog,
|
||||
TimingInfo, TokenInfo, UsageCheck, proxy_pool::ProxyPool,
|
||||
},
|
||||
},
|
||||
chat::{
|
||||
config::KeyConfig,
|
||||
constant::{Models, USAGE_CHECK_MODELS},
|
||||
error::StreamError,
|
||||
model::{
|
||||
ChatResponse, Choice, Delta, Message, MessageContent, ModelsResponse, Role, Usage,
|
||||
},
|
||||
stream::{StreamDecoder, StreamMessage},
|
||||
},
|
||||
common::{
|
||||
client::{AiServiceRequest, build_request},
|
||||
model::{
|
||||
@@ -30,9 +21,19 @@ use crate::{
|
||||
},
|
||||
utils::{
|
||||
TrimNewlines as _, format_time_ms, from_base64, generate_hash, get_available_models,
|
||||
get_token_profile, tokeninfo_to_token, validate_token_and_checksum,
|
||||
get_token_profile, get_token_usage, tokeninfo_to_token, validate_token_and_checksum,
|
||||
},
|
||||
},
|
||||
cursor::{
|
||||
config::KeyConfig,
|
||||
constant::{Models, USAGE_CHECK_MODELS},
|
||||
error::StreamError,
|
||||
model::{
|
||||
ChatResponse, Choice, Delta, Message, MessageContent, ModelsResponse, Role, Usage,
|
||||
},
|
||||
stream::{StreamDecoder, StreamMessage},
|
||||
},
|
||||
leak::intern_string,
|
||||
};
|
||||
use axum::{
|
||||
Json,
|
||||
@@ -116,7 +117,7 @@ pub async fn handle_models(
|
||||
from_base64(&token[*KEY_PREFIX_LEN..])
|
||||
.and_then(|decoded_bytes| KeyConfig::decode(&decoded_bytes[..]).ok())
|
||||
.and_then(|key_config| key_config.auth_token)
|
||||
.and_then(|info| tokeninfo_to_token(info))
|
||||
.and_then(tokeninfo_to_token)
|
||||
.map(|(token, checksum, client)| {
|
||||
(token, checksum, None, client, GENERAL_TIMEZONE.name())
|
||||
})
|
||||
@@ -178,25 +179,25 @@ pub async fn handle_chat(
|
||||
headers: HeaderMap,
|
||||
Json(request): Json<ChatRequest>,
|
||||
) -> Result<Response<Body>, (StatusCode, Json<ErrorResponse>)> {
|
||||
let allow_claude = AppConfig::get_allow_claude();
|
||||
|
||||
let is_search = request.model.ends_with("-online");
|
||||
let model_name = if is_search {
|
||||
request.model[..request.model.len() - 7].to_string()
|
||||
} else {
|
||||
request.model.clone()
|
||||
};
|
||||
|
||||
// 验证模型是否支持并获取模型信息
|
||||
let model =
|
||||
if Models::exists(&model_name) || (allow_claude && request.model.starts_with("claude-")) {
|
||||
Some(&model_name)
|
||||
let model = {
|
||||
let model_name = if is_search {
|
||||
&request.model[..request.model.len() - 7]
|
||||
} else {
|
||||
&request.model
|
||||
};
|
||||
|
||||
if let Some(model) = Models::find_id(model_name) {
|
||||
model
|
||||
} else {
|
||||
return Err((
|
||||
StatusCode::BAD_REQUEST,
|
||||
Json(ChatError::ModelNotSupported(request.model).to_json()),
|
||||
));
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
let request_time = chrono::Local::now();
|
||||
|
||||
@@ -304,7 +305,7 @@ pub async fn handle_chat(
|
||||
if log.token_info.token == auth_token {
|
||||
if let Some(profile) = &log.token_info.profile {
|
||||
if profile.stripe.membership_type == MembershipType::Free {
|
||||
let is_premium = USAGE_CHECK_MODELS.contains(&model_name.as_str());
|
||||
let is_premium = USAGE_CHECK_MODELS.contains(&model);
|
||||
need_profile_check = if is_premium {
|
||||
profile
|
||||
.usage
|
||||
@@ -342,15 +343,10 @@ pub async fn handle_chat(
|
||||
current_id = next_id;
|
||||
|
||||
// 如果需要获取用户使用情况,创建后台任务获取profile
|
||||
if model
|
||||
.map(|m| {
|
||||
Model::is_usage_check(
|
||||
m,
|
||||
UsageCheck::from_proto(current_config.usage_check_models.as_ref()),
|
||||
)
|
||||
})
|
||||
.unwrap_or(false)
|
||||
{
|
||||
if Model::is_usage_check(
|
||||
model,
|
||||
UsageCheck::from_proto(current_config.usage_check_models.as_ref()),
|
||||
) {
|
||||
let auth_token_clone = auth_token.clone();
|
||||
let state_clone = state_clone.clone();
|
||||
let log_id = next_id;
|
||||
@@ -393,7 +389,7 @@ pub async fn handle_chat(
|
||||
state.request_manager.request_logs.push(RequestLog {
|
||||
id: next_id,
|
||||
timestamp: request_time,
|
||||
model: request.model.clone(),
|
||||
model: intern_string(request.model),
|
||||
token_info: TokenInfo {
|
||||
token: auth_token.clone(),
|
||||
checksum: checksum.clone(),
|
||||
@@ -405,7 +401,7 @@ pub async fn handle_chat(
|
||||
timing: TimingInfo { total: 0.0 },
|
||||
stream: request.stream,
|
||||
status: LogStatus::Pending,
|
||||
error: None,
|
||||
error: ErrorInfo::None,
|
||||
});
|
||||
|
||||
if !*IS_UNLIMITED_REQUEST_LOGS
|
||||
@@ -420,7 +416,7 @@ pub async fn handle_chat(
|
||||
// 将消息转换为hex格式
|
||||
let hex_data = match super::adapter::encode_chat_message(
|
||||
request.messages,
|
||||
&model_name,
|
||||
model,
|
||||
current_config.disable_vision(),
|
||||
current_config.enable_slow_pool(),
|
||||
is_search,
|
||||
@@ -438,7 +434,7 @@ pub async fn handle_chat(
|
||||
.find(|log| log.id == current_id)
|
||||
{
|
||||
log.status = LogStatus::Failure;
|
||||
log.error = Some(e.to_string());
|
||||
log.error = ErrorInfo::Error(intern_string(e.to_string()));
|
||||
}
|
||||
state.request_manager.active_requests -= 1;
|
||||
state.request_manager.error_requests += 1;
|
||||
@@ -453,8 +449,8 @@ pub async fn handle_chat(
|
||||
|
||||
// 构建请求客户端
|
||||
let trace_id = uuid::Uuid::new_v4();
|
||||
let client = build_request(AiServiceRequest {
|
||||
client,
|
||||
let req = build_request(AiServiceRequest {
|
||||
client: client.clone(),
|
||||
auth_token: auth_token.as_str(),
|
||||
checksum: checksum.as_str(),
|
||||
client_key: client_key.as_str(),
|
||||
@@ -470,7 +466,7 @@ pub async fn handle_chat(
|
||||
});
|
||||
let trace_id = trace_id.simple();
|
||||
// 发送请求
|
||||
let response = client.body(hex_data).send().await;
|
||||
let response = req.body(hex_data).send().await;
|
||||
|
||||
// 处理请求结果
|
||||
let response = match response {
|
||||
@@ -503,7 +499,7 @@ pub async fn handle_chat(
|
||||
.find(|log| log.id == current_id)
|
||||
{
|
||||
log.status = LogStatus::Failure;
|
||||
log.error = Some(e.to_string());
|
||||
log.error = ErrorInfo::Error(intern_string(e.to_string()));
|
||||
}
|
||||
state.request_manager.active_requests -= 1;
|
||||
state.request_manager.error_requests += 1;
|
||||
@@ -536,16 +532,56 @@ pub async fn handle_chat(
|
||||
let is_start = Arc::new(AtomicBool::new(true));
|
||||
let start_time = std::time::Instant::now();
|
||||
let decoder = Arc::new(Mutex::new(StreamDecoder::new()));
|
||||
let is_usage_sent = Arc::new(AtomicBool::new(false));
|
||||
let need_usage = if request.stream_options.is_some_and(|opt| opt.include_usage) {
|
||||
Arc::new(Mutex::new(NeedUsage::Need {
|
||||
client,
|
||||
auth_token,
|
||||
checksum,
|
||||
client_key,
|
||||
timezone,
|
||||
is_pri,
|
||||
}))
|
||||
} else {
|
||||
Arc::new(Mutex::new(NeedUsage::None))
|
||||
};
|
||||
|
||||
// 定义消息处理器的上下文结构体
|
||||
struct MessageProcessContext<'a> {
|
||||
response_id: &'a str,
|
||||
model: &'a str,
|
||||
model: &'static str,
|
||||
is_start: &'a AtomicBool,
|
||||
start_time: std::time::Instant,
|
||||
state: &'a Mutex<AppState>,
|
||||
current_id: u64,
|
||||
need_usage: bool,
|
||||
need_usage: &'a Mutex<NeedUsage>,
|
||||
is_usage_sent: &'a AtomicBool,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
enum NeedUsage {
|
||||
#[default]
|
||||
None,
|
||||
Need {
|
||||
client: reqwest::Client,
|
||||
auth_token: String,
|
||||
checksum: String,
|
||||
client_key: String,
|
||||
timezone: &'static str,
|
||||
is_pri: bool,
|
||||
},
|
||||
}
|
||||
|
||||
impl NeedUsage {
|
||||
#[inline(always)]
|
||||
const fn is_need(&self) -> bool {
|
||||
matches!(*self, Self::Need { .. })
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn take(&mut self) -> Self {
|
||||
std::mem::take(self)
|
||||
}
|
||||
}
|
||||
|
||||
// 处理消息并生成响应数据的辅助函数
|
||||
@@ -558,17 +594,13 @@ pub async fn handle_chat(
|
||||
for message in messages {
|
||||
match message {
|
||||
StreamMessage::Content(text) => {
|
||||
let is_first = ctx.is_start.load(Ordering::SeqCst);
|
||||
let is_first = ctx.is_start.load(Ordering::Acquire);
|
||||
|
||||
let response = ChatResponse {
|
||||
id: ctx.response_id.to_string(),
|
||||
object: OBJECT_CHAT_COMPLETION_CHUNK,
|
||||
created: chrono::Utc::now().timestamp(),
|
||||
model: if is_first {
|
||||
Some(ctx.model.to_string())
|
||||
} else {
|
||||
None
|
||||
},
|
||||
model: if is_first { Some(ctx.model) } else { None },
|
||||
choices: vec![Choice {
|
||||
index: 0,
|
||||
message: None,
|
||||
@@ -579,7 +611,7 @@ pub async fn handle_chat(
|
||||
None
|
||||
},
|
||||
content: if is_first {
|
||||
ctx.is_start.store(false, Ordering::SeqCst);
|
||||
ctx.is_start.store(false, Ordering::Release);
|
||||
Some(text.trim_leading_newlines())
|
||||
} else {
|
||||
Some(text)
|
||||
@@ -588,7 +620,7 @@ pub async fn handle_chat(
|
||||
logprobs: None,
|
||||
finish_reason: None,
|
||||
}],
|
||||
usage: if ctx.need_usage {
|
||||
usage: if ctx.need_usage.lock().await.is_need() {
|
||||
TriState::Null
|
||||
} else {
|
||||
TriState::None
|
||||
@@ -600,6 +632,62 @@ pub async fn handle_chat(
|
||||
serde_json::to_string(&response).unwrap()
|
||||
));
|
||||
}
|
||||
StreamMessage::Usage(usage_uuid) => {
|
||||
if !ctx.is_usage_sent.load(Ordering::Acquire) {
|
||||
if let NeedUsage::Need {
|
||||
client,
|
||||
auth_token,
|
||||
checksum,
|
||||
client_key,
|
||||
timezone,
|
||||
is_pri,
|
||||
} = ctx.need_usage.lock().await.take()
|
||||
{
|
||||
let usage = get_token_usage(
|
||||
client,
|
||||
&auth_token,
|
||||
&checksum,
|
||||
&client_key,
|
||||
timezone,
|
||||
is_pri,
|
||||
usage_uuid,
|
||||
)
|
||||
.await;
|
||||
if let Some(ref usage) = usage {
|
||||
let mut state = ctx.state.lock().await;
|
||||
if let Some(log) = state
|
||||
.request_manager
|
||||
.request_logs
|
||||
.iter_mut()
|
||||
.rev()
|
||||
.find(|log| log.id == ctx.current_id)
|
||||
{
|
||||
if let Some(chain) = &mut log.chain {
|
||||
chain.usage = OptionUsage::Uasge {
|
||||
input: usage.prompt_tokens,
|
||||
output: usage.completion_tokens,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let response = ChatResponse {
|
||||
id: ctx.response_id.to_string(),
|
||||
object: OBJECT_CHAT_COMPLETION_CHUNK,
|
||||
created: chrono::Utc::now().timestamp(),
|
||||
model: None,
|
||||
choices: vec![],
|
||||
usage: TriState::Some(usage.unwrap_or_default()),
|
||||
};
|
||||
response_data.push_str(&format!(
|
||||
"data: {}\n\n",
|
||||
serde_json::to_string(&response).unwrap()
|
||||
));
|
||||
ctx.is_usage_sent.store(true, Ordering::Release);
|
||||
}
|
||||
} else {
|
||||
crate::debug_println!("usage is sent, but find {usage_uuid}");
|
||||
}
|
||||
}
|
||||
StreamMessage::StreamEnd => {
|
||||
// 计算总时间和首次片段时间
|
||||
let total_time = ctx.start_time.elapsed().as_secs_f64();
|
||||
@@ -632,7 +720,7 @@ pub async fn handle_chat(
|
||||
logprobs: None,
|
||||
finish_reason: Some(FINISH_REASON_STOP.to_string()),
|
||||
}],
|
||||
usage: if ctx.need_usage {
|
||||
usage: if ctx.need_usage.lock().await.is_need() {
|
||||
TriState::Null
|
||||
} else {
|
||||
TriState::None
|
||||
@@ -642,7 +730,9 @@ pub async fn handle_chat(
|
||||
"data: {}\n\n",
|
||||
serde_json::to_string(&response).unwrap()
|
||||
));
|
||||
if ctx.need_usage {
|
||||
if !ctx.is_usage_sent.load(Ordering::Acquire)
|
||||
&& ctx.need_usage.lock().await.is_need()
|
||||
{
|
||||
let response = ChatResponse {
|
||||
id: ctx.response_id.to_string(),
|
||||
object: OBJECT_CHAT_COMPLETION_CHUNK,
|
||||
@@ -659,6 +749,7 @@ pub async fn handle_chat(
|
||||
"data: {}\n\n",
|
||||
serde_json::to_string(&response).unwrap()
|
||||
));
|
||||
ctx.is_usage_sent.store(true, Ordering::Release);
|
||||
};
|
||||
}
|
||||
StreamMessage::Debug(debug_prompt) => {
|
||||
@@ -670,12 +761,14 @@ pub async fn handle_chat(
|
||||
.rev()
|
||||
.find(|log| log.id == ctx.current_id)
|
||||
{
|
||||
if let Some(chain) = &mut log.chain {
|
||||
chain.prompt.push_str(&debug_prompt);
|
||||
if log.chain.is_some() {
|
||||
crate::debug_println!("UB!1 {debug_prompt:?}");
|
||||
// chain.prompt.push_str(&debug_prompt);
|
||||
} else {
|
||||
log.chain = Some(Chain {
|
||||
prompt: debug_prompt,
|
||||
prompt: Prompt::new(debug_prompt),
|
||||
delays: vec![],
|
||||
usage: OptionUsage::None,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -708,7 +801,11 @@ pub async fn handle_chat(
|
||||
.find(|log| log.id == current_id)
|
||||
{
|
||||
log.status = LogStatus::Failure;
|
||||
log.error = Some(error_response.native_code());
|
||||
log.error =
|
||||
ErrorInfo::Error(intern_string(error_response.native_code()));
|
||||
if let Some(detail) = error_response.details() {
|
||||
log.error.add_detail(&detail)
|
||||
}
|
||||
log.timing.total =
|
||||
format_time_ms(start_time.elapsed().as_secs_f64());
|
||||
state.request_manager.error_requests += 1;
|
||||
@@ -741,7 +838,7 @@ pub async fn handle_chat(
|
||||
.find(|log| log.id == current_id)
|
||||
{
|
||||
log.status = LogStatus::Failure;
|
||||
log.error = Some("Empty stream response".to_string());
|
||||
log.error = ErrorInfo::Error(intern_string("Empty stream response"));
|
||||
state.request_manager.error_requests += 1;
|
||||
}
|
||||
}
|
||||
@@ -759,19 +856,15 @@ pub async fn handle_chat(
|
||||
let stream = stream
|
||||
.then({
|
||||
let decoder = decoder.clone();
|
||||
let response_id = response_id.clone();
|
||||
let model = request.model.clone();
|
||||
let is_start = is_start.clone();
|
||||
let state = state.clone();
|
||||
let need_usage = request.stream_options.is_some_and(|opt| opt.include_usage);
|
||||
|
||||
move |chunk| {
|
||||
let decoder = decoder.clone();
|
||||
let response_id = response_id.clone();
|
||||
let model = model.clone();
|
||||
let is_start = is_start.clone();
|
||||
let state = state.clone();
|
||||
let need_usage = need_usage;
|
||||
let is_usage_sent = is_usage_sent.clone();
|
||||
let need_usage = need_usage.clone();
|
||||
|
||||
async move {
|
||||
let chunk = match chunk {
|
||||
@@ -784,12 +877,13 @@ pub async fn handle_chat(
|
||||
|
||||
let ctx = MessageProcessContext {
|
||||
response_id: &response_id,
|
||||
model: &model,
|
||||
model,
|
||||
is_start: &is_start,
|
||||
start_time,
|
||||
state: &state,
|
||||
current_id,
|
||||
need_usage,
|
||||
need_usage: &need_usage,
|
||||
is_usage_sent: &is_usage_sent,
|
||||
};
|
||||
|
||||
// 使用decoder处理chunk
|
||||
@@ -852,8 +946,9 @@ pub async fn handle_chat(
|
||||
chain.delays = decoder.lock().await.take_content_delays();
|
||||
} else {
|
||||
log.chain = Some(Chain {
|
||||
prompt: String::new(),
|
||||
prompt: Prompt::Origin(String::new()),
|
||||
delays: decoder.lock().await.take_content_delays(),
|
||||
usage: OptionUsage::None,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -873,7 +968,8 @@ pub async fn handle_chat(
|
||||
let mut decoder = StreamDecoder::new();
|
||||
let mut full_text = String::with_capacity(1024);
|
||||
let mut stream = response.bytes_stream();
|
||||
let mut prompt = String::with_capacity(1024);
|
||||
let mut prompt = Prompt::None;
|
||||
let mut usage_uuid = String::new();
|
||||
|
||||
// 逐个处理chunks
|
||||
while let Some(chunk) = stream.next().await {
|
||||
@@ -895,8 +991,15 @@ pub async fn handle_chat(
|
||||
StreamMessage::Content(text) => {
|
||||
full_text.push_str(&text);
|
||||
}
|
||||
StreamMessage::Usage(uuid) => {
|
||||
usage_uuid = uuid;
|
||||
}
|
||||
StreamMessage::Debug(debug_prompt) => {
|
||||
prompt.push_str(&debug_prompt);
|
||||
if prompt.is_none() {
|
||||
prompt = Prompt::new(debug_prompt);
|
||||
} else {
|
||||
crate::debug_println!("UB!2 {debug_prompt:?}");
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
@@ -904,12 +1007,44 @@ pub async fn handle_chat(
|
||||
}
|
||||
Err(StreamError::ChatError(error)) => {
|
||||
let error_response = error.into_error_response();
|
||||
{
|
||||
let mut state = state.lock().await;
|
||||
if let Some(log) = state
|
||||
.request_manager
|
||||
.request_logs
|
||||
.iter_mut()
|
||||
.rev()
|
||||
.find(|log| log.id == current_id)
|
||||
{
|
||||
log.status = LogStatus::Failure;
|
||||
log.error =
|
||||
ErrorInfo::Error(intern_string(error_response.native_code()));
|
||||
if let Some(detail) = error_response.details() {
|
||||
log.error.add_detail(&detail)
|
||||
}
|
||||
state.request_manager.error_requests += 1;
|
||||
}
|
||||
}
|
||||
return Err((
|
||||
error_response.status_code(),
|
||||
Json(error_response.into_common()),
|
||||
));
|
||||
}
|
||||
Err(e) => {
|
||||
{
|
||||
let mut state = state.lock().await;
|
||||
if let Some(log) = state
|
||||
.request_manager
|
||||
.request_logs
|
||||
.iter_mut()
|
||||
.rev()
|
||||
.find(|log| log.id == current_id)
|
||||
{
|
||||
log.status = LogStatus::Failure;
|
||||
log.error = ErrorInfo::Error(intern_string(e.to_string()));
|
||||
state.request_manager.error_requests += 1;
|
||||
}
|
||||
}
|
||||
let error_response = ErrorResponse {
|
||||
status: ApiStatus::Error,
|
||||
code: Some(500),
|
||||
@@ -934,7 +1069,7 @@ pub async fn handle_chat(
|
||||
.find(|log| log.id == current_id)
|
||||
{
|
||||
log.status = LogStatus::Failure;
|
||||
log.error = Some("Empty response received".to_string());
|
||||
log.error = ErrorInfo::Error(intern_string("Empty response received"));
|
||||
state.request_manager.error_requests += 1;
|
||||
}
|
||||
}
|
||||
@@ -944,11 +1079,34 @@ pub async fn handle_chat(
|
||||
));
|
||||
}
|
||||
|
||||
let (usage1, usage2) = if !usage_uuid.is_empty() {
|
||||
let result = get_token_usage(
|
||||
client,
|
||||
&auth_token,
|
||||
&checksum,
|
||||
&client_key,
|
||||
timezone,
|
||||
is_pri,
|
||||
usage_uuid,
|
||||
)
|
||||
.await;
|
||||
let result2 = match result {
|
||||
Some(ref usage) => OptionUsage::Uasge {
|
||||
input: usage.prompt_tokens,
|
||||
output: usage.completion_tokens,
|
||||
},
|
||||
None => OptionUsage::None,
|
||||
};
|
||||
(result, result2)
|
||||
} else {
|
||||
(None, OptionUsage::None)
|
||||
};
|
||||
|
||||
let response_data = ChatResponse {
|
||||
id: format!("chatcmpl-{trace_id}"),
|
||||
object: OBJECT_CHAT_COMPLETION,
|
||||
created: chrono::Utc::now().timestamp(),
|
||||
model: Some(request.model),
|
||||
model: Some(model),
|
||||
choices: vec![Choice {
|
||||
index: 0,
|
||||
message: Some(Message {
|
||||
@@ -959,11 +1117,7 @@ pub async fn handle_chat(
|
||||
logprobs: None,
|
||||
finish_reason: Some(FINISH_REASON_STOP.to_string()),
|
||||
}],
|
||||
usage: TriState::Some(Usage {
|
||||
prompt_tokens: 0,
|
||||
completion_tokens: 0,
|
||||
total_tokens: 0,
|
||||
}),
|
||||
usage: TriState::Some(usage1.unwrap_or_default()),
|
||||
};
|
||||
|
||||
{
|
||||
@@ -982,6 +1136,7 @@ pub async fn handle_chat(
|
||||
log.chain = Some(Chain {
|
||||
prompt,
|
||||
delays: decoder.take_content_delays(),
|
||||
usage: usage2,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -996,3 +1151,11 @@ pub async fn handle_chat(
|
||||
.unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
// pub async fn handle_completion(
|
||||
// State(state): State<Arc<Mutex<AppState>>>,
|
||||
// headers: HeaderMap,
|
||||
// Json(request): Json<ChatRequest>,
|
||||
// ) -> Result<Response<Body>, (StatusCode, Json<ErrorResponse>)> {
|
||||
|
||||
// }
|
@@ -1,14 +1,15 @@
|
||||
use crate::chat::{
|
||||
use crate::common::utils::InstantExt as _;
|
||||
use crate::cursor::{
|
||||
aiserver::v1::{StreamChatResponse, WebReference},
|
||||
error::{ChatError, StreamError},
|
||||
};
|
||||
use crate::common::utils::InstantExt as _;
|
||||
use flate2::read::GzDecoder;
|
||||
use prost::Message;
|
||||
use std::io::Read;
|
||||
use std::time::Instant;
|
||||
|
||||
// 解压gzip数据
|
||||
#[inline]
|
||||
fn decompress_gzip(data: &[u8]) -> Option<Vec<u8>> {
|
||||
let mut decoder = GzDecoder::new(data);
|
||||
let mut decompressed = Vec::new();
|
||||
@@ -58,6 +59,8 @@ pub enum StreamMessage {
|
||||
ContentStart,
|
||||
// 消息内容
|
||||
Content(String),
|
||||
// 额度消耗
|
||||
Usage(String),
|
||||
// 流结束标志
|
||||
StreamEnd,
|
||||
}
|
||||
@@ -225,6 +228,7 @@ impl StreamDecoder {
|
||||
Ok(messages)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn process_message(
|
||||
&self,
|
||||
msg_type: u8,
|
||||
@@ -236,22 +240,25 @@ impl StreamDecoder {
|
||||
2 => self.handle_json_message(msg_data),
|
||||
3 => self.handle_gzip_json_message(msg_data),
|
||||
t => {
|
||||
eprintln!("收到未知消息类型: {},请尝试联系开发者以获取支持", t);
|
||||
crate::debug_println!("消息类型: {},消息内容: {}", t, hex::encode(msg_data));
|
||||
eprintln!("收到未知消息类型: {t},请尝试联系开发者以获取支持");
|
||||
crate::debug_println!("消息类型: {t},消息内容: {}", hex::encode(msg_data));
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn handle_text_message(&self, msg_data: &[u8]) -> Result<Option<StreamMessage>, StreamError> {
|
||||
if let Ok(response) = StreamChatResponse::decode(msg_data) {
|
||||
// println!("[text] StreamChatResponse [hex: {}]: {:?}", hex::encode(msg_data), response);
|
||||
// crate::debug_println!("[text] StreamChatResponse [hex: {}]: {:#?}", hex::encode(msg_data), response);
|
||||
if !response.text.is_empty() {
|
||||
Ok(Some(StreamMessage::Content(response.text)))
|
||||
} else if let Some(filled_prompt) = response.filled_prompt {
|
||||
Ok(Some(StreamMessage::Debug(filled_prompt)))
|
||||
} else if let Some(web_citation) = response.web_citation {
|
||||
Ok(Some(StreamMessage::WebReference(web_citation.references)))
|
||||
} else if let Some(usage_uuid) = response.usage_uuid {
|
||||
Ok(Some(StreamMessage::Usage(usage_uuid)))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
@@ -260,16 +267,19 @@ impl StreamDecoder {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn handle_gzip_message(&self, msg_data: &[u8]) -> Result<Option<StreamMessage>, StreamError> {
|
||||
if let Some(text) = decompress_gzip(msg_data) {
|
||||
if let Ok(response) = StreamChatResponse::decode(&text[..]) {
|
||||
// println!("[gzip] StreamChatResponse [hex: {}]: {:?}", hex::encode(msg_data), response);
|
||||
// crate::debug_println!("[gzip] StreamChatResponse [hex: {}]: {:#?}", hex::encode(msg_data), response);
|
||||
if !response.text.is_empty() {
|
||||
Ok(Some(StreamMessage::Content(response.text)))
|
||||
} else if let Some(filled_prompt) = response.filled_prompt {
|
||||
Ok(Some(StreamMessage::Debug(filled_prompt)))
|
||||
} else if let Some(web_citation) = response.web_citation {
|
||||
Ok(Some(StreamMessage::WebReference(web_citation.references)))
|
||||
} else if let Some(usage_uuid) = response.usage_uuid {
|
||||
Ok(Some(StreamMessage::Usage(usage_uuid)))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
@@ -281,12 +291,13 @@ impl StreamDecoder {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn handle_json_message(&self, msg_data: &[u8]) -> Result<Option<StreamMessage>, StreamError> {
|
||||
if msg_data.len() == 2 {
|
||||
return Ok(Some(StreamMessage::StreamEnd));
|
||||
}
|
||||
if let Ok(text) = String::from_utf8(msg_data.to_vec()) {
|
||||
// println!("[text] JSON消息 [hex: {}]: {}", hex::encode(msg_data), text);
|
||||
// crate::debug_println!("[text] JSON消息 [hex: {}]: {}", hex::encode(msg_data), text);
|
||||
if let Ok(error) = serde_json::from_str::<ChatError>(&text) {
|
||||
return Err(StreamError::ChatError(error));
|
||||
}
|
||||
@@ -294,6 +305,7 @@ impl StreamDecoder {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn handle_gzip_json_message(
|
||||
&self,
|
||||
msg_data: &[u8],
|
||||
@@ -303,7 +315,7 @@ impl StreamDecoder {
|
||||
return Ok(Some(StreamMessage::StreamEnd));
|
||||
}
|
||||
if let Ok(text) = String::from_utf8(text) {
|
||||
// println!("[gzip] JSON消息 [hex: {}]: {}", hex::encode(msg_data), text);
|
||||
// crate::debug_println!("[gzip] JSON消息 [hex: {}]: {}", hex::encode(msg_data), text);
|
||||
if let Ok(error) = serde_json::from_str::<ChatError>(&text) {
|
||||
return Err(StreamError::ChatError(error));
|
||||
}
|
||||
@@ -343,8 +355,11 @@ mod tests {
|
||||
println!("流结束");
|
||||
break;
|
||||
}
|
||||
StreamMessage::Usage(msg) => {
|
||||
println!("额度uuid: {msg}");
|
||||
}
|
||||
StreamMessage::Content(msg) => {
|
||||
println!("消息内容: {}", msg);
|
||||
println!("消息内容: {msg}");
|
||||
}
|
||||
StreamMessage::WebReference(refs) => {
|
||||
println!("网页引用:");
|
||||
@@ -356,7 +371,7 @@ mod tests {
|
||||
}
|
||||
}
|
||||
StreamMessage::Debug(prompt) => {
|
||||
println!("调试信息: {}", prompt);
|
||||
println!("调试信息: {prompt}");
|
||||
}
|
||||
StreamMessage::ContentStart => {
|
||||
println!("流开始");
|
||||
@@ -424,15 +439,18 @@ mod tests {
|
||||
for message in messages {
|
||||
match message {
|
||||
StreamMessage::StreamEnd => {
|
||||
println!("流结束 [hex: {}]", hex_str);
|
||||
println!("流结束 [hex: {hex_str}]");
|
||||
should_break = true;
|
||||
break;
|
||||
}
|
||||
StreamMessage::Usage(msg) => {
|
||||
println!("额度uuid: {msg}");
|
||||
}
|
||||
StreamMessage::Content(msg) => {
|
||||
println!("消息内容 [hex: {}]: {}", hex_str, msg);
|
||||
println!("消息内容 [hex: {hex_str}]: {msg}");
|
||||
}
|
||||
StreamMessage::WebReference(refs) => {
|
||||
println!("网页引用 [hex: {}]:", hex_str);
|
||||
println!("网页引用 [hex: {hex_str}]:");
|
||||
for (i, web_ref) in refs.iter().enumerate() {
|
||||
println!(
|
||||
"{}. {} - {} - {}",
|
||||
@@ -441,10 +459,10 @@ mod tests {
|
||||
}
|
||||
}
|
||||
StreamMessage::Debug(prompt) => {
|
||||
println!("调试信息 [hex: {}]: {}", hex_str, prompt);
|
||||
println!("调试信息 [hex: {hex_str}]: {prompt}");
|
||||
}
|
||||
StreamMessage::ContentStart => {
|
||||
println!("流开始 [hex: {}]", hex_str);
|
||||
println!("流开始 [hex: {hex_str}]");
|
||||
}
|
||||
}
|
||||
}
|
79
src/leak.rs
Normal file
79
src/leak.rs
Normal file
@@ -0,0 +1,79 @@
|
||||
use parking_lot::Mutex;
|
||||
use std::{collections::HashSet, sync::LazyLock};
|
||||
|
||||
#[derive(Default)]
|
||||
struct StringPool {
|
||||
pool: HashSet<&'static str>,
|
||||
}
|
||||
|
||||
impl StringPool {
|
||||
// 驻留字符串
|
||||
fn intern(&mut self, s: &str) -> &'static str {
|
||||
if let Some(&interned) = self.pool.get(s) {
|
||||
interned
|
||||
} else {
|
||||
// 如果字符串不存在,使用 Box::leak 将其泄漏,并添加到 pool 中
|
||||
let leaked: &'static str = Box::leak(Box::from(s));
|
||||
self.pool.insert(leaked);
|
||||
leaked
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 全局 StringPool 实例
|
||||
static STRING_POOL: LazyLock<Mutex<StringPool>> =
|
||||
LazyLock::new(|| Mutex::new(StringPool::default()));
|
||||
|
||||
pub fn intern_string<S: AsRef<str>>(s: S) -> &'static str {
|
||||
STRING_POOL.lock().intern(s.as_ref())
|
||||
}
|
||||
|
||||
// #[derive(Clone, Copy, PartialEq, Eq, Hash)]
|
||||
// pub struct InternedString(&'static str);
|
||||
|
||||
// impl InternedString {
|
||||
// #[inline(always)]
|
||||
// pub fn as_str(&self) -> &'static str {
|
||||
// self.0
|
||||
// }
|
||||
// }
|
||||
|
||||
// impl Deref for InternedString {
|
||||
// type Target = str;
|
||||
|
||||
// #[inline(always)]
|
||||
// fn deref(&self) -> &'static Self::Target {
|
||||
// self.0
|
||||
// }
|
||||
// }
|
||||
|
||||
// impl Borrow<str> for InternedString {
|
||||
// #[inline(always)]
|
||||
// fn borrow(&self) -> &'static str {
|
||||
// self.0
|
||||
// }
|
||||
// }
|
||||
|
||||
// impl core::fmt::Debug for InternedString {
|
||||
// #[inline(always)]
|
||||
// fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
// self.0.fmt(f)
|
||||
// }
|
||||
// }
|
||||
|
||||
// impl core::fmt::Display for InternedString {
|
||||
// #[inline(always)]
|
||||
// fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
// self.0.fmt(f)
|
||||
// }
|
||||
// }
|
||||
|
||||
// impl serde::Serialize for InternedString {
|
||||
// #[inline]
|
||||
// fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
// where
|
||||
// S: serde::Serializer,
|
||||
// {
|
||||
// serializer.serialize_str(self.0)
|
||||
// }
|
||||
// }
|
26
src/main.rs
26
src/main.rs
@@ -1,6 +1,7 @@
|
||||
mod app;
|
||||
mod chat;
|
||||
mod common;
|
||||
mod cursor;
|
||||
mod leak;
|
||||
|
||||
use app::{
|
||||
config::handle_config_update,
|
||||
@@ -11,9 +12,9 @@ use app::{
|
||||
ROUTE_PROXIES_ADD_PATH, ROUTE_PROXIES_DELETE_PATH, ROUTE_PROXIES_GET_PATH,
|
||||
ROUTE_PROXIES_PATH, ROUTE_PROXIES_SET_GENERAL_PATH, ROUTE_PROXIES_UPDATE_PATH,
|
||||
ROUTE_README_PATH, ROUTE_ROOT_PATH, ROUTE_STATIC_PATH, ROUTE_TOKENS_ADD_PATH,
|
||||
ROUTE_TOKENS_DELETE_PATH, ROUTE_TOKENS_GET_PATH, ROUTE_TOKENS_PATH,
|
||||
ROUTE_TOKENS_PROFILE_UPDATE_PATH, ROUTE_TOKENS_TAGS_UPDATE_PATH, ROUTE_TOKENS_UPDATE_PATH,
|
||||
ROUTE_USER_INFO_PATH,
|
||||
ROUTE_TOKENS_BY_TAG_GET_PATH, ROUTE_TOKENS_DELETE_PATH, ROUTE_TOKENS_GET_PATH,
|
||||
ROUTE_TOKENS_PATH, ROUTE_TOKENS_PROFILE_UPDATE_PATH, ROUTE_TOKENS_TAGS_GET_PATH,
|
||||
ROUTE_TOKENS_TAGS_UPDATE_PATH, ROUTE_TOKENS_UPDATE_PATH, ROUTE_USER_INFO_PATH,
|
||||
},
|
||||
lazy::{AUTH_TOKEN, ROUTE_CHAT_PATH, ROUTE_MODELS_PATH},
|
||||
model::*,
|
||||
@@ -22,21 +23,21 @@ use axum::{
|
||||
Router, middleware,
|
||||
routing::{get, post},
|
||||
};
|
||||
use chat::{
|
||||
use common::utils::{parse_string_from_env, parse_usize_from_env};
|
||||
use cursor::{
|
||||
middleware::admin_auth_middleware,
|
||||
route::{
|
||||
handle_about, handle_add_proxy, handle_add_tokens, handle_api_page,
|
||||
handle_basic_calibration, handle_build_key, handle_build_key_page, handle_config_page,
|
||||
handle_delete_proxies, handle_delete_tokens, handle_env_example, handle_get_checksum,
|
||||
handle_get_hash, handle_get_proxies, handle_get_timestamp_header, handle_get_tokens,
|
||||
handle_health, handle_logs, handle_logs_post, handle_proxies_page, handle_readme,
|
||||
handle_root, handle_set_general_proxy, handle_static, handle_tokens_page,
|
||||
handle_update_proxies, handle_update_token_tags, handle_update_tokens,
|
||||
handle_get_hash, handle_get_proxies, handle_get_timestamp_header, handle_get_token_tags,
|
||||
handle_get_tokens, handle_get_tokens_by_tag, handle_health, handle_logs, handle_logs_post,
|
||||
handle_proxies_page, handle_readme, handle_root, handle_set_general_proxy, handle_static,
|
||||
handle_tokens_page, handle_update_proxies, handle_update_token_tags, handle_update_tokens,
|
||||
handle_update_tokens_profile, handle_user_info,
|
||||
},
|
||||
service::{handle_chat, handle_models},
|
||||
};
|
||||
use common::utils::{parse_string_from_env, parse_usize_from_env};
|
||||
use std::sync::Arc;
|
||||
use tokio::signal;
|
||||
use tokio::sync::Mutex;
|
||||
@@ -65,7 +66,7 @@ async fn main() {
|
||||
AppConfig::init();
|
||||
|
||||
// 初始化应用状态
|
||||
let state = Arc::new(Mutex::new(AppState::new()));
|
||||
let state = Arc::new(Mutex::new(AppState::new().await));
|
||||
|
||||
// 尝试加载保存的配置
|
||||
if let Err(e) = AppConfig::load_saved_config() {
|
||||
@@ -154,10 +155,12 @@ async fn main() {
|
||||
.route(ROUTE_TOKENS_UPDATE_PATH, post(handle_update_tokens))
|
||||
.route(ROUTE_TOKENS_ADD_PATH, post(handle_add_tokens))
|
||||
.route(ROUTE_TOKENS_DELETE_PATH, post(handle_delete_tokens))
|
||||
.route(ROUTE_TOKENS_TAGS_GET_PATH, post(handle_get_token_tags))
|
||||
.route(
|
||||
ROUTE_TOKENS_TAGS_UPDATE_PATH,
|
||||
post(handle_update_token_tags),
|
||||
)
|
||||
.route(ROUTE_TOKENS_BY_TAG_GET_PATH, post(handle_get_tokens_by_tag))
|
||||
.route(
|
||||
ROUTE_TOKENS_PROFILE_UPDATE_PATH,
|
||||
post(handle_update_tokens_profile),
|
||||
@@ -210,6 +213,7 @@ async fn main() {
|
||||
#[cfg(feature = "__preview")]
|
||||
println!("当前是测试版,有问题及时反馈哦~");
|
||||
|
||||
app::lazy::get_start_time();
|
||||
let listener = tokio::net::TcpListener::bind(&addr)
|
||||
.await
|
||||
.unwrap_or_else(|e| {
|
||||
|
@@ -66,8 +66,8 @@
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>允许所有Claude模型:</label>
|
||||
<select id="enable_all_claude">
|
||||
<label>长上下文:</label>
|
||||
<select id="enable_long_context">
|
||||
<option value="">保持不变</option>
|
||||
<option value="true">启用</option>
|
||||
<option value="false">禁用</option>
|
||||
@@ -137,7 +137,7 @@
|
||||
},
|
||||
vision_ability: document.getElementById('vision_ability').value,
|
||||
enable_slow_pool: parseBooleanFromString(document.getElementById('enable_slow_pool').value),
|
||||
enable_all_claude: parseBooleanFromString(document.getElementById('enable_all_claude').value),
|
||||
enable_long_context: parseBooleanFromString(document.getElementById('enable_long_context').value),
|
||||
usage_check_models: {
|
||||
type: document.getElementById('usage_check_models_type').value,
|
||||
content: document.getElementById('usage_check_models_list').value
|
||||
@@ -180,7 +180,7 @@
|
||||
}
|
||||
|
||||
// 对于布尔值,需要特殊处理
|
||||
if (key === 'enable_slow_pool' || key === 'enable_all_claude' ||
|
||||
if (key === 'enable_slow_pool' || key === 'enable_long_context' ||
|
||||
key === 'enable_dynamic_key' || key === 'include_web_references') {
|
||||
// 只有当值不为null且与缓存不同时才添加
|
||||
if (value !== null && value !== configCache[key]) {
|
||||
@@ -255,8 +255,8 @@
|
||||
document.getElementById('vision_ability').value = visionValue;
|
||||
document.getElementById('enable_slow_pool').value =
|
||||
parseStringFromBoolean(data.data.enable_slow_pool, '');
|
||||
document.getElementById('enable_all_claude').value =
|
||||
parseStringFromBoolean(data.data.enable_all_claude, '');
|
||||
document.getElementById('enable_long_context').value =
|
||||
parseStringFromBoolean(data.data.enable_long_context, '');
|
||||
|
||||
// 处理使用量检查模型
|
||||
const usageCheckModelsType = data.data.usage_check_models?.type || '';
|
||||
@@ -279,7 +279,7 @@
|
||||
content: pageContent || { type: 'default' },
|
||||
vision_ability: visionValue,
|
||||
enable_slow_pool: data.data.enable_slow_pool,
|
||||
enable_all_claude: data.data.enable_all_claude,
|
||||
enable_long_context: data.data.enable_long_context,
|
||||
usage_check_models: {
|
||||
type: usageCheckModelsType,
|
||||
content: usageCheckModelsList.value
|
||||
|
@@ -1066,7 +1066,7 @@
|
||||
<td>${new Date(log.timestamp).toLocaleString()}</td>
|
||||
<td>${log.model}</td>
|
||||
<td><div class="token-info-tooltip"><button class="info-button" onclick='showTokenModal(${JSON.stringify(log.token_info)})'>查看详情<div class="tooltip-content">${formatSimpleTokenInfo(log.token_info)}</div></button></div></td>
|
||||
<td>${log.chain ? `<div class="token-info-tooltip prompt-preview"><button class="info-button view-conversation" data-prompt="${encodeURIComponent(log.chain.prompt)}" data-delays='${delaysData}'>查看对话<div class="tooltip-content">${formatDialogPreview(log.chain.prompt)}</div></button></div>` : '-'}</td>
|
||||
<td>${log.chain ? `<div class="token-info-tooltip prompt-preview"><button class="info-button view-conversation" data-prompt="${encodeURIComponent(JSON.stringify(log.chain.prompt))}" data-delays='${delaysData}'>查看对话<div class="tooltip-content">${formatDialogPreview(JSON.stringify(log.chain.prompt))}</div></button></div>` : '-'}</td>
|
||||
<td>${formatTiming(log.timing.total)}</td>
|
||||
<td>${log.stream ? '是' : '否'}</td>
|
||||
<td>${log.status}</td>
|
||||
@@ -1309,6 +1309,21 @@
|
||||
function parsePrompt(promptStr) {
|
||||
if (!promptStr) return [];
|
||||
|
||||
// 尝试解析JSON格式的prompt
|
||||
try {
|
||||
const parsed = JSON.parse(promptStr);
|
||||
|
||||
// 如果是已解析的格式
|
||||
if (Array.isArray(parsed)) {
|
||||
return parsed.map(msg => ({
|
||||
role: msg.role,
|
||||
content: msg.content
|
||||
}));
|
||||
}
|
||||
} catch (e) {
|
||||
// 如果解析JSON失败,说明是原始字符串格式,使用原来的解析逻辑
|
||||
}
|
||||
|
||||
const messages = [];
|
||||
const lines = promptStr.split('\n');
|
||||
let currentRole = '';
|
||||
@@ -1383,13 +1398,12 @@
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
|
||||
// 将HTML标签文本用引号包裹,使其更易读
|
||||
// return escaped.replace(/<(\/?[^>]+)>/g, '"<$1>"');
|
||||
return escaped;
|
||||
}
|
||||
|
||||
return `<table class="message-table"><thead><tr><th>角色</th><th>内容</th></tr></thead><tbody>${messages.map(msg => `<tr><td>${roleLabels[msg.role] || msg.role}</td><td>${escapeHtml(msg.content).replace(/\n/g, '<br>')}</td></tr>`).join('')}</tbody></table>`;
|
||||
return `<table class="message-table"><thead><tr><th>角色</th><th>内容</th></tr></thead><tbody>${messages.map(msg =>
|
||||
`<tr><td>${roleLabels[msg.role] || msg.role}</td><td>${escapeHtml(msg.content).replace(/\n/g, '<br>')}</td></tr>`
|
||||
).join('')}</tbody></table>`;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -50,7 +50,7 @@ fn update_sqlite_tokens(
|
||||
})?;
|
||||
for row in rows {
|
||||
let (key, value) = row?;
|
||||
println!("{}: {}", key, value);
|
||||
println!("{key}: {value}");
|
||||
}
|
||||
|
||||
// 自动创建项并更新值
|
||||
|
Reference in New Issue
Block a user