kevin commited on
Commit
2887ce2
·
1 Parent(s): bf1c024

rust cursor2api

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
.env.example ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 当前配置为默认值,请根据需要修改
2
+
3
+ # 服务器监听端口
4
+ PORT=3000
5
+
6
+ # 路由前缀,必须以 / 开头(如果不为空)
7
+ ROUTE_PREFIX=
8
+
9
+ # 最高权限的认证令牌,必填
10
+ AUTH_TOKEN=
11
+
12
+ # 共享的认证令牌,仅Chat端点权限(轮询与AUTH_TOKEN同步),无其余权限
13
+ SHARED_AUTH_TOKEN=
14
+
15
+ # 启用流式响应检查,关闭则无法响应错误,代价是会对第一个块解析2次
16
+ ENABLE_STREAM_CHECK=true
17
+
18
+ # 流式消息结束后发送包含"finish_reason"为"stop"的空消息块
19
+ INCLUDE_STOP_REASON_STREAM=true
20
+
21
+ # 令牌文件路径
22
+ TOKEN_FILE=.token
23
+
24
+ # 令牌列表文件路径
25
+ TOKEN_LIST_FILE=.token-list
26
+
27
+ # (实验性)是否启用慢速池(true/false)
28
+ ENABLE_SLOW_POOL=false
29
+
30
+ # 允许claude开头的模型请求绕过内置模型限制(true/false)
31
+ PASS_ANY_CLAUDE=false
32
+
33
+ # 图片处理能力配置
34
+ # 可选值:
35
+ # - none 或 disabled:禁用图片功能
36
+ # - base64 或 base64-only:仅支持 base64 编码的图片
37
+ # - all 或 base64-http:支持 base64 和 HTTP 图片
38
+ # 注意:启用 HTTP 支持可能会暴露服务器 IP
39
+ VISION_ABILITY=base64
40
+
41
+ # 默认提示词
42
+ DEFAULT_INSTRUCTIONS="Respond in Chinese by default"
43
+
44
+ # 反向代理服务器主机名,你猜怎么用
45
+ REVERSE_PROXY_HOST=
46
+
47
+ # 请求体大小限制(单位为MB)
48
+ # 默认为2MB (2,097,152 字节)
49
+ REQUEST_BODY_LIMIT_MB=2
50
+
51
+ # OpenAI 请求时,token 和 checksum 的分隔符
52
+ TOKEN_DELIMITER=,
.gitignore ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /target
2
+ /get-token/target
3
+ /*.log
4
+ /*.env
5
+ /static/*.min.html
6
+ /static/*.min.css
7
+ /static/*.min.js
8
+ /scripts/.asset-hashes.json
9
+ node_modules
10
+ .DS_Store
11
+ /.vscode
12
+ /.cargo
13
+ /.token
14
+ /.token-list
15
+ /cursor-api
16
+ /cursor-api.exe
17
+ /release
18
+
19
+ /*.py
20
+ /logs
Cargo.lock ADDED
@@ -0,0 +1,2360 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # This file is automatically @generated by Cargo.
2
+ # It is not intended for manual editing.
3
+ version = 4
4
+
5
+ [[package]]
6
+ name = "addr2line"
7
+ version = "0.24.2"
8
+ source = "registry+https://github.com/rust-lang/crates.io-index"
9
+ checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
10
+ dependencies = [
11
+ "gimli",
12
+ ]
13
+
14
+ [[package]]
15
+ name = "adler2"
16
+ version = "2.0.0"
17
+ source = "registry+https://github.com/rust-lang/crates.io-index"
18
+ checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
19
+
20
+ [[package]]
21
+ name = "aho-corasick"
22
+ version = "1.1.3"
23
+ source = "registry+https://github.com/rust-lang/crates.io-index"
24
+ checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
25
+ dependencies = [
26
+ "memchr",
27
+ ]
28
+
29
+ [[package]]
30
+ name = "alloc-no-stdlib"
31
+ version = "2.0.4"
32
+ source = "registry+https://github.com/rust-lang/crates.io-index"
33
+ checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3"
34
+
35
+ [[package]]
36
+ name = "alloc-stdlib"
37
+ version = "0.2.2"
38
+ source = "registry+https://github.com/rust-lang/crates.io-index"
39
+ checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece"
40
+ dependencies = [
41
+ "alloc-no-stdlib",
42
+ ]
43
+
44
+ [[package]]
45
+ name = "android-tzdata"
46
+ version = "0.1.1"
47
+ source = "registry+https://github.com/rust-lang/crates.io-index"
48
+ checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
49
+
50
+ [[package]]
51
+ name = "android_system_properties"
52
+ version = "0.1.5"
53
+ source = "registry+https://github.com/rust-lang/crates.io-index"
54
+ checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
55
+ dependencies = [
56
+ "libc",
57
+ ]
58
+
59
+ [[package]]
60
+ name = "anyhow"
61
+ version = "1.0.95"
62
+ source = "registry+https://github.com/rust-lang/crates.io-index"
63
+ checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04"
64
+
65
+ [[package]]
66
+ name = "async-compression"
67
+ version = "0.4.18"
68
+ source = "registry+https://github.com/rust-lang/crates.io-index"
69
+ checksum = "df895a515f70646414f4b45c0b79082783b80552b373a68283012928df56f522"
70
+ dependencies = [
71
+ "brotli",
72
+ "flate2",
73
+ "futures-core",
74
+ "memchr",
75
+ "pin-project-lite",
76
+ "tokio",
77
+ ]
78
+
79
+ [[package]]
80
+ name = "async-trait"
81
+ version = "0.1.85"
82
+ source = "registry+https://github.com/rust-lang/crates.io-index"
83
+ checksum = "3f934833b4b7233644e5848f235df3f57ed8c80f1528a26c3dfa13d2147fa056"
84
+ dependencies = [
85
+ "proc-macro2",
86
+ "quote",
87
+ "syn",
88
+ ]
89
+
90
+ [[package]]
91
+ name = "atomic-waker"
92
+ version = "1.1.2"
93
+ source = "registry+https://github.com/rust-lang/crates.io-index"
94
+ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
95
+
96
+ [[package]]
97
+ name = "autocfg"
98
+ version = "1.4.0"
99
+ source = "registry+https://github.com/rust-lang/crates.io-index"
100
+ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
101
+
102
+ [[package]]
103
+ name = "axum"
104
+ version = "0.7.9"
105
+ source = "registry+https://github.com/rust-lang/crates.io-index"
106
+ checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f"
107
+ dependencies = [
108
+ "async-trait",
109
+ "axum-core",
110
+ "bytes",
111
+ "futures-util",
112
+ "http",
113
+ "http-body",
114
+ "http-body-util",
115
+ "hyper",
116
+ "hyper-util",
117
+ "itoa",
118
+ "matchit",
119
+ "memchr",
120
+ "mime",
121
+ "percent-encoding",
122
+ "pin-project-lite",
123
+ "rustversion",
124
+ "serde",
125
+ "serde_json",
126
+ "serde_path_to_error",
127
+ "serde_urlencoded",
128
+ "sync_wrapper",
129
+ "tokio",
130
+ "tower",
131
+ "tower-layer",
132
+ "tower-service",
133
+ "tracing",
134
+ ]
135
+
136
+ [[package]]
137
+ name = "axum-core"
138
+ version = "0.4.5"
139
+ source = "registry+https://github.com/rust-lang/crates.io-index"
140
+ checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199"
141
+ dependencies = [
142
+ "async-trait",
143
+ "bytes",
144
+ "futures-util",
145
+ "http",
146
+ "http-body",
147
+ "http-body-util",
148
+ "mime",
149
+ "pin-project-lite",
150
+ "rustversion",
151
+ "sync_wrapper",
152
+ "tower-layer",
153
+ "tower-service",
154
+ "tracing",
155
+ ]
156
+
157
+ [[package]]
158
+ name = "backtrace"
159
+ version = "0.3.74"
160
+ source = "registry+https://github.com/rust-lang/crates.io-index"
161
+ checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a"
162
+ dependencies = [
163
+ "addr2line",
164
+ "cfg-if",
165
+ "libc",
166
+ "miniz_oxide",
167
+ "object",
168
+ "rustc-demangle",
169
+ "windows-targets",
170
+ ]
171
+
172
+ [[package]]
173
+ name = "base64"
174
+ version = "0.22.1"
175
+ source = "registry+https://github.com/rust-lang/crates.io-index"
176
+ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
177
+
178
+ [[package]]
179
+ name = "bitflags"
180
+ version = "1.3.2"
181
+ source = "registry+https://github.com/rust-lang/crates.io-index"
182
+ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
183
+
184
+ [[package]]
185
+ name = "bitflags"
186
+ version = "2.7.0"
187
+ source = "registry+https://github.com/rust-lang/crates.io-index"
188
+ checksum = "1be3f42a67d6d345ecd59f675f3f012d6974981560836e938c22b424b85ce1be"
189
+
190
+ [[package]]
191
+ name = "block-buffer"
192
+ version = "0.10.4"
193
+ source = "registry+https://github.com/rust-lang/crates.io-index"
194
+ checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
195
+ dependencies = [
196
+ "generic-array",
197
+ ]
198
+
199
+ [[package]]
200
+ name = "brotli"
201
+ version = "7.0.0"
202
+ source = "registry+https://github.com/rust-lang/crates.io-index"
203
+ checksum = "cc97b8f16f944bba54f0433f07e30be199b6dc2bd25937444bbad560bcea29bd"
204
+ dependencies = [
205
+ "alloc-no-stdlib",
206
+ "alloc-stdlib",
207
+ "brotli-decompressor",
208
+ ]
209
+
210
+ [[package]]
211
+ name = "brotli-decompressor"
212
+ version = "4.0.1"
213
+ source = "registry+https://github.com/rust-lang/crates.io-index"
214
+ checksum = "9a45bd2e4095a8b518033b128020dd4a55aab1c0a381ba4404a472630f4bc362"
215
+ dependencies = [
216
+ "alloc-no-stdlib",
217
+ "alloc-stdlib",
218
+ ]
219
+
220
+ [[package]]
221
+ name = "bumpalo"
222
+ version = "3.16.0"
223
+ source = "registry+https://github.com/rust-lang/crates.io-index"
224
+ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
225
+
226
+ [[package]]
227
+ name = "bytemuck"
228
+ version = "1.21.0"
229
+ source = "registry+https://github.com/rust-lang/crates.io-index"
230
+ checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3"
231
+
232
+ [[package]]
233
+ name = "byteorder"
234
+ version = "1.5.0"
235
+ source = "registry+https://github.com/rust-lang/crates.io-index"
236
+ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
237
+
238
+ [[package]]
239
+ name = "byteorder-lite"
240
+ version = "0.1.0"
241
+ source = "registry+https://github.com/rust-lang/crates.io-index"
242
+ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
243
+
244
+ [[package]]
245
+ name = "bytes"
246
+ version = "1.9.0"
247
+ source = "registry+https://github.com/rust-lang/crates.io-index"
248
+ checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b"
249
+
250
+ [[package]]
251
+ name = "cc"
252
+ version = "1.2.9"
253
+ source = "registry+https://github.com/rust-lang/crates.io-index"
254
+ checksum = "c8293772165d9345bdaaa39b45b2109591e63fe5e6fbc23c6ff930a048aa310b"
255
+ dependencies = [
256
+ "shlex",
257
+ ]
258
+
259
+ [[package]]
260
+ name = "cfg-if"
261
+ version = "1.0.0"
262
+ source = "registry+https://github.com/rust-lang/crates.io-index"
263
+ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
264
+
265
+ [[package]]
266
+ name = "chrono"
267
+ version = "0.4.39"
268
+ source = "registry+https://github.com/rust-lang/crates.io-index"
269
+ checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825"
270
+ dependencies = [
271
+ "android-tzdata",
272
+ "iana-time-zone",
273
+ "num-traits",
274
+ "serde",
275
+ "windows-targets",
276
+ ]
277
+
278
+ [[package]]
279
+ name = "color_quant"
280
+ version = "1.1.0"
281
+ source = "registry+https://github.com/rust-lang/crates.io-index"
282
+ checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
283
+
284
+ [[package]]
285
+ name = "core-foundation"
286
+ version = "0.9.4"
287
+ source = "registry+https://github.com/rust-lang/crates.io-index"
288
+ checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
289
+ dependencies = [
290
+ "core-foundation-sys",
291
+ "libc",
292
+ ]
293
+
294
+ [[package]]
295
+ name = "core-foundation-sys"
296
+ version = "0.8.7"
297
+ source = "registry+https://github.com/rust-lang/crates.io-index"
298
+ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
299
+
300
+ [[package]]
301
+ name = "cpufeatures"
302
+ version = "0.2.16"
303
+ source = "registry+https://github.com/rust-lang/crates.io-index"
304
+ checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3"
305
+ dependencies = [
306
+ "libc",
307
+ ]
308
+
309
+ [[package]]
310
+ name = "crc32fast"
311
+ version = "1.4.2"
312
+ source = "registry+https://github.com/rust-lang/crates.io-index"
313
+ checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
314
+ dependencies = [
315
+ "cfg-if",
316
+ ]
317
+
318
+ [[package]]
319
+ name = "crypto-common"
320
+ version = "0.1.6"
321
+ source = "registry+https://github.com/rust-lang/crates.io-index"
322
+ checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
323
+ dependencies = [
324
+ "generic-array",
325
+ "typenum",
326
+ ]
327
+
328
+ [[package]]
329
+ name = "cursor-api"
330
+ version = "0.1.3-rc.3.4"
331
+ dependencies = [
332
+ "axum",
333
+ "base64",
334
+ "bytes",
335
+ "chrono",
336
+ "dotenvy",
337
+ "flate2",
338
+ "futures",
339
+ "gif",
340
+ "hex",
341
+ "image",
342
+ "paste",
343
+ "prost",
344
+ "prost-build",
345
+ "rand",
346
+ "regex",
347
+ "reqwest",
348
+ "serde",
349
+ "serde_json",
350
+ "sha2",
351
+ "sysinfo",
352
+ "tokio",
353
+ "tokio-stream",
354
+ "tower-http",
355
+ "urlencoding",
356
+ "uuid",
357
+ ]
358
+
359
+ [[package]]
360
+ name = "digest"
361
+ version = "0.10.7"
362
+ source = "registry+https://github.com/rust-lang/crates.io-index"
363
+ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
364
+ dependencies = [
365
+ "block-buffer",
366
+ "crypto-common",
367
+ ]
368
+
369
+ [[package]]
370
+ name = "displaydoc"
371
+ version = "0.2.5"
372
+ source = "registry+https://github.com/rust-lang/crates.io-index"
373
+ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
374
+ dependencies = [
375
+ "proc-macro2",
376
+ "quote",
377
+ "syn",
378
+ ]
379
+
380
+ [[package]]
381
+ name = "dotenvy"
382
+ version = "0.15.7"
383
+ source = "registry+https://github.com/rust-lang/crates.io-index"
384
+ checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
385
+
386
+ [[package]]
387
+ name = "either"
388
+ version = "1.13.0"
389
+ source = "registry+https://github.com/rust-lang/crates.io-index"
390
+ checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
391
+
392
+ [[package]]
393
+ name = "encoding_rs"
394
+ version = "0.8.35"
395
+ source = "registry+https://github.com/rust-lang/crates.io-index"
396
+ checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
397
+ dependencies = [
398
+ "cfg-if",
399
+ ]
400
+
401
+ [[package]]
402
+ name = "equivalent"
403
+ version = "1.0.1"
404
+ source = "registry+https://github.com/rust-lang/crates.io-index"
405
+ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
406
+
407
+ [[package]]
408
+ name = "errno"
409
+ version = "0.3.10"
410
+ source = "registry+https://github.com/rust-lang/crates.io-index"
411
+ checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
412
+ dependencies = [
413
+ "libc",
414
+ "windows-sys 0.59.0",
415
+ ]
416
+
417
+ [[package]]
418
+ name = "fastrand"
419
+ version = "2.3.0"
420
+ source = "registry+https://github.com/rust-lang/crates.io-index"
421
+ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
422
+
423
+ [[package]]
424
+ name = "fdeflate"
425
+ version = "0.3.7"
426
+ source = "registry+https://github.com/rust-lang/crates.io-index"
427
+ checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c"
428
+ dependencies = [
429
+ "simd-adler32",
430
+ ]
431
+
432
+ [[package]]
433
+ name = "fixedbitset"
434
+ version = "0.4.2"
435
+ source = "registry+https://github.com/rust-lang/crates.io-index"
436
+ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
437
+
438
+ [[package]]
439
+ name = "flate2"
440
+ version = "1.0.35"
441
+ source = "registry+https://github.com/rust-lang/crates.io-index"
442
+ checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c"
443
+ dependencies = [
444
+ "crc32fast",
445
+ "miniz_oxide",
446
+ ]
447
+
448
+ [[package]]
449
+ name = "fnv"
450
+ version = "1.0.7"
451
+ source = "registry+https://github.com/rust-lang/crates.io-index"
452
+ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
453
+
454
+ [[package]]
455
+ name = "foreign-types"
456
+ version = "0.3.2"
457
+ source = "registry+https://github.com/rust-lang/crates.io-index"
458
+ checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
459
+ dependencies = [
460
+ "foreign-types-shared",
461
+ ]
462
+
463
+ [[package]]
464
+ name = "foreign-types-shared"
465
+ version = "0.1.1"
466
+ source = "registry+https://github.com/rust-lang/crates.io-index"
467
+ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
468
+
469
+ [[package]]
470
+ name = "form_urlencoded"
471
+ version = "1.2.1"
472
+ source = "registry+https://github.com/rust-lang/crates.io-index"
473
+ checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
474
+ dependencies = [
475
+ "percent-encoding",
476
+ ]
477
+
478
+ [[package]]
479
+ name = "futures"
480
+ version = "0.3.31"
481
+ source = "registry+https://github.com/rust-lang/crates.io-index"
482
+ checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
483
+ dependencies = [
484
+ "futures-channel",
485
+ "futures-core",
486
+ "futures-io",
487
+ "futures-sink",
488
+ "futures-task",
489
+ "futures-util",
490
+ ]
491
+
492
+ [[package]]
493
+ name = "futures-channel"
494
+ version = "0.3.31"
495
+ source = "registry+https://github.com/rust-lang/crates.io-index"
496
+ checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
497
+ dependencies = [
498
+ "futures-core",
499
+ "futures-sink",
500
+ ]
501
+
502
+ [[package]]
503
+ name = "futures-core"
504
+ version = "0.3.31"
505
+ source = "registry+https://github.com/rust-lang/crates.io-index"
506
+ checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
507
+
508
+ [[package]]
509
+ name = "futures-io"
510
+ version = "0.3.31"
511
+ source = "registry+https://github.com/rust-lang/crates.io-index"
512
+ checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
513
+
514
+ [[package]]
515
+ name = "futures-macro"
516
+ version = "0.3.31"
517
+ source = "registry+https://github.com/rust-lang/crates.io-index"
518
+ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
519
+ dependencies = [
520
+ "proc-macro2",
521
+ "quote",
522
+ "syn",
523
+ ]
524
+
525
+ [[package]]
526
+ name = "futures-sink"
527
+ version = "0.3.31"
528
+ source = "registry+https://github.com/rust-lang/crates.io-index"
529
+ checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
530
+
531
+ [[package]]
532
+ name = "futures-task"
533
+ version = "0.3.31"
534
+ source = "registry+https://github.com/rust-lang/crates.io-index"
535
+ checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
536
+
537
+ [[package]]
538
+ name = "futures-util"
539
+ version = "0.3.31"
540
+ source = "registry+https://github.com/rust-lang/crates.io-index"
541
+ checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
542
+ dependencies = [
543
+ "futures-channel",
544
+ "futures-core",
545
+ "futures-io",
546
+ "futures-macro",
547
+ "futures-sink",
548
+ "futures-task",
549
+ "memchr",
550
+ "pin-project-lite",
551
+ "pin-utils",
552
+ "slab",
553
+ ]
554
+
555
+ [[package]]
556
+ name = "generic-array"
557
+ version = "0.14.7"
558
+ source = "registry+https://github.com/rust-lang/crates.io-index"
559
+ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
560
+ dependencies = [
561
+ "typenum",
562
+ "version_check",
563
+ ]
564
+
565
+ [[package]]
566
+ name = "getrandom"
567
+ version = "0.2.15"
568
+ source = "registry+https://github.com/rust-lang/crates.io-index"
569
+ checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
570
+ dependencies = [
571
+ "cfg-if",
572
+ "libc",
573
+ "wasi",
574
+ ]
575
+
576
+ [[package]]
577
+ name = "gif"
578
+ version = "0.13.1"
579
+ source = "registry+https://github.com/rust-lang/crates.io-index"
580
+ checksum = "3fb2d69b19215e18bb912fa30f7ce15846e301408695e44e0ef719f1da9e19f2"
581
+ dependencies = [
582
+ "color_quant",
583
+ "weezl",
584
+ ]
585
+
586
+ [[package]]
587
+ name = "gimli"
588
+ version = "0.31.1"
589
+ source = "registry+https://github.com/rust-lang/crates.io-index"
590
+ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
591
+
592
+ [[package]]
593
+ name = "h2"
594
+ version = "0.4.7"
595
+ source = "registry+https://github.com/rust-lang/crates.io-index"
596
+ checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e"
597
+ dependencies = [
598
+ "atomic-waker",
599
+ "bytes",
600
+ "fnv",
601
+ "futures-core",
602
+ "futures-sink",
603
+ "http",
604
+ "indexmap",
605
+ "slab",
606
+ "tokio",
607
+ "tokio-util",
608
+ "tracing",
609
+ ]
610
+
611
+ [[package]]
612
+ name = "hashbrown"
613
+ version = "0.15.2"
614
+ source = "registry+https://github.com/rust-lang/crates.io-index"
615
+ checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
616
+
617
+ [[package]]
618
+ name = "heck"
619
+ version = "0.5.0"
620
+ source = "registry+https://github.com/rust-lang/crates.io-index"
621
+ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
622
+
623
+ [[package]]
624
+ name = "hex"
625
+ version = "0.4.3"
626
+ source = "registry+https://github.com/rust-lang/crates.io-index"
627
+ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
628
+
629
+ [[package]]
630
+ name = "http"
631
+ version = "1.2.0"
632
+ source = "registry+https://github.com/rust-lang/crates.io-index"
633
+ checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea"
634
+ dependencies = [
635
+ "bytes",
636
+ "fnv",
637
+ "itoa",
638
+ ]
639
+
640
+ [[package]]
641
+ name = "http-body"
642
+ version = "1.0.1"
643
+ source = "registry+https://github.com/rust-lang/crates.io-index"
644
+ checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
645
+ dependencies = [
646
+ "bytes",
647
+ "http",
648
+ ]
649
+
650
+ [[package]]
651
+ name = "http-body-util"
652
+ version = "0.1.2"
653
+ source = "registry+https://github.com/rust-lang/crates.io-index"
654
+ checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f"
655
+ dependencies = [
656
+ "bytes",
657
+ "futures-util",
658
+ "http",
659
+ "http-body",
660
+ "pin-project-lite",
661
+ ]
662
+
663
+ [[package]]
664
+ name = "httparse"
665
+ version = "1.9.5"
666
+ source = "registry+https://github.com/rust-lang/crates.io-index"
667
+ checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946"
668
+
669
+ [[package]]
670
+ name = "httpdate"
671
+ version = "1.0.3"
672
+ source = "registry+https://github.com/rust-lang/crates.io-index"
673
+ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
674
+
675
+ [[package]]
676
+ name = "hyper"
677
+ version = "1.5.2"
678
+ source = "registry+https://github.com/rust-lang/crates.io-index"
679
+ checksum = "256fb8d4bd6413123cc9d91832d78325c48ff41677595be797d90f42969beae0"
680
+ dependencies = [
681
+ "bytes",
682
+ "futures-channel",
683
+ "futures-util",
684
+ "h2",
685
+ "http",
686
+ "http-body",
687
+ "httparse",
688
+ "httpdate",
689
+ "itoa",
690
+ "pin-project-lite",
691
+ "smallvec",
692
+ "tokio",
693
+ "want",
694
+ ]
695
+
696
+ [[package]]
697
+ name = "hyper-rustls"
698
+ version = "0.27.5"
699
+ source = "registry+https://github.com/rust-lang/crates.io-index"
700
+ checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2"
701
+ dependencies = [
702
+ "futures-util",
703
+ "http",
704
+ "hyper",
705
+ "hyper-util",
706
+ "rustls",
707
+ "rustls-pki-types",
708
+ "tokio",
709
+ "tokio-rustls",
710
+ "tower-service",
711
+ ]
712
+
713
+ [[package]]
714
+ name = "hyper-tls"
715
+ version = "0.6.0"
716
+ source = "registry+https://github.com/rust-lang/crates.io-index"
717
+ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
718
+ dependencies = [
719
+ "bytes",
720
+ "http-body-util",
721
+ "hyper",
722
+ "hyper-util",
723
+ "native-tls",
724
+ "tokio",
725
+ "tokio-native-tls",
726
+ "tower-service",
727
+ ]
728
+
729
+ [[package]]
730
+ name = "hyper-util"
731
+ version = "0.1.10"
732
+ source = "registry+https://github.com/rust-lang/crates.io-index"
733
+ checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4"
734
+ dependencies = [
735
+ "bytes",
736
+ "futures-channel",
737
+ "futures-util",
738
+ "http",
739
+ "http-body",
740
+ "hyper",
741
+ "pin-project-lite",
742
+ "socket2",
743
+ "tokio",
744
+ "tower-service",
745
+ "tracing",
746
+ ]
747
+
748
+ [[package]]
749
+ name = "iana-time-zone"
750
+ version = "0.1.61"
751
+ source = "registry+https://github.com/rust-lang/crates.io-index"
752
+ checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220"
753
+ dependencies = [
754
+ "android_system_properties",
755
+ "core-foundation-sys",
756
+ "iana-time-zone-haiku",
757
+ "js-sys",
758
+ "wasm-bindgen",
759
+ "windows-core 0.52.0",
760
+ ]
761
+
762
+ [[package]]
763
+ name = "iana-time-zone-haiku"
764
+ version = "0.1.2"
765
+ source = "registry+https://github.com/rust-lang/crates.io-index"
766
+ checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
767
+ dependencies = [
768
+ "cc",
769
+ ]
770
+
771
+ [[package]]
772
+ name = "icu_collections"
773
+ version = "1.5.0"
774
+ source = "registry+https://github.com/rust-lang/crates.io-index"
775
+ checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526"
776
+ dependencies = [
777
+ "displaydoc",
778
+ "yoke",
779
+ "zerofrom",
780
+ "zerovec",
781
+ ]
782
+
783
+ [[package]]
784
+ name = "icu_locid"
785
+ version = "1.5.0"
786
+ source = "registry+https://github.com/rust-lang/crates.io-index"
787
+ checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637"
788
+ dependencies = [
789
+ "displaydoc",
790
+ "litemap",
791
+ "tinystr",
792
+ "writeable",
793
+ "zerovec",
794
+ ]
795
+
796
+ [[package]]
797
+ name = "icu_locid_transform"
798
+ version = "1.5.0"
799
+ source = "registry+https://github.com/rust-lang/crates.io-index"
800
+ checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e"
801
+ dependencies = [
802
+ "displaydoc",
803
+ "icu_locid",
804
+ "icu_locid_transform_data",
805
+ "icu_provider",
806
+ "tinystr",
807
+ "zerovec",
808
+ ]
809
+
810
+ [[package]]
811
+ name = "icu_locid_transform_data"
812
+ version = "1.5.0"
813
+ source = "registry+https://github.com/rust-lang/crates.io-index"
814
+ checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e"
815
+
816
+ [[package]]
817
+ name = "icu_normalizer"
818
+ version = "1.5.0"
819
+ source = "registry+https://github.com/rust-lang/crates.io-index"
820
+ checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f"
821
+ dependencies = [
822
+ "displaydoc",
823
+ "icu_collections",
824
+ "icu_normalizer_data",
825
+ "icu_properties",
826
+ "icu_provider",
827
+ "smallvec",
828
+ "utf16_iter",
829
+ "utf8_iter",
830
+ "write16",
831
+ "zerovec",
832
+ ]
833
+
834
+ [[package]]
835
+ name = "icu_normalizer_data"
836
+ version = "1.5.0"
837
+ source = "registry+https://github.com/rust-lang/crates.io-index"
838
+ checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516"
839
+
840
+ [[package]]
841
+ name = "icu_properties"
842
+ version = "1.5.1"
843
+ source = "registry+https://github.com/rust-lang/crates.io-index"
844
+ checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5"
845
+ dependencies = [
846
+ "displaydoc",
847
+ "icu_collections",
848
+ "icu_locid_transform",
849
+ "icu_properties_data",
850
+ "icu_provider",
851
+ "tinystr",
852
+ "zerovec",
853
+ ]
854
+
855
+ [[package]]
856
+ name = "icu_properties_data"
857
+ version = "1.5.0"
858
+ source = "registry+https://github.com/rust-lang/crates.io-index"
859
+ checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569"
860
+
861
+ [[package]]
862
+ name = "icu_provider"
863
+ version = "1.5.0"
864
+ source = "registry+https://github.com/rust-lang/crates.io-index"
865
+ checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9"
866
+ dependencies = [
867
+ "displaydoc",
868
+ "icu_locid",
869
+ "icu_provider_macros",
870
+ "stable_deref_trait",
871
+ "tinystr",
872
+ "writeable",
873
+ "yoke",
874
+ "zerofrom",
875
+ "zerovec",
876
+ ]
877
+
878
+ [[package]]
879
+ name = "icu_provider_macros"
880
+ version = "1.5.0"
881
+ source = "registry+https://github.com/rust-lang/crates.io-index"
882
+ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
883
+ dependencies = [
884
+ "proc-macro2",
885
+ "quote",
886
+ "syn",
887
+ ]
888
+
889
+ [[package]]
890
+ name = "idna"
891
+ version = "1.0.3"
892
+ source = "registry+https://github.com/rust-lang/crates.io-index"
893
+ checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e"
894
+ dependencies = [
895
+ "idna_adapter",
896
+ "smallvec",
897
+ "utf8_iter",
898
+ ]
899
+
900
+ [[package]]
901
+ name = "idna_adapter"
902
+ version = "1.2.0"
903
+ source = "registry+https://github.com/rust-lang/crates.io-index"
904
+ checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71"
905
+ dependencies = [
906
+ "icu_normalizer",
907
+ "icu_properties",
908
+ ]
909
+
910
+ [[package]]
911
+ name = "image"
912
+ version = "0.25.5"
913
+ source = "registry+https://github.com/rust-lang/crates.io-index"
914
+ checksum = "cd6f44aed642f18953a158afeb30206f4d50da59fbc66ecb53c66488de73563b"
915
+ dependencies = [
916
+ "bytemuck",
917
+ "byteorder-lite",
918
+ "color_quant",
919
+ "gif",
920
+ "image-webp",
921
+ "num-traits",
922
+ "png",
923
+ "zune-core",
924
+ "zune-jpeg",
925
+ ]
926
+
927
+ [[package]]
928
+ name = "image-webp"
929
+ version = "0.2.1"
930
+ source = "registry+https://github.com/rust-lang/crates.io-index"
931
+ checksum = "b77d01e822461baa8409e156015a1d91735549f0f2c17691bd2d996bef238f7f"
932
+ dependencies = [
933
+ "byteorder-lite",
934
+ "quick-error",
935
+ ]
936
+
937
+ [[package]]
938
+ name = "indexmap"
939
+ version = "2.7.0"
940
+ source = "registry+https://github.com/rust-lang/crates.io-index"
941
+ checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f"
942
+ dependencies = [
943
+ "equivalent",
944
+ "hashbrown",
945
+ ]
946
+
947
+ [[package]]
948
+ name = "ipnet"
949
+ version = "2.10.1"
950
+ source = "registry+https://github.com/rust-lang/crates.io-index"
951
+ checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708"
952
+
953
+ [[package]]
954
+ name = "itertools"
955
+ version = "0.13.0"
956
+ source = "registry+https://github.com/rust-lang/crates.io-index"
957
+ checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
958
+ dependencies = [
959
+ "either",
960
+ ]
961
+
962
+ [[package]]
963
+ name = "itoa"
964
+ version = "1.0.14"
965
+ source = "registry+https://github.com/rust-lang/crates.io-index"
966
+ checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
967
+
968
+ [[package]]
969
+ name = "js-sys"
970
+ version = "0.3.77"
971
+ source = "registry+https://github.com/rust-lang/crates.io-index"
972
+ checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
973
+ dependencies = [
974
+ "once_cell",
975
+ "wasm-bindgen",
976
+ ]
977
+
978
+ [[package]]
979
+ name = "libc"
980
+ version = "0.2.169"
981
+ source = "registry+https://github.com/rust-lang/crates.io-index"
982
+ checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
983
+
984
+ [[package]]
985
+ name = "linux-raw-sys"
986
+ version = "0.4.15"
987
+ source = "registry+https://github.com/rust-lang/crates.io-index"
988
+ checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
989
+
990
+ [[package]]
991
+ name = "litemap"
992
+ version = "0.7.4"
993
+ source = "registry+https://github.com/rust-lang/crates.io-index"
994
+ checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104"
995
+
996
+ [[package]]
997
+ name = "log"
998
+ version = "0.4.22"
999
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1000
+ checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
1001
+
1002
+ [[package]]
1003
+ name = "matchit"
1004
+ version = "0.7.3"
1005
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1006
+ checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94"
1007
+
1008
+ [[package]]
1009
+ name = "memchr"
1010
+ version = "2.7.4"
1011
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1012
+ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
1013
+
1014
+ [[package]]
1015
+ name = "mime"
1016
+ version = "0.3.17"
1017
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1018
+ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
1019
+
1020
+ [[package]]
1021
+ name = "miniz_oxide"
1022
+ version = "0.8.2"
1023
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1024
+ checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394"
1025
+ dependencies = [
1026
+ "adler2",
1027
+ "simd-adler32",
1028
+ ]
1029
+
1030
+ [[package]]
1031
+ name = "mio"
1032
+ version = "1.0.3"
1033
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1034
+ checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd"
1035
+ dependencies = [
1036
+ "libc",
1037
+ "wasi",
1038
+ "windows-sys 0.52.0",
1039
+ ]
1040
+
1041
+ [[package]]
1042
+ name = "multimap"
1043
+ version = "0.10.0"
1044
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1045
+ checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03"
1046
+
1047
+ [[package]]
1048
+ name = "native-tls"
1049
+ version = "0.2.12"
1050
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1051
+ checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466"
1052
+ dependencies = [
1053
+ "libc",
1054
+ "log",
1055
+ "openssl",
1056
+ "openssl-probe",
1057
+ "openssl-sys",
1058
+ "schannel",
1059
+ "security-framework",
1060
+ "security-framework-sys",
1061
+ "tempfile",
1062
+ ]
1063
+
1064
+ [[package]]
1065
+ name = "ntapi"
1066
+ version = "0.4.1"
1067
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1068
+ checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4"
1069
+ dependencies = [
1070
+ "winapi",
1071
+ ]
1072
+
1073
+ [[package]]
1074
+ name = "num-traits"
1075
+ version = "0.2.19"
1076
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1077
+ checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
1078
+ dependencies = [
1079
+ "autocfg",
1080
+ ]
1081
+
1082
+ [[package]]
1083
+ name = "object"
1084
+ version = "0.36.7"
1085
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1086
+ checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
1087
+ dependencies = [
1088
+ "memchr",
1089
+ ]
1090
+
1091
+ [[package]]
1092
+ name = "once_cell"
1093
+ version = "1.20.2"
1094
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1095
+ checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
1096
+
1097
+ [[package]]
1098
+ name = "openssl"
1099
+ version = "0.10.68"
1100
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1101
+ checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5"
1102
+ dependencies = [
1103
+ "bitflags 2.7.0",
1104
+ "cfg-if",
1105
+ "foreign-types",
1106
+ "libc",
1107
+ "once_cell",
1108
+ "openssl-macros",
1109
+ "openssl-sys",
1110
+ ]
1111
+
1112
+ [[package]]
1113
+ name = "openssl-macros"
1114
+ version = "0.1.1"
1115
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1116
+ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
1117
+ dependencies = [
1118
+ "proc-macro2",
1119
+ "quote",
1120
+ "syn",
1121
+ ]
1122
+
1123
+ [[package]]
1124
+ name = "openssl-probe"
1125
+ version = "0.1.5"
1126
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1127
+ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
1128
+
1129
+ [[package]]
1130
+ name = "openssl-sys"
1131
+ version = "0.9.104"
1132
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1133
+ checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741"
1134
+ dependencies = [
1135
+ "cc",
1136
+ "libc",
1137
+ "pkg-config",
1138
+ "vcpkg",
1139
+ ]
1140
+
1141
+ [[package]]
1142
+ name = "paste"
1143
+ version = "1.0.15"
1144
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1145
+ checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
1146
+
1147
+ [[package]]
1148
+ name = "percent-encoding"
1149
+ version = "2.3.1"
1150
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1151
+ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
1152
+
1153
+ [[package]]
1154
+ name = "petgraph"
1155
+ version = "0.6.5"
1156
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1157
+ checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db"
1158
+ dependencies = [
1159
+ "fixedbitset",
1160
+ "indexmap",
1161
+ ]
1162
+
1163
+ [[package]]
1164
+ name = "pin-project-lite"
1165
+ version = "0.2.16"
1166
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1167
+ checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
1168
+
1169
+ [[package]]
1170
+ name = "pin-utils"
1171
+ version = "0.1.0"
1172
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1173
+ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
1174
+
1175
+ [[package]]
1176
+ name = "pkg-config"
1177
+ version = "0.3.31"
1178
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1179
+ checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2"
1180
+
1181
+ [[package]]
1182
+ name = "png"
1183
+ version = "0.17.16"
1184
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1185
+ checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526"
1186
+ dependencies = [
1187
+ "bitflags 1.3.2",
1188
+ "crc32fast",
1189
+ "fdeflate",
1190
+ "flate2",
1191
+ "miniz_oxide",
1192
+ ]
1193
+
1194
+ [[package]]
1195
+ name = "ppv-lite86"
1196
+ version = "0.2.20"
1197
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1198
+ checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
1199
+ dependencies = [
1200
+ "zerocopy",
1201
+ ]
1202
+
1203
+ [[package]]
1204
+ name = "prettyplease"
1205
+ version = "0.2.29"
1206
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1207
+ checksum = "6924ced06e1f7dfe3fa48d57b9f74f55d8915f5036121bef647ef4b204895fac"
1208
+ dependencies = [
1209
+ "proc-macro2",
1210
+ "syn",
1211
+ ]
1212
+
1213
+ [[package]]
1214
+ name = "proc-macro2"
1215
+ version = "1.0.93"
1216
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1217
+ checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
1218
+ dependencies = [
1219
+ "unicode-ident",
1220
+ ]
1221
+
1222
+ [[package]]
1223
+ name = "prost"
1224
+ version = "0.13.4"
1225
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1226
+ checksum = "2c0fef6c4230e4ccf618a35c59d7ede15dea37de8427500f50aff708806e42ec"
1227
+ dependencies = [
1228
+ "bytes",
1229
+ "prost-derive",
1230
+ ]
1231
+
1232
+ [[package]]
1233
+ name = "prost-build"
1234
+ version = "0.13.4"
1235
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1236
+ checksum = "d0f3e5beed80eb580c68e2c600937ac2c4eedabdfd5ef1e5b7ea4f3fba84497b"
1237
+ dependencies = [
1238
+ "heck",
1239
+ "itertools",
1240
+ "log",
1241
+ "multimap",
1242
+ "once_cell",
1243
+ "petgraph",
1244
+ "prettyplease",
1245
+ "prost",
1246
+ "prost-types",
1247
+ "regex",
1248
+ "syn",
1249
+ "tempfile",
1250
+ ]
1251
+
1252
+ [[package]]
1253
+ name = "prost-derive"
1254
+ version = "0.13.4"
1255
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1256
+ checksum = "157c5a9d7ea5c2ed2d9fb8f495b64759f7816c7eaea54ba3978f0d63000162e3"
1257
+ dependencies = [
1258
+ "anyhow",
1259
+ "itertools",
1260
+ "proc-macro2",
1261
+ "quote",
1262
+ "syn",
1263
+ ]
1264
+
1265
+ [[package]]
1266
+ name = "prost-types"
1267
+ version = "0.13.4"
1268
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1269
+ checksum = "cc2f1e56baa61e93533aebc21af4d2134b70f66275e0fcdf3cbe43d77ff7e8fc"
1270
+ dependencies = [
1271
+ "prost",
1272
+ ]
1273
+
1274
+ [[package]]
1275
+ name = "quick-error"
1276
+ version = "2.0.1"
1277
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1278
+ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
1279
+
1280
+ [[package]]
1281
+ name = "quote"
1282
+ version = "1.0.38"
1283
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1284
+ checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
1285
+ dependencies = [
1286
+ "proc-macro2",
1287
+ ]
1288
+
1289
+ [[package]]
1290
+ name = "rand"
1291
+ version = "0.8.5"
1292
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1293
+ checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
1294
+ dependencies = [
1295
+ "libc",
1296
+ "rand_chacha",
1297
+ "rand_core",
1298
+ ]
1299
+
1300
+ [[package]]
1301
+ name = "rand_chacha"
1302
+ version = "0.3.1"
1303
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1304
+ checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
1305
+ dependencies = [
1306
+ "ppv-lite86",
1307
+ "rand_core",
1308
+ ]
1309
+
1310
+ [[package]]
1311
+ name = "rand_core"
1312
+ version = "0.6.4"
1313
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1314
+ checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
1315
+ dependencies = [
1316
+ "getrandom",
1317
+ ]
1318
+
1319
+ [[package]]
1320
+ name = "regex"
1321
+ version = "1.11.1"
1322
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1323
+ checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
1324
+ dependencies = [
1325
+ "aho-corasick",
1326
+ "memchr",
1327
+ "regex-automata",
1328
+ "regex-syntax",
1329
+ ]
1330
+
1331
+ [[package]]
1332
+ name = "regex-automata"
1333
+ version = "0.4.9"
1334
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1335
+ checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
1336
+ dependencies = [
1337
+ "aho-corasick",
1338
+ "memchr",
1339
+ "regex-syntax",
1340
+ ]
1341
+
1342
+ [[package]]
1343
+ name = "regex-syntax"
1344
+ version = "0.8.5"
1345
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1346
+ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
1347
+
1348
+ [[package]]
1349
+ name = "reqwest"
1350
+ version = "0.12.12"
1351
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1352
+ checksum = "43e734407157c3c2034e0258f5e4473ddb361b1e85f95a66690d67264d7cd1da"
1353
+ dependencies = [
1354
+ "async-compression",
1355
+ "base64",
1356
+ "bytes",
1357
+ "encoding_rs",
1358
+ "futures-core",
1359
+ "futures-util",
1360
+ "h2",
1361
+ "http",
1362
+ "http-body",
1363
+ "http-body-util",
1364
+ "hyper",
1365
+ "hyper-rustls",
1366
+ "hyper-tls",
1367
+ "hyper-util",
1368
+ "ipnet",
1369
+ "js-sys",
1370
+ "log",
1371
+ "mime",
1372
+ "native-tls",
1373
+ "once_cell",
1374
+ "percent-encoding",
1375
+ "pin-project-lite",
1376
+ "rustls-pemfile",
1377
+ "serde",
1378
+ "serde_json",
1379
+ "serde_urlencoded",
1380
+ "sync_wrapper",
1381
+ "system-configuration",
1382
+ "tokio",
1383
+ "tokio-native-tls",
1384
+ "tokio-util",
1385
+ "tower",
1386
+ "tower-service",
1387
+ "url",
1388
+ "wasm-bindgen",
1389
+ "wasm-bindgen-futures",
1390
+ "wasm-streams",
1391
+ "web-sys",
1392
+ "windows-registry",
1393
+ ]
1394
+
1395
+ [[package]]
1396
+ name = "ring"
1397
+ version = "0.17.8"
1398
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1399
+ checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d"
1400
+ dependencies = [
1401
+ "cc",
1402
+ "cfg-if",
1403
+ "getrandom",
1404
+ "libc",
1405
+ "spin",
1406
+ "untrusted",
1407
+ "windows-sys 0.52.0",
1408
+ ]
1409
+
1410
+ [[package]]
1411
+ name = "rustc-demangle"
1412
+ version = "0.1.24"
1413
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1414
+ checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
1415
+
1416
+ [[package]]
1417
+ name = "rustix"
1418
+ version = "0.38.43"
1419
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1420
+ checksum = "a78891ee6bf2340288408954ac787aa063d8e8817e9f53abb37c695c6d834ef6"
1421
+ dependencies = [
1422
+ "bitflags 2.7.0",
1423
+ "errno",
1424
+ "libc",
1425
+ "linux-raw-sys",
1426
+ "windows-sys 0.59.0",
1427
+ ]
1428
+
1429
+ [[package]]
1430
+ name = "rustls"
1431
+ version = "0.23.21"
1432
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1433
+ checksum = "8f287924602bf649d949c63dc8ac8b235fa5387d394020705b80c4eb597ce5b8"
1434
+ dependencies = [
1435
+ "once_cell",
1436
+ "rustls-pki-types",
1437
+ "rustls-webpki",
1438
+ "subtle",
1439
+ "zeroize",
1440
+ ]
1441
+
1442
+ [[package]]
1443
+ name = "rustls-pemfile"
1444
+ version = "2.2.0"
1445
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1446
+ checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50"
1447
+ dependencies = [
1448
+ "rustls-pki-types",
1449
+ ]
1450
+
1451
+ [[package]]
1452
+ name = "rustls-pki-types"
1453
+ version = "1.10.1"
1454
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1455
+ checksum = "d2bf47e6ff922db3825eb750c4e2ff784c6ff8fb9e13046ef6a1d1c5401b0b37"
1456
+
1457
+ [[package]]
1458
+ name = "rustls-webpki"
1459
+ version = "0.102.8"
1460
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1461
+ checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9"
1462
+ dependencies = [
1463
+ "ring",
1464
+ "rustls-pki-types",
1465
+ "untrusted",
1466
+ ]
1467
+
1468
+ [[package]]
1469
+ name = "rustversion"
1470
+ version = "1.0.19"
1471
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1472
+ checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4"
1473
+
1474
+ [[package]]
1475
+ name = "ryu"
1476
+ version = "1.0.18"
1477
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1478
+ checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
1479
+
1480
+ [[package]]
1481
+ name = "schannel"
1482
+ version = "0.1.27"
1483
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1484
+ checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d"
1485
+ dependencies = [
1486
+ "windows-sys 0.59.0",
1487
+ ]
1488
+
1489
+ [[package]]
1490
+ name = "security-framework"
1491
+ version = "2.11.1"
1492
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1493
+ checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
1494
+ dependencies = [
1495
+ "bitflags 2.7.0",
1496
+ "core-foundation",
1497
+ "core-foundation-sys",
1498
+ "libc",
1499
+ "security-framework-sys",
1500
+ ]
1501
+
1502
+ [[package]]
1503
+ name = "security-framework-sys"
1504
+ version = "2.14.0"
1505
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1506
+ checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32"
1507
+ dependencies = [
1508
+ "core-foundation-sys",
1509
+ "libc",
1510
+ ]
1511
+
1512
+ [[package]]
1513
+ name = "serde"
1514
+ version = "1.0.217"
1515
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1516
+ checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
1517
+ dependencies = [
1518
+ "serde_derive",
1519
+ ]
1520
+
1521
+ [[package]]
1522
+ name = "serde_derive"
1523
+ version = "1.0.217"
1524
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1525
+ checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
1526
+ dependencies = [
1527
+ "proc-macro2",
1528
+ "quote",
1529
+ "syn",
1530
+ ]
1531
+
1532
+ [[package]]
1533
+ name = "serde_json"
1534
+ version = "1.0.135"
1535
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1536
+ checksum = "2b0d7ba2887406110130a978386c4e1befb98c674b4fba677954e4db976630d9"
1537
+ dependencies = [
1538
+ "itoa",
1539
+ "memchr",
1540
+ "ryu",
1541
+ "serde",
1542
+ ]
1543
+
1544
+ [[package]]
1545
+ name = "serde_path_to_error"
1546
+ version = "0.1.16"
1547
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1548
+ checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6"
1549
+ dependencies = [
1550
+ "itoa",
1551
+ "serde",
1552
+ ]
1553
+
1554
+ [[package]]
1555
+ name = "serde_urlencoded"
1556
+ version = "0.7.1"
1557
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1558
+ checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
1559
+ dependencies = [
1560
+ "form_urlencoded",
1561
+ "itoa",
1562
+ "ryu",
1563
+ "serde",
1564
+ ]
1565
+
1566
+ [[package]]
1567
+ name = "sha2"
1568
+ version = "0.10.8"
1569
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1570
+ checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
1571
+ dependencies = [
1572
+ "cfg-if",
1573
+ "cpufeatures",
1574
+ "digest",
1575
+ ]
1576
+
1577
+ [[package]]
1578
+ name = "shlex"
1579
+ version = "1.3.0"
1580
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1581
+ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
1582
+
1583
+ [[package]]
1584
+ name = "simd-adler32"
1585
+ version = "0.3.7"
1586
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1587
+ checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
1588
+
1589
+ [[package]]
1590
+ name = "slab"
1591
+ version = "0.4.9"
1592
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1593
+ checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
1594
+ dependencies = [
1595
+ "autocfg",
1596
+ ]
1597
+
1598
+ [[package]]
1599
+ name = "smallvec"
1600
+ version = "1.13.2"
1601
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1602
+ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
1603
+
1604
+ [[package]]
1605
+ name = "socket2"
1606
+ version = "0.5.8"
1607
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1608
+ checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8"
1609
+ dependencies = [
1610
+ "libc",
1611
+ "windows-sys 0.52.0",
1612
+ ]
1613
+
1614
+ [[package]]
1615
+ name = "spin"
1616
+ version = "0.9.8"
1617
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1618
+ checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
1619
+
1620
+ [[package]]
1621
+ name = "stable_deref_trait"
1622
+ version = "1.2.0"
1623
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1624
+ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
1625
+
1626
+ [[package]]
1627
+ name = "subtle"
1628
+ version = "2.6.1"
1629
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1630
+ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
1631
+
1632
+ [[package]]
1633
+ name = "syn"
1634
+ version = "2.0.96"
1635
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1636
+ checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80"
1637
+ dependencies = [
1638
+ "proc-macro2",
1639
+ "quote",
1640
+ "unicode-ident",
1641
+ ]
1642
+
1643
+ [[package]]
1644
+ name = "sync_wrapper"
1645
+ version = "1.0.2"
1646
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1647
+ checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
1648
+ dependencies = [
1649
+ "futures-core",
1650
+ ]
1651
+
1652
+ [[package]]
1653
+ name = "synstructure"
1654
+ version = "0.13.1"
1655
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1656
+ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
1657
+ dependencies = [
1658
+ "proc-macro2",
1659
+ "quote",
1660
+ "syn",
1661
+ ]
1662
+
1663
+ [[package]]
1664
+ name = "sysinfo"
1665
+ version = "0.33.1"
1666
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1667
+ checksum = "4fc858248ea01b66f19d8e8a6d55f41deaf91e9d495246fd01368d99935c6c01"
1668
+ dependencies = [
1669
+ "core-foundation-sys",
1670
+ "libc",
1671
+ "memchr",
1672
+ "ntapi",
1673
+ "windows",
1674
+ ]
1675
+
1676
+ [[package]]
1677
+ name = "system-configuration"
1678
+ version = "0.6.1"
1679
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1680
+ checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b"
1681
+ dependencies = [
1682
+ "bitflags 2.7.0",
1683
+ "core-foundation",
1684
+ "system-configuration-sys",
1685
+ ]
1686
+
1687
+ [[package]]
1688
+ name = "system-configuration-sys"
1689
+ version = "0.6.0"
1690
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1691
+ checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4"
1692
+ dependencies = [
1693
+ "core-foundation-sys",
1694
+ "libc",
1695
+ ]
1696
+
1697
+ [[package]]
1698
+ name = "tempfile"
1699
+ version = "3.15.0"
1700
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1701
+ checksum = "9a8a559c81686f576e8cd0290cd2a24a2a9ad80c98b3478856500fcbd7acd704"
1702
+ dependencies = [
1703
+ "cfg-if",
1704
+ "fastrand",
1705
+ "getrandom",
1706
+ "once_cell",
1707
+ "rustix",
1708
+ "windows-sys 0.59.0",
1709
+ ]
1710
+
1711
+ [[package]]
1712
+ name = "tinystr"
1713
+ version = "0.7.6"
1714
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1715
+ checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f"
1716
+ dependencies = [
1717
+ "displaydoc",
1718
+ "zerovec",
1719
+ ]
1720
+
1721
+ [[package]]
1722
+ name = "tokio"
1723
+ version = "1.43.0"
1724
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1725
+ checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e"
1726
+ dependencies = [
1727
+ "backtrace",
1728
+ "bytes",
1729
+ "libc",
1730
+ "mio",
1731
+ "pin-project-lite",
1732
+ "socket2",
1733
+ "tokio-macros",
1734
+ "windows-sys 0.52.0",
1735
+ ]
1736
+
1737
+ [[package]]
1738
+ name = "tokio-macros"
1739
+ version = "2.5.0"
1740
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1741
+ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
1742
+ dependencies = [
1743
+ "proc-macro2",
1744
+ "quote",
1745
+ "syn",
1746
+ ]
1747
+
1748
+ [[package]]
1749
+ name = "tokio-native-tls"
1750
+ version = "0.3.1"
1751
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1752
+ checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
1753
+ dependencies = [
1754
+ "native-tls",
1755
+ "tokio",
1756
+ ]
1757
+
1758
+ [[package]]
1759
+ name = "tokio-rustls"
1760
+ version = "0.26.1"
1761
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1762
+ checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37"
1763
+ dependencies = [
1764
+ "rustls",
1765
+ "tokio",
1766
+ ]
1767
+
1768
+ [[package]]
1769
+ name = "tokio-stream"
1770
+ version = "0.1.17"
1771
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1772
+ checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047"
1773
+ dependencies = [
1774
+ "futures-core",
1775
+ "pin-project-lite",
1776
+ "tokio",
1777
+ ]
1778
+
1779
+ [[package]]
1780
+ name = "tokio-util"
1781
+ version = "0.7.13"
1782
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1783
+ checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078"
1784
+ dependencies = [
1785
+ "bytes",
1786
+ "futures-core",
1787
+ "futures-sink",
1788
+ "pin-project-lite",
1789
+ "tokio",
1790
+ ]
1791
+
1792
+ [[package]]
1793
+ name = "tower"
1794
+ version = "0.5.2"
1795
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1796
+ checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9"
1797
+ dependencies = [
1798
+ "futures-core",
1799
+ "futures-util",
1800
+ "pin-project-lite",
1801
+ "sync_wrapper",
1802
+ "tokio",
1803
+ "tower-layer",
1804
+ "tower-service",
1805
+ "tracing",
1806
+ ]
1807
+
1808
+ [[package]]
1809
+ name = "tower-http"
1810
+ version = "0.6.2"
1811
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1812
+ checksum = "403fa3b783d4b626a8ad51d766ab03cb6d2dbfc46b1c5d4448395e6628dc9697"
1813
+ dependencies = [
1814
+ "bitflags 2.7.0",
1815
+ "bytes",
1816
+ "http",
1817
+ "http-body",
1818
+ "http-body-util",
1819
+ "pin-project-lite",
1820
+ "tower-layer",
1821
+ "tower-service",
1822
+ ]
1823
+
1824
+ [[package]]
1825
+ name = "tower-layer"
1826
+ version = "0.3.3"
1827
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1828
+ checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
1829
+
1830
+ [[package]]
1831
+ name = "tower-service"
1832
+ version = "0.3.3"
1833
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1834
+ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
1835
+
1836
+ [[package]]
1837
+ name = "tracing"
1838
+ version = "0.1.41"
1839
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1840
+ checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
1841
+ dependencies = [
1842
+ "log",
1843
+ "pin-project-lite",
1844
+ "tracing-core",
1845
+ ]
1846
+
1847
+ [[package]]
1848
+ name = "tracing-core"
1849
+ version = "0.1.33"
1850
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1851
+ checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c"
1852
+ dependencies = [
1853
+ "once_cell",
1854
+ ]
1855
+
1856
+ [[package]]
1857
+ name = "try-lock"
1858
+ version = "0.2.5"
1859
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1860
+ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
1861
+
1862
+ [[package]]
1863
+ name = "typenum"
1864
+ version = "1.17.0"
1865
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1866
+ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
1867
+
1868
+ [[package]]
1869
+ name = "unicode-ident"
1870
+ version = "1.0.14"
1871
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1872
+ checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
1873
+
1874
+ [[package]]
1875
+ name = "untrusted"
1876
+ version = "0.9.0"
1877
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1878
+ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
1879
+
1880
+ [[package]]
1881
+ name = "url"
1882
+ version = "2.5.4"
1883
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1884
+ checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60"
1885
+ dependencies = [
1886
+ "form_urlencoded",
1887
+ "idna",
1888
+ "percent-encoding",
1889
+ ]
1890
+
1891
+ [[package]]
1892
+ name = "urlencoding"
1893
+ version = "2.1.3"
1894
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1895
+ checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
1896
+
1897
+ [[package]]
1898
+ name = "utf16_iter"
1899
+ version = "1.0.5"
1900
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1901
+ checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246"
1902
+
1903
+ [[package]]
1904
+ name = "utf8_iter"
1905
+ version = "1.0.4"
1906
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1907
+ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
1908
+
1909
+ [[package]]
1910
+ name = "uuid"
1911
+ version = "1.11.1"
1912
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1913
+ checksum = "b913a3b5fe84142e269d63cc62b64319ccaf89b748fc31fe025177f767a756c4"
1914
+ dependencies = [
1915
+ "getrandom",
1916
+ ]
1917
+
1918
+ [[package]]
1919
+ name = "vcpkg"
1920
+ version = "0.2.15"
1921
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1922
+ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
1923
+
1924
+ [[package]]
1925
+ name = "version_check"
1926
+ version = "0.9.5"
1927
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1928
+ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
1929
+
1930
+ [[package]]
1931
+ name = "want"
1932
+ version = "0.3.1"
1933
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1934
+ checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
1935
+ dependencies = [
1936
+ "try-lock",
1937
+ ]
1938
+
1939
+ [[package]]
1940
+ name = "wasi"
1941
+ version = "0.11.0+wasi-snapshot-preview1"
1942
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1943
+ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
1944
+
1945
+ [[package]]
1946
+ name = "wasm-bindgen"
1947
+ version = "0.2.100"
1948
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1949
+ checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
1950
+ dependencies = [
1951
+ "cfg-if",
1952
+ "once_cell",
1953
+ "rustversion",
1954
+ "wasm-bindgen-macro",
1955
+ ]
1956
+
1957
+ [[package]]
1958
+ name = "wasm-bindgen-backend"
1959
+ version = "0.2.100"
1960
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1961
+ checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
1962
+ dependencies = [
1963
+ "bumpalo",
1964
+ "log",
1965
+ "proc-macro2",
1966
+ "quote",
1967
+ "syn",
1968
+ "wasm-bindgen-shared",
1969
+ ]
1970
+
1971
+ [[package]]
1972
+ name = "wasm-bindgen-futures"
1973
+ version = "0.4.50"
1974
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1975
+ checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61"
1976
+ dependencies = [
1977
+ "cfg-if",
1978
+ "js-sys",
1979
+ "once_cell",
1980
+ "wasm-bindgen",
1981
+ "web-sys",
1982
+ ]
1983
+
1984
+ [[package]]
1985
+ name = "wasm-bindgen-macro"
1986
+ version = "0.2.100"
1987
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1988
+ checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
1989
+ dependencies = [
1990
+ "quote",
1991
+ "wasm-bindgen-macro-support",
1992
+ ]
1993
+
1994
+ [[package]]
1995
+ name = "wasm-bindgen-macro-support"
1996
+ version = "0.2.100"
1997
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1998
+ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
1999
+ dependencies = [
2000
+ "proc-macro2",
2001
+ "quote",
2002
+ "syn",
2003
+ "wasm-bindgen-backend",
2004
+ "wasm-bindgen-shared",
2005
+ ]
2006
+
2007
+ [[package]]
2008
+ name = "wasm-bindgen-shared"
2009
+ version = "0.2.100"
2010
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2011
+ checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
2012
+ dependencies = [
2013
+ "unicode-ident",
2014
+ ]
2015
+
2016
+ [[package]]
2017
+ name = "wasm-streams"
2018
+ version = "0.4.2"
2019
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2020
+ checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65"
2021
+ dependencies = [
2022
+ "futures-util",
2023
+ "js-sys",
2024
+ "wasm-bindgen",
2025
+ "wasm-bindgen-futures",
2026
+ "web-sys",
2027
+ ]
2028
+
2029
+ [[package]]
2030
+ name = "web-sys"
2031
+ version = "0.3.77"
2032
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2033
+ checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2"
2034
+ dependencies = [
2035
+ "js-sys",
2036
+ "wasm-bindgen",
2037
+ ]
2038
+
2039
+ [[package]]
2040
+ name = "weezl"
2041
+ version = "0.1.8"
2042
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2043
+ checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082"
2044
+
2045
+ [[package]]
2046
+ name = "winapi"
2047
+ version = "0.3.9"
2048
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2049
+ checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
2050
+ dependencies = [
2051
+ "winapi-i686-pc-windows-gnu",
2052
+ "winapi-x86_64-pc-windows-gnu",
2053
+ ]
2054
+
2055
+ [[package]]
2056
+ name = "winapi-i686-pc-windows-gnu"
2057
+ version = "0.4.0"
2058
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2059
+ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
2060
+
2061
+ [[package]]
2062
+ name = "winapi-x86_64-pc-windows-gnu"
2063
+ version = "0.4.0"
2064
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2065
+ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
2066
+
2067
+ [[package]]
2068
+ name = "windows"
2069
+ version = "0.57.0"
2070
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2071
+ checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143"
2072
+ dependencies = [
2073
+ "windows-core 0.57.0",
2074
+ "windows-targets",
2075
+ ]
2076
+
2077
+ [[package]]
2078
+ name = "windows-core"
2079
+ version = "0.52.0"
2080
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2081
+ checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
2082
+ dependencies = [
2083
+ "windows-targets",
2084
+ ]
2085
+
2086
+ [[package]]
2087
+ name = "windows-core"
2088
+ version = "0.57.0"
2089
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2090
+ checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d"
2091
+ dependencies = [
2092
+ "windows-implement",
2093
+ "windows-interface",
2094
+ "windows-result 0.1.2",
2095
+ "windows-targets",
2096
+ ]
2097
+
2098
+ [[package]]
2099
+ name = "windows-implement"
2100
+ version = "0.57.0"
2101
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2102
+ checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7"
2103
+ dependencies = [
2104
+ "proc-macro2",
2105
+ "quote",
2106
+ "syn",
2107
+ ]
2108
+
2109
+ [[package]]
2110
+ name = "windows-interface"
2111
+ version = "0.57.0"
2112
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2113
+ checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7"
2114
+ dependencies = [
2115
+ "proc-macro2",
2116
+ "quote",
2117
+ "syn",
2118
+ ]
2119
+
2120
+ [[package]]
2121
+ name = "windows-registry"
2122
+ version = "0.2.0"
2123
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2124
+ checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0"
2125
+ dependencies = [
2126
+ "windows-result 0.2.0",
2127
+ "windows-strings",
2128
+ "windows-targets",
2129
+ ]
2130
+
2131
+ [[package]]
2132
+ name = "windows-result"
2133
+ version = "0.1.2"
2134
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2135
+ checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8"
2136
+ dependencies = [
2137
+ "windows-targets",
2138
+ ]
2139
+
2140
+ [[package]]
2141
+ name = "windows-result"
2142
+ version = "0.2.0"
2143
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2144
+ checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e"
2145
+ dependencies = [
2146
+ "windows-targets",
2147
+ ]
2148
+
2149
+ [[package]]
2150
+ name = "windows-strings"
2151
+ version = "0.1.0"
2152
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2153
+ checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10"
2154
+ dependencies = [
2155
+ "windows-result 0.2.0",
2156
+ "windows-targets",
2157
+ ]
2158
+
2159
+ [[package]]
2160
+ name = "windows-sys"
2161
+ version = "0.52.0"
2162
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2163
+ checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
2164
+ dependencies = [
2165
+ "windows-targets",
2166
+ ]
2167
+
2168
+ [[package]]
2169
+ name = "windows-sys"
2170
+ version = "0.59.0"
2171
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2172
+ checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
2173
+ dependencies = [
2174
+ "windows-targets",
2175
+ ]
2176
+
2177
+ [[package]]
2178
+ name = "windows-targets"
2179
+ version = "0.52.6"
2180
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2181
+ checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
2182
+ dependencies = [
2183
+ "windows_aarch64_gnullvm",
2184
+ "windows_aarch64_msvc",
2185
+ "windows_i686_gnu",
2186
+ "windows_i686_gnullvm",
2187
+ "windows_i686_msvc",
2188
+ "windows_x86_64_gnu",
2189
+ "windows_x86_64_gnullvm",
2190
+ "windows_x86_64_msvc",
2191
+ ]
2192
+
2193
+ [[package]]
2194
+ name = "windows_aarch64_gnullvm"
2195
+ version = "0.52.6"
2196
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2197
+ checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
2198
+
2199
+ [[package]]
2200
+ name = "windows_aarch64_msvc"
2201
+ version = "0.52.6"
2202
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2203
+ checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
2204
+
2205
+ [[package]]
2206
+ name = "windows_i686_gnu"
2207
+ version = "0.52.6"
2208
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2209
+ checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
2210
+
2211
+ [[package]]
2212
+ name = "windows_i686_gnullvm"
2213
+ version = "0.52.6"
2214
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2215
+ checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
2216
+
2217
+ [[package]]
2218
+ name = "windows_i686_msvc"
2219
+ version = "0.52.6"
2220
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2221
+ checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
2222
+
2223
+ [[package]]
2224
+ name = "windows_x86_64_gnu"
2225
+ version = "0.52.6"
2226
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2227
+ checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
2228
+
2229
+ [[package]]
2230
+ name = "windows_x86_64_gnullvm"
2231
+ version = "0.52.6"
2232
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2233
+ checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
2234
+
2235
+ [[package]]
2236
+ name = "windows_x86_64_msvc"
2237
+ version = "0.52.6"
2238
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2239
+ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
2240
+
2241
+ [[package]]
2242
+ name = "write16"
2243
+ version = "1.0.0"
2244
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2245
+ checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936"
2246
+
2247
+ [[package]]
2248
+ name = "writeable"
2249
+ version = "0.5.5"
2250
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2251
+ checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
2252
+
2253
+ [[package]]
2254
+ name = "yoke"
2255
+ version = "0.7.5"
2256
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2257
+ checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40"
2258
+ dependencies = [
2259
+ "serde",
2260
+ "stable_deref_trait",
2261
+ "yoke-derive",
2262
+ "zerofrom",
2263
+ ]
2264
+
2265
+ [[package]]
2266
+ name = "yoke-derive"
2267
+ version = "0.7.5"
2268
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2269
+ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154"
2270
+ dependencies = [
2271
+ "proc-macro2",
2272
+ "quote",
2273
+ "syn",
2274
+ "synstructure",
2275
+ ]
2276
+
2277
+ [[package]]
2278
+ name = "zerocopy"
2279
+ version = "0.7.35"
2280
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2281
+ checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
2282
+ dependencies = [
2283
+ "byteorder",
2284
+ "zerocopy-derive",
2285
+ ]
2286
+
2287
+ [[package]]
2288
+ name = "zerocopy-derive"
2289
+ version = "0.7.35"
2290
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2291
+ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
2292
+ dependencies = [
2293
+ "proc-macro2",
2294
+ "quote",
2295
+ "syn",
2296
+ ]
2297
+
2298
+ [[package]]
2299
+ name = "zerofrom"
2300
+ version = "0.1.5"
2301
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2302
+ checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e"
2303
+ dependencies = [
2304
+ "zerofrom-derive",
2305
+ ]
2306
+
2307
+ [[package]]
2308
+ name = "zerofrom-derive"
2309
+ version = "0.1.5"
2310
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2311
+ checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808"
2312
+ dependencies = [
2313
+ "proc-macro2",
2314
+ "quote",
2315
+ "syn",
2316
+ "synstructure",
2317
+ ]
2318
+
2319
+ [[package]]
2320
+ name = "zeroize"
2321
+ version = "1.8.1"
2322
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2323
+ checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
2324
+
2325
+ [[package]]
2326
+ name = "zerovec"
2327
+ version = "0.10.4"
2328
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2329
+ checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079"
2330
+ dependencies = [
2331
+ "yoke",
2332
+ "zerofrom",
2333
+ "zerovec-derive",
2334
+ ]
2335
+
2336
+ [[package]]
2337
+ name = "zerovec-derive"
2338
+ version = "0.10.3"
2339
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2340
+ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
2341
+ dependencies = [
2342
+ "proc-macro2",
2343
+ "quote",
2344
+ "syn",
2345
+ ]
2346
+
2347
+ [[package]]
2348
+ name = "zune-core"
2349
+ version = "0.4.12"
2350
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2351
+ checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a"
2352
+
2353
+ [[package]]
2354
+ name = "zune-jpeg"
2355
+ version = "0.4.14"
2356
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2357
+ checksum = "99a5bab8d7dedf81405c4bb1f2b83ea057643d9cb28778cea9eecddeedd2e028"
2358
+ dependencies = [
2359
+ "zune-core",
2360
+ ]
Cargo.toml ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [package]
2
+ name = "cursor-api"
3
+ version = "0.1.3-rc.3.4"
4
+ edition = "2021"
5
+ authors = ["wisdgod <nav@wisdgod.com>"]
6
+ description = "OpenAI format compatibility layer for the Cursor API"
7
+ repository = "https://github.com/wisdgod/cursor-api"
8
+
9
+ [build-dependencies]
10
+ prost-build = "0.13.4"
11
+ sha2 = { version = "0.10.8", default-features = false }
12
+ serde_json = "1.0.134"
13
+
14
+ [dependencies]
15
+ axum = { version = "0.7.9", features = ["json"] }
16
+ base64 = { version = "0.22.1", default-features = false, features = ["std"] }
17
+ # brotli = { version = "7.0.0", default-features = false, features = ["std"] }
18
+ bytes = "1.9.0"
19
+ chrono = { version = "0.4.39", default-features = false, features = ["std", "clock", "now", "serde"] }
20
+ dotenvy = "0.15.7"
21
+ flate2 = { version = "1.0.35", default-features = false, features = ["rust_backend"] }
22
+ futures = { version = "0.3.31", default-features = false, features = ["std"] }
23
+ gif = { version = "0.13.1", default-features = false, features = ["std"] }
24
+ hex = { version = "0.4.3", default-features = false, features = ["std"] }
25
+ image = { version = "0.25.5", default-features = false, features = ["jpeg", "png", "gif", "webp"] }
26
+ paste = "1.0.15"
27
+ prost = "0.13.4"
28
+ rand = { version = "0.8.5", default-features = false, features = ["std", "std_rng"] }
29
+ regex = { version = "1.11.1", default-features = false, features = ["std", "perf"] }
30
+ reqwest = { version = "0.12.12", default-features = false, features = ["gzip", "brotli", "json", "stream", "__tls", "charset", "default-tls", "h2", "http2", "macos-system-configuration"] }
31
+ serde = { version = "1.0.217", default-features = false, features = ["std", "derive"] }
32
+ serde_json = "1.0.135"
33
+ sha2 = { version = "0.10.8", default-features = false }
34
+ sysinfo = { version = "0.33.1", default-features = false, features = ["system"] }
35
+ tokio = { version = "1.43.0", features = ["rt-multi-thread", "macros", "net", "sync", "time"] }
36
+ tokio-stream = { version = "0.1.17", features = ["time"] }
37
+ tower-http = { version = "0.6.2", features = ["cors", "limit"] }
38
+ urlencoding = "2.1.3"
39
+ uuid = { version = "1.11.1", features = ["v4"] }
40
+
41
+ [profile.release]
42
+ lto = true
43
+ codegen-units = 1
44
+ panic = 'abort'
45
+ strip = true
46
+ opt-level = 3
Cross.toml ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [target.x86_64-unknown-linux-gnu]
2
+ pre-build = [
3
+ "set -e",
4
+ "apt-get update",
5
+ "apt-get install -y --no-install-recommends build-essential protobuf-compiler pkg-config libssl-dev nodejs npm",
6
+ "rm -rf /var/lib/apt/lists/*"
7
+ ]
8
+
9
+ [target.x86_64-unknown-freebsd]
10
+ pre-build = [
11
+ "pkg update",
12
+ "pkg install -y node20 www/npm protobuf ca_root_nss bash gmake pkgconf openssl",
13
+ "export SSL_CERT_FILE=/etc/ssl/cert.pem"
14
+ ]
Dockerfile ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # AMD64 构建阶段
2
+ FROM --platform=linux/amd64 rust:1.84.0-slim-bookworm as builder-amd64
3
+ WORKDIR /app
4
+ RUN apt-get update && \
5
+ apt-get install -y --no-install-recommends \
6
+ build-essential protobuf-compiler pkg-config libssl-dev nodejs npm \
7
+ && rm -rf /var/lib/apt/lists/*
8
+ COPY . .
9
+ ENV RUSTFLAGS="-C link-arg=-s"
10
+ RUN cargo build --release && \
11
+ cp target/release/cursor-api /app/cursor-api
12
+
13
+ # ARM64 构建阶段
14
+ FROM --platform=linux/arm64 rust:1.84.0-slim-bookworm as builder-arm64
15
+ WORKDIR /app
16
+ RUN apt-get update && \
17
+ apt-get install -y --no-install-recommends \
18
+ build-essential protobuf-compiler pkg-config libssl-dev nodejs npm \
19
+ && rm -rf /var/lib/apt/lists/*
20
+ COPY . .
21
+ ENV RUSTFLAGS="-C link-arg=-s"
22
+ RUN cargo build --release && \
23
+ cp target/release/cursor-api /app/cursor-api
24
+
25
+ # AMD64 运行阶段
26
+ FROM --platform=linux/amd64 debian:bookworm-slim as run-amd64
27
+ WORKDIR /app
28
+ ENV TZ=Asia/Shanghai
29
+ RUN apt-get update && \
30
+ apt-get install -y --no-install-recommends \
31
+ ca-certificates tzdata \
32
+ && rm -rf /var/lib/apt/lists/*
33
+ COPY --from=builder-amd64 /app/cursor-api .
34
+
35
+ # ARM64 运行阶段
36
+ FROM --platform=linux/arm64 debian:bookworm-slim as run-arm64
37
+ WORKDIR /app
38
+ ENV TZ=Asia/Shanghai
39
+ RUN apt-get update && \
40
+ apt-get install -y --no-install-recommends \
41
+ ca-certificates tzdata \
42
+ && rm -rf /var/lib/apt/lists/*
43
+ COPY --from=builder-arm64 /app/cursor-api .
44
+
45
+ # 通用配置
46
+ FROM run-${TARGETARCH}
47
+ ENV PORT=3000
48
+ EXPOSE ${PORT}
49
+ CMD ["./cursor-api"]
README.md CHANGED
@@ -5,6 +5,7 @@ colorFrom: gray
5
  colorTo: pink
6
  sdk: docker
7
  pinned: false
 
8
  ---
9
 
10
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
5
  colorTo: pink
6
  sdk: docker
7
  pinned: false
8
+ app_port: 3000
9
  ---
10
 
11
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
build.rs ADDED
@@ -0,0 +1,165 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use sha2::{Digest, Sha256};
2
+ use std::collections::HashMap;
3
+ use std::fs;
4
+ use std::io::Result;
5
+ use std::path::{Path, PathBuf};
6
+ use std::process::Command;
7
+
8
+ // 支持的文件类型
9
+ const SUPPORTED_EXTENSIONS: [&str; 3] = ["html", "js", "css"];
10
+
11
+ fn check_and_install_deps() -> Result<()> {
12
+ let scripts_dir = Path::new("scripts");
13
+ let node_modules = scripts_dir.join("node_modules");
14
+
15
+ if !node_modules.exists() {
16
+ println!("cargo:warning=Installing minifier dependencies...");
17
+ let status = Command::new("npm")
18
+ .current_dir(scripts_dir)
19
+ .arg("install")
20
+ .status()?;
21
+
22
+ if !status.success() {
23
+ panic!("Failed to install npm dependencies");
24
+ }
25
+ println!("cargo:warning=Dependencies installed successfully");
26
+ }
27
+ Ok(())
28
+ }
29
+
30
+ fn get_files_hash() -> Result<HashMap<PathBuf, String>> {
31
+ let mut file_hashes = HashMap::new();
32
+ let static_dir = Path::new("static");
33
+
34
+ if static_dir.exists() {
35
+ for entry in fs::read_dir(static_dir)? {
36
+ let entry = entry?;
37
+ let path = entry.path();
38
+
39
+ // 检查是否是支持的文件类型,且不是已经压缩的文件
40
+ if let Some(ext) = path.extension().and_then(|e| e.to_str()) {
41
+ if SUPPORTED_EXTENSIONS.contains(&ext) && !path.to_string_lossy().contains(".min.")
42
+ {
43
+ let content = fs::read(&path)?;
44
+ let mut hasher = Sha256::new();
45
+ hasher.update(&content);
46
+ let hash = format!("{:x}", hasher.finalize());
47
+ file_hashes.insert(path, hash);
48
+ }
49
+ }
50
+ }
51
+ }
52
+
53
+ Ok(file_hashes)
54
+ }
55
+
56
+ fn load_saved_hashes() -> Result<HashMap<PathBuf, String>> {
57
+ let hash_file = Path::new("scripts/.asset-hashes.json");
58
+ if hash_file.exists() {
59
+ let content = fs::read_to_string(hash_file)?;
60
+ let hash_map: HashMap<String, String> = serde_json::from_str(&content)?;
61
+ Ok(hash_map
62
+ .into_iter()
63
+ .map(|(k, v)| (PathBuf::from(k), v))
64
+ .collect())
65
+ } else {
66
+ Ok(HashMap::new())
67
+ }
68
+ }
69
+
70
+ fn save_hashes(hashes: &HashMap<PathBuf, String>) -> Result<()> {
71
+ let hash_file = Path::new("scripts/.asset-hashes.json");
72
+ let string_map: HashMap<String, String> = hashes
73
+ .iter()
74
+ .map(|(k, v)| (k.to_string_lossy().into_owned(), v.clone()))
75
+ .collect();
76
+ let content = serde_json::to_string_pretty(&string_map)?;
77
+ fs::write(hash_file, content)?;
78
+ Ok(())
79
+ }
80
+
81
+ fn minify_assets() -> Result<()> {
82
+ // 获取现有文件的哈希
83
+ let current_hashes = get_files_hash()?;
84
+
85
+ if current_hashes.is_empty() {
86
+ println!("cargo:warning=No files to minify");
87
+ return Ok(());
88
+ }
89
+
90
+ // 加载保存的哈希值
91
+ let saved_hashes = load_saved_hashes()?;
92
+
93
+ // 找出需要更新的文件
94
+ let files_to_update: Vec<_> = current_hashes
95
+ .iter()
96
+ .filter(|(path, current_hash)| {
97
+ let ext = path.extension().and_then(|e| e.to_str()).unwrap_or("");
98
+ let min_path = path.with_file_name(format!(
99
+ "{}.min.{}",
100
+ path.file_stem().unwrap().to_string_lossy(),
101
+ ext
102
+ ));
103
+
104
+ // 检查压缩后的文件是否存在
105
+ if !min_path.exists() {
106
+ return true;
107
+ }
108
+
109
+ // 检查原始文件是否发生变化
110
+ saved_hashes
111
+ .get(*path)
112
+ .map_or(true, |saved_hash| saved_hash != *current_hash)
113
+ })
114
+ .map(|(path, _)| path.file_name().unwrap().to_string_lossy().into_owned())
115
+ .collect();
116
+
117
+ if files_to_update.is_empty() {
118
+ println!("cargo:warning=No files need to be updated");
119
+ return Ok(());
120
+ }
121
+
122
+ println!("cargo:warning=Minifying {} files...", files_to_update.len());
123
+
124
+ // 运行压缩脚本
125
+ let status = Command::new("node")
126
+ .arg("scripts/minify.js")
127
+ .args(&files_to_update)
128
+ .status()?;
129
+
130
+ if !status.success() {
131
+ panic!("Asset minification failed");
132
+ }
133
+
134
+ // 保存新的哈希值
135
+ save_hashes(&current_hashes)?;
136
+
137
+ Ok(())
138
+ }
139
+
140
+ fn main() -> Result<()> {
141
+ // Proto 文件处理
142
+ println!("cargo:rerun-if-changed=src/chat/aiserver/v1/lite.proto");
143
+ let mut config = prost_build::Config::new();
144
+ // config.type_attribute(".", "#[derive(serde::Serialize, serde::Deserialize)]");
145
+ // config.type_attribute(
146
+ // "aiserver.v1.ThrowErrorCheckRequest",
147
+ // "#[derive(serde::Serialize, serde::Deserialize)]"
148
+ // );
149
+ config
150
+ .compile_protos(&["src/chat/aiserver/v1/lite.proto"], &["src/chat/aiserver/v1/"])
151
+ .unwrap();
152
+
153
+ // 静态资源文件处理
154
+ println!("cargo:rerun-if-changed=scripts/minify.js");
155
+ println!("cargo:rerun-if-changed=scripts/package.json");
156
+ println!("cargo:rerun-if-changed=static");
157
+
158
+ // 检查并安装依赖
159
+ check_and_install_deps()?;
160
+
161
+ // 运行资源压缩
162
+ minify_assets()?;
163
+
164
+ Ok(())
165
+ }
get-token/Cargo.lock ADDED
@@ -0,0 +1,189 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # This file is automatically @generated by Cargo.
2
+ # It is not intended for manual editing.
3
+ version = 4
4
+
5
+ [[package]]
6
+ name = "ahash"
7
+ version = "0.8.11"
8
+ source = "registry+https://github.com/rust-lang/crates.io-index"
9
+ checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
10
+ dependencies = [
11
+ "cfg-if",
12
+ "once_cell",
13
+ "version_check",
14
+ "zerocopy",
15
+ ]
16
+
17
+ [[package]]
18
+ name = "bitflags"
19
+ version = "2.6.0"
20
+ source = "registry+https://github.com/rust-lang/crates.io-index"
21
+ checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
22
+
23
+ [[package]]
24
+ name = "cc"
25
+ version = "1.2.5"
26
+ source = "registry+https://github.com/rust-lang/crates.io-index"
27
+ checksum = "c31a0499c1dc64f458ad13872de75c0eb7e3fdb0e67964610c914b034fc5956e"
28
+ dependencies = [
29
+ "shlex",
30
+ ]
31
+
32
+ [[package]]
33
+ name = "cfg-if"
34
+ version = "1.0.0"
35
+ source = "registry+https://github.com/rust-lang/crates.io-index"
36
+ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
37
+
38
+ [[package]]
39
+ name = "fallible-iterator"
40
+ version = "0.3.0"
41
+ source = "registry+https://github.com/rust-lang/crates.io-index"
42
+ checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649"
43
+
44
+ [[package]]
45
+ name = "fallible-streaming-iterator"
46
+ version = "0.1.9"
47
+ source = "registry+https://github.com/rust-lang/crates.io-index"
48
+ checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
49
+
50
+ [[package]]
51
+ name = "get-token"
52
+ version = "0.1.0"
53
+ dependencies = [
54
+ "rusqlite",
55
+ ]
56
+
57
+ [[package]]
58
+ name = "hashbrown"
59
+ version = "0.14.5"
60
+ source = "registry+https://github.com/rust-lang/crates.io-index"
61
+ checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
62
+ dependencies = [
63
+ "ahash",
64
+ ]
65
+
66
+ [[package]]
67
+ name = "hashlink"
68
+ version = "0.9.1"
69
+ source = "registry+https://github.com/rust-lang/crates.io-index"
70
+ checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af"
71
+ dependencies = [
72
+ "hashbrown",
73
+ ]
74
+
75
+ [[package]]
76
+ name = "libsqlite3-sys"
77
+ version = "0.30.1"
78
+ source = "registry+https://github.com/rust-lang/crates.io-index"
79
+ checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149"
80
+ dependencies = [
81
+ "cc",
82
+ "pkg-config",
83
+ "vcpkg",
84
+ ]
85
+
86
+ [[package]]
87
+ name = "once_cell"
88
+ version = "1.20.2"
89
+ source = "registry+https://github.com/rust-lang/crates.io-index"
90
+ checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
91
+
92
+ [[package]]
93
+ name = "pkg-config"
94
+ version = "0.3.31"
95
+ source = "registry+https://github.com/rust-lang/crates.io-index"
96
+ checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2"
97
+
98
+ [[package]]
99
+ name = "proc-macro2"
100
+ version = "1.0.92"
101
+ source = "registry+https://github.com/rust-lang/crates.io-index"
102
+ checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
103
+ dependencies = [
104
+ "unicode-ident",
105
+ ]
106
+
107
+ [[package]]
108
+ name = "quote"
109
+ version = "1.0.37"
110
+ source = "registry+https://github.com/rust-lang/crates.io-index"
111
+ checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
112
+ dependencies = [
113
+ "proc-macro2",
114
+ ]
115
+
116
+ [[package]]
117
+ name = "rusqlite"
118
+ version = "0.32.1"
119
+ source = "registry+https://github.com/rust-lang/crates.io-index"
120
+ checksum = "7753b721174eb8ff87a9a0e799e2d7bc3749323e773db92e0984debb00019d6e"
121
+ dependencies = [
122
+ "bitflags",
123
+ "fallible-iterator",
124
+ "fallible-streaming-iterator",
125
+ "hashlink",
126
+ "libsqlite3-sys",
127
+ "smallvec",
128
+ ]
129
+
130
+ [[package]]
131
+ name = "shlex"
132
+ version = "1.3.0"
133
+ source = "registry+https://github.com/rust-lang/crates.io-index"
134
+ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
135
+
136
+ [[package]]
137
+ name = "smallvec"
138
+ version = "1.13.2"
139
+ source = "registry+https://github.com/rust-lang/crates.io-index"
140
+ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
141
+
142
+ [[package]]
143
+ name = "syn"
144
+ version = "2.0.91"
145
+ source = "registry+https://github.com/rust-lang/crates.io-index"
146
+ checksum = "d53cbcb5a243bd33b7858b1d7f4aca2153490815872d86d955d6ea29f743c035"
147
+ dependencies = [
148
+ "proc-macro2",
149
+ "quote",
150
+ "unicode-ident",
151
+ ]
152
+
153
+ [[package]]
154
+ name = "unicode-ident"
155
+ version = "1.0.14"
156
+ source = "registry+https://github.com/rust-lang/crates.io-index"
157
+ checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
158
+
159
+ [[package]]
160
+ name = "vcpkg"
161
+ version = "0.2.15"
162
+ source = "registry+https://github.com/rust-lang/crates.io-index"
163
+ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
164
+
165
+ [[package]]
166
+ name = "version_check"
167
+ version = "0.9.5"
168
+ source = "registry+https://github.com/rust-lang/crates.io-index"
169
+ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
170
+
171
+ [[package]]
172
+ name = "zerocopy"
173
+ version = "0.7.35"
174
+ source = "registry+https://github.com/rust-lang/crates.io-index"
175
+ checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
176
+ dependencies = [
177
+ "zerocopy-derive",
178
+ ]
179
+
180
+ [[package]]
181
+ name = "zerocopy-derive"
182
+ version = "0.7.35"
183
+ source = "registry+https://github.com/rust-lang/crates.io-index"
184
+ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
185
+ dependencies = [
186
+ "proc-macro2",
187
+ "quote",
188
+ "syn",
189
+ ]
get-token/Cargo.toml ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [package]
2
+ name = "get-token"
3
+ version = "0.1.0"
4
+ edition = "2021"
5
+
6
+ [dependencies]
7
+ rusqlite = { version = "0.32.1", default-features = false, features = ["bundled"] }
8
+
9
+ [profile.release]
10
+ lto = true
11
+ codegen-units = 1
12
+ panic = 'abort'
13
+ strip = true
14
+ opt-level = 3
get-token/README.md ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Cursor Token 获取工具
2
+
3
+ 这个工具用于从 Cursor 编辑器的本地数据库中获取访问令牌。
4
+
5
+ ## 系统要求
6
+
7
+ - Rust 编程环境
8
+ - Cargo 包管理器
9
+
10
+ ## 构建说明
11
+
12
+ ### Windows
13
+
14
+ 1. 安装 Rust
15
+ ```powershell
16
+ winget install Rustlang.Rust
17
+ # 或访问 https://rustup.rs/ 下载安装程序
18
+ ```
19
+
20
+ 2. 克隆项目并构建
21
+ ```powershell
22
+ git clone <repository-url>
23
+ cd get-token
24
+ cargo build --release
25
+ ```
26
+
27
+ 3. 构建完成后,可执行文件位于 `target/release/get-token.exe`
28
+
29
+ ### macOS
30
+
31
+ 1. 安装 Rust
32
+ ```bash
33
+ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
34
+ ```
35
+
36
+ 2. 克隆项目并构建
37
+ ```bash
38
+ git clone <repository-url>
39
+ cd get-token
40
+ cargo build --release
41
+ ```
42
+
43
+ 3. 构建完成后,可执行文件位于 `target/release/get-token`
44
+
45
+ ## 使用方法
46
+
47
+ 直接运行编译好的可执行文件即可:
48
+
49
+ - Windows: `.\target\release\get-token.exe`
50
+ - macOS: `./target/release/get-token`
51
+
52
+ 程序将自动查找并显示 Cursor 编辑器的访问令牌。
53
+
54
+ ## 注意事项
55
+
56
+ - 确保 Cursor 编辑器已经安装并且至少登录过一次
57
+ - Windows 数据库路径:`%USERPROFILE%\AppData\Roaming\Cursor\User\globalStorage\state.vscdb`
58
+ - macOS 数据库路径:`~/Library/Application Support/Cursor/User/globalStorage/state.vscdb`
get-token/src/main.rs ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use rusqlite::Connection;
2
+ use std::env;
3
+ use std::path::PathBuf;
4
+
5
+ fn main() {
6
+ let home_dir = env::var("HOME")
7
+ .or_else(|_| env::var("USERPROFILE"))
8
+ .unwrap();
9
+ let db_path = if cfg!(target_os = "windows") {
10
+ PathBuf::from(home_dir).join(r"AppData\Roaming\Cursor\User\globalStorage\state.vscdb")
11
+ } else {
12
+ PathBuf::from(home_dir)
13
+ .join("Library/Application Support/Cursor/User/globalStorage/state.vscdb")
14
+ };
15
+
16
+ match Connection::open(&db_path) {
17
+ Ok(conn) => {
18
+ match conn.query_row(
19
+ "SELECT value FROM ItemTable WHERE key = 'cursorAuth/accessToken'",
20
+ [],
21
+ |row| row.get::<_, String>(0),
22
+ ) {
23
+ Ok(token) => println!("访问令牌: {}", token.trim()),
24
+ Err(err) => eprintln!("获取令牌时出错: {}", err),
25
+ }
26
+ }
27
+ Err(err) => eprintln!("无法打开数据库: {}", err),
28
+ }
29
+ }
scripts/build.ps1 ADDED
@@ -0,0 +1,126 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 参数处理
2
+ param(
3
+ [switch]$Static,
4
+ [switch]$Help,
5
+ [ValidateSet("x86_64", "aarch64", "i686")]
6
+ [string]$Architecture
7
+ )
8
+
9
+ # 设置错误时停止执行
10
+ $ErrorActionPreference = "Stop"
11
+
12
+ # 颜色输出函数
13
+ function Write-Info { param($Message) Write-Host "[INFO] $Message" -ForegroundColor Blue }
14
+ function Write-Warn { param($Message) Write-Host "[WARN] $Message" -ForegroundColor Yellow }
15
+ function Write-Error { param($Message) Write-Host "[ERROR] $Message" -ForegroundColor Red; exit 1 }
16
+
17
+ # 检查必要的工具
18
+ function Check-Requirements {
19
+ $tools = @("cargo", "protoc", "npm", "node")
20
+ $missing = @()
21
+
22
+ foreach ($tool in $tools) {
23
+ if (-not (Get-Command $tool -ErrorAction SilentlyContinue)) {
24
+ $missing += $tool
25
+ }
26
+ }
27
+
28
+ if ($missing.Count -gt 0) {
29
+ Write-Error "缺少必要工具: $($missing -join ', ')"
30
+ }
31
+ }
32
+
33
+ # 帮助信息
34
+ function Show-Help {
35
+ Write-Host @"
36
+ 用法: $(Split-Path $MyInvocation.ScriptName -Leaf) [选项]
37
+
38
+ 选项:
39
+ -Static 使用静态链接(默认动态链接)
40
+ -Help 显示此帮助信息
41
+
42
+ 不带参数时使用默认配置构建
43
+ "@
44
+ }
45
+
46
+ # 构建函数
47
+ function Build-Target {
48
+ param (
49
+ [string]$Target,
50
+ [string]$RustFlags
51
+ )
52
+
53
+ Write-Info "正在构建 $Target..."
54
+
55
+ # 设置环境变量
56
+ $env:RUSTFLAGS = $RustFlags
57
+
58
+ # 构建
59
+ if ($Target -ne (rustc -Vv | Select-String "host: (.*)" | ForEach-Object { $_.Matches.Groups[1].Value })) {
60
+ cargo build --target $Target --release
61
+ } else {
62
+ cargo build --release
63
+ }
64
+
65
+ # 移动编译产物到 release 目录
66
+ $binaryName = "cursor-api"
67
+ if ($Static) {
68
+ $binaryName += "-static"
69
+ }
70
+
71
+ $binaryPath = if ($Target -eq (rustc -Vv | Select-String "host: (.*)" | ForEach-Object { $_.Matches.Groups[1].Value })) {
72
+ "target/release/cursor-api.exe"
73
+ } else {
74
+ "target/$Target/release/cursor-api.exe"
75
+ }
76
+
77
+ if (Test-Path $binaryPath) {
78
+ Copy-Item $binaryPath "release/$binaryName-$Target.exe"
79
+ Write-Info "完成构建 $Target"
80
+ } else {
81
+ Write-Warn "构建产物未找到: $Target"
82
+ Write-Warn "查找路径: $binaryPath"
83
+ Write-Warn "当前目录内容:"
84
+ Get-ChildItem -Recurse target/
85
+ return $false
86
+ }
87
+
88
+ return $true
89
+ }
90
+
91
+ if ($Help) {
92
+ Show-Help
93
+ exit 0
94
+ }
95
+
96
+ # 检查依赖
97
+ Check-Requirements
98
+
99
+ # 创建 release 目录
100
+ New-Item -ItemType Directory -Force -Path release | Out-Null
101
+
102
+ # 设置静态链接标志
103
+ $rustFlags = ""
104
+ if ($Static) {
105
+ $rustFlags = "-C target-feature=+crt-static"
106
+ }
107
+
108
+ # 获取目标架构
109
+ $arch = if ($Architecture) {
110
+ $Architecture
111
+ } else {
112
+ switch ($env:PROCESSOR_ARCHITECTURE) {
113
+ "AMD64" { "x86_64" }
114
+ "ARM64" { "aarch64" }
115
+ "X86" { "i686" }
116
+ default { Write-Error "不支持的架构: $env:PROCESSOR_ARCHITECTURE" }
117
+ }
118
+ }
119
+ $target = "$arch-pc-windows-msvc"
120
+
121
+ Write-Info "开始构建..."
122
+ if (-not (Build-Target -Target $target -RustFlags $rustFlags)) {
123
+ Write-Error "构建失败"
124
+ }
125
+
126
+ Write-Info "构建完成!"
scripts/build.sh ADDED
@@ -0,0 +1,194 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+
4
+ # 颜色输出函数
5
+ info() { echo -e "\033[1;34m[INFO]\033[0m $*"; }
6
+ warn() { echo -e "\033[1;33m[WARN]\033[0m $*"; }
7
+ error() { echo -e "\033[1;31m[ERROR]\033[0m $*" >&2; exit 1; }
8
+
9
+ # 检查必要的工具
10
+ check_requirements() {
11
+ local missing_tools=()
12
+
13
+ # 基础工具检查
14
+ for tool in cargo protoc npm node; do
15
+ if ! command -v "$tool" &>/dev/null; then
16
+ missing_tools+=("$tool")
17
+ fi
18
+ done
19
+
20
+ if [[ ${#missing_tools[@]} -gt 0 ]]; then
21
+ error "缺少必要工具: ${missing_tools[*]}"
22
+ fi
23
+ }
24
+
25
+ # 解析参数
26
+ USE_STATIC=false
27
+
28
+ while [[ $# -gt 0 ]]; do
29
+ case $1 in
30
+ --static) USE_STATIC=true ;;
31
+ --help) show_help; exit 0 ;;
32
+ *) error "未知参数: $1" ;;
33
+ esac
34
+ shift
35
+ done
36
+
37
+ # 帮助信息
38
+ show_help() {
39
+ cat << EOF
40
+ 用法: $(basename "$0") [选项]
41
+
42
+ 选项:
43
+ --static 使用静态链接(默认动态链接)
44
+ --help 显示此帮助信息
45
+
46
+ 不带参数时只编译当前平台
47
+ EOF
48
+ }
49
+
50
+ # 并行构建函数
51
+ build_target() {
52
+ local target=$1
53
+ local extension=""
54
+ local rustflags="${2:-}"
55
+
56
+ info "正在构建 $target..."
57
+
58
+ # 确定文件后缀
59
+ [[ $target == *"windows"* ]] && extension=".exe"
60
+
61
+ # 构建
62
+ if [[ $target != "$CURRENT_TARGET" ]]; then
63
+ env RUSTFLAGS="$rustflags" cargo build --target "$target" --release
64
+ else
65
+ env RUSTFLAGS="$rustflags" cargo build --release
66
+ fi
67
+
68
+ # 移动编译产物到 release 目录
69
+ local binary_name="cursor-api"
70
+ [[ $USE_STATIC == true ]] && binary_name+="-static"
71
+
72
+ local binary_path
73
+ if [[ $target == "$CURRENT_TARGET" ]]; then
74
+ binary_path="target/release/cursor-api$extension"
75
+ else
76
+ binary_path="target/$target/release/cursor-api$extension"
77
+ fi
78
+
79
+ if [[ -f "$binary_path" ]]; then
80
+ cp "$binary_path" "release/${binary_name}-$target$extension"
81
+ info "完成构建 $target"
82
+ else
83
+ warn "构建产物未找到: $target"
84
+ warn "查找路径: $binary_path"
85
+ warn "当前目录内容:"
86
+ ls -R target/
87
+ return 1
88
+ fi
89
+ }
90
+
91
+ # 获取 CPU 架构和操作系统
92
+ ARCH=$(uname -m | sed 's/^aarch64\|arm64$/aarch64/;s/^x86_64\|x86-64\|x64\|amd64$/x86_64/')
93
+ OS=$(uname -s)
94
+
95
+ # 确定当前系统的目标平台
96
+ get_target() {
97
+ local arch=$1
98
+ local os=$2
99
+ case "$os" in
100
+ "Darwin") echo "${arch}-apple-darwin" ;;
101
+ "Linux")
102
+ if [[ $USE_STATIC == true ]]; then
103
+ echo "${arch}-unknown-linux-musl"
104
+ else
105
+ echo "${arch}-unknown-linux-gnu"
106
+ fi
107
+ ;;
108
+ "MINGW"*|"MSYS"*|"CYGWIN"*|"Windows_NT") echo "${arch}-pc-windows-msvc" ;;
109
+ "FreeBSD") echo "${arch}-unknown-freebsd" ;;
110
+ *) error "不支持的系统: $os" ;;
111
+ esac
112
+ }
113
+
114
+ # 设置当前目标平台
115
+ CURRENT_TARGET=$(get_target "$ARCH" "$OS")
116
+
117
+ # 检查是否成功获取目标平台
118
+ [ -z "$CURRENT_TARGET" ] && error "无法确定当前系统的目标平台"
119
+
120
+ # 获取系统对应的所有目标
121
+ get_targets() {
122
+ case "$1" in
123
+ "linux")
124
+ # Linux 只构建当前架构
125
+ echo "$CURRENT_TARGET"
126
+ ;;
127
+ "freebsd")
128
+ # FreeBSD 只构建当前架构
129
+ echo "$CURRENT_TARGET"
130
+ ;;
131
+ "windows")
132
+ # Windows 只构建当前架构
133
+ echo "$CURRENT_TARGET"
134
+ ;;
135
+ "macos")
136
+ # macOS 构建所有 macOS 目标
137
+ echo "x86_64-apple-darwin aarch64-apple-darwin"
138
+ ;;
139
+ *) error "不支持的系统组: $1" ;;
140
+ esac
141
+ }
142
+
143
+ # 检查依赖
144
+ check_requirements
145
+
146
+ # 确定要构建的目标
147
+ case "$OS" in
148
+ Darwin)
149
+ TARGETS=($(get_targets "macos"))
150
+ ;;
151
+ Linux)
152
+ TARGETS=($(get_targets "linux"))
153
+ ;;
154
+ FreeBSD)
155
+ TARGETS=($(get_targets "freebsd"))
156
+ ;;
157
+ MINGW*|MSYS*|CYGWIN*|Windows_NT)
158
+ TARGETS=($(get_targets "windows"))
159
+ ;;
160
+ *) error "不支持的系统: $OS" ;;
161
+ esac
162
+
163
+ # 创建 release 目录
164
+ mkdir -p release
165
+
166
+ # 设置静态链接标志
167
+ RUSTFLAGS="-C link-arg=-s"
168
+ [[ $USE_STATIC == true ]] && RUSTFLAGS="-C target-feature=+crt-static -C link-arg=-s"
169
+
170
+ # 并行构建所有目标
171
+ info "开始构建..."
172
+ for target in "${TARGETS[@]}"; do
173
+ build_target "$target" "$RUSTFLAGS" &
174
+ done
175
+
176
+ # 等待所有构建完成
177
+ wait
178
+
179
+ # 为 macOS 平台创建通用二进制
180
+ if [[ "$OS" == "Darwin" ]] && [[ ${#TARGETS[@]} -gt 1 ]]; then
181
+ binary_suffix=""
182
+ [[ $USE_STATIC == true ]] && binary_suffix="-static"
183
+
184
+ if [[ -f "release/cursor-api${binary_suffix}-x86_64-apple-darwin" ]] && \
185
+ [[ -f "release/cursor-api${binary_suffix}-aarch64-apple-darwin" ]]; then
186
+ info "创建 macOS 通用二进制..."
187
+ lipo -create \
188
+ "release/cursor-api${binary_suffix}-x86_64-apple-darwin" \
189
+ "release/cursor-api${binary_suffix}-aarch64-apple-darwin" \
190
+ -output "release/cursor-api${binary_suffix}-universal-apple-darwin"
191
+ fi
192
+ fi
193
+
194
+ info "构建完成!"
scripts/minify.js ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env node
2
+
3
+ const { minify: minifyHtml } = require('html-minifier-terser');
4
+ const { minify: minifyJs } = require('terser');
5
+ const CleanCSS = require('clean-css');
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+
9
+ // 配置选项
10
+ const options = {
11
+ collapseWhitespace: true,
12
+ removeComments: true,
13
+ removeEmptyAttributes: true,
14
+ removeOptionalTags: true,
15
+ removeRedundantAttributes: true,
16
+ removeScriptTypeAttributes: true,
17
+ removeStyleLinkTypeAttributes: true,
18
+ minifyCSS: true,
19
+ minifyJS: true,
20
+ processScripts: ['application/json'],
21
+ };
22
+
23
+ // CSS 压缩选项
24
+ const cssOptions = {
25
+ level: 2
26
+ };
27
+
28
+ // 处理文件
29
+ async function minifyFile(inputPath, outputPath) {
30
+ try {
31
+ const ext = path.extname(inputPath).toLowerCase();
32
+ const content = fs.readFileSync(inputPath, 'utf8');
33
+ let minified;
34
+
35
+ switch (ext) {
36
+ case '.html':
37
+ minified = await minifyHtml(content, options);
38
+ break;
39
+ case '.js':
40
+ const result = await minifyJs(content);
41
+ minified = result.code;
42
+ break;
43
+ case '.css':
44
+ minified = new CleanCSS(cssOptions).minify(content).styles;
45
+ break;
46
+ default:
47
+ throw new Error(`Unsupported file type: ${ext}`);
48
+ }
49
+
50
+ fs.writeFileSync(outputPath, minified);
51
+ console.log(`✓ Minified ${path.basename(inputPath)} -> ${path.basename(outputPath)}`);
52
+ } catch (err) {
53
+ console.error(`✗ Error processing ${inputPath}:`, err);
54
+ process.exit(1);
55
+ }
56
+ }
57
+
58
+ // 主函数
59
+ async function main() {
60
+ // 获取命令行参数,跳过前两个参数(node和脚本路径)
61
+ const files = process.argv.slice(2);
62
+
63
+ if (files.length === 0) {
64
+ console.error('No input files specified');
65
+ process.exit(1);
66
+ }
67
+
68
+ const staticDir = path.join(__dirname, '..', 'static');
69
+
70
+ for (const file of files) {
71
+ const inputPath = path.join(staticDir, file);
72
+ const ext = path.extname(file);
73
+ const outputPath = path.join(
74
+ staticDir,
75
+ file.replace(ext, `.min${ext}`)
76
+ );
77
+ await minifyFile(inputPath, outputPath);
78
+ }
79
+ }
80
+
81
+ main();
scripts/package-lock.json ADDED
@@ -0,0 +1,267 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "html-minifier-scripts",
3
+ "version": "1.0.0",
4
+ "lockfileVersion": 3,
5
+ "requires": true,
6
+ "packages": {
7
+ "": {
8
+ "name": "html-minifier-scripts",
9
+ "version": "1.0.0",
10
+ "dependencies": {
11
+ "clean-css": "^5.3.3",
12
+ "html-minifier-terser": "^7.2.0",
13
+ "terser": "^5.37.0"
14
+ },
15
+ "engines": {
16
+ "node": ">=14.0.0"
17
+ }
18
+ },
19
+ "node_modules/@jridgewell/gen-mapping": {
20
+ "version": "0.3.8",
21
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
22
+ "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==",
23
+ "license": "MIT",
24
+ "dependencies": {
25
+ "@jridgewell/set-array": "^1.2.1",
26
+ "@jridgewell/sourcemap-codec": "^1.4.10",
27
+ "@jridgewell/trace-mapping": "^0.3.24"
28
+ },
29
+ "engines": {
30
+ "node": ">=6.0.0"
31
+ }
32
+ },
33
+ "node_modules/@jridgewell/resolve-uri": {
34
+ "version": "3.1.2",
35
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
36
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
37
+ "license": "MIT",
38
+ "engines": {
39
+ "node": ">=6.0.0"
40
+ }
41
+ },
42
+ "node_modules/@jridgewell/set-array": {
43
+ "version": "1.2.1",
44
+ "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
45
+ "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
46
+ "license": "MIT",
47
+ "engines": {
48
+ "node": ">=6.0.0"
49
+ }
50
+ },
51
+ "node_modules/@jridgewell/source-map": {
52
+ "version": "0.3.6",
53
+ "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz",
54
+ "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==",
55
+ "license": "MIT",
56
+ "dependencies": {
57
+ "@jridgewell/gen-mapping": "^0.3.5",
58
+ "@jridgewell/trace-mapping": "^0.3.25"
59
+ }
60
+ },
61
+ "node_modules/@jridgewell/sourcemap-codec": {
62
+ "version": "1.5.0",
63
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
64
+ "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
65
+ "license": "MIT"
66
+ },
67
+ "node_modules/@jridgewell/trace-mapping": {
68
+ "version": "0.3.25",
69
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
70
+ "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
71
+ "license": "MIT",
72
+ "dependencies": {
73
+ "@jridgewell/resolve-uri": "^3.1.0",
74
+ "@jridgewell/sourcemap-codec": "^1.4.14"
75
+ }
76
+ },
77
+ "node_modules/acorn": {
78
+ "version": "8.14.0",
79
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
80
+ "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
81
+ "license": "MIT",
82
+ "bin": {
83
+ "acorn": "bin/acorn"
84
+ },
85
+ "engines": {
86
+ "node": ">=0.4.0"
87
+ }
88
+ },
89
+ "node_modules/buffer-from": {
90
+ "version": "1.1.2",
91
+ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
92
+ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
93
+ "license": "MIT"
94
+ },
95
+ "node_modules/camel-case": {
96
+ "version": "4.1.2",
97
+ "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz",
98
+ "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==",
99
+ "license": "MIT",
100
+ "dependencies": {
101
+ "pascal-case": "^3.1.2",
102
+ "tslib": "^2.0.3"
103
+ }
104
+ },
105
+ "node_modules/clean-css": {
106
+ "version": "5.3.3",
107
+ "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz",
108
+ "integrity": "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==",
109
+ "license": "MIT",
110
+ "dependencies": {
111
+ "source-map": "~0.6.0"
112
+ },
113
+ "engines": {
114
+ "node": ">= 10.0"
115
+ }
116
+ },
117
+ "node_modules/commander": {
118
+ "version": "10.0.1",
119
+ "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz",
120
+ "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==",
121
+ "license": "MIT",
122
+ "engines": {
123
+ "node": ">=14"
124
+ }
125
+ },
126
+ "node_modules/dot-case": {
127
+ "version": "3.0.4",
128
+ "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz",
129
+ "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==",
130
+ "license": "MIT",
131
+ "dependencies": {
132
+ "no-case": "^3.0.4",
133
+ "tslib": "^2.0.3"
134
+ }
135
+ },
136
+ "node_modules/entities": {
137
+ "version": "4.5.0",
138
+ "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
139
+ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
140
+ "license": "BSD-2-Clause",
141
+ "engines": {
142
+ "node": ">=0.12"
143
+ },
144
+ "funding": {
145
+ "url": "https://github.com/fb55/entities?sponsor=1"
146
+ }
147
+ },
148
+ "node_modules/html-minifier-terser": {
149
+ "version": "7.2.0",
150
+ "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-7.2.0.tgz",
151
+ "integrity": "sha512-tXgn3QfqPIpGl9o+K5tpcj3/MN4SfLtsx2GWwBC3SSd0tXQGyF3gsSqad8loJgKZGM3ZxbYDd5yhiBIdWpmvLA==",
152
+ "license": "MIT",
153
+ "dependencies": {
154
+ "camel-case": "^4.1.2",
155
+ "clean-css": "~5.3.2",
156
+ "commander": "^10.0.0",
157
+ "entities": "^4.4.0",
158
+ "param-case": "^3.0.4",
159
+ "relateurl": "^0.2.7",
160
+ "terser": "^5.15.1"
161
+ },
162
+ "bin": {
163
+ "html-minifier-terser": "cli.js"
164
+ },
165
+ "engines": {
166
+ "node": "^14.13.1 || >=16.0.0"
167
+ }
168
+ },
169
+ "node_modules/lower-case": {
170
+ "version": "2.0.2",
171
+ "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz",
172
+ "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==",
173
+ "license": "MIT",
174
+ "dependencies": {
175
+ "tslib": "^2.0.3"
176
+ }
177
+ },
178
+ "node_modules/no-case": {
179
+ "version": "3.0.4",
180
+ "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz",
181
+ "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==",
182
+ "license": "MIT",
183
+ "dependencies": {
184
+ "lower-case": "^2.0.2",
185
+ "tslib": "^2.0.3"
186
+ }
187
+ },
188
+ "node_modules/param-case": {
189
+ "version": "3.0.4",
190
+ "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz",
191
+ "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==",
192
+ "license": "MIT",
193
+ "dependencies": {
194
+ "dot-case": "^3.0.4",
195
+ "tslib": "^2.0.3"
196
+ }
197
+ },
198
+ "node_modules/pascal-case": {
199
+ "version": "3.1.2",
200
+ "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz",
201
+ "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==",
202
+ "license": "MIT",
203
+ "dependencies": {
204
+ "no-case": "^3.0.4",
205
+ "tslib": "^2.0.3"
206
+ }
207
+ },
208
+ "node_modules/relateurl": {
209
+ "version": "0.2.7",
210
+ "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz",
211
+ "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==",
212
+ "license": "MIT",
213
+ "engines": {
214
+ "node": ">= 0.10"
215
+ }
216
+ },
217
+ "node_modules/source-map": {
218
+ "version": "0.6.1",
219
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
220
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
221
+ "license": "BSD-3-Clause",
222
+ "engines": {
223
+ "node": ">=0.10.0"
224
+ }
225
+ },
226
+ "node_modules/source-map-support": {
227
+ "version": "0.5.21",
228
+ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
229
+ "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
230
+ "license": "MIT",
231
+ "dependencies": {
232
+ "buffer-from": "^1.0.0",
233
+ "source-map": "^0.6.0"
234
+ }
235
+ },
236
+ "node_modules/terser": {
237
+ "version": "5.37.0",
238
+ "resolved": "https://registry.npmjs.org/terser/-/terser-5.37.0.tgz",
239
+ "integrity": "sha512-B8wRRkmre4ERucLM/uXx4MOV5cbnOlVAqUst+1+iLKPI0dOgFO28f84ptoQt9HEI537PMzfYa/d+GEPKTRXmYA==",
240
+ "license": "BSD-2-Clause",
241
+ "dependencies": {
242
+ "@jridgewell/source-map": "^0.3.3",
243
+ "acorn": "^8.8.2",
244
+ "commander": "^2.20.0",
245
+ "source-map-support": "~0.5.20"
246
+ },
247
+ "bin": {
248
+ "terser": "bin/terser"
249
+ },
250
+ "engines": {
251
+ "node": ">=10"
252
+ }
253
+ },
254
+ "node_modules/terser/node_modules/commander": {
255
+ "version": "2.20.3",
256
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
257
+ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
258
+ "license": "MIT"
259
+ },
260
+ "node_modules/tslib": {
261
+ "version": "2.8.1",
262
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
263
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
264
+ "license": "0BSD"
265
+ }
266
+ }
267
+ }
scripts/package.json ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "html-minifier-scripts",
3
+ "version": "1.0.0",
4
+ "private": true,
5
+ "engines": {
6
+ "node": ">=14.0.0"
7
+ },
8
+ "dependencies": {
9
+ "clean-css": "^5.3.3",
10
+ "html-minifier-terser": "^7.2.0",
11
+ "terser": "^5.37.0"
12
+ }
13
+ }
scripts/setup.ps1 ADDED
@@ -0,0 +1,179 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ���ô���ʱִֹͣ��
2
+ $ErrorActionPreference = "Stop"
3
+ $ProgressPreference = "SilentlyContinue" # �ӿ������ٶ�
4
+
5
+ # ��ɫ�������
6
+ function Write-Info { param($Message) Write-Host "[INFO] $Message" -ForegroundColor Blue }
7
+ function Write-Warn { param($Message) Write-Host "[WARN] $Message" -ForegroundColor Yellow }
8
+ function Write-Success { param($Message) Write-Host "[SUCCESS] $Message" -ForegroundColor Green }
9
+ function Write-Error { param($Message) Write-Host "[ERROR] $Message" -ForegroundColor Red; exit 1 }
10
+
11
+ # ������ԱȨ��
12
+ function Test-Administrator {
13
+ $user = [Security.Principal.WindowsIdentity]::GetCurrent()
14
+ $principal = New-Object Security.Principal.WindowsPrincipal $user
15
+ return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
16
+ }
17
+
18
+ if (-not (Test-Administrator)) {
19
+ Write-Error "���Թ���ԱȨ�����д˽ű�"
20
+ }
21
+
22
+ # ������Ϣ
23
+ function Show-Help {
24
+ Write-Host @"
25
+ �÷�: $(Split-Path $MyInvocation.ScriptName -Leaf) [ѡ��]
26
+
27
+ ѡ��:
28
+ -NoVS ����װ Visual Studio Build Tools
29
+ -NoRust ����װ Rust
30
+ -NoNode ����װ Node.js
31
+ -Help ��ʾ�˰�����Ϣ
32
+
33
+ ʾ��:
34
+ .\setup.ps1
35
+ .\setup.ps1 -NoVS
36
+ .\setup.ps1 -NoRust -NoNode
37
+ "@
38
+ }
39
+
40
+ # ��������
41
+ param(
42
+ [switch]$NoVS,
43
+ [switch]$NoRust,
44
+ [switch]$NoNode,
45
+ [switch]$Help
46
+ )
47
+
48
+ if ($Help) {
49
+ Show-Help
50
+ exit 0
51
+ }
52
+
53
+ # ��鲢��װ Chocolatey
54
+ function Install-Chocolatey {
55
+ Write-Info "��� Chocolatey..."
56
+ if (-not (Get-Command choco -ErrorAction SilentlyContinue)) {
57
+ Write-Info "��װ Chocolatey..."
58
+ Set-ExecutionPolicy Bypass -Scope Process -Force
59
+ [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072
60
+ try {
61
+ Invoke-Expression ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))
62
+ }
63
+ catch {
64
+ Write-Error "��װ Chocolatey ʧ��: $_"
65
+ }
66
+ # ˢ�»�������
67
+ $env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User")
68
+ }
69
+ }
70
+
71
+ # ��װ Visual Studio Build Tools
72
+ function Install-VSBuildTools {
73
+ if ($NoVS) {
74
+ Write-Info "���� Visual Studio Build Tools ��װ"
75
+ return
76
+ }
77
+
78
+ Write-Info "��� Visual Studio Build Tools..."
79
+ $vsPath = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe"
80
+ if (-not (Test-Path $vsPath)) {
81
+ Write-Info "��װ Visual Studio Build Tools..."
82
+ try {
83
+ # ���ذ�װ����
84
+ $vsInstallerUrl = "https://aka.ms/vs/17/release/vs_BuildTools.exe"
85
+ $vsInstallerPath = "$env:TEMP\vs_BuildTools.exe"
86
+ Invoke-WebRequest -Uri $vsInstallerUrl -OutFile $vsInstallerPath
87
+
88
+ # ��װ
89
+ $process = Start-Process -FilePath $vsInstallerPath -ArgumentList `
90
+ "--quiet", "--wait", "--norestart", "--nocache", `
91
+ "--installPath", "${env:ProgramFiles(x86)}\Microsoft Visual Studio\2022\BuildTools", `
92
+ "--add", "Microsoft.VisualStudio.Workload.VCTools" `
93
+ -NoNewWindow -Wait -PassThru
94
+
95
+ if ($process.ExitCode -ne 0) {
96
+ Write-Error "Visual Studio Build Tools ��װʧ��"
97
+ }
98
+
99
+ Remove-Item $vsInstallerPath -Force
100
+ }
101
+ catch {
102
+ Write-Error "��װ Visual Studio Build Tools ʧ��: $_"
103
+ }
104
+ }
105
+ else {
106
+ Write-Info "Visual Studio Build Tools �Ѱ�װ"
107
+ }
108
+ }
109
+
110
+ # ��װ Rust
111
+ function Install-Rust {
112
+ if ($NoRust) {
113
+ Write-Info "���� Rust ��װ"
114
+ return
115
+ }
116
+
117
+ Write-Info "��� Rust..."
118
+ if (-not (Get-Command rustc -ErrorAction SilentlyContinue)) {
119
+ Write-Info "��װ Rust..."
120
+ try {
121
+ $rustupInit = "$env:TEMP\rustup-init.exe"
122
+ Invoke-WebRequest -Uri "https://win.rustup.rs" -OutFile $rustupInit
123
+ Start-Process -FilePath $rustupInit -ArgumentList "-y" -Wait
124
+ Remove-Item $rustupInit -Force
125
+
126
+ # ˢ�»�������
127
+ $env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User")
128
+ }
129
+ catch {
130
+ Write-Error "��װ Rust ʧ��: $_"
131
+ }
132
+ }
133
+
134
+ # ����Ŀ��ƽ̨
135
+ Write-Info "���� Rust Ŀ��ƽ̨..."
136
+ $arch = if ([Environment]::Is64BitOperatingSystem) { "x86_64" } else { "i686" }
137
+ rustup target add "$arch-pc-windows-msvc"
138
+ }
139
+
140
+ # ��װ��������
141
+ function Install-Tools {
142
+ Write-Info "��װ��Ҫ����..."
143
+
144
+ # ��װ protoc
145
+ if (-not (Get-Command protoc -ErrorAction SilentlyContinue)) {
146
+ Write-Info "��װ Protocol Buffers..."
147
+ choco install -y protoc
148
+ }
149
+
150
+ # ��װ Git
151
+ if (-not (Get-Command git -ErrorAction SilentlyContinue)) {
152
+ Write-Info "��װ Git..."
153
+ choco install -y git
154
+ }
155
+
156
+ # ��װ Node.js
157
+ if (-not $NoNode -and -not (Get-Command node -ErrorAction SilentlyContinue)) {
158
+ Write-Info "��װ Node.js..."
159
+ choco install -y nodejs
160
+ }
161
+
162
+ # ˢ�»�������
163
+ $env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User")
164
+ }
165
+
166
+ # ������
167
+ try {
168
+ Write-Info "��ʼ��װ��Ҫ���..."
169
+
170
+ Install-Chocolatey
171
+ Install-VSBuildTools
172
+ Install-Rust
173
+ Install-Tools
174
+
175
+ Write-Success "��װ��ɣ�"
176
+ }
177
+ catch {
178
+ Write-Error "��װ�����г��ִ���: $_"
179
+ }
scripts/setup.sh ADDED
@@ -0,0 +1,157 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+
3
+ # 设置错误时退出
4
+ set -e
5
+
6
+ # 颜色输出
7
+ RED='\033[0;31m'
8
+ GREEN='\033[0;32m'
9
+ BLUE='\033[0;34m'
10
+ NC='\033[0m' # No Color
11
+
12
+ info() {
13
+ echo -e "${BLUE}[INFO] $1${NC}"
14
+ }
15
+
16
+ error() {
17
+ echo -e "${RED}[ERROR] $1${NC}"
18
+ exit 1
19
+ }
20
+
21
+ # 检查是否为 root 用户(FreeBSD 和 Linux)
22
+ if [ "$(uname)" != "Darwin" ] && [ "$EUID" -ne 0 ]; then
23
+ error "请使用 root 权限运行此脚本 (sudo ./setup.sh)"
24
+ fi
25
+
26
+ # 检测包管理器
27
+ if command -v brew &> /dev/null; then
28
+ PKG_MANAGER="brew"
29
+ info "检测到 macOS/Homebrew 系统"
30
+ elif command -v pkg &> /dev/null; then
31
+ PKG_MANAGER="pkg"
32
+ info "检测到 FreeBSD 系统"
33
+ elif command -v apt-get &> /dev/null; then
34
+ PKG_MANAGER="apt-get"
35
+ info "检测到 Debian/Ubuntu 系统"
36
+ elif command -v dnf &> /dev/null; then
37
+ PKG_MANAGER="dnf"
38
+ info "检测到 Fedora/RHEL 系统"
39
+ elif command -v yum &> /dev/null; then
40
+ PKG_MANAGER="yum"
41
+ info "检测到 CentOS 系统"
42
+ else
43
+ error "未检测到支持的包管理器"
44
+ fi
45
+
46
+ # 更新包管理器缓存
47
+ info "更新包管理器缓存..."
48
+ case $PKG_MANAGER in
49
+ "brew")
50
+ brew update
51
+ ;;
52
+ "pkg")
53
+ pkg update
54
+ ;;
55
+ *)
56
+ $PKG_MANAGER update -y
57
+ ;;
58
+ esac
59
+
60
+ # 安装基础构建工具
61
+ info "安装基础构建工具..."
62
+ case $PKG_MANAGER in
63
+ "brew")
64
+ brew install \
65
+ protobuf \
66
+ pkg-config \
67
+ openssl \
68
+ curl \
69
+ git \
70
+ node
71
+ ;;
72
+ "pkg")
73
+ pkg install -y \
74
+ gmake \
75
+ protobuf \
76
+ pkgconf \
77
+ openssl \
78
+ curl \
79
+ git \
80
+ node
81
+ ;;
82
+ "apt-get")
83
+ $PKG_MANAGER install -y --no-install-recommends \
84
+ build-essential \
85
+ protobuf-compiler \
86
+ pkg-config \
87
+ libssl-dev \
88
+ ca-certificates \
89
+ curl \
90
+ tzdata \
91
+ git
92
+ ;;
93
+ *)
94
+ $PKG_MANAGER install -y \
95
+ gcc \
96
+ gcc-c++ \
97
+ make \
98
+ protobuf-compiler \
99
+ pkg-config \
100
+ openssl-devel \
101
+ ca-certificates \
102
+ curl \
103
+ tzdata \
104
+ git
105
+ ;;
106
+ esac
107
+
108
+ # 安装 Node.js 和 npm(如果还没有通过包管理器安装)
109
+ if ! command -v node &> /dev/null && [ "$PKG_MANAGER" != "brew" ] && [ "$PKG_MANAGER" != "pkg" ]; then
110
+ info "安装 Node.js 和 npm..."
111
+ if [ "$PKG_MANAGER" = "apt-get" ]; then
112
+ curl -fsSL https://deb.nodesource.com/setup_lts.x | bash -
113
+ $PKG_MANAGER install -y nodejs
114
+ else
115
+ curl -fsSL https://rpm.nodesource.com/setup_lts.x | bash -
116
+ $PKG_MANAGER install -y nodejs
117
+ fi
118
+ fi
119
+
120
+ # 安装 Rust(如果未安装)
121
+ if ! command -v rustc &> /dev/null; then
122
+ info "安装 Rust..."
123
+ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
124
+ . "$HOME/.cargo/env"
125
+ fi
126
+
127
+ # 添加目标平台
128
+ info "添加 Rust 目标平台..."
129
+ case "$(uname)" in
130
+ "FreeBSD")
131
+ rustup target add x86_64-unknown-freebsd
132
+ ;;
133
+ "Darwin")
134
+ rustup target add x86_64-apple-darwin aarch64-apple-darwin
135
+ ;;
136
+ *)
137
+ rustup target add x86_64-unknown-linux-gnu
138
+ ;;
139
+ esac
140
+
141
+ # 清理包管理器缓存
142
+ case $PKG_MANAGER in
143
+ "apt-get")
144
+ rm -rf /var/lib/apt/lists/*
145
+ ;;
146
+ "pkg")
147
+ pkg clean -y
148
+ ;;
149
+ esac
150
+
151
+ # 设置时区(除了 macOS)
152
+ if [ "$(uname)" != "Darwin" ]; then
153
+ info "设置时区为 Asia/Shanghai..."
154
+ ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
155
+ fi
156
+
157
+ echo -e "${GREEN}安装完成!${NC}"
src/app.rs ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ pub mod config;
2
+ pub mod constant;
3
+ pub mod model;
4
+ pub mod lazy;
src/app/config.rs ADDED
@@ -0,0 +1,229 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use super::{
2
+ constant::AUTHORIZATION_BEARER_PREFIX,
3
+ lazy::AUTH_TOKEN,
4
+ model::AppConfig,
5
+ };
6
+ use crate::common::models::{
7
+ config::{ConfigData, ConfigUpdateRequest},
8
+ ApiStatus, ErrorResponse, NormalResponse,
9
+ };
10
+ use axum::{
11
+ http::{header::AUTHORIZATION, HeaderMap, StatusCode},
12
+ Json,
13
+ };
14
+
15
+ // 定义处理更新操作的宏
16
+ macro_rules! handle_update {
17
+ ($request:expr, $field:ident, $update_fn:expr, $field_name:expr) => {
18
+ if let Some($field) = $request.$field {
19
+ if let Err(e) = $update_fn($field) {
20
+ return Err((
21
+ StatusCode::INTERNAL_SERVER_ERROR,
22
+ Json(ErrorResponse {
23
+ status: ApiStatus::Failed,
24
+ code: Some(500),
25
+ error: Some(format!("更新 {} 失败: {}", $field_name, e)),
26
+ message: None,
27
+ }),
28
+ ));
29
+ }
30
+ }
31
+ };
32
+ }
33
+
34
+ // 定义处理重置操作的宏
35
+ macro_rules! handle_reset {
36
+ ($request:expr, $field:ident, $reset_fn:expr, $field_name:expr) => {
37
+ if $request.$field.is_some() {
38
+ if let Err(e) = $reset_fn() {
39
+ return Err((
40
+ StatusCode::INTERNAL_SERVER_ERROR,
41
+ Json(ErrorResponse {
42
+ status: ApiStatus::Failed,
43
+ code: Some(500),
44
+ error: Some(format!("重置 {} 失败: {}", $field_name, e)),
45
+ message: None,
46
+ }),
47
+ ));
48
+ }
49
+ }
50
+ };
51
+ }
52
+
53
+ pub async fn handle_config_update(
54
+ headers: HeaderMap,
55
+ Json(request): Json<ConfigUpdateRequest>,
56
+ ) -> Result<Json<NormalResponse<ConfigData>>, (StatusCode, Json<ErrorResponse>)> {
57
+ let auth_header = headers
58
+ .get(AUTHORIZATION)
59
+ .and_then(|h| h.to_str().ok())
60
+ .and_then(|h| h.strip_prefix(AUTHORIZATION_BEARER_PREFIX))
61
+ .ok_or((
62
+ StatusCode::UNAUTHORIZED,
63
+ Json(ErrorResponse {
64
+ status: ApiStatus::Failed,
65
+ code: Some(401),
66
+ error: Some("未提供认证令牌".to_string()),
67
+ message: None,
68
+ }),
69
+ ))?;
70
+
71
+ if auth_header != AUTH_TOKEN.as_str() {
72
+ return Err((
73
+ StatusCode::UNAUTHORIZED,
74
+ Json(ErrorResponse {
75
+ status: ApiStatus::Failed,
76
+ code: Some(401),
77
+ error: Some("无效的认证令牌".to_string()),
78
+ message: None,
79
+ }),
80
+ ));
81
+ }
82
+
83
+ match request.action.as_str() {
84
+ "get" => Ok(Json(NormalResponse {
85
+ status: ApiStatus::Success,
86
+ data: Some(ConfigData {
87
+ page_content: AppConfig::get_page_content(&request.path),
88
+ enable_stream_check: AppConfig::get_stream_check(),
89
+ include_stop_stream: AppConfig::get_stop_stream(),
90
+ vision_ability: AppConfig::get_vision_ability(),
91
+ enable_slow_pool: AppConfig::get_slow_pool(),
92
+ enable_all_claude: AppConfig::get_allow_claude(),
93
+ check_usage_models: AppConfig::get_usage_check(),
94
+ }),
95
+ message: None,
96
+ })),
97
+
98
+ "update" => {
99
+ // 处理页面内容更新
100
+ if !request.path.is_empty() && request.content.is_some() {
101
+ let content = request.content.unwrap();
102
+ if let Err(e) = AppConfig::update_page_content(&request.path, content) {
103
+ return Err((
104
+ StatusCode::INTERNAL_SERVER_ERROR,
105
+ Json(ErrorResponse {
106
+ status: ApiStatus::Failed,
107
+ code: Some(500),
108
+ error: Some(format!("更新页面内容失败: {}", e)),
109
+ message: None,
110
+ }),
111
+ ));
112
+ }
113
+ }
114
+
115
+ handle_update!(
116
+ request,
117
+ enable_stream_check,
118
+ AppConfig::update_stream_check,
119
+ "enable_stream_check"
120
+ );
121
+ handle_update!(
122
+ request,
123
+ include_stop_stream,
124
+ AppConfig::update_stop_stream,
125
+ "include_stop_stream"
126
+ );
127
+ handle_update!(
128
+ request,
129
+ vision_ability,
130
+ AppConfig::update_vision_ability,
131
+ "vision_ability"
132
+ );
133
+ handle_update!(
134
+ request,
135
+ enable_slow_pool,
136
+ AppConfig::update_slow_pool,
137
+ "enable_slow_pool"
138
+ );
139
+ handle_update!(
140
+ request,
141
+ enable_all_claude,
142
+ AppConfig::update_allow_claude,
143
+ "enable_all_claude"
144
+ );
145
+ handle_update!(
146
+ request,
147
+ check_usage_models,
148
+ AppConfig::update_usage_check,
149
+ "check_usage_models"
150
+ );
151
+
152
+ Ok(Json(NormalResponse {
153
+ status: ApiStatus::Success,
154
+ data: None,
155
+ message: Some("配置已更新".to_string()),
156
+ }))
157
+ }
158
+
159
+ "reset" => {
160
+ // 重置页面内容
161
+ if !request.path.is_empty() {
162
+ if let Err(e) = AppConfig::reset_page_content(&request.path) {
163
+ return Err((
164
+ StatusCode::INTERNAL_SERVER_ERROR,
165
+ Json(ErrorResponse {
166
+ status: ApiStatus::Failed,
167
+ code: Some(500),
168
+ error: Some(format!("重置页面内容失败: {}", e)),
169
+ message: None,
170
+ }),
171
+ ));
172
+ }
173
+ }
174
+
175
+ handle_reset!(
176
+ request,
177
+ enable_stream_check,
178
+ AppConfig::reset_stream_check,
179
+ "enable_stream_check"
180
+ );
181
+ handle_reset!(
182
+ request,
183
+ include_stop_stream,
184
+ AppConfig::reset_stop_stream,
185
+ "include_stop_stream"
186
+ );
187
+ handle_reset!(
188
+ request,
189
+ vision_ability,
190
+ AppConfig::reset_vision_ability,
191
+ "vision_ability"
192
+ );
193
+ handle_reset!(
194
+ request,
195
+ enable_slow_pool,
196
+ AppConfig::reset_slow_pool,
197
+ "enable_slow_pool"
198
+ );
199
+ handle_reset!(
200
+ request,
201
+ enable_all_claude,
202
+ AppConfig::reset_allow_claude,
203
+ "enable_all_claude"
204
+ );
205
+ handle_reset!(
206
+ request,
207
+ check_usage_models,
208
+ AppConfig::reset_usage_check,
209
+ "check_usage_models"
210
+ );
211
+
212
+ Ok(Json(NormalResponse {
213
+ status: ApiStatus::Success,
214
+ data: None,
215
+ message: Some("配置已重置".to_string()),
216
+ }))
217
+ }
218
+
219
+ _ => Err((
220
+ StatusCode::BAD_REQUEST,
221
+ Json(ErrorResponse {
222
+ status: ApiStatus::Failed,
223
+ code: Some(400),
224
+ error: Some("无效的操作类型".to_string()),
225
+ message: None,
226
+ }),
227
+ )),
228
+ }
229
+ }
src/app/constant.rs ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ macro_rules! def_pub_const {
2
+ ($name:ident, $value:expr) => {
3
+ pub const $name: &'static str = $value;
4
+ };
5
+ }
6
+
7
+ def_pub_const!(PKG_VERSION, env!("CARGO_PKG_VERSION"));
8
+ // def_pub_const!(PKG_NAME, env!("CARGO_PKG_NAME"));
9
+ // def_pub_const!(PKG_DESCRIPTION, env!("CARGO_PKG_DESCRIPTION"));
10
+ // def_pub_const!(PKG_AUTHORS, env!("CARGO_PKG_AUTHORS"));
11
+ // def_pub_const!(PKG_REPOSITORY, env!("CARGO_PKG_REPOSITORY"));
12
+
13
+ def_pub_const!(EMPTY_STRING, "");
14
+
15
+ def_pub_const!(ROUTE_ROOT_PATH, "/");
16
+ def_pub_const!(ROUTE_HEALTH_PATH, "/health");
17
+ def_pub_const!(ROUTE_GET_HASH, "/get-hash");
18
+ def_pub_const!(ROUTE_GET_CHECKSUM, "/get-checksum");
19
+ def_pub_const!(ROUTE_GET_TIMESTAMP_HEADER, "/get-tsheader");
20
+ def_pub_const!(ROUTE_USER_INFO_PATH, "/userinfo");
21
+ def_pub_const!(ROUTE_API_PATH, "/api");
22
+ def_pub_const!(ROUTE_LOGS_PATH, "/logs");
23
+ def_pub_const!(ROUTE_CONFIG_PATH, "/config");
24
+ def_pub_const!(ROUTE_TOKENINFO_PATH, "/tokeninfo");
25
+ def_pub_const!(ROUTE_GET_TOKENINFO_PATH, "/get-tokeninfo");
26
+ def_pub_const!(ROUTE_UPDATE_TOKENINFO_PATH, "/update-tokeninfo");
27
+ def_pub_const!(ROUTE_ENV_EXAMPLE_PATH, "/env-example");
28
+ def_pub_const!(ROUTE_STATIC_PATH, "/static/:path");
29
+ def_pub_const!(ROUTE_SHARED_STYLES_PATH, "/static/shared-styles.css");
30
+ def_pub_const!(ROUTE_SHARED_JS_PATH, "/static/shared.js");
31
+ def_pub_const!(ROUTE_ABOUT_PATH, "/about");
32
+ def_pub_const!(ROUTE_README_PATH, "/readme");
33
+ def_pub_const!(ROUTE_BASIC_CALIBRATION_PATH, "/basic-calibration");
34
+
35
+ def_pub_const!(DEFAULT_TOKEN_FILE_NAME, ".token");
36
+ def_pub_const!(DEFAULT_TOKEN_LIST_FILE_NAME, ".token-list");
37
+
38
+ def_pub_const!(STATUS_PENDING, "pending");
39
+ def_pub_const!(STATUS_SUCCESS, "success");
40
+ def_pub_const!(STATUS_FAILED, "failed");
41
+
42
+ def_pub_const!(HEADER_NAME_GHOST_MODE, "x-ghost-mode");
43
+
44
+ def_pub_const!(TRUE, "true");
45
+ def_pub_const!(FALSE, "false");
46
+
47
+ // def_pub_const!(CONTENT_TYPE_PROTO, "application/proto");
48
+ def_pub_const!(CONTENT_TYPE_CONNECT_PROTO, "application/connect+proto");
49
+ def_pub_const!(CONTENT_TYPE_TEXT_HTML_WITH_UTF8, "text/html;charset=utf-8");
50
+ def_pub_const!(CONTENT_TYPE_TEXT_PLAIN_WITH_UTF8, "text/plain;charset=utf-8");
51
+ def_pub_const!(CONTENT_TYPE_TEXT_CSS_WITH_UTF8, "text/css;charset=utf-8");
52
+ def_pub_const!(CONTENT_TYPE_TEXT_JS_WITH_UTF8, "text/javascript;charset=utf-8");
53
+
54
+ def_pub_const!(AUTHORIZATION_BEARER_PREFIX, "Bearer ");
55
+
56
+ def_pub_const!(CURSOR_API2_HOST, "api2.cursor.sh");
57
+ def_pub_const!(CURSOR_HOST, "www.cursor.com");
58
+ def_pub_const!(CURSOR_SETTINGS_URL, "https://www.cursor.com/settings");
59
+
60
+ def_pub_const!(OBJECT_CHAT_COMPLETION, "chat.completion");
61
+ def_pub_const!(OBJECT_CHAT_COMPLETION_CHUNK, "chat.completion.chunk");
62
+
63
+ // def_pub_const!(CURSOR_API2_STREAM_CHAT, "StreamChat");
64
+ // def_pub_const!(CURSOR_API2_GET_USER_INFO, "GetUserInfo");
65
+
66
+ def_pub_const!(FINISH_REASON_STOP, "stop");
67
+
68
+ def_pub_const!(ERR_UPDATE_CONFIG, "无法更新配置");
69
+ def_pub_const!(ERR_RESET_CONFIG, "无法重置配置");
70
+ def_pub_const!(ERR_INVALID_PATH, "无效的路径");
71
+
72
+ // def_pub_const!(ERR_CHECKSUM_NO_GOOD, "checksum no good");
src/app/lazy.rs ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use crate::{
2
+ app::constant::{
3
+ CURSOR_API2_HOST, CURSOR_HOST, DEFAULT_TOKEN_FILE_NAME, DEFAULT_TOKEN_LIST_FILE_NAME,
4
+ EMPTY_STRING,
5
+ },
6
+ common::utils::{parse_ascii_char_from_env, parse_string_from_env},
7
+ };
8
+ use std::sync::LazyLock;
9
+
10
+ macro_rules! def_pub_static {
11
+ // 基础版本:直接存储 String
12
+ ($name:ident, $value:expr) => {
13
+ pub static $name: LazyLock<String> = LazyLock::new(|| $value);
14
+ };
15
+
16
+ // 环境变量版本
17
+ ($name:ident, env: $env_key:expr, default: $default:expr) => {
18
+ pub static $name: LazyLock<String> =
19
+ LazyLock::new(|| parse_string_from_env($env_key, $default).trim().to_string());
20
+ };
21
+ }
22
+
23
+ // macro_rules! def_pub_static_getter {
24
+ // ($name:ident) => {
25
+ // paste::paste! {
26
+ // pub fn [<get_ $name:lower>]() -> String {
27
+ // (*$name).clone()
28
+ // }
29
+ // }
30
+ // };
31
+ // }
32
+
33
+ def_pub_static!(ROUTE_PREFIX, env: "ROUTE_PREFIX", default: EMPTY_STRING);
34
+ def_pub_static!(AUTH_TOKEN, env: "AUTH_TOKEN", default: EMPTY_STRING);
35
+ def_pub_static!(TOKEN_FILE, env: "TOKEN_FILE", default: DEFAULT_TOKEN_FILE_NAME);
36
+ def_pub_static!(TOKEN_LIST_FILE, env: "TOKEN_LIST_FILE", default: DEFAULT_TOKEN_LIST_FILE_NAME);
37
+ def_pub_static!(ROUTE_MODELS_PATH, format!("{}/v1/models", *ROUTE_PREFIX));
38
+ def_pub_static!(
39
+ ROUTE_CHAT_PATH,
40
+ format!("{}/v1/chat/completions", *ROUTE_PREFIX)
41
+ );
42
+
43
+ pub static START_TIME: LazyLock<chrono::DateTime<chrono::Local>> =
44
+ LazyLock::new(chrono::Local::now);
45
+
46
+ pub fn get_start_time() -> chrono::DateTime<chrono::Local> {
47
+ *START_TIME
48
+ }
49
+
50
+ def_pub_static!(DEFAULT_INSTRUCTIONS, env: "DEFAULT_INSTRUCTIONS", default: "Respond in Chinese by default");
51
+
52
+ def_pub_static!(REVERSE_PROXY_HOST, env: "REVERSE_PROXY_HOST", default: EMPTY_STRING);
53
+
54
+ def_pub_static!(SHARED_AUTH_TOKEN, env: "SHARED_AUTH_TOKEN", default: EMPTY_STRING);
55
+
56
+ pub static USE_SHARE: LazyLock<bool> = LazyLock::new(|| !SHARED_AUTH_TOKEN.is_empty());
57
+
58
+ pub static TOKEN_DELIMITER: LazyLock<char> = LazyLock::new(|| {
59
+ let delimiter = parse_ascii_char_from_env("TOKEN_DELIMITER", ',');
60
+ if delimiter.is_ascii_alphabetic()
61
+ || delimiter.is_ascii_digit()
62
+ || delimiter == '+'
63
+ || delimiter == '/'
64
+ {
65
+ ','
66
+ } else {
67
+ delimiter
68
+ }
69
+ });
70
+
71
+ pub static TOKEN_DELIMITER_LEN: LazyLock<usize> = LazyLock::new(|| TOKEN_DELIMITER.len_utf8());
72
+
73
+ pub static USE_PROXY: LazyLock<bool> = LazyLock::new(|| !REVERSE_PROXY_HOST.is_empty());
74
+
75
+ pub static CURSOR_API2_CHAT_URL: LazyLock<String> = LazyLock::new(|| {
76
+ let host = if *USE_PROXY {
77
+ &*REVERSE_PROXY_HOST
78
+ } else {
79
+ CURSOR_API2_HOST
80
+ };
81
+ format!("https://{}/aiserver.v1.AiService/StreamChat", host)
82
+ });
83
+
84
+ pub static CURSOR_API2_STRIPE_URL: LazyLock<String> = LazyLock::new(|| {
85
+ let host = if *USE_PROXY {
86
+ &*REVERSE_PROXY_HOST
87
+ } else {
88
+ CURSOR_API2_HOST
89
+ };
90
+ format!("https://{}/auth/full_stripe_profile", host)
91
+ });
92
+
93
+ pub static CURSOR_USAGE_API_URL: LazyLock<String> = LazyLock::new(|| {
94
+ let host = if *USE_PROXY {
95
+ &*REVERSE_PROXY_HOST
96
+ } else {
97
+ CURSOR_HOST
98
+ };
99
+ format!("https://{}/api/usage", host)
100
+ });
101
+
102
+ pub static CURSOR_USER_API_URL: LazyLock<String> = LazyLock::new(|| {
103
+ let host = if *USE_PROXY {
104
+ &*REVERSE_PROXY_HOST
105
+ } else {
106
+ CURSOR_HOST
107
+ };
108
+ format!("https://{}/api/auth/me", host)
109
+ });
110
+
111
+ // pub static DEBUG: LazyLock<bool> = LazyLock::new(|| parse_bool_from_env("DEBUG", false));
112
+
113
+ // #[macro_export]
114
+ // macro_rules! debug_println {
115
+ // ($($arg:tt)*) => {
116
+ // if *crate::app::statics::DEBUG {
117
+ // println!($($arg)*);
118
+ // }
119
+ // };
120
+ // }
src/app/model.rs ADDED
@@ -0,0 +1,338 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use crate::{
2
+ app::constant::{
3
+ ERR_INVALID_PATH, ERR_RESET_CONFIG, ERR_UPDATE_CONFIG, ROUTE_ABOUT_PATH, ROUTE_CONFIG_PATH,
4
+ ROUTE_LOGS_PATH, ROUTE_README_PATH, ROUTE_ROOT_PATH, ROUTE_SHARED_JS_PATH,
5
+ ROUTE_SHARED_STYLES_PATH, ROUTE_TOKENINFO_PATH, ROUTE_API_PATH,
6
+ },
7
+ common::models::userinfo::TokenProfile,
8
+ };
9
+ use crate::chat::model::Message;
10
+ use std::sync::{LazyLock, RwLock};
11
+ use serde::{Deserialize, Serialize};
12
+
13
+ // 页面内容类型枚举
14
+ #[derive(Clone, Serialize, Deserialize)]
15
+ #[serde(tag = "type", content = "content")]
16
+ pub enum PageContent {
17
+ #[serde(rename = "default")]
18
+ Default, // 默认行为
19
+ #[serde(rename = "text")]
20
+ Text(String), // 纯文本
21
+ #[serde(rename = "html")]
22
+ Html(String), // HTML 内容
23
+ }
24
+
25
+ impl Default for PageContent {
26
+ fn default() -> Self {
27
+ Self::Default
28
+ }
29
+ }
30
+
31
+ mod usage_check;
32
+ pub use usage_check::UsageCheck;
33
+
34
+ // 静态配置
35
+ #[derive(Clone)]
36
+ pub struct AppConfig {
37
+ stream_check: bool,
38
+ stop_stream: bool,
39
+ vision_ability: VisionAbility,
40
+ slow_pool: bool,
41
+ allow_claude: bool,
42
+ pages: Pages,
43
+ usage_check: UsageCheck,
44
+ }
45
+
46
+ #[derive(Serialize, Deserialize, Clone)]
47
+ pub enum VisionAbility {
48
+ #[serde(rename = "none", alias = "disabled")]
49
+ None,
50
+ #[serde(rename = "base64", alias = "base64-only")]
51
+ Base64,
52
+ #[serde(rename = "all", alias = "base64-http")]
53
+ All,
54
+ }
55
+
56
+ impl VisionAbility {
57
+ pub fn from_str(s: &str) -> Self {
58
+ match s.to_lowercase().as_str() {
59
+ "none" | "disabled" => Self::None,
60
+ "base64" | "base64-only" => Self::Base64,
61
+ "all" | "base64-http" => Self::All,
62
+ _ => Self::default(),
63
+ }
64
+ }
65
+ }
66
+
67
+ impl Default for VisionAbility {
68
+ fn default() -> Self {
69
+ Self::Base64
70
+ }
71
+ }
72
+
73
+ #[derive(Clone, Default)]
74
+ pub struct Pages {
75
+ pub root_content: PageContent,
76
+ pub logs_content: PageContent,
77
+ pub config_content: PageContent,
78
+ pub tokeninfo_content: PageContent,
79
+ pub shared_styles_content: PageContent,
80
+ pub shared_js_content: PageContent,
81
+ pub about_content: PageContent,
82
+ pub readme_content: PageContent,
83
+ pub api_content: PageContent,
84
+ }
85
+
86
+ // 运行时状态
87
+ pub struct AppState {
88
+ pub total_requests: u64,
89
+ pub active_requests: u64,
90
+ pub error_requests: u64,
91
+ pub request_logs: Vec<RequestLog>,
92
+ pub token_infos: Vec<TokenInfo>,
93
+ }
94
+
95
+ // 全局配置实例
96
+ pub static APP_CONFIG: LazyLock<RwLock<AppConfig>> = LazyLock::new(|| {
97
+ RwLock::new(AppConfig::default())
98
+ });
99
+
100
+ impl Default for AppConfig {
101
+ fn default() -> Self {
102
+ Self {
103
+ stream_check: true,
104
+ stop_stream: true,
105
+ vision_ability: VisionAbility::Base64,
106
+ slow_pool: false,
107
+ allow_claude: false,
108
+ pages: Pages::default(),
109
+ usage_check: UsageCheck::Default,
110
+ }
111
+ }
112
+ }
113
+
114
+ macro_rules! config_methods {
115
+ ($($field:ident: $type:ty, $default:expr;)*) => {
116
+ $(
117
+ paste::paste! {
118
+ pub fn [<get_ $field>]() -> $type {
119
+ APP_CONFIG
120
+ .read()
121
+ .map(|config| config.$field.clone())
122
+ .unwrap_or($default)
123
+ }
124
+
125
+ pub fn [<update_ $field>](value: $type) -> Result<(), &'static str> {
126
+ if let Ok(mut config) = APP_CONFIG.write() {
127
+ config.$field = value;
128
+ Ok(())
129
+ } else {
130
+ Err(ERR_UPDATE_CONFIG)
131
+ }
132
+ }
133
+
134
+ pub fn [<reset_ $field>]() -> Result<(), &'static str> {
135
+ if let Ok(mut config) = APP_CONFIG.write() {
136
+ config.$field = $default;
137
+ Ok(())
138
+ } else {
139
+ Err(ERR_RESET_CONFIG)
140
+ }
141
+ }
142
+ }
143
+ )*
144
+ };
145
+ }
146
+
147
+ impl AppConfig {
148
+ pub fn init(
149
+ stream_check: bool,
150
+ stop_stream: bool,
151
+ vision_ability: VisionAbility,
152
+ slow_pool: bool,
153
+ allow_claude: bool,
154
+ ) {
155
+ if let Ok(mut config) = APP_CONFIG.write() {
156
+ config.stream_check = stream_check;
157
+ config.stop_stream = stop_stream;
158
+ config.vision_ability = vision_ability;
159
+ config.slow_pool = slow_pool;
160
+ config.allow_claude = allow_claude;
161
+ }
162
+ }
163
+
164
+ config_methods! {
165
+ stream_check: bool, true;
166
+ stop_stream: bool, true;
167
+ slow_pool: bool, false;
168
+ allow_claude: bool, false;
169
+ }
170
+
171
+ pub fn get_vision_ability() -> VisionAbility {
172
+ APP_CONFIG
173
+ .read()
174
+ .map(|config| config.vision_ability.clone())
175
+ .unwrap_or_default()
176
+ }
177
+
178
+ pub fn get_page_content(path: &str) -> Option<PageContent> {
179
+ APP_CONFIG.read().ok().map(|config| match path {
180
+ ROUTE_ROOT_PATH => config.pages.root_content.clone(),
181
+ ROUTE_LOGS_PATH => config.pages.logs_content.clone(),
182
+ ROUTE_CONFIG_PATH => config.pages.config_content.clone(),
183
+ ROUTE_TOKENINFO_PATH => config.pages.tokeninfo_content.clone(),
184
+ ROUTE_SHARED_STYLES_PATH => config.pages.shared_styles_content.clone(),
185
+ ROUTE_SHARED_JS_PATH => config.pages.shared_js_content.clone(),
186
+ ROUTE_ABOUT_PATH => config.pages.about_content.clone(),
187
+ ROUTE_README_PATH => config.pages.readme_content.clone(),
188
+ ROUTE_API_PATH => config.pages.api_content.clone(),
189
+ _ => PageContent::default(),
190
+ })
191
+ }
192
+
193
+ pub fn get_usage_check() -> UsageCheck {
194
+ APP_CONFIG
195
+ .read()
196
+ .map(|config| config.usage_check.clone())
197
+ .unwrap_or_default()
198
+ }
199
+
200
+ pub fn update_vision_ability(new_ability: VisionAbility) -> Result<(), &'static str> {
201
+ if let Ok(mut config) = APP_CONFIG.write() {
202
+ config.vision_ability = new_ability;
203
+ Ok(())
204
+ } else {
205
+ Err(ERR_UPDATE_CONFIG)
206
+ }
207
+ }
208
+
209
+ pub fn update_page_content(path: &str, content: PageContent) -> Result<(), &'static str> {
210
+ if let Ok(mut config) = APP_CONFIG.write() {
211
+ match path {
212
+ ROUTE_ROOT_PATH => config.pages.root_content = content,
213
+ ROUTE_LOGS_PATH => config.pages.logs_content = content,
214
+ ROUTE_CONFIG_PATH => config.pages.config_content = content,
215
+ ROUTE_TOKENINFO_PATH => config.pages.tokeninfo_content = content,
216
+ ROUTE_SHARED_STYLES_PATH => config.pages.shared_styles_content = content,
217
+ ROUTE_SHARED_JS_PATH => config.pages.shared_js_content = content,
218
+ ROUTE_ABOUT_PATH => config.pages.about_content = content,
219
+ ROUTE_README_PATH => config.pages.readme_content = content,
220
+ ROUTE_API_PATH => config.pages.api_content = content,
221
+ _ => return Err(ERR_INVALID_PATH),
222
+ }
223
+ Ok(())
224
+ } else {
225
+ Err(ERR_UPDATE_CONFIG)
226
+ }
227
+ }
228
+
229
+ pub fn update_usage_check(rule: UsageCheck) -> Result<(), &'static str> {
230
+ if let Ok(mut config) = APP_CONFIG.write() {
231
+ config.usage_check = rule;
232
+ Ok(())
233
+ } else {
234
+ Err(ERR_UPDATE_CONFIG)
235
+ }
236
+ }
237
+
238
+ pub fn reset_vision_ability() -> Result<(), &'static str> {
239
+ if let Ok(mut config) = APP_CONFIG.write() {
240
+ config.vision_ability = VisionAbility::Base64;
241
+ Ok(())
242
+ } else {
243
+ Err(ERR_RESET_CONFIG)
244
+ }
245
+ }
246
+
247
+ pub fn reset_page_content(path: &str) -> Result<(), &'static str> {
248
+ if let Ok(mut config) = APP_CONFIG.write() {
249
+ match path {
250
+ ROUTE_ROOT_PATH => config.pages.root_content = PageContent::default(),
251
+ ROUTE_LOGS_PATH => config.pages.logs_content = PageContent::default(),
252
+ ROUTE_CONFIG_PATH => config.pages.config_content = PageContent::default(),
253
+ ROUTE_TOKENINFO_PATH => config.pages.tokeninfo_content = PageContent::default(),
254
+ ROUTE_SHARED_STYLES_PATH => {
255
+ config.pages.shared_styles_content = PageContent::default()
256
+ }
257
+ ROUTE_SHARED_JS_PATH => config.pages.shared_js_content = PageContent::default(),
258
+ ROUTE_ABOUT_PATH => config.pages.about_content = PageContent::default(),
259
+ ROUTE_README_PATH => config.pages.readme_content = PageContent::default(),
260
+ ROUTE_API_PATH => config.pages.api_content = PageContent::default(),
261
+ _ => return Err(ERR_INVALID_PATH),
262
+ }
263
+ Ok(())
264
+ } else {
265
+ Err(ERR_RESET_CONFIG)
266
+ }
267
+ }
268
+
269
+ pub fn reset_usage_check() -> Result<(), &'static str> {
270
+ if let Ok(mut config) = APP_CONFIG.write() {
271
+ config.usage_check = UsageCheck::default();
272
+ Ok(())
273
+ } else {
274
+ Err(ERR_RESET_CONFIG)
275
+ }
276
+ }
277
+ }
278
+
279
+ impl AppState {
280
+ pub fn new(token_infos: Vec<TokenInfo>) -> Self {
281
+ Self {
282
+ total_requests: 0,
283
+ active_requests: 0,
284
+ error_requests: 0,
285
+ request_logs: Vec::new(),
286
+ token_infos,
287
+ }
288
+ }
289
+ }
290
+
291
+ // 请求日志
292
+ #[derive(Serialize, Clone)]
293
+ pub struct RequestLog {
294
+ pub id: u64,
295
+ pub timestamp: chrono::DateTime<chrono::Local>,
296
+ pub model: String,
297
+ pub token_info: TokenInfo,
298
+ #[serde(skip_serializing_if = "Option::is_none")]
299
+ pub prompt: Option<String>,
300
+ pub timing: TimingInfo,
301
+ pub stream: bool,
302
+ pub status: &'static str,
303
+ #[serde(skip_serializing_if = "Option::is_none")]
304
+ pub error: Option<String>,
305
+ }
306
+
307
+ #[derive(Serialize, Clone)]
308
+ pub struct TimingInfo {
309
+ pub total: f64, // 总用时(秒)
310
+ #[serde(skip_serializing_if = "Option::is_none")]
311
+ pub first: Option<f64>, // 首字时间(秒)
312
+ }
313
+
314
+ // 聊天请求
315
+ #[derive(Deserialize)]
316
+ pub struct ChatRequest {
317
+ pub model: String,
318
+ pub messages: Vec<Message>,
319
+ #[serde(default)]
320
+ pub stream: bool,
321
+ }
322
+
323
+ // 用于存储 token 信息
324
+ #[derive(Serialize, Clone)]
325
+ pub struct TokenInfo {
326
+ pub token: String,
327
+ pub checksum: String,
328
+ #[serde(skip_serializing_if = "Option::is_none")]
329
+ pub profile: Option<TokenProfile>,
330
+ }
331
+
332
+ // TokenUpdateRequest 结构体
333
+ #[derive(Deserialize)]
334
+ pub struct TokenUpdateRequest {
335
+ pub tokens: String,
336
+ #[serde(default)]
337
+ pub token_list: Option<String>,
338
+ }
src/app/model/usage_check.rs ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use crate::chat::constant::AVAILABLE_MODELS;
2
+ use serde::{Deserialize, Serialize};
3
+
4
+ #[derive(Clone)]
5
+ pub enum UsageCheck {
6
+ None,
7
+ Default,
8
+ All,
9
+ Custom(Vec<&'static str>),
10
+ }
11
+
12
+ impl Default for UsageCheck {
13
+ fn default() -> Self {
14
+ Self::Default
15
+ }
16
+ }
17
+
18
+ impl Serialize for UsageCheck {
19
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
20
+ where
21
+ S: serde::Serializer,
22
+ {
23
+ use serde::ser::SerializeStruct;
24
+ let mut state = serializer.serialize_struct("UsageCheck", 1)?;
25
+ match self {
26
+ UsageCheck::None => {
27
+ state.serialize_field("type", "none")?;
28
+ }
29
+ UsageCheck::Default => {
30
+ state.serialize_field("type", "default")?;
31
+ }
32
+ UsageCheck::All => {
33
+ state.serialize_field("type", "all")?;
34
+ }
35
+ UsageCheck::Custom(models) => {
36
+ state.serialize_field("type", "list")?;
37
+ state.serialize_field("content", &models.join(","))?;
38
+ }
39
+ }
40
+ state.end()
41
+ }
42
+ }
43
+
44
+ impl<'de> Deserialize<'de> for UsageCheck {
45
+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
46
+ where
47
+ D: serde::Deserializer<'de>,
48
+ {
49
+ #[derive(Deserialize)]
50
+ #[serde(tag = "type", content = "content")]
51
+ enum UsageCheckHelper {
52
+ #[serde(rename = "none")]
53
+ None,
54
+ #[serde(rename = "default")]
55
+ Default,
56
+ #[serde(rename = "all")]
57
+ All,
58
+ #[serde(rename = "list")]
59
+ Custom(String),
60
+ }
61
+
62
+ let helper = UsageCheckHelper::deserialize(deserializer)?;
63
+ Ok(match helper {
64
+ UsageCheckHelper::None => UsageCheck::None,
65
+ UsageCheckHelper::Default => UsageCheck::Default,
66
+ UsageCheckHelper::All => UsageCheck::All,
67
+ UsageCheckHelper::Custom(list) => {
68
+ if list.is_empty() {
69
+ return Ok(UsageCheck::None);
70
+ }
71
+
72
+ let models: Vec<&'static str> = list
73
+ .split(',')
74
+ .filter_map(|model| {
75
+ let model = model.trim();
76
+ AVAILABLE_MODELS
77
+ .iter()
78
+ .find(|m| m.id == model)
79
+ .map(|m| m.id)
80
+ })
81
+ .collect();
82
+
83
+ if models.is_empty() {
84
+ UsageCheck::None
85
+ } else {
86
+ UsageCheck::Custom(models)
87
+ }
88
+ }
89
+ })
90
+ }
91
+ }
src/chat.rs ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ pub mod adapter;
2
+ pub mod aiserver;
3
+ pub mod constant;
4
+ pub mod error;
5
+ pub mod model;
6
+ pub mod route;
7
+ pub mod service;
8
+ pub mod stream;
src/chat/adapter.rs ADDED
@@ -0,0 +1,403 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use base64::{engine::general_purpose::STANDARD as BASE64, Engine as _};
2
+ use image::guess_format;
3
+ use prost::Message as _;
4
+ use uuid::Uuid;
5
+
6
+ use crate::app::{
7
+ constant::EMPTY_STRING,
8
+ lazy::DEFAULT_INSTRUCTIONS,
9
+ model::{AppConfig, VisionAbility},
10
+ };
11
+
12
+ use super::{
13
+ aiserver::v1::{
14
+ conversation_message, image_proto, AzureState, ConversationMessage, ExplicitContext, GetChatRequest, ImageProto, ModelDetails
15
+ },
16
+ constant::{ERR_UNSUPPORTED_GIF, ERR_UNSUPPORTED_IMAGE_FORMAT, LONG_CONTEXT_MODELS},
17
+ model::{Message, MessageContent, Role},
18
+ };
19
+
20
+ async fn process_chat_inputs(inputs: Vec<Message>) -> (String, Vec<ConversationMessage>) {
21
+ // 收集 system 指令
22
+ let instructions = inputs
23
+ .iter()
24
+ .filter(|input| input.role == Role::System)
25
+ .map(|input| match &input.content {
26
+ MessageContent::Text(text) => text.clone(),
27
+ MessageContent::Vision(contents) => contents
28
+ .iter()
29
+ .filter_map(|content| {
30
+ if content.content_type == "text" {
31
+ content.text.clone()
32
+ } else {
33
+ None
34
+ }
35
+ })
36
+ .collect::<Vec<String>>()
37
+ .join("\n"),
38
+ })
39
+ .collect::<Vec<String>>()
40
+ .join("\n\n");
41
+
42
+ // 使用默认指令或收集到的指令
43
+ let instructions = if instructions.is_empty() {
44
+ DEFAULT_INSTRUCTIONS.clone()
45
+ } else {
46
+ instructions
47
+ };
48
+
49
+ // 过滤出 user 和 assistant 对话
50
+ let mut chat_inputs: Vec<Message> = inputs
51
+ .into_iter()
52
+ .filter(|input| input.role == Role::User || input.role == Role::Assistant)
53
+ .collect();
54
+
55
+ // 处理空对话情况
56
+ if chat_inputs.is_empty() {
57
+ return (
58
+ instructions,
59
+ vec![ConversationMessage {
60
+ text: EMPTY_STRING.into(),
61
+ r#type: conversation_message::MessageType::Human as i32,
62
+ attached_code_chunks: vec![],
63
+ codebase_context_chunks: vec![],
64
+ commits: vec![],
65
+ pull_requests: vec![],
66
+ git_diffs: vec![],
67
+ assistant_suggested_diffs: vec![],
68
+ interpreter_results: vec![],
69
+ images: vec![],
70
+ attached_folders: vec![],
71
+ approximate_lint_errors: vec![],
72
+ bubble_id: Uuid::new_v4().to_string(),
73
+ server_bubble_id: None,
74
+ attached_folders_new: vec![],
75
+ lints: vec![],
76
+ user_responses_to_suggested_code_blocks: vec![],
77
+ relevant_files: vec![],
78
+ tool_results: vec![],
79
+ notepads: vec![],
80
+ is_capability_iteration: Some(false),
81
+ capabilities: vec![],
82
+ edit_trail_contexts: vec![],
83
+ suggested_code_blocks: vec![],
84
+ diffs_for_compressing_files: vec![],
85
+ multi_file_linter_errors: vec![],
86
+ diff_histories: vec![],
87
+ recently_viewed_files: vec![],
88
+ recent_locations_history: vec![],
89
+ is_agentic: false,
90
+ file_diff_trajectories: vec![],
91
+ conversation_summary: None,
92
+ }],
93
+ );
94
+ }
95
+
96
+ // 如果第一条是 assistant,插入空的 user 消息
97
+ if chat_inputs
98
+ .first()
99
+ .map_or(false, |input| input.role == Role::Assistant)
100
+ {
101
+ chat_inputs.insert(
102
+ 0,
103
+ Message {
104
+ role: Role::User,
105
+ content: MessageContent::Text(EMPTY_STRING.into()),
106
+ },
107
+ );
108
+ }
109
+
110
+ // 处理连续相同角色的情况
111
+ let mut i = 1;
112
+ while i < chat_inputs.len() {
113
+ if chat_inputs[i].role == chat_inputs[i - 1].role {
114
+ let insert_role = if chat_inputs[i].role == Role::User {
115
+ Role::Assistant
116
+ } else {
117
+ Role::User
118
+ };
119
+ chat_inputs.insert(
120
+ i,
121
+ Message {
122
+ role: insert_role,
123
+ content: MessageContent::Text(EMPTY_STRING.into()),
124
+ },
125
+ );
126
+ }
127
+ i += 1;
128
+ }
129
+
130
+ // 确保最后一条是 user
131
+ if chat_inputs
132
+ .last()
133
+ .map_or(false, |input| input.role == Role::Assistant)
134
+ {
135
+ chat_inputs.push(Message {
136
+ role: Role::User,
137
+ content: MessageContent::Text(EMPTY_STRING.into()),
138
+ });
139
+ }
140
+
141
+ // 转换为 proto messages
142
+ let mut messages = Vec::new();
143
+ for input in chat_inputs {
144
+ let (text, images) = match input.content {
145
+ MessageContent::Text(text) => (text, vec![]),
146
+ MessageContent::Vision(contents) => {
147
+ let mut text_parts = Vec::new();
148
+ let mut images = Vec::new();
149
+
150
+ for content in contents {
151
+ match content.content_type.as_str() {
152
+ "text" => {
153
+ if let Some(text) = content.text {
154
+ text_parts.push(text);
155
+ }
156
+ }
157
+ "image_url" => {
158
+ if let Some(image_url) = &content.image_url {
159
+ let url = image_url.url.clone();
160
+ let result =
161
+ tokio::spawn(async move { fetch_image_data(&url).await });
162
+ if let Ok(Ok((image_data, dimensions))) = result.await {
163
+ images.push(ImageProto {
164
+ data: image_data,
165
+ dimension: dimensions,
166
+ });
167
+ }
168
+ }
169
+ }
170
+ _ => {}
171
+ }
172
+ }
173
+ (text_parts.join("\n"), images)
174
+ }
175
+ };
176
+
177
+ messages.push(ConversationMessage {
178
+ text,
179
+ r#type: if input.role == Role::User {
180
+ conversation_message::MessageType::Human as i32
181
+ } else {
182
+ conversation_message::MessageType::Ai as i32
183
+ },
184
+ attached_code_chunks: vec![],
185
+ codebase_context_chunks: vec![],
186
+ commits: vec![],
187
+ pull_requests: vec![],
188
+ git_diffs: vec![],
189
+ assistant_suggested_diffs: vec![],
190
+ interpreter_results: vec![],
191
+ images,
192
+ attached_folders: vec![],
193
+ approximate_lint_errors: vec![],
194
+ bubble_id: Uuid::new_v4().to_string(),
195
+ server_bubble_id: None,
196
+ attached_folders_new: vec![],
197
+ lints: vec![],
198
+ user_responses_to_suggested_code_blocks: vec![],
199
+ relevant_files: vec![],
200
+ tool_results: vec![],
201
+ notepads: vec![],
202
+ is_capability_iteration: None,
203
+ capabilities: vec![],
204
+ edit_trail_contexts: vec![],
205
+ suggested_code_blocks: vec![],
206
+ diffs_for_compressing_files: vec![],
207
+ multi_file_linter_errors: vec![],
208
+ diff_histories: vec![],
209
+ recently_viewed_files: vec![],
210
+ recent_locations_history: vec![],
211
+ is_agentic: false,
212
+ file_diff_trajectories: vec![],
213
+ conversation_summary: None,
214
+ });
215
+ }
216
+
217
+ (instructions, messages)
218
+ }
219
+
220
+ async fn fetch_image_data(
221
+ url: &str,
222
+ ) -> Result<(Vec<u8>, Option<image_proto::Dimension>), Box<dyn std::error::Error + Send + Sync>> {
223
+ // 在进入异步操作前获取并释放锁
224
+ let vision_ability = AppConfig::get_vision_ability();
225
+
226
+ match vision_ability {
227
+ VisionAbility::None => Err("图片功能已禁用".into()),
228
+
229
+ VisionAbility::Base64 => {
230
+ if !url.starts_with("data:image/") {
231
+ return Err("仅支持 base64 编码的图片".into());
232
+ }
233
+ process_base64_image(url)
234
+ }
235
+
236
+ VisionAbility::All => {
237
+ if url.starts_with("data:image/") {
238
+ process_base64_image(url)
239
+ } else {
240
+ process_http_image(url).await
241
+ }
242
+ }
243
+ }
244
+ }
245
+
246
+ // 处理 base64 编码的图片
247
+ fn process_base64_image(
248
+ url: &str,
249
+ ) -> Result<(Vec<u8>, Option<image_proto::Dimension>), Box<dyn std::error::Error + Send + Sync>> {
250
+ let parts: Vec<&str> = url.split("base64,").collect();
251
+ if parts.len() != 2 {
252
+ return Err("无效的 base64 图片格式".into());
253
+ }
254
+
255
+ // 检查图片格式
256
+ let format = parts[0].to_lowercase();
257
+ if !format.contains("png")
258
+ && !format.contains("jpeg")
259
+ && !format.contains("jpg")
260
+ && !format.contains("webp")
261
+ && !format.contains("gif")
262
+ {
263
+ return Err(ERR_UNSUPPORTED_IMAGE_FORMAT.into());
264
+ }
265
+
266
+ let image_data = BASE64.decode(parts[1])?;
267
+
268
+ // 检查是否为动态 GIF
269
+ if format.contains("gif") {
270
+ if let Ok(frames) = gif::DecodeOptions::new().read_info(std::io::Cursor::new(&image_data)) {
271
+ if frames.into_iter().count() > 1 {
272
+ return Err(ERR_UNSUPPORTED_GIF.into());
273
+ }
274
+ }
275
+ }
276
+
277
+ // 获取图片尺寸
278
+ let dimensions = if let Ok(img) = image::load_from_memory(&image_data) {
279
+ Some(image_proto::Dimension {
280
+ width: img.width() as i32,
281
+ height: img.height() as i32,
282
+ })
283
+ } else {
284
+ None
285
+ };
286
+
287
+ Ok((image_data, dimensions))
288
+ }
289
+
290
+ // 处理 HTTP 图片 URL
291
+ async fn process_http_image(
292
+ url: &str,
293
+ ) -> Result<(Vec<u8>, Option<image_proto::Dimension>), Box<dyn std::error::Error + Send + Sync>> {
294
+ let response = reqwest::get(url).await?;
295
+ let image_data = response.bytes().await?.to_vec();
296
+ let format = guess_format(&image_data)?;
297
+
298
+ // 检查图片格式
299
+ match format {
300
+ image::ImageFormat::Png | image::ImageFormat::Jpeg | image::ImageFormat::WebP => {
301
+ // 这些格式都支持
302
+ }
303
+ image::ImageFormat::Gif => {
304
+ if let Ok(frames) =
305
+ gif::DecodeOptions::new().read_info(std::io::Cursor::new(&image_data))
306
+ {
307
+ if frames.into_iter().count() > 1 {
308
+ return Err(ERR_UNSUPPORTED_GIF.into());
309
+ }
310
+ }
311
+ }
312
+ _ => return Err(ERR_UNSUPPORTED_IMAGE_FORMAT.into()),
313
+ }
314
+
315
+ // 获取图片尺寸
316
+ let dimensions = if let Ok(img) = image::load_from_memory_with_format(&image_data, format) {
317
+ Some(image_proto::Dimension {
318
+ width: img.width() as i32,
319
+ height: img.height() as i32,
320
+ })
321
+ } else {
322
+ None
323
+ };
324
+
325
+ Ok((image_data, dimensions))
326
+ }
327
+
328
+ pub async fn encode_chat_message(
329
+ inputs: Vec<Message>,
330
+ model_name: &str,
331
+ ) -> Result<Vec<u8>, Box<dyn std::error::Error + Send + Sync>> {
332
+ // 在进入异步操作前获取并释放锁
333
+ let enable_slow_pool = {
334
+ if AppConfig::get_slow_pool() {
335
+ Some(true)
336
+ } else {
337
+ None
338
+ }
339
+ };
340
+
341
+ let (instructions, messages) = process_chat_inputs(inputs).await;
342
+
343
+ let explicit_context = if !instructions.trim().is_empty() {
344
+ Some(ExplicitContext {
345
+ context: instructions,
346
+ repo_context: None,
347
+ })
348
+ } else {
349
+ None
350
+ };
351
+
352
+ let chat = GetChatRequest {
353
+ current_file: None,
354
+ conversation: messages,
355
+ repositories: vec![],
356
+ explicit_context,
357
+ workspace_root_path: None,
358
+ code_blocks: vec![],
359
+ model_details: Some(ModelDetails {
360
+ model_name: Some(model_name.to_string()),
361
+ api_key: None,
362
+ enable_ghost_mode: None,
363
+ azure_state: Some(AzureState {
364
+ api_key: String::new(),
365
+ base_url: String::new(),
366
+ deployment: String::new(),
367
+ use_azure: false,
368
+ }),
369
+ enable_slow_pool,
370
+ openai_api_base_url: None,
371
+ }),
372
+ documentation_identifiers: vec![],
373
+ request_id: Uuid::new_v4().to_string(),
374
+ linter_errors: None,
375
+ summary: None,
376
+ summary_up_until_index: None,
377
+ allow_long_file_scan: Some(false),
378
+ is_bash: Some(false),
379
+ conversation_id: Uuid::new_v4().to_string(),
380
+ can_handle_filenames_after_language_ids: Some(true),
381
+ use_web: None,
382
+ quotes: vec![],
383
+ debug_info: None,
384
+ workspace_id: None,
385
+ external_links: vec![],
386
+ commit_notes: vec![],
387
+ long_context_mode: Some(LONG_CONTEXT_MODELS.contains(&model_name)),
388
+ is_eval: Some(false),
389
+ desired_max_tokens: None,
390
+ context_ast: None,
391
+ is_composer: None,
392
+ runnable_code_blocks: Some(false),
393
+ should_cache: Some(false),
394
+ };
395
+
396
+ let mut encoded = Vec::new();
397
+ chat.encode(&mut encoded)?;
398
+
399
+ let len_prefix = format!("{:010x}", encoded.len()).to_uppercase();
400
+ let content = hex::encode_upper(&encoded);
401
+
402
+ Ok(hex::decode(len_prefix + &content)?)
403
+ }
src/chat/aiserver.rs ADDED
@@ -0,0 +1 @@
 
 
1
+ pub mod v1;
src/chat/aiserver/v1.rs ADDED
@@ -0,0 +1 @@
 
 
1
+ include!(concat!(env!("OUT_DIR"), "/aiserver.v1.rs"));
src/chat/aiserver/v1/lite.proto ADDED
@@ -0,0 +1,1156 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ syntax = "proto3";
2
+ package aiserver.v1;
3
+ enum ClientSideToolV2 { // aiserver.v1.ClientSideToolV2
4
+ CLIENT_SIDE_TOOL_V2_UNSPECIFIED = 0;
5
+ CLIENT_SIDE_TOOL_V2_READ_SEMSEARCH_FILES = 1;
6
+ CLIENT_SIDE_TOOL_V2_READ_FILE_FOR_IMPORTS = 2;
7
+ CLIENT_SIDE_TOOL_V2_RIPGREP_SEARCH = 3;
8
+ CLIENT_SIDE_TOOL_V2_RUN_TERMINAL_COMMAND = 4;
9
+ CLIENT_SIDE_TOOL_V2_READ_FILE = 5;
10
+ CLIENT_SIDE_TOOL_V2_LIST_DIR = 6;
11
+ CLIENT_SIDE_TOOL_V2_EDIT_FILE = 7;
12
+ CLIENT_SIDE_TOOL_V2_FILE_SEARCH = 8;
13
+ CLIENT_SIDE_TOOL_V2_SEMANTIC_SEARCH_FULL = 9;
14
+ CLIENT_SIDE_TOOL_V2_CREATE_FILE = 10;
15
+ CLIENT_SIDE_TOOL_V2_DELETE_FILE = 11;
16
+ }
17
+ enum EmbeddingModel { // aiserver.v1.EmbeddingModel
18
+ EMBEDDING_MODEL_UNSPECIFIED = 0;
19
+ EMBEDDING_MODEL_VOYAGE_CODE_2 = 1;
20
+ EMBEDDING_MODEL_TEXT_EMBEDDINGS_LARGE_3 = 2;
21
+ EMBEDDING_MODEL_QWEN_1_5B_CUSTOM = 3;
22
+ }
23
+ enum ChunkType { // aiserver.v1.ChunkType
24
+ CHUNK_TYPE_UNSPECIFIED = 0;
25
+ CHUNK_TYPE_CODEBASE = 1;
26
+ CHUNK_TYPE_LONG_FILE = 2;
27
+ CHUNK_TYPE_DOCS = 3;
28
+ }
29
+ enum FastApplySource { // aiserver.v1.FastApplySource
30
+ FAST_APPLY_SOURCE_UNSPECIFIED = 0;
31
+ FAST_APPLY_SOURCE_COMPOSER = 1;
32
+ FAST_APPLY_SOURCE_CLICKED_APPLY = 2;
33
+ FAST_APPLY_SOURCE_CACHED_APPLY = 3;
34
+ }
35
+ enum BuiltinTool { // aiserver.v1.BuiltinTool
36
+ BUILTIN_TOOL_UNSPECIFIED = 0;
37
+ BUILTIN_TOOL_SEARCH = 1;
38
+ BUILTIN_TOOL_READ_CHUNK = 2;
39
+ BUILTIN_TOOL_GOTODEF = 3;
40
+ BUILTIN_TOOL_EDIT = 4;
41
+ BUILTIN_TOOL_UNDO_EDIT = 5;
42
+ BUILTIN_TOOL_END = 6;
43
+ BUILTIN_TOOL_NEW_FILE = 7;
44
+ BUILTIN_TOOL_ADD_TEST = 8;
45
+ BUILTIN_TOOL_RUN_TEST = 9;
46
+ BUILTIN_TOOL_DELETE_TEST = 10;
47
+ BUILTIN_TOOL_SAVE_FILE = 11;
48
+ BUILTIN_TOOL_GET_TESTS = 12;
49
+ BUILTIN_TOOL_GET_SYMBOLS = 13;
50
+ BUILTIN_TOOL_SEMANTIC_SEARCH = 14;
51
+ BUILTIN_TOOL_GET_PROJECT_STRUCTURE = 15;
52
+ BUILTIN_TOOL_CREATE_RM_FILES = 16;
53
+ BUILTIN_TOOL_RUN_TERMINAL_COMMANDS = 17;
54
+ BUILTIN_TOOL_NEW_EDIT = 18;
55
+ BUILTIN_TOOL_READ_WITH_LINTER = 19;
56
+ }
57
+ enum FeatureType { // aiserver.v1.FeatureType
58
+ FEATURE_TYPE_UNSPECIFIED = 0;
59
+ FEATURE_TYPE_EDIT = 1;
60
+ FEATURE_TYPE_GENERATE = 2;
61
+ FEATURE_TYPE_INLINE_LONG_COMPLETION = 3;
62
+ }
63
+ enum TaskStatus { // aiserver.v1.TaskStatus
64
+ TASK_STATUS_UNSPECIFIED = 0;
65
+ TASK_STATUS_RUNNING = 1;
66
+ TASK_STATUS_PAUSED = 2;
67
+ TASK_STATUS_DONE = 3;
68
+ TASK_STATUS_NOT_STARTED = 4;
69
+ }
70
+ enum RerankerAlgorithm { // aiserver.v1.RerankerAlgorithm
71
+ RERANKER_ALGORITHM_UNSPECIFIED = 0;
72
+ RERANKER_ALGORITHM_LULEA = 1;
73
+ RERANKER_ALGORITHM_UMEA = 2;
74
+ RERANKER_ALGORITHM_NONE = 3;
75
+ RERANKER_ALGORITHM_LLAMA = 4;
76
+ RERANKER_ALGORITHM_STARCODER_V1 = 5;
77
+ RERANKER_ALGORITHM_GPT_3_5_LOGPROBS = 6;
78
+ RERANKER_ALGORITHM_LULEA_HAIKU = 7;
79
+ RERANKER_ALGORITHM_COHERE = 8;
80
+ RERANKER_ALGORITHM_VOYAGE = 9;
81
+ RERANKER_ALGORITHM_VOYAGE_EMBEDS = 10;
82
+ RERANKER_ALGORITHM_IDENTITY = 11;
83
+ RERANKER_ALGORITHM_ADA_EMBEDS = 12;
84
+ }
85
+ enum RechunkerChoice { // aiserver.v1.RechunkerChoice
86
+ RECHUNKER_CHOICE_UNSPECIFIED = 0;
87
+ RECHUNKER_CHOICE_IDENTITY = 1;
88
+ RECHUNKER_CHOICE_600_TOKS = 2;
89
+ RECHUNKER_CHOICE_2400_TOKS = 3;
90
+ RECHUNKER_CHOICE_4000_TOKS = 4;
91
+ }
92
+ enum LintGenerator { // aiserver.v1.LintGenerator
93
+ LINT_GENERATOR_UNSPECIFIED = 0;
94
+ LINT_GENERATOR_NAIVE = 1;
95
+ LINT_GENERATOR_COMMENT_PIPELINE = 2;
96
+ LINT_GENERATOR_SIMPLE_BUG = 3;
97
+ LINT_GENERATOR_SIMPLE_LINT_RULES = 4;
98
+ }
99
+ enum LintDiscriminator { // aiserver.v1.LintDiscriminator
100
+ LINT_DISCRIMINATOR_UNSPECIFIED = 0;
101
+ LINT_DISCRIMINATOR_SPECIFIC_RULES = 1;
102
+ LINT_DISCRIMINATOR_COMPILE_ERRORS = 2;
103
+ LINT_DISCRIMINATOR_CHANGE_BEHAVIOR = 3;
104
+ LINT_DISCRIMINATOR_RELEVANCE = 4;
105
+ LINT_DISCRIMINATOR_USER_AWARENESS = 5;
106
+ LINT_DISCRIMINATOR_CORRECTNESS = 6;
107
+ LINT_DISCRIMINATOR_CHUNKING = 7;
108
+ LINT_DISCRIMINATOR_TYPO = 8;
109
+ LINT_DISCRIMINATOR_CONFIDENCE = 9;
110
+ LINT_DISCRIMINATOR_DISMISSED_BUGS = 10;
111
+ }
112
+ enum CppSource { // aiserver.v1.CppSource
113
+ CPP_SOURCE_UNSPECIFIED = 0;
114
+ CPP_SOURCE_LINE_CHANGE = 1;
115
+ CPP_SOURCE_TYPING = 2;
116
+ CPP_SOURCE_OPTION_HOLD = 3;
117
+ CPP_SOURCE_LINTER_ERRORS = 4;
118
+ CPP_SOURCE_PARAMETER_HINTS = 5;
119
+ CPP_SOURCE_CURSOR_PREDICTION = 6;
120
+ CPP_SOURCE_MANUAL_TRIGGER = 7;
121
+ CPP_SOURCE_EDITOR_CHANGE = 8;
122
+ }
123
+ enum ChunkingStrategy { // aiserver.v1.ChunkingStrategy
124
+ CHUNKING_STRATEGY_UNSPECIFIED = 0;
125
+ CHUNKING_STRATEGY_DEFAULT = 1;
126
+ }
127
+ message ErrorDetails { // aiserver.v1.ErrorDetails
128
+ enum Error { // aiserver.v1.ErrorDetails.Error
129
+ ERROR_UNSPECIFIED = 0;
130
+ ERROR_BAD_API_KEY = 1;
131
+ ERROR_NOT_LOGGED_IN = 2;
132
+ ERROR_INVALID_AUTH_ID = 3;
133
+ ERROR_NOT_HIGH_ENOUGH_PERMISSIONS = 4;
134
+ ERROR_BAD_MODEL_NAME = 5;
135
+ ERROR_USER_NOT_FOUND = 6;
136
+ ERROR_FREE_USER_RATE_LIMIT_EXCEEDED = 7;
137
+ ERROR_PRO_USER_RATE_LIMIT_EXCEEDED = 8;
138
+ ERROR_FREE_USER_USAGE_LIMIT = 9;
139
+ ERROR_PRO_USER_USAGE_LIMIT = 10;
140
+ ERROR_AUTH_TOKEN_NOT_FOUND = 11;
141
+ ERROR_AUTH_TOKEN_EXPIRED = 12;
142
+ ERROR_OPENAI = 13;
143
+ ERROR_OPENAI_RATE_LIMIT_EXCEEDED = 14;
144
+ ERROR_OPENAI_ACCOUNT_LIMIT_EXCEEDED = 15;
145
+ ERROR_TASK_UUID_NOT_FOUND = 16;
146
+ ERROR_TASK_NO_PERMISSIONS = 17;
147
+ ERROR_AGENT_REQUIRES_LOGIN = 18;
148
+ ERROR_AGENT_ENGINE_NOT_FOUND = 19;
149
+ ERROR_MAX_TOKENS = 20;
150
+ ERROR_USER_ABORTED_REQUEST = 21;
151
+ ERROR_GENERIC_RATE_LIMIT_EXCEEDED = 22;
152
+ ERROR_PRO_USER_ONLY = 23;
153
+ ERROR_API_KEY_NOT_SUPPORTED = 24;
154
+ ERROR_SLASH_EDIT_FILE_TOO_LONG = 26;
155
+ ERROR_FILE_UNSUPPORTED = 27;
156
+ ERROR_GPT_4_VISION_PREVIEW_RATE_LIMIT = 28;
157
+ ERROR_CUSTOM_MESSAGE = 29;
158
+ ERROR_OUTDATED_CLIENT = 30;
159
+ ERROR_CLAUDE_IMAGE_TOO_LARGE = 31;
160
+ ERROR_GITGRAPH_NOT_FOUND = 32;
161
+ ERROR_FILE_NOT_FOUND = 33;
162
+ ERROR_API_KEY_RATE_LIMIT = 34;
163
+ ERROR_DEBOUNCED = 35;
164
+ ERROR_BAD_REQUEST = 36;
165
+ ERROR_REPOSITORY_SERVICE_REPOSITORY_IS_NOT_INITIALIZED = 37;
166
+ ERROR_UNAUTHORIZED = 38;
167
+ ERROR_NOT_FOUND = 39;
168
+ ERROR_DEPRECATED = 40;
169
+ ERROR_RESOURCE_EXHAUSTED = 41;
170
+ }
171
+ Error error = 1;
172
+ CustomErrorDetails details = 2;
173
+ optional bool is_expected = 3;
174
+ }
175
+
176
+ message CustomErrorDetails { // aiserver.v1.CustomErrorDetails
177
+ string title = 1;
178
+ string detail = 2;
179
+ optional bool allow_command_links_potentially_unsafe_please_only_use_for_handwritten_trusted_markdown = 3;
180
+ optional bool is_retryable = 4;
181
+ optional bool show_request_id = 5;
182
+ }
183
+
184
+ message GetChatRequest { // aiserver.v1.GetChatRequest
185
+ CurrentFileInfo current_file = 1;
186
+ repeated ConversationMessage conversation = 2;
187
+ repeated RepositoryInfo repositories = 3;
188
+ ExplicitContext explicit_context = 4;
189
+ optional string workspace_root_path = 5;
190
+ repeated CodeBlock code_blocks = 6;
191
+ ModelDetails model_details = 7;
192
+ repeated string documentation_identifiers = 8;
193
+ string request_id = 9;
194
+ LinterErrors linter_errors = 10;
195
+ optional string summary = 11;
196
+ optional int32 summary_up_until_index = 12;
197
+ optional bool allow_long_file_scan = 13;
198
+ optional bool is_bash = 14;
199
+ string conversation_id = 15;
200
+ optional bool can_handle_filenames_after_language_ids = 16;
201
+ optional string use_web = 17;
202
+ repeated ChatQuote quotes = 18;
203
+ optional DebugInfo debug_info = 19;
204
+ optional string workspace_id = 20;
205
+ repeated ChatExternalLink external_links = 21;
206
+ repeated CommitNote commit_notes = 23;
207
+ optional bool long_context_mode = 22;
208
+ optional bool is_eval = 24;
209
+ optional int32 desired_max_tokens = 26;
210
+ ContextAST context_ast = 25;
211
+ optional bool is_composer = 27;
212
+ optional bool runnable_code_blocks = 28;
213
+ optional bool should_cache = 29;
214
+ }
215
+ message CurrentFileInfo { // aiserver.v1.CurrentFileInfo
216
+ message NotebookCell { // aiserver.v1.CurrentFileInfo.NotebookCell
217
+ }
218
+ string relative_workspace_path = 1;
219
+ string contents = 2;
220
+ bool rely_on_filesync = 18;
221
+ optional string sha_256_hash = 17;
222
+ repeated NotebookCell cells = 16;
223
+ repeated BM25Chunk top_chunks = 10;
224
+ int32 contents_start_at_line = 9;
225
+ CursorPosition cursor_position = 3;
226
+ repeated DataframeInfo dataframes = 4;
227
+ int32 total_number_of_lines = 8;
228
+ string language_id = 5;
229
+ CursorRange selection = 6;
230
+ optional int32 alternative_version_id = 11;
231
+ repeated Diagnostic diagnostics = 7;
232
+ optional int32 file_version = 14;
233
+ repeated int32 cell_start_lines = 15;
234
+ string workspace_root_path = 19;
235
+ }
236
+ message BM25Chunk { // aiserver.v1.BM25Chunk
237
+ string content = 1;
238
+ SimplestRange range = 2;
239
+ int32 score = 3;
240
+ string relative_path = 4;
241
+ }
242
+ message SimplestRange { // aiserver.v1.SimplestRange
243
+ int32 start_line = 1;
244
+ int32 end_line_inclusive = 2;
245
+ }
246
+ message CursorPosition { // aiserver.v1.CursorPosition
247
+ int32 line = 1;
248
+ int32 column = 2;
249
+ }
250
+ message DataframeInfo { // aiserver.v1.DataframeInfo
251
+ message Column { // aiserver.v1.DataframeInfo.Column
252
+ string key = 1;
253
+ string type = 2;
254
+ }
255
+ string name = 1;
256
+ string shape = 2;
257
+ int32 data_dimensionality = 3;
258
+ repeated Column columns = 6;
259
+ int32 row_count = 7;
260
+ string index_column = 8;
261
+ }
262
+ message CursorRange { // aiserver.v1.CursorRange
263
+ CursorPosition start_position = 1;
264
+ CursorPosition end_position = 2;
265
+ }
266
+ message Diagnostic { // aiserver.v1.Diagnostic
267
+ enum DiagnosticSeverity { // aiserver.v1.Diagnostic.DiagnosticSeverity
268
+ DIAGNOSTIC_SEVERITY_UNSPECIFIED = 0;
269
+ DIAGNOSTIC_SEVERITY_ERROR = 1;
270
+ DIAGNOSTIC_SEVERITY_WARNING = 2;
271
+ DIAGNOSTIC_SEVERITY_INFORMATION = 3;
272
+ DIAGNOSTIC_SEVERITY_HINT = 4;
273
+ }
274
+ message RelatedInformation { // aiserver.v1.Diagnostic.RelatedInformation
275
+ string message = 1;
276
+ CursorRange range = 2;
277
+ }
278
+ string message = 1;
279
+ CursorRange range = 2;
280
+ DiagnosticSeverity severity = 3;
281
+ repeated RelatedInformation related_information = 4;
282
+ }
283
+ message ConversationMessage { // aiserver.v1.ConversationMessage
284
+ enum MessageType { // aiserver.v1.ConversationMessage.MessageType
285
+ MESSAGE_TYPE_UNSPECIFIED = 0;
286
+ MESSAGE_TYPE_HUMAN = 1;
287
+ MESSAGE_TYPE_AI = 2;
288
+ }
289
+ message CodeChunk { // aiserver.v1.ConversationMessage.CodeChunk
290
+ enum SummarizationStrategy { // aiserver.v1.ConversationMessage.CodeChunk.SummarizationStrategy
291
+ SUMMARIZATION_STRATEGY_NONE_UNSPECIFIED = 0;
292
+ SUMMARIZATION_STRATEGY_SUMMARIZED = 1;
293
+ SUMMARIZATION_STRATEGY_EMBEDDED = 2;
294
+ }
295
+ enum Intent { // aiserver.v1.ConversationMessage.CodeChunk.Intent
296
+ INTENT_UNSPECIFIED = 0;
297
+ INTENT_COMPOSER_FILE = 1;
298
+ INTENT_COMPRESSED_COMPOSER_FILE = 2;
299
+ INTENT_RECENTLY_VIEWED_FILE = 3;
300
+ INTENT_OUTLINE = 4;
301
+ INTENT_MENTIONED_FILE = 5;
302
+ }
303
+ string relative_workspace_path = 1;
304
+ int32 start_line_number = 2;
305
+ repeated string lines = 3;
306
+ optional SummarizationStrategy summarization_strategy = 4;
307
+ string language_identifier = 5;
308
+ optional Intent intent = 6;
309
+ optional bool is_final_version = 7;
310
+ optional bool is_first_version = 8;
311
+ optional bool contents_are_missing = 9;
312
+ }
313
+ message ApproximateLintError { // aiserver.v1.ConversationMessage.ApproximateLintError
314
+ string message = 1;
315
+ string value = 2;
316
+ int32 start_line = 3;
317
+ int32 end_line = 4;
318
+ int32 start_column = 5;
319
+ int32 end_column = 6;
320
+ }
321
+ message Lints { // aiserver.v1.ConversationMessage.Lints
322
+ GetLintsForChangeResponse lints = 1;
323
+ string chat_codeblock_model_value = 2;
324
+ }
325
+ message ToolResult { // aiserver.v1.ConversationMessage.ToolResult
326
+ message CodeChunk { // aiserver.v1.ConversationMessage.CodeChunk
327
+ enum SummarizationStrategy { // aiserver.v1.ConversationMessage.CodeChunk.SummarizationStrategy
328
+ SUMMARIZATION_STRATEGY_NONE_UNSPECIFIED = 0;
329
+ SUMMARIZATION_STRATEGY_SUMMARIZED = 1;
330
+ SUMMARIZATION_STRATEGY_EMBEDDED = 2;
331
+ }
332
+ enum Intent { // aiserver.v1.ConversationMessage.CodeChunk.Intent
333
+ INTENT_UNSPECIFIED = 0;
334
+ INTENT_COMPOSER_FILE = 1;
335
+ INTENT_COMPRESSED_COMPOSER_FILE = 2;
336
+ INTENT_RECENTLY_VIEWED_FILE = 3;
337
+ INTENT_OUTLINE = 4;
338
+ INTENT_MENTIONED_FILE = 5;
339
+ }
340
+ string relative_workspace_path = 1;
341
+ int32 start_line_number = 2;
342
+ repeated string lines = 3;
343
+ optional SummarizationStrategy summarization_strategy = 4;
344
+ string language_identifier = 5;
345
+ optional Intent intent = 6;
346
+ optional bool is_final_version = 7;
347
+ optional bool is_first_version = 8;
348
+ optional bool contents_are_missing = 9;
349
+ }
350
+ string tool_call_id = 1;
351
+ string tool_name = 2;
352
+ uint32 tool_index = 3;
353
+ string args = 4;
354
+ string raw_args = 5;
355
+ repeated CodeChunk attached_code_chunks = 6;
356
+ optional string content = 7;
357
+ ClientSideToolV2Result result = 8;
358
+ optional ToolResultError error = 9;
359
+ }
360
+ message NotepadContext { // aiserver.v1.ConversationMessage.NotepadContext
361
+ message CodeChunk { // aiserver.v1.ConversationMessage.CodeChunk
362
+ enum SummarizationStrategy { // aiserver.v1.ConversationMessage.CodeChunk.SummarizationStrategy
363
+ SUMMARIZATION_STRATEGY_NONE_UNSPECIFIED = 0;
364
+ SUMMARIZATION_STRATEGY_SUMMARIZED = 1;
365
+ SUMMARIZATION_STRATEGY_EMBEDDED = 2;
366
+ }
367
+ enum Intent { // aiserver.v1.ConversationMessage.CodeChunk.Intent
368
+ INTENT_UNSPECIFIED = 0;
369
+ INTENT_COMPOSER_FILE = 1;
370
+ INTENT_COMPRESSED_COMPOSER_FILE = 2;
371
+ INTENT_RECENTLY_VIEWED_FILE = 3;
372
+ INTENT_OUTLINE = 4;
373
+ INTENT_MENTIONED_FILE = 5;
374
+ }
375
+ string relative_workspace_path = 1;
376
+ int32 start_line_number = 2;
377
+ repeated string lines = 3;
378
+ optional SummarizationStrategy summarization_strategy = 4;
379
+ string language_identifier = 5;
380
+ optional Intent intent = 6;
381
+ optional bool is_final_version = 7;
382
+ optional bool is_first_version = 8;
383
+ optional bool contents_are_missing = 9;
384
+ }
385
+ string name = 1;
386
+ string text = 2;
387
+ repeated CodeChunk attached_code_chunks = 3;
388
+ repeated string attached_folders = 4;
389
+ repeated Commit commits = 5;
390
+ repeated PullRequest pull_requests = 6;
391
+ repeated GitDiff git_diffs = 7;
392
+ repeated ImageProto images = 8;
393
+ }
394
+ message EditTrailContext { // aiserver.v1.ConversationMessage.EditTrailContext
395
+ message EditLocation { // aiserver.v1.ConversationMessage.EditLocation
396
+ string relative_workspace_path = 1;
397
+ SimplestRange range = 3;
398
+ SimplestRange initial_range = 4;
399
+ string context_lines = 5;
400
+ string text = 6;
401
+ SimplestRange text_range = 7;
402
+ }
403
+ string unique_id = 1;
404
+ repeated EditLocation edit_trail_sorted = 2;
405
+ }
406
+ message RecentLocation { // aiserver.v1.ConversationMessage.RecentLocation
407
+ string relative_workspace_path = 1;
408
+ int32 line_number = 2;
409
+ }
410
+ string text = 1;
411
+ MessageType type = 2;
412
+ repeated CodeChunk attached_code_chunks = 3;
413
+ repeated CodeBlock codebase_context_chunks = 4;
414
+ repeated Commit commits = 5;
415
+ repeated PullRequest pull_requests = 6;
416
+ repeated GitDiff git_diffs = 7;
417
+ repeated SimpleFileDiff assistant_suggested_diffs = 8;
418
+ repeated InterpreterResult interpreter_results = 9;
419
+ repeated ImageProto images = 10;
420
+ repeated string attached_folders = 11;
421
+ repeated ApproximateLintError approximate_lint_errors = 12;
422
+ string bubble_id = 13;
423
+ optional string server_bubble_id = 32;
424
+ repeated FolderInfo attached_folders_new = 14;
425
+ repeated Lints lints = 15;
426
+ repeated UserResponseToSuggestedCodeBlock user_responses_to_suggested_code_blocks = 16;
427
+ repeated string relevant_files = 17;
428
+ repeated ToolResult tool_results = 18;
429
+ repeated NotepadContext notepads = 19;
430
+ optional bool is_capability_iteration = 20;
431
+ repeated ComposerCapabilityRequest capabilities = 21;
432
+ repeated EditTrailContext edit_trail_contexts = 22;
433
+ repeated SuggestedCodeBlock suggested_code_blocks = 23;
434
+ repeated RedDiff diffs_for_compressing_files = 24;
435
+ repeated LinterErrorsWithoutFileContents multi_file_linter_errors = 25;
436
+ repeated DiffHistoryData diff_histories = 26;
437
+ repeated CodeChunk recently_viewed_files = 27;
438
+ repeated RecentLocation recent_locations_history = 28;
439
+ bool is_agentic = 29;
440
+ repeated ComposerFileDiffHistory file_diff_trajectories = 30;
441
+ optional ConversationSummary conversation_summary = 31;
442
+ }
443
+ message CodeBlock { // aiserver.v1.CodeBlock
444
+ message Signatures { // aiserver.v1.CodeBlock.Signatures
445
+ repeated CursorRange ranges = 1;
446
+ }
447
+ string relative_workspace_path = 1;
448
+ optional string file_contents = 2;
449
+ CursorRange range = 3;
450
+ string contents = 4;
451
+ Signatures signatures = 5;
452
+ optional string override_contents = 6;
453
+ optional string original_contents = 7;
454
+ repeated DetailedLine detailed_lines = 8;
455
+ }
456
+ message DetailedLine { // aiserver.v1.DetailedLine
457
+ string text = 1;
458
+ float line_number = 2;
459
+ bool is_signature = 3;
460
+ }
461
+ message Commit { // aiserver.v1.Commit
462
+ string sha = 1;
463
+ string message = 2;
464
+ string description = 3;
465
+ repeated FileDiff diff = 4;
466
+ string author = 5;
467
+ string date = 6;
468
+ }
469
+ message FileDiff { // aiserver.v1.FileDiff
470
+ message Chunk { // aiserver.v1.FileDiff.Chunk
471
+ string content = 1;
472
+ repeated string lines = 2;
473
+ int32 old_start = 3;
474
+ int32 old_lines = 4;
475
+ int32 new_start = 5;
476
+ int32 new_lines = 6;
477
+ }
478
+ string from = 1;
479
+ string to = 2;
480
+ repeated Chunk chunks = 3;
481
+ }
482
+ message PullRequest { // aiserver.v1.PullRequest
483
+ string title = 1;
484
+ string body = 2;
485
+ repeated FileDiff diff = 3;
486
+ }
487
+ message GitDiff { // aiserver.v1.GitDiff
488
+ enum DiffType { // aiserver.v1.GitDiff.DiffType
489
+ DIFF_TYPE_UNSPECIFIED = 0;
490
+ DIFF_TYPE_DIFF_TO_HEAD = 1;
491
+ DIFF_TYPE_DIFF_FROM_BRANCH_TO_MAIN = 2;
492
+ }
493
+ repeated FileDiff diffs = 1;
494
+ DiffType diff_type = 2;
495
+ }
496
+ message SimpleFileDiff { // aiserver.v1.SimpleFileDiff
497
+ message Chunk { // aiserver.v1.SimpleFileDiff.Chunk
498
+ repeated string old_lines = 1;
499
+ repeated string new_lines = 2;
500
+ LineRange old_range = 3;
501
+ LineRange new_range = 4;
502
+ }
503
+ string relative_workspace_path = 1;
504
+ repeated Chunk chunks = 3;
505
+ }
506
+ message LineRange { // aiserver.v1.LineRange
507
+ int32 start_line_number = 1;
508
+ int32 end_line_number_inclusive = 2;
509
+ }
510
+ message InterpreterResult { // aiserver.v1.InterpreterResult
511
+ string output = 1;
512
+ bool success = 2;
513
+ }
514
+ message ImageProto { // aiserver.v1.ImageProto
515
+ message Dimension { // aiserver.v1.ImageProto.Dimension
516
+ int32 width = 1;
517
+ int32 height = 2;
518
+ }
519
+ bytes data = 1;
520
+ Dimension dimension = 2;
521
+ }
522
+ message FolderInfo { // aiserver.v1.FolderInfo
523
+ string relative_path = 1;
524
+ repeated FolderFileInfo files = 2;
525
+ }
526
+ message FolderFileInfo { // aiserver.v1.FolderFileInfo
527
+ string relative_path = 1;
528
+ string content = 2;
529
+ bool truncated = 3;
530
+ float score = 4;
531
+ }
532
+ message GetLintsForChangeResponse { // aiserver.v1.GetLintsForChangeResponse
533
+ message Lint { // aiserver.v1.GetLintsForChangeResponse.Lint
534
+ message QuickFix { // aiserver.v1.GetLintsForChangeResponse.Lint.QuickFix
535
+ message Edit { // aiserver.v1.GetLintsForChangeResponse.Lint.QuickFix.Edit
536
+ string relative_workspace_path = 1;
537
+ string text = 2;
538
+ int32 start_line_number_one_indexed = 3;
539
+ int32 start_column_one_indexed = 4;
540
+ int32 end_line_number_inclusive_one_indexed = 5;
541
+ int32 end_column_one_indexed = 6;
542
+ }
543
+ string message = 1;
544
+ string kind = 2;
545
+ bool is_preferred = 3;
546
+ repeated Edit edits = 4;
547
+ }
548
+ string message = 1;
549
+ string severity = 2;
550
+ string relative_workspace_path = 3;
551
+ int32 start_line_number_one_indexed = 4;
552
+ int32 start_column_one_indexed = 5;
553
+ int32 end_line_number_inclusive_one_indexed = 6;
554
+ int32 end_column_one_indexed = 7;
555
+ repeated QuickFix quick_fixes = 9;
556
+ }
557
+ repeated Lint lints = 1;
558
+ }
559
+ message UserResponseToSuggestedCodeBlock { // aiserver.v1.UserResponseToSuggestedCodeBlock
560
+ enum UserResponseType { // aiserver.v1.UserResponseToSuggestedCodeBlock.UserResponseType
561
+ USER_RESPONSE_TYPE_UNSPECIFIED = 0;
562
+ USER_RESPONSE_TYPE_ACCEPT = 1;
563
+ USER_RESPONSE_TYPE_REJECT = 2;
564
+ USER_RESPONSE_TYPE_MODIFY = 3;
565
+ }
566
+ UserResponseType user_response_type = 1;
567
+ string file_path = 2;
568
+ optional FileDiff user_modifications_to_suggested_code_blocks = 3;
569
+ }
570
+ message ClientSideToolV2Result { // aiserver.v1.ClientSideToolV2Result
571
+ ClientSideToolV2 tool = 1;
572
+ ReadSemsearchFilesResult read_semsearch_files_result = 2;
573
+ ReadFileForImportsResult read_file_for_imports_result = 3;
574
+ RipgrepSearchResult ripgrep_search_result = 4;
575
+ RunTerminalCommandResult run_terminal_command_result = 5;
576
+ ReadFileResult read_file_result = 6;
577
+ ListDirResult list_dir_result = 9;
578
+ EditFileResult edit_file_result = 10;
579
+ ToolCallFileSearchResult file_search_result = 11;
580
+ SemanticSearchFullResult semantic_search_full_result = 18;
581
+ CreateFileResult create_file_result = 19;
582
+ DeleteFileResult delete_file_result = 20;
583
+ optional ToolResultError error = 8;
584
+ }
585
+ message ReadSemsearchFilesResult { // aiserver.v1.ReadSemsearchFilesResult
586
+ repeated CodeResult code_results = 1;
587
+ }
588
+ message CodeResult { // aiserver.v1.CodeResult
589
+ CodeBlock code_block = 1;
590
+ float score = 2;
591
+ }
592
+ message ReadFileForImportsResult { // aiserver.v1.ReadFileForImportsResult
593
+ string contents = 1;
594
+ }
595
+ message RipgrepSearchResult { // aiserver.v1.RipgrepSearchResult
596
+ RipgrepSearchResultInternal internal = 1;
597
+ }
598
+ message RipgrepSearchResultInternal { // aiserver.v1.RipgrepSearchResultInternal
599
+ message IFileMatch { // aiserver.v1.RipgrepSearchResultInternal.IFileMatch
600
+ message ITextSearchResult { // aiserver.v1.RipgrepSearchResultInternal.ITextSearchResult
601
+ message ITextSearchMatch { // aiserver.v1.RipgrepSearchResultInternal.ITextSearchMatch
602
+ message ISearchRangeSetPairing { // aiserver.v1.RipgrepSearchResultInternal.ISearchRangeSetPairing
603
+ message ISearchRange { // aiserver.v1.RipgrepSearchResultInternal.ISearchRange
604
+ int32 start_line_number = 1;
605
+ int32 start_column = 2;
606
+ int32 end_line_number = 3;
607
+ int32 end_column = 4;
608
+ }
609
+ ISearchRange source = 1;
610
+ ISearchRange preview = 2;
611
+ }
612
+ optional string uri = 1;
613
+ repeated ISearchRangeSetPairing range_locations = 2;
614
+ string preview_text = 3;
615
+ optional int32 webview_index = 4;
616
+ optional string cell_fragment = 5;
617
+ }
618
+ message ITextSearchContext { // aiserver.v1.RipgrepSearchResultInternal.ITextSearchContext
619
+ optional string uri = 1;
620
+ string text = 2;
621
+ int32 line_number = 3;
622
+ }
623
+ ITextSearchMatch match = 1;
624
+ ITextSearchContext context = 2;
625
+ }
626
+ string resource = 1;
627
+ repeated ITextSearchResult results = 2;
628
+ }
629
+ enum SearchCompletionExitCode { // aiserver.v1.RipgrepSearchResultInternal.SearchCompletionExitCode
630
+ SEARCH_COMPLETION_EXIT_CODE_UNSPECIFIED = 0;
631
+ SEARCH_COMPLETION_EXIT_CODE_NORMAL = 1;
632
+ SEARCH_COMPLETION_EXIT_CODE_NEW_SEARCH_STARTED = 2;
633
+ }
634
+ message ITextSearchCompleteMessage { // aiserver.v1.RipgrepSearchResultInternal.ITextSearchCompleteMessage
635
+ enum TextSearchCompleteMessageType { // aiserver.v1.RipgrepSearchResultInternal.TextSearchCompleteMessageType
636
+ TEXT_SEARCH_COMPLETE_MESSAGE_TYPE_UNSPECIFIED = 0;
637
+ TEXT_SEARCH_COMPLETE_MESSAGE_TYPE_INFORMATION = 1;
638
+ TEXT_SEARCH_COMPLETE_MESSAGE_TYPE_WARNING = 2;
639
+ }
640
+ string text = 1;
641
+ TextSearchCompleteMessageType type = 2;
642
+ optional bool trusted = 3;
643
+ }
644
+ message IFileSearchStats { // aiserver.v1.RipgrepSearchResultInternal.IFileSearchStats
645
+ message ISearchEngineStats { // aiserver.v1.RipgrepSearchResultInternal.ISearchEngineStats
646
+ int32 file_walk_time = 1;
647
+ int32 directories_walked = 2;
648
+ int32 files_walked = 3;
649
+ int32 cmd_time = 4;
650
+ optional int32 cmd_result_count = 5;
651
+ }
652
+ message ICachedSearchStats { // aiserver.v1.RipgrepSearchResultInternal.ICachedSearchStats
653
+ bool cache_was_resolved = 1;
654
+ int32 cache_lookup_time = 2;
655
+ int32 cache_filter_time = 3;
656
+ int32 cache_entry_count = 4;
657
+ }
658
+ message IFileSearchProviderStats { // aiserver.v1.RipgrepSearchResultInternal.IFileSearchProviderStats
659
+ int32 provider_time = 1;
660
+ int32 post_process_time = 2;
661
+ }
662
+ enum FileSearchProviderType { // aiserver.v1.RipgrepSearchResultInternal.IFileSearchStats.FileSearchProviderType
663
+ FILE_SEARCH_PROVIDER_TYPE_UNSPECIFIED = 0;
664
+ FILE_SEARCH_PROVIDER_TYPE_FILE_SEARCH_PROVIDER = 1;
665
+ FILE_SEARCH_PROVIDER_TYPE_SEARCH_PROCESS = 2;
666
+ }
667
+ bool from_cache = 1;
668
+ ISearchEngineStats search_engine_stats = 2;
669
+ ICachedSearchStats cached_search_stats = 3;
670
+ IFileSearchProviderStats file_search_provider_stats = 4;
671
+ int32 result_count = 5;
672
+ FileSearchProviderType type = 6;
673
+ optional int32 sorting_time = 7;
674
+ }
675
+ message ITextSearchStats { // aiserver.v1.RipgrepSearchResultInternal.ITextSearchStats
676
+ enum TextSearchProviderType { // aiserver.v1.RipgrepSearchResultInternal.ITextSearchStats.TextSearchProviderType
677
+ TEXT_SEARCH_PROVIDER_TYPE_UNSPECIFIED = 0;
678
+ TEXT_SEARCH_PROVIDER_TYPE_TEXT_SEARCH_PROVIDER = 1;
679
+ TEXT_SEARCH_PROVIDER_TYPE_SEARCH_PROCESS = 2;
680
+ TEXT_SEARCH_PROVIDER_TYPE_AI_TEXT_SEARCH_PROVIDER = 3;
681
+ }
682
+ TextSearchProviderType type = 1;
683
+ }
684
+ repeated IFileMatch results = 1;
685
+ optional SearchCompletionExitCode exit = 2;
686
+ optional bool limit_hit = 3;
687
+ repeated ITextSearchCompleteMessage messages = 4;
688
+ IFileSearchStats file_search_stats = 5;
689
+ ITextSearchStats text_search_stats = 6;
690
+ }
691
+ message RunTerminalCommandResult { // aiserver.v1.RunTerminalCommandResult
692
+ string output = 1;
693
+ int32 exit_code = 2;
694
+ optional bool rejected = 3;
695
+ bool popped_out_into_background = 4;
696
+ }
697
+ message ReadFileResult { // aiserver.v1.ReadFileResult
698
+ string contents = 1;
699
+ bool did_downgrade_to_line_range = 2;
700
+ bool did_shorten_line_range = 3;
701
+ bool did_set_default_line_range = 4;
702
+ optional string full_file_contents = 5;
703
+ optional string outline = 6;
704
+ optional int32 start_line_one_indexed = 7;
705
+ optional int32 end_line_one_indexed_inclusive = 8;
706
+ string relative_workspace_path = 9;
707
+ bool did_shorten_char_range = 10;
708
+ }
709
+ message ListDirResult { // aiserver.v1.ListDirResult
710
+ message File { // aiserver.v1.ListDirResult.File
711
+ message Timestamp { // google.protobuf.Timestamp
712
+ int64 seconds = 1;
713
+ int32 nanos = 2;
714
+ }
715
+ string name = 1;
716
+ bool is_directory = 2;
717
+ optional int64 size = 3;
718
+ optional Timestamp last_modified = 4;
719
+ optional int32 num_children = 5;
720
+ optional int32 num_lines = 6;
721
+ }
722
+ repeated File files = 1;
723
+ string directory_relative_workspace_path = 2;
724
+ }
725
+ message EditFileResult { // aiserver.v1.EditFileResult
726
+ message FileDiff { // aiserver.v1.EditFileResult.FileDiff
727
+ message ChunkDiff { // aiserver.v1.EditFileResult.FileDiff.ChunkDiff
728
+ string diff_string = 1;
729
+ int32 old_start = 2;
730
+ int32 new_start = 3;
731
+ int32 old_lines = 4;
732
+ int32 new_lines = 5;
733
+ int32 lines_removed = 6;
734
+ int32 lines_added = 7;
735
+ }
736
+ enum Editor { // aiserver.v1.EditFileResult.FileDiff.Editor
737
+ EDITOR_UNSPECIFIED = 0;
738
+ EDITOR_AI = 1;
739
+ EDITOR_HUMAN = 2;
740
+ }
741
+ repeated ChunkDiff chunks = 1;
742
+ Editor editor = 2;
743
+ bool hit_timeout = 3;
744
+ }
745
+ FileDiff diff = 1;
746
+ bool is_applied = 2;
747
+ bool apply_failed = 3;
748
+ }
749
+ message ToolCallFileSearchResult { // aiserver.v1.ToolCallFileSearchResult
750
+ message File { // aiserver.v1.ToolCallFileSearchResult.File
751
+ string uri = 1;
752
+ }
753
+ repeated File files = 1;
754
+ optional bool limit_hit = 2;
755
+ int32 num_results = 3;
756
+ }
757
+ message SemanticSearchFullResult { // aiserver.v1.SemanticSearchFullResult
758
+ repeated CodeResult code_results = 1;
759
+ }
760
+ message CreateFileResult { // aiserver.v1.CreateFileResult
761
+ bool file_created_successfully = 1;
762
+ bool file_already_exists = 2;
763
+ }
764
+ message DeleteFileResult { // aiserver.v1.DeleteFileResult
765
+ bool rejected = 1;
766
+ bool file_non_existent = 2;
767
+ bool file_deleted_successfully = 3;
768
+ }
769
+ message ToolResultError { // aiserver.v1.ToolResultError
770
+ string client_visible_error_message = 1;
771
+ string model_visible_error_message = 2;
772
+ }
773
+ message ComposerCapabilityRequest { // aiserver.v1.ComposerCapabilityRequest
774
+ enum ComposerCapabilityType { // aiserver.v1.ComposerCapabilityRequest.ComposerCapabilityType
775
+ COMPOSER_CAPABILITY_TYPE_UNSPECIFIED = 0;
776
+ COMPOSER_CAPABILITY_TYPE_LOOP_ON_LINTS = 1;
777
+ COMPOSER_CAPABILITY_TYPE_LOOP_ON_TESTS = 2;
778
+ COMPOSER_CAPABILITY_TYPE_MEGA_PLANNER = 3;
779
+ COMPOSER_CAPABILITY_TYPE_LOOP_ON_COMMAND = 4;
780
+ COMPOSER_CAPABILITY_TYPE_TOOL_CALL = 5;
781
+ COMPOSER_CAPABILITY_TYPE_DIFF_REVIEW = 6;
782
+ COMPOSER_CAPABILITY_TYPE_CONTEXT_PICKING = 7;
783
+ COMPOSER_CAPABILITY_TYPE_EDIT_TRAIL = 8;
784
+ COMPOSER_CAPABILITY_TYPE_AUTO_CONTEXT = 9;
785
+ COMPOSER_CAPABILITY_TYPE_CONTEXT_PLANNER = 10;
786
+ COMPOSER_CAPABILITY_TYPE_DIFF_HISTORY = 11;
787
+ COMPOSER_CAPABILITY_TYPE_REMEMBER_THIS = 12;
788
+ COMPOSER_CAPABILITY_TYPE_DECOMPOSER = 13;
789
+ COMPOSER_CAPABILITY_TYPE_USES_CODEBASE = 14;
790
+ COMPOSER_CAPABILITY_TYPE_TOOL_FORMER = 15;
791
+ }
792
+ message LoopOnLintsCapability { // aiserver.v1.ComposerCapabilityRequest.LoopOnLintsCapability
793
+ repeated LinterErrors linter_errors = 1;
794
+ optional string custom_instructions = 2;
795
+ }
796
+ message LoopOnTestsCapability { // aiserver.v1.ComposerCapabilityRequest.LoopOnTestsCapability
797
+ repeated string test_names = 1;
798
+ optional string custom_instructions = 2;
799
+ }
800
+ message MegaPlannerCapability { // aiserver.v1.ComposerCapabilityRequest.MegaPlannerCapability
801
+ optional string custom_instructions = 1;
802
+ }
803
+ message LoopOnCommandCapability { // aiserver.v1.ComposerCapabilityRequest.LoopOnCommandCapability
804
+ string command = 1;
805
+ optional string custom_instructions = 2;
806
+ optional string output = 3;
807
+ optional int32 exit_code = 4;
808
+ }
809
+ message ToolCallCapability { // aiserver.v1.ComposerCapabilityRequest.ToolCallCapability
810
+ message ToolSchema { // aiserver.v1.ComposerCapabilityRequest.ToolSchema
811
+ enum ToolType { // aiserver.v1.ComposerCapabilityRequest.ToolType
812
+ TOOL_TYPE_UNSPECIFIED = 0;
813
+ TOOL_TYPE_ADD_FILE_TO_CONTEXT = 1;
814
+ TOOL_TYPE_RUN_TERMINAL_COMMAND = 2;
815
+ TOOL_TYPE_ITERATE = 3;
816
+ TOOL_TYPE_REMOVE_FILE_FROM_CONTEXT = 4;
817
+ TOOL_TYPE_SEMANTIC_SEARCH_CODEBASE = 5;
818
+ }
819
+ ToolType type = 1;
820
+ string name = 2;
821
+ repeated string required = 4;
822
+ }
823
+ optional string custom_instructions = 1;
824
+ repeated ToolSchema tool_schemas = 2;
825
+ repeated string relevant_files = 3;
826
+ repeated string files_in_context = 4;
827
+ repeated string semantic_search_files = 5;
828
+ }
829
+ message DiffReviewCapability { // aiserver.v1.ComposerCapabilityRequest.DiffReviewCapability
830
+ message SimpleFileDiff { // aiserver.v1.ComposerCapabilityRequest.DiffReviewCapability.SimpleFileDiff
831
+ message Chunk { // aiserver.v1.ComposerCapabilityRequest.DiffReviewCapability.SimpleFileDiff.Chunk
832
+ repeated string old_lines = 1;
833
+ repeated string new_lines = 2;
834
+ LineRange old_range = 3;
835
+ LineRange new_range = 4;
836
+ }
837
+ string relative_workspace_path = 1;
838
+ repeated Chunk chunks = 3;
839
+ }
840
+ optional string custom_instructions = 1;
841
+ repeated SimpleFileDiff diffs = 2;
842
+ }
843
+ message ContextPickingCapability { // aiserver.v1.ComposerCapabilityRequest.ContextPickingCapability
844
+ optional string custom_instructions = 1;
845
+ repeated string potential_context_files = 2;
846
+ repeated CodeChunk potential_context_code_chunks = 3;
847
+ repeated string files_in_context = 4;
848
+ }
849
+ message EditTrailCapability { // aiserver.v1.ComposerCapabilityRequest.EditTrailCapability
850
+ optional string custom_instructions = 1;
851
+ }
852
+ message AutoContextCapability { // aiserver.v1.ComposerCapabilityRequest.AutoContextCapability
853
+ optional string custom_instructions = 1;
854
+ repeated string additional_files = 2;
855
+ }
856
+ message ContextPlannerCapability { // aiserver.v1.ComposerCapabilityRequest.ContextPlannerCapability
857
+ optional string custom_instructions = 1;
858
+ repeated CodeChunk attached_code_chunks = 2;
859
+ }
860
+ message RememberThisCapability { // aiserver.v1.ComposerCapabilityRequest.RememberThisCapability
861
+ optional string custom_instructions = 1;
862
+ string memory = 2;
863
+ }
864
+ message DecomposerCapability { // aiserver.v1.ComposerCapabilityRequest.DecomposerCapability
865
+ optional string custom_instructions = 1;
866
+ }
867
+ ComposerCapabilityType type = 1;
868
+ LoopOnLintsCapability loop_on_lints = 2;
869
+ LoopOnTestsCapability loop_on_tests = 3;
870
+ MegaPlannerCapability mega_planner = 4;
871
+ LoopOnCommandCapability loop_on_command = 5;
872
+ ToolCallCapability tool_call = 6;
873
+ DiffReviewCapability diff_review = 7;
874
+ ContextPickingCapability context_picking = 8;
875
+ EditTrailCapability edit_trail = 9;
876
+ AutoContextCapability auto_context = 10;
877
+ ContextPlannerCapability context_planner = 11;
878
+ RememberThisCapability remember_this = 12;
879
+ DecomposerCapability decomposer = 13;
880
+ }
881
+ message LinterErrors { // aiserver.v1.LinterErrors
882
+ string relative_workspace_path = 1;
883
+ repeated LinterError errors = 2;
884
+ string file_contents = 3;
885
+ }
886
+ message LinterError { // aiserver.v1.LinterError
887
+ message RelatedInformation { // aiserver.v1.Diagnostic.RelatedInformation
888
+ string message = 1;
889
+ CursorRange range = 2;
890
+ }
891
+ enum DiagnosticSeverity { // aiserver.v1.Diagnostic.DiagnosticSeverity
892
+ DIAGNOSTIC_SEVERITY_UNSPECIFIED = 0;
893
+ DIAGNOSTIC_SEVERITY_ERROR = 1;
894
+ DIAGNOSTIC_SEVERITY_WARNING = 2;
895
+ DIAGNOSTIC_SEVERITY_INFORMATION = 3;
896
+ DIAGNOSTIC_SEVERITY_HINT = 4;
897
+ }
898
+ string message = 1;
899
+ CursorRange range = 2;
900
+ optional string source = 3;
901
+ repeated RelatedInformation related_information = 4;
902
+ optional DiagnosticSeverity severity = 5;
903
+ }
904
+ message CodeChunk { // aiserver.v1.CodeChunk
905
+ enum SummarizationStrategy { // aiserver.v1.CodeChunk.SummarizationStrategy
906
+ SUMMARIZATION_STRATEGY_NONE_UNSPECIFIED = 0;
907
+ SUMMARIZATION_STRATEGY_SUMMARIZED = 1;
908
+ SUMMARIZATION_STRATEGY_EMBEDDED = 2;
909
+ }
910
+ enum Intent { // aiserver.v1.CodeChunk.Intent
911
+ INTENT_UNSPECIFIED = 0;
912
+ INTENT_COMPOSER_FILE = 1;
913
+ INTENT_COMPRESSED_COMPOSER_FILE = 2;
914
+ }
915
+ string relative_workspace_path = 1;
916
+ int32 start_line_number = 2;
917
+ repeated string lines = 3;
918
+ optional SummarizationStrategy summarization_strategy = 4;
919
+ string language_identifier = 5;
920
+ optional Intent intent = 6;
921
+ optional bool is_final_version = 7;
922
+ optional bool is_first_version = 8;
923
+ }
924
+ message SuggestedCodeBlock { // aiserver.v1.SuggestedCodeBlock
925
+ string relative_workspace_path = 1;
926
+ }
927
+ message RedDiff { // aiserver.v1.RedDiff
928
+ string relative_workspace_path = 1;
929
+ repeated SimplestRange red_ranges = 2;
930
+ repeated SimplestRange red_ranges_reversed = 3;
931
+ string start_hash = 4;
932
+ string end_hash = 5;
933
+ }
934
+ message LinterErrorsWithoutFileContents { // aiserver.v1.LinterErrorsWithoutFileContents
935
+ string relative_workspace_path = 1;
936
+ repeated LinterError errors = 2;
937
+ }
938
+ message DiffHistoryData { // aiserver.v1.DiffHistoryData
939
+ string relative_workspace_path = 1;
940
+ repeated ComposerFileDiff diffs = 2;
941
+ double timestamp = 3;
942
+ string unique_id = 4;
943
+ ComposerFileDiff start_to_end_diff = 5;
944
+ }
945
+ message ComposerFileDiff { // aiserver.v1.ComposerFileDiff
946
+ message ChunkDiff { // aiserver.v1.ComposerFileDiff.ChunkDiff
947
+ string diff_string = 1;
948
+ int32 old_start = 2;
949
+ int32 new_start = 3;
950
+ int32 old_lines = 4;
951
+ int32 new_lines = 5;
952
+ int32 lines_removed = 6;
953
+ int32 lines_added = 7;
954
+ }
955
+ enum Editor { // aiserver.v1.ComposerFileDiff.Editor
956
+ EDITOR_UNSPECIFIED = 0;
957
+ EDITOR_AI = 1;
958
+ EDITOR_HUMAN = 2;
959
+ }
960
+ repeated ChunkDiff chunks = 1;
961
+ Editor editor = 2;
962
+ bool hit_timeout = 3;
963
+ }
964
+ message ComposerFileDiffHistory { // aiserver.v1.ComposerFileDiffHistory
965
+ string file_name = 1;
966
+ repeated string diff_history = 2;
967
+ repeated double diff_history_timestamps = 3;
968
+ }
969
+ message ConversationSummary { // aiserver.v1.ConversationSummary
970
+ string summary = 1;
971
+ string truncation_last_bubble_id_inclusive = 2;
972
+ string client_should_start_sending_from_inclusive_bubble_id = 3;
973
+ }
974
+ message RepositoryInfo { // aiserver.v1.RepositoryInfo
975
+ string relative_workspace_path = 1;
976
+ repeated string remote_urls = 2;
977
+ repeated string remote_names = 3;
978
+ string repo_name = 4;
979
+ string repo_owner = 5;
980
+ bool is_tracked = 6;
981
+ bool is_local = 7;
982
+ optional int32 num_files = 8;
983
+ optional double orthogonal_transform_seed = 9;
984
+ optional EmbeddingModel preferred_embedding_model = 10;
985
+ }
986
+ message ExplicitContext { // aiserver.v1.ExplicitContext
987
+ string context = 1;
988
+ optional string repo_context = 2;
989
+ }
990
+ message ModelDetails { // aiserver.v1.ModelDetails
991
+ optional string model_name = 1;
992
+ optional string api_key = 2;
993
+ optional bool enable_ghost_mode = 3;
994
+ optional AzureState azure_state = 4;
995
+ optional bool enable_slow_pool = 5;
996
+ optional string openai_api_base_url = 6;
997
+ }
998
+ message AzureState { // aiserver.v1.AzureState
999
+ string api_key = 1;
1000
+ string base_url = 2;
1001
+ string deployment = 3;
1002
+ bool use_azure = 4;
1003
+ }
1004
+ message ChatQuote { // aiserver.v1.ChatQuote
1005
+ string markdown = 1;
1006
+ string bubble_id = 2;
1007
+ int32 section_index = 3;
1008
+ }
1009
+ message DebugInfo { // aiserver.v1.DebugInfo
1010
+ message Breakpoint { // aiserver.v1.DebugInfo.Breakpoint
1011
+ string relative_workspace_path = 1;
1012
+ int32 line_number = 2;
1013
+ repeated string lines_before_breakpoint = 3;
1014
+ repeated string lines_after_breakpoint = 4;
1015
+ optional string exception_info = 5;
1016
+ }
1017
+ message CallStackFrame { // aiserver.v1.DebugInfo.CallStackFrame
1018
+ message Scope { // aiserver.v1.DebugInfo.Scope
1019
+ message Variable { // aiserver.v1.DebugInfo.Variable
1020
+ string name = 1;
1021
+ string value = 2;
1022
+ optional string type = 3;
1023
+ }
1024
+ string name = 1;
1025
+ repeated Variable variables = 2;
1026
+ }
1027
+ string relative_workspace_path = 1;
1028
+ int32 line_number = 2;
1029
+ string function_name = 3;
1030
+ repeated Scope scopes = 4;
1031
+ }
1032
+ Breakpoint breakpoint = 1;
1033
+ repeated CallStackFrame call_stack = 2;
1034
+ repeated CodeBlock history = 3;
1035
+ }
1036
+ message ChatExternalLink { // aiserver.v1.ChatExternalLink
1037
+ string url = 1;
1038
+ string uuid = 2;
1039
+ }
1040
+ message CommitNote { // aiserver.v1.CommitNote
1041
+ string note = 1;
1042
+ string commit_hash = 2;
1043
+ }
1044
+ message ContextAST { // aiserver.v1.ContextAST
1045
+ repeated ContainerTree files = 1;
1046
+ }
1047
+ message ContainerTree { // aiserver.v1.ContainerTree
1048
+ string relative_workspace_path = 1;
1049
+ repeated ContainerTreeNode nodes = 2;
1050
+ }
1051
+ message ContainerTreeNode { // aiserver.v1.ContainerTreeNode
1052
+ message Container { // aiserver.v1.ContainerTreeNode.Container
1053
+ message Reference { // aiserver.v1.ContainerTreeNode.Reference
1054
+ string value = 1;
1055
+ string relative_workspace_path = 2;
1056
+ }
1057
+ string doc_string = 1;
1058
+ string header = 2;
1059
+ string trailer = 3;
1060
+ repeated ContainerTreeNode children = 5;
1061
+ repeated Reference references = 6;
1062
+ double score = 7;
1063
+ }
1064
+ message Blob { // aiserver.v1.ContainerTreeNode.Blob
1065
+ optional string value = 1;
1066
+ }
1067
+ message Symbol { // aiserver.v1.ContainerTreeNode.Symbol
1068
+ message Reference { // aiserver.v1.ContainerTreeNode.Reference
1069
+ string value = 1;
1070
+ string relative_workspace_path = 2;
1071
+ }
1072
+ string doc_string = 1;
1073
+ string value = 2;
1074
+ repeated Reference references = 6;
1075
+ double score = 7;
1076
+ }
1077
+ Container container = 1;
1078
+ Blob blob = 2;
1079
+ Symbol symbol = 3;
1080
+ }
1081
+ message StreamChatResponse { // aiserver.v1.StreamChatResponse
1082
+ message ChunkIdentity { // aiserver.v1.StreamChatResponse.ChunkIdentity
1083
+ string file_name = 1;
1084
+ int32 start_line = 2;
1085
+ int32 end_line = 3;
1086
+ string text = 4;
1087
+ ChunkType chunk_type = 5;
1088
+ }
1089
+ string text = 1;
1090
+ optional string server_bubble_id = 22;
1091
+ optional string debugging_only_chat_prompt = 2;
1092
+ optional int32 debugging_only_token_count = 3;
1093
+ DocumentationCitation document_citation = 4;
1094
+ optional string filled_prompt = 5;
1095
+ optional bool is_big_file = 6;
1096
+ optional string intermediate_text = 7;
1097
+ optional bool is_using_slow_request = 10;
1098
+ optional ChunkIdentity chunk_identity = 8;
1099
+ optional DocsReference docs_reference = 9;
1100
+ optional WebCitation web_citation = 11;
1101
+ optional StatusUpdates status_updates = 12;
1102
+ optional ServerTimingInfo timing_info = 13;
1103
+ optional SymbolLink symbol_link = 14;
1104
+ optional FileLink file_link = 15;
1105
+ optional ConversationSummary conversation_summary = 16;
1106
+ optional ServiceStatusUpdate service_status_update = 17;
1107
+ }
1108
+ message DocumentationCitation { // aiserver.v1.DocumentationCitation
1109
+ repeated DocumentationChunk chunks = 1;
1110
+ }
1111
+ message DocumentationChunk { // aiserver.v1.DocumentationChunk
1112
+ string doc_name = 1;
1113
+ string page_url = 2;
1114
+ string documentation_chunk = 3;
1115
+ float score = 4;
1116
+ string page_title = 5;
1117
+ }
1118
+ message DocsReference { // aiserver.v1.DocsReference
1119
+ string title = 1;
1120
+ string url = 2;
1121
+ }
1122
+ message WebCitation { // aiserver.v1.WebCitation
1123
+ repeated WebReference references = 1;
1124
+ }
1125
+ message WebReference { // aiserver.v1.WebReference
1126
+ string title = 2;
1127
+ string url = 1;
1128
+ }
1129
+ message StatusUpdates { // aiserver.v1.StatusUpdates
1130
+ repeated StatusUpdate updates = 1;
1131
+ }
1132
+ message StatusUpdate { // aiserver.v1.StatusUpdate
1133
+ string message = 1;
1134
+ optional string metadata = 2;
1135
+ }
1136
+ message ServerTimingInfo { // aiserver.v1.ServerTimingInfo
1137
+ double server_start_time = 1;
1138
+ double server_first_token_time = 2;
1139
+ double server_request_sent_time = 3;
1140
+ double server_end_time = 4;
1141
+ }
1142
+ message SymbolLink { // aiserver.v1.SymbolLink
1143
+ string symbol_name = 1;
1144
+ string symbol_search_string = 2;
1145
+ string relative_workspace_path = 3;
1146
+ int32 rough_line_number = 4;
1147
+ }
1148
+ message FileLink { // aiserver.v1.FileLink
1149
+ string display_name = 1;
1150
+ string relative_workspace_path = 2;
1151
+ }
1152
+ message ServiceStatusUpdate { // aiserver.v1.ServiceStatusUpdate
1153
+ string message = 1;
1154
+ string codicon = 2;
1155
+ optional bool allow_command_links_potentially_unsafe_please_only_use_for_handwritten_trusted_markdown = 3;
1156
+ }
src/chat/constant.rs ADDED
@@ -0,0 +1,193 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use super::model::Model;
2
+
3
+ macro_rules! def_pub_const {
4
+ ($name:ident, $value:expr) => {
5
+ pub const $name: &'static str = $value;
6
+ };
7
+ }
8
+ def_pub_const!(ERR_UNSUPPORTED_GIF, "不支持动态 GIF");
9
+ def_pub_const!(ERR_UNSUPPORTED_IMAGE_FORMAT, "不支持的图片格式,仅支持 PNG、JPEG、WEBP 和非动态 GIF");
10
+ def_pub_const!(ERR_NODATA, "No data");
11
+
12
+ const MODEL_OBJECT: &str = "model";
13
+ const CREATED: &i64 = &1706659200;
14
+
15
+ def_pub_const!(ANTHROPIC, "anthropic");
16
+ def_pub_const!(CURSOR, "cursor");
17
+ def_pub_const!(GOOGLE, "google");
18
+ def_pub_const!(OPENAI, "openai");
19
+
20
+ def_pub_const!(CLAUDE_3_5_SONNET, "claude-3.5-sonnet");
21
+ def_pub_const!(GPT_4, "gpt-4");
22
+ def_pub_const!(GPT_4O, "gpt-4o");
23
+ def_pub_const!(CLAUDE_3_OPUS, "claude-3-opus");
24
+ def_pub_const!(CURSOR_FAST, "cursor-fast");
25
+ def_pub_const!(CURSOR_SMALL, "cursor-small");
26
+ def_pub_const!(GPT_3_5_TURBO, "gpt-3.5-turbo");
27
+ def_pub_const!(GPT_4_TURBO_2024_04_09, "gpt-4-turbo-2024-04-09");
28
+ def_pub_const!(GPT_4O_128K, "gpt-4o-128k");
29
+ def_pub_const!(GEMINI_1_5_FLASH_500K, "gemini-1.5-flash-500k");
30
+ def_pub_const!(CLAUDE_3_HAIKU_200K, "claude-3-haiku-200k");
31
+ def_pub_const!(CLAUDE_3_5_SONNET_200K, "claude-3-5-sonnet-200k");
32
+ def_pub_const!(CLAUDE_3_5_SONNET_20241022, "claude-3-5-sonnet-20241022");
33
+ def_pub_const!(GPT_4O_MINI, "gpt-4o-mini");
34
+ def_pub_const!(O1_MINI, "o1-mini");
35
+ def_pub_const!(O1_PREVIEW, "o1-preview");
36
+ def_pub_const!(O1, "o1");
37
+ def_pub_const!(CLAUDE_3_5_HAIKU, "claude-3.5-haiku");
38
+ def_pub_const!(GEMINI_EXP_1206, "gemini-exp-1206");
39
+ def_pub_const!(
40
+ GEMINI_2_0_FLASH_THINKING_EXP,
41
+ "gemini-2.0-flash-thinking-exp"
42
+ );
43
+ def_pub_const!(GEMINI_2_0_FLASH_EXP, "gemini-2.0-flash-exp");
44
+
45
+ pub const AVAILABLE_MODELS: [Model; 21] = [
46
+ Model {
47
+ id: CLAUDE_3_5_SONNET,
48
+ created: CREATED,
49
+ object: MODEL_OBJECT,
50
+ owned_by: ANTHROPIC,
51
+ },
52
+ Model {
53
+ id: GPT_4,
54
+ created: CREATED,
55
+ object: MODEL_OBJECT,
56
+ owned_by: OPENAI,
57
+ },
58
+ Model {
59
+ id: GPT_4O,
60
+ created: CREATED,
61
+ object: MODEL_OBJECT,
62
+ owned_by: OPENAI,
63
+ },
64
+ Model {
65
+ id: CLAUDE_3_OPUS,
66
+ created: CREATED,
67
+ object: MODEL_OBJECT,
68
+ owned_by: ANTHROPIC,
69
+ },
70
+ Model {
71
+ id: CURSOR_FAST,
72
+ created: CREATED,
73
+ object: MODEL_OBJECT,
74
+ owned_by: CURSOR,
75
+ },
76
+ Model {
77
+ id: CURSOR_SMALL,
78
+ created: CREATED,
79
+ object: MODEL_OBJECT,
80
+ owned_by: CURSOR,
81
+ },
82
+ Model {
83
+ id: GPT_3_5_TURBO,
84
+ created: CREATED,
85
+ object: MODEL_OBJECT,
86
+ owned_by: OPENAI,
87
+ },
88
+ Model {
89
+ id: GPT_4_TURBO_2024_04_09,
90
+ created: CREATED,
91
+ object: MODEL_OBJECT,
92
+ owned_by: OPENAI,
93
+ },
94
+ Model {
95
+ id: GPT_4O_128K,
96
+ created: CREATED,
97
+ object: MODEL_OBJECT,
98
+ owned_by: OPENAI,
99
+ },
100
+ Model {
101
+ id: GEMINI_1_5_FLASH_500K,
102
+ created: CREATED,
103
+ object: MODEL_OBJECT,
104
+ owned_by: GOOGLE,
105
+ },
106
+ Model {
107
+ id: CLAUDE_3_HAIKU_200K,
108
+ created: CREATED,
109
+ object: MODEL_OBJECT,
110
+ owned_by: ANTHROPIC,
111
+ },
112
+ Model {
113
+ id: CLAUDE_3_5_SONNET_200K,
114
+ created: CREATED,
115
+ object: MODEL_OBJECT,
116
+ owned_by: ANTHROPIC,
117
+ },
118
+ Model {
119
+ id: CLAUDE_3_5_SONNET_20241022,
120
+ created: CREATED,
121
+ object: MODEL_OBJECT,
122
+ owned_by: ANTHROPIC,
123
+ },
124
+ Model {
125
+ id: GPT_4O_MINI,
126
+ created: CREATED,
127
+ object: MODEL_OBJECT,
128
+ owned_by: OPENAI,
129
+ },
130
+ Model {
131
+ id: O1_MINI,
132
+ created: CREATED,
133
+ object: MODEL_OBJECT,
134
+ owned_by: OPENAI,
135
+ },
136
+ Model {
137
+ id: O1_PREVIEW,
138
+ created: CREATED,
139
+ object: MODEL_OBJECT,
140
+ owned_by: OPENAI,
141
+ },
142
+ Model {
143
+ id: O1,
144
+ created: CREATED,
145
+ object: MODEL_OBJECT,
146
+ owned_by: OPENAI,
147
+ },
148
+ Model {
149
+ id: CLAUDE_3_5_HAIKU,
150
+ created: CREATED,
151
+ object: MODEL_OBJECT,
152
+ owned_by: ANTHROPIC,
153
+ },
154
+ Model {
155
+ id: GEMINI_EXP_1206,
156
+ created: CREATED,
157
+ object: MODEL_OBJECT,
158
+ owned_by: GOOGLE,
159
+ },
160
+ Model {
161
+ id: GEMINI_2_0_FLASH_THINKING_EXP,
162
+ created: CREATED,
163
+ object: MODEL_OBJECT,
164
+ owned_by: GOOGLE,
165
+ },
166
+ Model {
167
+ id: GEMINI_2_0_FLASH_EXP,
168
+ created: CREATED,
169
+ object: MODEL_OBJECT,
170
+ owned_by: GOOGLE,
171
+ },
172
+ ];
173
+
174
+ pub const USAGE_CHECK_MODELS: [&str; 11] = [
175
+ CLAUDE_3_5_SONNET_20241022,
176
+ CLAUDE_3_5_SONNET,
177
+ GEMINI_EXP_1206,
178
+ GPT_4,
179
+ GPT_4_TURBO_2024_04_09,
180
+ GPT_4O,
181
+ CLAUDE_3_5_HAIKU,
182
+ GPT_4O_128K,
183
+ GEMINI_1_5_FLASH_500K,
184
+ CLAUDE_3_HAIKU_200K,
185
+ CLAUDE_3_5_SONNET_200K,
186
+ ];
187
+
188
+ pub const LONG_CONTEXT_MODELS: [&str; 4] = [
189
+ GPT_4O_128K,
190
+ GEMINI_1_5_FLASH_500K,
191
+ CLAUDE_3_HAIKU_200K,
192
+ CLAUDE_3_5_SONNET_200K,
193
+ ];
src/chat/error.rs ADDED
@@ -0,0 +1,169 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use super::aiserver::v1::error_details::Error as ErrorType;
2
+ use reqwest::StatusCode;
3
+ use serde::{Deserialize, Serialize};
4
+
5
+ #[derive(Deserialize)]
6
+ pub struct ChatError {
7
+ error: ErrorBody,
8
+ }
9
+
10
+ #[derive(Deserialize)]
11
+ pub struct ErrorBody {
12
+ code: String,
13
+ // message: String, always: Error
14
+ details: Vec<ErrorDetail>,
15
+ }
16
+
17
+ #[derive(Deserialize)]
18
+ pub struct ErrorDetail {
19
+ // #[serde(rename = "type")]
20
+ // error_type: String, always: aiserver.v1.ErrorDetails
21
+ debug: ErrorDebug,
22
+ value: String,
23
+ }
24
+
25
+ #[derive(Deserialize)]
26
+ pub struct ErrorDebug {
27
+ error: String,
28
+ details: ErrorDetails,
29
+ // #[serde(rename = "isExpected")]
30
+ // is_expected: Option<bool>,
31
+ }
32
+
33
+ #[derive(Deserialize)]
34
+ pub struct ErrorDetails {
35
+ title: String,
36
+ detail: String,
37
+ // #[serde(rename = "isRetryable")]
38
+ // is_retryable: Option<bool>,
39
+ }
40
+
41
+ use crate::common::models::{ApiStatus, ErrorResponse as CommonErrorResponse};
42
+
43
+ impl ChatError {
44
+ pub fn to_error_response(&self) -> ErrorResponse {
45
+ if self.error.details.is_empty() {
46
+ return ErrorResponse {
47
+ status: 500,
48
+ code: "unknown".to_string(),
49
+ error: None,
50
+ };
51
+ }
52
+ ErrorResponse {
53
+ status: self.status_code(),
54
+ code: self.error.code.clone(),
55
+ error: Some(Error {
56
+ message: self.error.details[0].debug.details.title.clone(),
57
+ details: self.error.details[0].debug.details.detail.clone(),
58
+ value: self.error.details[0].value.clone(),
59
+ }),
60
+ }
61
+ }
62
+
63
+ pub fn status_code(&self) -> u16 {
64
+ match ErrorType::from_str_name(&self.error.details[0].debug.error) {
65
+ Some(error) => match error {
66
+ ErrorType::Unspecified => 500,
67
+ ErrorType::BadApiKey
68
+ | ErrorType::InvalidAuthId
69
+ | ErrorType::AuthTokenNotFound
70
+ | ErrorType::AuthTokenExpired
71
+ | ErrorType::Unauthorized => 401,
72
+ ErrorType::NotLoggedIn
73
+ | ErrorType::NotHighEnoughPermissions
74
+ | ErrorType::AgentRequiresLogin
75
+ | ErrorType::ProUserOnly
76
+ | ErrorType::TaskNoPermissions => 403,
77
+ ErrorType::NotFound
78
+ | ErrorType::UserNotFound
79
+ | ErrorType::TaskUuidNotFound
80
+ | ErrorType::AgentEngineNotFound
81
+ | ErrorType::GitgraphNotFound
82
+ | ErrorType::FileNotFound => 404,
83
+ ErrorType::FreeUserRateLimitExceeded
84
+ | ErrorType::ProUserRateLimitExceeded
85
+ | ErrorType::OpenaiRateLimitExceeded
86
+ | ErrorType::OpenaiAccountLimitExceeded
87
+ | ErrorType::GenericRateLimitExceeded
88
+ | ErrorType::Gpt4VisionPreviewRateLimit
89
+ | ErrorType::ApiKeyRateLimit => 429,
90
+ ErrorType::BadRequest
91
+ | ErrorType::BadModelName
92
+ | ErrorType::SlashEditFileTooLong
93
+ | ErrorType::FileUnsupported
94
+ | ErrorType::ClaudeImageTooLarge => 400,
95
+ ErrorType::Deprecated
96
+ | ErrorType::FreeUserUsageLimit
97
+ | ErrorType::ProUserUsageLimit
98
+ | ErrorType::ResourceExhausted
99
+ | ErrorType::Openai
100
+ | ErrorType::MaxTokens
101
+ | ErrorType::ApiKeyNotSupported
102
+ | ErrorType::UserAbortedRequest
103
+ | ErrorType::CustomMessage
104
+ | ErrorType::OutdatedClient
105
+ | ErrorType::Debounced
106
+ | ErrorType::RepositoryServiceRepositoryIsNotInitialized => 500,
107
+ },
108
+ None => 500,
109
+ }
110
+ }
111
+
112
+ // pub fn is_expected(&self) -> bool {
113
+ // self.error.details[0].debug.is_expected.unwrap_or_default()
114
+ // }
115
+ }
116
+
117
+ #[derive(Serialize)]
118
+ pub struct ErrorResponse {
119
+ pub status: u16,
120
+ pub code: String,
121
+ #[serde(skip_serializing_if = "Option::is_none")]
122
+ pub error: Option<Error>,
123
+ }
124
+
125
+ #[derive(Serialize)]
126
+ pub struct Error {
127
+ pub message: String,
128
+ pub details: String,
129
+ pub value: String,
130
+ }
131
+
132
+ impl ErrorResponse {
133
+ // pub fn to_json(&self) -> serde_json::Value {
134
+ // serde_json::to_value(self).unwrap()
135
+ // }
136
+
137
+ pub fn status_code(&self) -> StatusCode {
138
+ StatusCode::from_u16(self.status).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR)
139
+ }
140
+
141
+ pub fn native_code(&self) -> String {
142
+ self.code.replace("_", " ")
143
+ }
144
+
145
+ pub fn to_common(self) -> CommonErrorResponse {
146
+ CommonErrorResponse {
147
+ status: ApiStatus::Error,
148
+ code: Some(self.status),
149
+ error: self.error.as_ref().map(|error| error.message.clone()).or(Some(self.code.clone())),
150
+ message: self.error.as_ref().map(|error| error.details.clone()),
151
+ }
152
+ }
153
+ }
154
+
155
+ pub enum StreamError {
156
+ ChatError(ChatError),
157
+ DataLengthLessThan5,
158
+ EmptyMessage,
159
+ }
160
+
161
+ impl std::fmt::Display for StreamError {
162
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
163
+ match self {
164
+ StreamError::ChatError(error) => write!(f, "{}", error.error.details[0].debug.details.title),
165
+ StreamError::DataLengthLessThan5 => write!(f, "data length less than 5"),
166
+ StreamError::EmptyMessage => write!(f, "empty message"),
167
+ }
168
+ }
169
+ }
src/chat/model.rs ADDED
@@ -0,0 +1,107 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use serde::{Deserialize, Serialize};
2
+
3
+ #[derive(Serialize, Deserialize)]
4
+ #[serde(untagged)]
5
+ pub enum MessageContent {
6
+ Text(String),
7
+ Vision(Vec<VisionMessageContent>),
8
+ }
9
+
10
+ #[derive(Serialize, Deserialize)]
11
+ pub struct VisionMessageContent {
12
+ #[serde(rename = "type")]
13
+ pub content_type: String,
14
+ #[serde(skip_serializing_if = "Option::is_none")]
15
+ pub text: Option<String>,
16
+ #[serde(skip_serializing_if = "Option::is_none")]
17
+ pub image_url: Option<ImageUrl>,
18
+ }
19
+
20
+ #[derive(Serialize, Deserialize)]
21
+ pub struct ImageUrl {
22
+ pub url: String,
23
+ #[serde(skip_serializing_if = "Option::is_none")]
24
+ pub detail: Option<String>,
25
+ }
26
+
27
+ #[derive(Serialize, Deserialize)]
28
+ pub struct Message {
29
+ pub role: Role,
30
+ pub content: MessageContent,
31
+ }
32
+
33
+ #[derive(Serialize, Deserialize, PartialEq)]
34
+ pub enum Role {
35
+ #[serde(rename = "system", alias = "developer")]
36
+ System,
37
+ #[serde(rename = "user", alias = "human")]
38
+ User,
39
+ #[serde(rename = "assistant", alias = "ai")]
40
+ Assistant,
41
+ }
42
+
43
+ #[derive(Serialize)]
44
+ pub struct ChatResponse {
45
+ pub id: String,
46
+ pub object: String,
47
+ pub created: i64,
48
+ #[serde(skip_serializing_if = "Option::is_none")]
49
+ pub model: Option<String>,
50
+ pub choices: Vec<Choice>,
51
+ #[serde(skip_serializing_if = "Option::is_none")]
52
+ pub usage: Option<Usage>,
53
+ }
54
+
55
+ #[derive(Serialize)]
56
+ pub struct Choice {
57
+ pub index: i32,
58
+ #[serde(skip_serializing_if = "Option::is_none")]
59
+ pub message: Option<Message>,
60
+ #[serde(skip_serializing_if = "Option::is_none")]
61
+ pub delta: Option<Delta>,
62
+ pub finish_reason: Option<String>,
63
+ }
64
+
65
+ #[derive(Serialize)]
66
+ pub struct Delta {
67
+ #[serde(skip_serializing_if = "Option::is_none")]
68
+ pub role: Option<Role>,
69
+ #[serde(skip_serializing_if = "Option::is_none")]
70
+ pub content: Option<String>,
71
+ }
72
+
73
+ #[derive(Serialize)]
74
+ pub struct Usage {
75
+ pub prompt_tokens: u32,
76
+ pub completion_tokens: u32,
77
+ pub total_tokens: u32,
78
+ }
79
+
80
+ // 模型定义
81
+ #[derive(Serialize, Clone)]
82
+ pub struct Model {
83
+ pub id: &'static str,
84
+ pub created: &'static i64,
85
+ pub object: &'static str,
86
+ pub owned_by: &'static str,
87
+ }
88
+
89
+ use crate::app::model::{AppConfig, UsageCheck};
90
+ use super::constant::USAGE_CHECK_MODELS;
91
+
92
+ impl Model {
93
+ pub fn is_usage_check(&self) -> bool {
94
+ match AppConfig::get_usage_check() {
95
+ UsageCheck::None => false,
96
+ UsageCheck::Default => USAGE_CHECK_MODELS.contains(&self.id),
97
+ UsageCheck::All => true,
98
+ UsageCheck::Custom(models) => models.contains(&self.id),
99
+ }
100
+ }
101
+ }
102
+
103
+ #[derive(Serialize)]
104
+ pub struct ModelsResponse {
105
+ pub object: &'static str,
106
+ pub data: &'static [Model],
107
+ }
src/chat/route.rs ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ mod logs;
2
+ pub use logs::{handle_logs, handle_logs_post};
3
+ mod health;
4
+ pub use health::{handle_health, handle_root};
5
+ mod token;
6
+ pub use token::{
7
+ handle_basic_calibration, handle_get_checksum, handle_get_hash, handle_get_timestamp_header,
8
+ handle_get_tokeninfo, handle_tokeninfo_page, handle_update_tokeninfo,
9
+ handle_update_tokeninfo_post,
10
+ };
11
+ mod profile;
12
+ pub use profile::handle_user_info;
13
+ mod config;
14
+ pub use config::{
15
+ handle_about, handle_config_page, handle_env_example, handle_readme, handle_static,
16
+ };
17
+ mod api;
18
+ pub use api::handle_api_page;
src/chat/route/api.rs ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use axum::response::{IntoResponse, Response};
2
+ use reqwest::header::CONTENT_TYPE;
3
+
4
+ use crate::{
5
+ app::constant::{
6
+ CONTENT_TYPE_TEXT_HTML_WITH_UTF8, CONTENT_TYPE_TEXT_PLAIN_WITH_UTF8, ROUTE_API_PATH,
7
+ },
8
+ AppConfig, PageContent,
9
+ };
10
+
11
+ pub async fn handle_api_page() -> impl IntoResponse {
12
+ match AppConfig::get_page_content(ROUTE_API_PATH).unwrap_or_default() {
13
+ PageContent::Default => Response::builder()
14
+ .header(CONTENT_TYPE, CONTENT_TYPE_TEXT_HTML_WITH_UTF8)
15
+ .body(include_str!("../../../static/api.min.html").to_string())
16
+ .unwrap(),
17
+ PageContent::Text(content) => Response::builder()
18
+ .header(CONTENT_TYPE, CONTENT_TYPE_TEXT_PLAIN_WITH_UTF8)
19
+ .body(content.clone())
20
+ .unwrap(),
21
+ PageContent::Html(content) => Response::builder()
22
+ .header(CONTENT_TYPE, CONTENT_TYPE_TEXT_HTML_WITH_UTF8)
23
+ .body(content.clone())
24
+ .unwrap(),
25
+ }
26
+ }
src/chat/route/config.rs ADDED
@@ -0,0 +1,110 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use crate::app::{
2
+ constant::{
3
+ CONTENT_TYPE_TEXT_CSS_WITH_UTF8, CONTENT_TYPE_TEXT_HTML_WITH_UTF8,
4
+ CONTENT_TYPE_TEXT_JS_WITH_UTF8, CONTENT_TYPE_TEXT_PLAIN_WITH_UTF8, ROUTE_ABOUT_PATH,
5
+ ROUTE_CONFIG_PATH, ROUTE_README_PATH, ROUTE_SHARED_JS_PATH, ROUTE_SHARED_STYLES_PATH,
6
+ },
7
+ model::{AppConfig, PageContent},
8
+ };
9
+ use axum::{
10
+ body::Body,
11
+ extract::Path,
12
+ http::{
13
+ header::{CONTENT_TYPE, LOCATION},
14
+ StatusCode,
15
+ },
16
+ response::{IntoResponse, Response},
17
+ };
18
+
19
+ pub async fn handle_env_example() -> impl IntoResponse {
20
+ Response::builder()
21
+ .header(CONTENT_TYPE, CONTENT_TYPE_TEXT_PLAIN_WITH_UTF8)
22
+ .body(include_str!("../../../.env.example").to_string())
23
+ .unwrap()
24
+ }
25
+
26
+ // 配置页面处理函数
27
+ pub async fn handle_config_page() -> impl IntoResponse {
28
+ match AppConfig::get_page_content(ROUTE_CONFIG_PATH).unwrap_or_default() {
29
+ PageContent::Default => Response::builder()
30
+ .header(CONTENT_TYPE, CONTENT_TYPE_TEXT_HTML_WITH_UTF8)
31
+ .body(include_str!("../../../static/config.min.html").to_string())
32
+ .unwrap(),
33
+ PageContent::Text(content) => Response::builder()
34
+ .header(CONTENT_TYPE, CONTENT_TYPE_TEXT_PLAIN_WITH_UTF8)
35
+ .body(content.clone())
36
+ .unwrap(),
37
+ PageContent::Html(content) => Response::builder()
38
+ .header(CONTENT_TYPE, CONTENT_TYPE_TEXT_HTML_WITH_UTF8)
39
+ .body(content.clone())
40
+ .unwrap(),
41
+ }
42
+ }
43
+
44
+ pub async fn handle_static(Path(path): Path<String>) -> impl IntoResponse {
45
+ match path.as_str() {
46
+ "shared-styles.css" => {
47
+ match AppConfig::get_page_content(ROUTE_SHARED_STYLES_PATH).unwrap_or_default() {
48
+ PageContent::Default => Response::builder()
49
+ .header(CONTENT_TYPE, CONTENT_TYPE_TEXT_CSS_WITH_UTF8)
50
+ .body(include_str!("../../../static/shared-styles.min.css").to_string())
51
+ .unwrap(),
52
+ PageContent::Text(content) | PageContent::Html(content) => Response::builder()
53
+ .header(CONTENT_TYPE, CONTENT_TYPE_TEXT_CSS_WITH_UTF8)
54
+ .body(content.clone())
55
+ .unwrap(),
56
+ }
57
+ }
58
+ "shared.js" => {
59
+ match AppConfig::get_page_content(ROUTE_SHARED_JS_PATH).unwrap_or_default() {
60
+ PageContent::Default => Response::builder()
61
+ .header(CONTENT_TYPE, CONTENT_TYPE_TEXT_JS_WITH_UTF8)
62
+ .body(include_str!("../../../static/shared.min.js").to_string())
63
+ .unwrap(),
64
+ PageContent::Text(content) | PageContent::Html(content) => Response::builder()
65
+ .header(CONTENT_TYPE, CONTENT_TYPE_TEXT_JS_WITH_UTF8)
66
+ .body(content.clone())
67
+ .unwrap(),
68
+ }
69
+ }
70
+ _ => Response::builder()
71
+ .status(StatusCode::NOT_FOUND)
72
+ .body("Not found".to_string())
73
+ .unwrap(),
74
+ }
75
+ }
76
+
77
+ pub async fn handle_readme() -> impl IntoResponse {
78
+ match AppConfig::get_page_content(ROUTE_README_PATH).unwrap_or_default() {
79
+ PageContent::Default => Response::builder()
80
+ .header(CONTENT_TYPE, CONTENT_TYPE_TEXT_HTML_WITH_UTF8)
81
+ .body(include_str!("../../../static/readme.min.html").to_string())
82
+ .unwrap(),
83
+ PageContent::Text(content) => Response::builder()
84
+ .header(CONTENT_TYPE, CONTENT_TYPE_TEXT_PLAIN_WITH_UTF8)
85
+ .body(content.clone())
86
+ .unwrap(),
87
+ PageContent::Html(content) => Response::builder()
88
+ .header(CONTENT_TYPE, CONTENT_TYPE_TEXT_HTML_WITH_UTF8)
89
+ .body(content.clone())
90
+ .unwrap(),
91
+ }
92
+ }
93
+
94
+ pub async fn handle_about() -> impl IntoResponse {
95
+ match AppConfig::get_page_content(ROUTE_ABOUT_PATH).unwrap_or_default() {
96
+ PageContent::Default => Response::builder()
97
+ .status(StatusCode::TEMPORARY_REDIRECT)
98
+ .header(LOCATION, ROUTE_README_PATH)
99
+ .body(Body::empty())
100
+ .unwrap(),
101
+ PageContent::Text(content) => Response::builder()
102
+ .header(CONTENT_TYPE, CONTENT_TYPE_TEXT_PLAIN_WITH_UTF8)
103
+ .body(Body::from(content.clone()))
104
+ .unwrap(),
105
+ PageContent::Html(content) => Response::builder()
106
+ .header(CONTENT_TYPE, CONTENT_TYPE_TEXT_HTML_WITH_UTF8)
107
+ .body(Body::from(content.clone()))
108
+ .unwrap(),
109
+ }
110
+ }
src/chat/route/health.rs ADDED
@@ -0,0 +1,136 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use crate::{
2
+ app::{
3
+ constant::{
4
+ AUTHORIZATION_BEARER_PREFIX, CONTENT_TYPE_TEXT_HTML_WITH_UTF8,
5
+ CONTENT_TYPE_TEXT_PLAIN_WITH_UTF8, PKG_VERSION, ROUTE_ABOUT_PATH, ROUTE_API_PATH,
6
+ ROUTE_BASIC_CALIBRATION_PATH, ROUTE_CONFIG_PATH, ROUTE_ENV_EXAMPLE_PATH,
7
+ ROUTE_GET_CHECKSUM, ROUTE_GET_HASH, ROUTE_GET_TIMESTAMP_HEADER,
8
+ ROUTE_GET_TOKENINFO_PATH, ROUTE_HEALTH_PATH, ROUTE_LOGS_PATH, ROUTE_README_PATH,
9
+ ROUTE_ROOT_PATH, ROUTE_STATIC_PATH, ROUTE_TOKENINFO_PATH, ROUTE_UPDATE_TOKENINFO_PATH,
10
+ ROUTE_USER_INFO_PATH,
11
+ },
12
+ lazy::{get_start_time, AUTH_TOKEN, ROUTE_CHAT_PATH, ROUTE_MODELS_PATH},
13
+ model::{AppConfig, AppState, PageContent},
14
+ },
15
+ chat::constant::AVAILABLE_MODELS,
16
+ common::models::{
17
+ health::{CpuInfo, HealthCheckResponse, MemoryInfo, SystemInfo, SystemStats},
18
+ ApiStatus,
19
+ },
20
+ };
21
+ use axum::{
22
+ body::Body,
23
+ extract::State,
24
+ http::{
25
+ header::{CONTENT_TYPE, LOCATION},
26
+ HeaderMap, StatusCode,
27
+ },
28
+ response::{IntoResponse, Response},
29
+ Json,
30
+ };
31
+ use chrono::Local;
32
+ use reqwest::header::AUTHORIZATION;
33
+ use std::sync::Arc;
34
+ use sysinfo::{CpuRefreshKind, MemoryRefreshKind, RefreshKind, System};
35
+ use tokio::sync::Mutex;
36
+
37
+ pub async fn handle_root() -> impl IntoResponse {
38
+ match AppConfig::get_page_content(ROUTE_ROOT_PATH).unwrap_or_default() {
39
+ PageContent::Default => Response::builder()
40
+ .status(StatusCode::TEMPORARY_REDIRECT)
41
+ .header(LOCATION, ROUTE_HEALTH_PATH)
42
+ .body(Body::empty())
43
+ .unwrap(),
44
+ PageContent::Text(content) => Response::builder()
45
+ .header(CONTENT_TYPE, CONTENT_TYPE_TEXT_PLAIN_WITH_UTF8)
46
+ .body(Body::from(content.clone()))
47
+ .unwrap(),
48
+ PageContent::Html(content) => Response::builder()
49
+ .header(CONTENT_TYPE, CONTENT_TYPE_TEXT_HTML_WITH_UTF8)
50
+ .body(Body::from(content.clone()))
51
+ .unwrap(),
52
+ }
53
+ }
54
+
55
+ pub async fn handle_health(
56
+ State(state): State<Arc<Mutex<AppState>>>,
57
+ headers: HeaderMap,
58
+ ) -> Json<HealthCheckResponse> {
59
+ let start_time = get_start_time();
60
+ let uptime = (Local::now() - start_time).num_seconds();
61
+
62
+ // 先检查 headers 是否包含有效的认证信息
63
+ let stats = if headers
64
+ .get(AUTHORIZATION)
65
+ .and_then(|h| h.to_str().ok())
66
+ .and_then(|h| h.strip_prefix(AUTHORIZATION_BEARER_PREFIX))
67
+ .map_or(false, |token| token == AUTH_TOKEN.as_str())
68
+ {
69
+ // 只有在需要系统信息时才创建实例
70
+ let mut sys = System::new_with_specifics(
71
+ RefreshKind::nothing()
72
+ .with_memory(MemoryRefreshKind::everything())
73
+ .with_cpu(CpuRefreshKind::everything()),
74
+ );
75
+
76
+ std::thread::sleep(sysinfo::MINIMUM_CPU_UPDATE_INTERVAL);
77
+
78
+ // 刷新 CPU 和内存信息
79
+ sys.refresh_memory();
80
+ sys.refresh_cpu_usage();
81
+
82
+ let pid = std::process::id() as usize;
83
+ let process = sys.process(pid.into());
84
+
85
+ // 获取内存信息
86
+ let memory = process.map(|p| p.memory()).unwrap_or(0);
87
+
88
+ // 获取 CPU 使用率
89
+ let cpu_usage = sys.global_cpu_usage();
90
+
91
+ let state = state.lock().await;
92
+
93
+ Some(SystemStats {
94
+ started: start_time.to_string(),
95
+ total_requests: state.total_requests,
96
+ active_requests: state.active_requests,
97
+ system: SystemInfo {
98
+ memory: MemoryInfo {
99
+ rss: memory, // 物理内存使用量(字节)
100
+ },
101
+ cpu: CpuInfo {
102
+ usage: cpu_usage, // CPU 使用率(百分比)
103
+ },
104
+ },
105
+ })
106
+ } else {
107
+ None
108
+ };
109
+
110
+ Json(HealthCheckResponse {
111
+ status: ApiStatus::Healthy,
112
+ version: PKG_VERSION,
113
+ uptime,
114
+ stats,
115
+ models: AVAILABLE_MODELS.iter().map(|m| m.id).collect::<Vec<_>>(),
116
+ endpoints: vec![
117
+ ROUTE_CHAT_PATH.as_str(),
118
+ ROUTE_MODELS_PATH.as_str(),
119
+ ROUTE_TOKENINFO_PATH,
120
+ ROUTE_UPDATE_TOKENINFO_PATH,
121
+ ROUTE_GET_TOKENINFO_PATH,
122
+ ROUTE_LOGS_PATH,
123
+ ROUTE_ENV_EXAMPLE_PATH,
124
+ ROUTE_CONFIG_PATH,
125
+ ROUTE_STATIC_PATH,
126
+ ROUTE_ABOUT_PATH,
127
+ ROUTE_README_PATH,
128
+ ROUTE_API_PATH,
129
+ ROUTE_GET_HASH,
130
+ ROUTE_GET_CHECKSUM,
131
+ ROUTE_GET_TIMESTAMP_HEADER,
132
+ ROUTE_BASIC_CALIBRATION_PATH,
133
+ ROUTE_USER_INFO_PATH,
134
+ ],
135
+ })
136
+ }
src/chat/route/logs.rs ADDED
@@ -0,0 +1,109 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use crate::{
2
+ app::{
3
+ constant::{
4
+ AUTHORIZATION_BEARER_PREFIX, CONTENT_TYPE_TEXT_HTML_WITH_UTF8,
5
+ CONTENT_TYPE_TEXT_PLAIN_WITH_UTF8, ROUTE_LOGS_PATH,
6
+ },
7
+ lazy::AUTH_TOKEN,
8
+ model::{AppConfig, AppState, PageContent, RequestLog},
9
+ },
10
+ common::{models::ApiStatus, utils::extract_token},
11
+ };
12
+ use axum::{
13
+ body::Body,
14
+ extract::State,
15
+ http::{
16
+ header::{AUTHORIZATION, CONTENT_TYPE},
17
+ HeaderMap, StatusCode,
18
+ },
19
+ response::{IntoResponse, Response},
20
+ Json,
21
+ };
22
+ use chrono::Local;
23
+ use std::sync::Arc;
24
+ use tokio::sync::Mutex;
25
+
26
+ // 日志处理
27
+ pub async fn handle_logs() -> impl IntoResponse {
28
+ match AppConfig::get_page_content(ROUTE_LOGS_PATH).unwrap_or_default() {
29
+ PageContent::Default => Response::builder()
30
+ .header(CONTENT_TYPE, CONTENT_TYPE_TEXT_HTML_WITH_UTF8)
31
+ .body(Body::from(
32
+ include_str!("../../../static/logs.min.html").to_string(),
33
+ ))
34
+ .unwrap(),
35
+ PageContent::Text(content) => Response::builder()
36
+ .header(CONTENT_TYPE, CONTENT_TYPE_TEXT_PLAIN_WITH_UTF8)
37
+ .body(Body::from(content.clone()))
38
+ .unwrap(),
39
+ PageContent::Html(content) => Response::builder()
40
+ .header(CONTENT_TYPE, CONTENT_TYPE_TEXT_HTML_WITH_UTF8)
41
+ .body(Body::from(content.clone()))
42
+ .unwrap(),
43
+ }
44
+ }
45
+
46
+ pub async fn handle_logs_post(
47
+ State(state): State<Arc<Mutex<AppState>>>,
48
+ headers: HeaderMap,
49
+ ) -> Result<Json<LogsResponse>, StatusCode> {
50
+ let auth_token = AUTH_TOKEN.as_str();
51
+
52
+ // 获取认证头
53
+ let auth_header = headers
54
+ .get(AUTHORIZATION)
55
+ .and_then(|h| h.to_str().ok())
56
+ .and_then(|h| h.strip_prefix(AUTHORIZATION_BEARER_PREFIX))
57
+ .ok_or(StatusCode::UNAUTHORIZED)?;
58
+
59
+ let state = state.lock().await;
60
+
61
+ // 如果是管理员token,返回所有日志
62
+ if auth_header == auth_token {
63
+ return Ok(Json(LogsResponse {
64
+ status: ApiStatus::Success,
65
+ total: state.total_requests,
66
+ active: Some(state.active_requests),
67
+ error: Some(state.error_requests),
68
+ logs: state.request_logs.clone(),
69
+ timestamp: Local::now().to_string(),
70
+ }));
71
+ }
72
+
73
+ // 解析 token
74
+ let token_part = extract_token(auth_header).ok_or(StatusCode::UNAUTHORIZED)?;
75
+
76
+ // 否则筛选出token匹配的日志
77
+ let filtered_logs: Vec<RequestLog> = state
78
+ .request_logs
79
+ .iter()
80
+ .filter(|log| log.token_info.token == token_part)
81
+ .cloned()
82
+ .collect();
83
+
84
+ // 如果没有匹配的日志,返回未授权错误
85
+ if filtered_logs.is_empty() {
86
+ return Err(StatusCode::UNAUTHORIZED);
87
+ }
88
+
89
+ Ok(Json(LogsResponse {
90
+ status: ApiStatus::Success,
91
+ total: filtered_logs.len() as u64,
92
+ active: None,
93
+ error: None,
94
+ logs: filtered_logs,
95
+ timestamp: Local::now().to_string(),
96
+ }))
97
+ }
98
+
99
+ #[derive(serde::Serialize)]
100
+ pub struct LogsResponse {
101
+ pub status: ApiStatus,
102
+ pub total: u64,
103
+ #[serde(skip_serializing_if = "Option::is_none")]
104
+ pub active: Option<u64>,
105
+ #[serde(skip_serializing_if = "Option::is_none")]
106
+ pub error: Option<u64>,
107
+ pub logs: Vec<RequestLog>,
108
+ pub timestamp: String,
109
+ }
src/chat/route/profile.rs ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use crate::{
2
+ chat::constant::ERR_NODATA,
3
+ common::{models::userinfo::GetUserInfo, utils::{extract_token, get_token_profile}},
4
+ };
5
+ use axum::Json;
6
+
7
+ use super::token::TokenRequest;
8
+
9
+ pub async fn handle_user_info(Json(request): Json<TokenRequest>) -> Json<GetUserInfo> {
10
+ let auth_token = match request.token {
11
+ Some(token) => token,
12
+ None => {
13
+ return Json(GetUserInfo::Error {
14
+ error: ERR_NODATA.to_string(),
15
+ })
16
+ }
17
+ };
18
+
19
+ let token = match extract_token(&auth_token) {
20
+ Some(token) => token,
21
+ None => {
22
+ return Json(GetUserInfo::Error {
23
+ error: ERR_NODATA.to_string(),
24
+ })
25
+ }
26
+ };
27
+
28
+ match get_token_profile(&token).await {
29
+ Some(usage) => Json(GetUserInfo::Usage(usage)),
30
+ None => Json(GetUserInfo::Error {
31
+ error: ERR_NODATA.to_string(),
32
+ }),
33
+ }
34
+ }
src/chat/route/token.rs ADDED
@@ -0,0 +1,276 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use crate::{
2
+ app::{
3
+ constant::{
4
+ AUTHORIZATION_BEARER_PREFIX, CONTENT_TYPE_TEXT_HTML_WITH_UTF8,
5
+ CONTENT_TYPE_TEXT_PLAIN_WITH_UTF8, ROUTE_TOKENINFO_PATH,
6
+ },
7
+ lazy::{AUTH_TOKEN, TOKEN_FILE, TOKEN_LIST_FILE},
8
+ model::{AppConfig, AppState, PageContent, TokenUpdateRequest},
9
+ },
10
+ common::{
11
+ models::{ApiStatus, NormalResponseNoData},
12
+ utils::{
13
+ extract_time, extract_time_ks, extract_user_id, generate_checksum_with_default, generate_checksum_with_repair, generate_hash, generate_timestamp_header, load_tokens, validate_token_and_checksum
14
+ },
15
+ },
16
+ };
17
+ use axum::{
18
+ extract::{Query, State},
19
+ http::{
20
+ header::{AUTHORIZATION, CONTENT_TYPE},
21
+ HeaderMap,
22
+ },
23
+ response::{IntoResponse, Response},
24
+ Json,
25
+ };
26
+ use reqwest::StatusCode;
27
+ use serde::{Deserialize, Serialize};
28
+ use std::sync::Arc;
29
+ use tokio::sync::Mutex;
30
+
31
+ pub async fn handle_get_hash() -> Response {
32
+ let hash = generate_hash();
33
+
34
+ let mut headers = HeaderMap::new();
35
+ headers.insert(
36
+ CONTENT_TYPE,
37
+ CONTENT_TYPE_TEXT_PLAIN_WITH_UTF8.parse().unwrap(),
38
+ );
39
+
40
+ (headers, hash).into_response()
41
+ }
42
+
43
+ #[derive(Deserialize)]
44
+ pub struct ChecksumQuery {
45
+ #[serde(default)]
46
+ pub checksum: Option<String>,
47
+ }
48
+
49
+ pub async fn handle_get_checksum(Query(query): Query<ChecksumQuery>) -> Response {
50
+ let checksum = match query.checksum {
51
+ None => generate_checksum_with_default(),
52
+ Some(checksum) => generate_checksum_with_repair(&checksum),
53
+ };
54
+
55
+ let mut headers = HeaderMap::new();
56
+ headers.insert(
57
+ CONTENT_TYPE,
58
+ CONTENT_TYPE_TEXT_PLAIN_WITH_UTF8.parse().unwrap(),
59
+ );
60
+
61
+ (headers, checksum).into_response()
62
+ }
63
+
64
+ pub async fn handle_get_timestamp_header() -> Response {
65
+ let timestamp_header = generate_timestamp_header();
66
+
67
+ let mut headers = HeaderMap::new();
68
+ headers.insert(
69
+ CONTENT_TYPE,
70
+ CONTENT_TYPE_TEXT_PLAIN_WITH_UTF8.parse().unwrap(),
71
+ );
72
+
73
+ (headers, timestamp_header).into_response()
74
+ }
75
+
76
+ // 更新 TokenInfo 处理
77
+ pub async fn handle_update_tokeninfo(
78
+ State(state): State<Arc<Mutex<AppState>>>,
79
+ ) -> Json<NormalResponseNoData> {
80
+ // 重新加载 tokens
81
+ let token_infos = load_tokens();
82
+
83
+ // 更新应用状态
84
+ {
85
+ let mut state = state.lock().await;
86
+ state.token_infos = token_infos;
87
+ }
88
+
89
+ Json(NormalResponseNoData {
90
+ status: ApiStatus::Success,
91
+ message: Some("Token list has been reloaded".to_string()),
92
+ })
93
+ }
94
+
95
+ // 获取 TokenInfo 处理
96
+ pub async fn handle_get_tokeninfo(
97
+ headers: HeaderMap,
98
+ ) -> Result<Json<TokenInfoResponse>, StatusCode> {
99
+ // 验证 AUTH_TOKEN
100
+ let auth_header = headers
101
+ .get(AUTHORIZATION)
102
+ .and_then(|h| h.to_str().ok())
103
+ .and_then(|h| h.strip_prefix(AUTHORIZATION_BEARER_PREFIX))
104
+ .ok_or(StatusCode::UNAUTHORIZED)?;
105
+
106
+ if auth_header != AUTH_TOKEN.as_str() {
107
+ return Err(StatusCode::UNAUTHORIZED);
108
+ }
109
+
110
+ let token_file = TOKEN_FILE.as_str();
111
+ let token_list_file = TOKEN_LIST_FILE.as_str();
112
+
113
+ // 读取文件内容
114
+ let tokens = std::fs::read_to_string(&token_file).unwrap_or_else(|_| String::new());
115
+ let token_list = std::fs::read_to_string(&token_list_file).unwrap_or_else(|_| String::new());
116
+
117
+ // 获取 tokens_count
118
+ let tokens_count = {
119
+ {
120
+ tokens.len()
121
+ }
122
+ };
123
+
124
+ Ok(Json(TokenInfoResponse {
125
+ status: ApiStatus::Success,
126
+ token_file: token_file.to_string(),
127
+ token_list_file: token_list_file.to_string(),
128
+ tokens: Some(tokens),
129
+ tokens_count: Some(tokens_count),
130
+ token_list: Some(token_list),
131
+ message: None,
132
+ }))
133
+ }
134
+
135
+ #[derive(Serialize)]
136
+ pub struct TokenInfoResponse {
137
+ pub status: ApiStatus,
138
+ pub token_file: String,
139
+ pub token_list_file: String,
140
+ #[serde(skip_serializing_if = "Option::is_none")]
141
+ pub tokens: Option<String>,
142
+ #[serde(skip_serializing_if = "Option::is_none")]
143
+ pub tokens_count: Option<usize>,
144
+ #[serde(skip_serializing_if = "Option::is_none")]
145
+ pub token_list: Option<String>,
146
+ #[serde(skip_serializing_if = "Option::is_none")]
147
+ pub message: Option<String>,
148
+ }
149
+
150
+ pub async fn handle_update_tokeninfo_post(
151
+ State(state): State<Arc<Mutex<AppState>>>,
152
+ headers: HeaderMap,
153
+ Json(request): Json<TokenUpdateRequest>,
154
+ ) -> Result<Json<TokenInfoResponse>, StatusCode> {
155
+ // 验证 AUTH_TOKEN
156
+ let auth_header = headers
157
+ .get(AUTHORIZATION)
158
+ .and_then(|h| h.to_str().ok())
159
+ .and_then(|h| h.strip_prefix(AUTHORIZATION_BEARER_PREFIX))
160
+ .ok_or(StatusCode::UNAUTHORIZED)?;
161
+
162
+ if auth_header != AUTH_TOKEN.as_str() {
163
+ return Err(StatusCode::UNAUTHORIZED);
164
+ }
165
+
166
+ let token_file = TOKEN_FILE.as_str();
167
+ let token_list_file = TOKEN_LIST_FILE.as_str();
168
+
169
+ // 写入文件
170
+ std::fs::write(&token_file, &request.tokens).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
171
+
172
+ if let Some(token_list) = &request.token_list {
173
+ std::fs::write(&token_list_file, token_list)
174
+ .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
175
+ }
176
+
177
+ // 重新加载 tokens
178
+ let token_infos = load_tokens();
179
+ let token_infos_len = token_infos.len();
180
+
181
+ // 更新应用状态
182
+ {
183
+ let mut state = state.lock().await;
184
+ state.token_infos = token_infos;
185
+ }
186
+
187
+ Ok(Json(TokenInfoResponse {
188
+ status: ApiStatus::Success,
189
+ token_file: token_file.to_string(),
190
+ token_list_file: token_list_file.to_string(),
191
+ tokens: None,
192
+ tokens_count: Some(token_infos_len),
193
+ token_list: None,
194
+ message: Some("Token files have been updated and reloaded".to_string()),
195
+ }))
196
+ }
197
+
198
+ pub async fn handle_tokeninfo_page() -> impl IntoResponse {
199
+ match AppConfig::get_page_content(ROUTE_TOKENINFO_PATH).unwrap_or_default() {
200
+ PageContent::Default => Response::builder()
201
+ .header(CONTENT_TYPE, CONTENT_TYPE_TEXT_HTML_WITH_UTF8)
202
+ .body(include_str!("../../../static/tokeninfo.min.html").to_string())
203
+ .unwrap(),
204
+ PageContent::Text(content) => Response::builder()
205
+ .header(CONTENT_TYPE, CONTENT_TYPE_TEXT_PLAIN_WITH_UTF8)
206
+ .body(content.clone())
207
+ .unwrap(),
208
+ PageContent::Html(content) => Response::builder()
209
+ .header(CONTENT_TYPE, CONTENT_TYPE_TEXT_HTML_WITH_UTF8)
210
+ .body(content.clone())
211
+ .unwrap(),
212
+ }
213
+ }
214
+
215
+ #[derive(Deserialize)]
216
+ pub struct TokenRequest {
217
+ pub token: Option<String>,
218
+ }
219
+
220
+ #[derive(Serialize)]
221
+ pub struct BasicCalibrationResponse {
222
+ pub status: ApiStatus,
223
+ pub message: Option<String>,
224
+ #[serde(skip_serializing_if = "Option::is_none")]
225
+ pub user_id: Option<String>,
226
+ #[serde(skip_serializing_if = "Option::is_none")]
227
+ pub create_at: Option<String>,
228
+ #[serde(skip_serializing_if = "Option::is_none")]
229
+ pub checksum_time: Option<u64>,
230
+ }
231
+
232
+ pub async fn handle_basic_calibration(
233
+ Json(request): Json<TokenRequest>,
234
+ ) -> Json<BasicCalibrationResponse> {
235
+ // 从请求头中获取并验证 auth token
236
+ let auth_token = match request.token {
237
+ Some(token) => token,
238
+ None => {
239
+ return Json(BasicCalibrationResponse {
240
+ status: ApiStatus::Error,
241
+ message: Some("未提供授权令牌".to_string()),
242
+ user_id: None,
243
+ create_at: None,
244
+ checksum_time: None,
245
+ })
246
+ }
247
+ };
248
+
249
+ // 校验 token 和 checksum
250
+ let (token, checksum) = match validate_token_and_checksum(&auth_token) {
251
+ Some(parts) => parts,
252
+ None => {
253
+ return Json(BasicCalibrationResponse {
254
+ status: ApiStatus::Error,
255
+ message: Some("无效令牌或无效校验和".to_string()),
256
+ user_id: None,
257
+ create_at: None,
258
+ checksum_time: None,
259
+ })
260
+ }
261
+ };
262
+
263
+ // 提取用户ID和创建时间
264
+ let user_id = extract_user_id(&token);
265
+ let create_at = extract_time(&token).map(|dt| dt.to_string());
266
+ let checksum_time = extract_time_ks(&checksum[..8]);
267
+
268
+ // 返回校验结果
269
+ Json(BasicCalibrationResponse {
270
+ status: ApiStatus::Success,
271
+ message: Some("校验成功".to_string()),
272
+ user_id,
273
+ create_at,
274
+ checksum_time,
275
+ })
276
+ }
src/chat/service.rs ADDED
@@ -0,0 +1,710 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use crate::{
2
+ app::{
3
+ constant::{
4
+ AUTHORIZATION_BEARER_PREFIX, FINISH_REASON_STOP, OBJECT_CHAT_COMPLETION,
5
+ OBJECT_CHAT_COMPLETION_CHUNK, STATUS_FAILED, STATUS_PENDING, STATUS_SUCCESS,
6
+ },
7
+ lazy::{AUTH_TOKEN, SHARED_AUTH_TOKEN, USE_SHARE},
8
+ model::{AppConfig, AppState, ChatRequest, RequestLog, TimingInfo, TokenInfo},
9
+ },
10
+ chat::{
11
+ constant::{AVAILABLE_MODELS, USAGE_CHECK_MODELS},
12
+ error::StreamError,
13
+ model::{
14
+ ChatResponse, Choice, Delta, Message, MessageContent, ModelsResponse, Role, Usage,
15
+ },
16
+ stream::{parse_stream_data, StreamMessage},
17
+ },
18
+ common::{
19
+ client::build_client,
20
+ models::{error::ChatError, userinfo::MembershipType, ErrorResponse},
21
+ utils::{format_time_ms, generate_checksum_with_repair, get_token_profile, validate_token_and_checksum},
22
+ },
23
+ };
24
+ use axum::{
25
+ body::Body,
26
+ extract::State,
27
+ http::{
28
+ header::{AUTHORIZATION, CONTENT_TYPE},
29
+ HeaderMap, StatusCode,
30
+ },
31
+ response::Response,
32
+ Json,
33
+ };
34
+ use bytes::Bytes;
35
+ use futures::{Stream, StreamExt};
36
+ use std::{
37
+ convert::Infallible,
38
+ sync::{atomic::AtomicBool, Arc},
39
+ };
40
+ use std::{
41
+ pin::Pin,
42
+ sync::atomic::{AtomicUsize, Ordering},
43
+ };
44
+ use tokio::sync::Mutex;
45
+ use uuid::Uuid;
46
+
47
+ const REQUEST_LOGS_LIMIT: usize = 1000;
48
+
49
+ // 模型列表处理
50
+ pub async fn handle_models() -> Json<ModelsResponse> {
51
+ Json(ModelsResponse {
52
+ object: "list",
53
+ data: &AVAILABLE_MODELS,
54
+ })
55
+ }
56
+
57
+ // 聊天处理函数的签名
58
+ pub async fn handle_chat(
59
+ State(state): State<Arc<Mutex<AppState>>>,
60
+ headers: HeaderMap,
61
+ Json(request): Json<ChatRequest>,
62
+ ) -> Result<Response<Body>, (StatusCode, Json<ErrorResponse>)> {
63
+ let allow_claude = AppConfig::get_allow_claude();
64
+ // 验证模型是否支持并获取模型信息
65
+ let model = AVAILABLE_MODELS.iter().find(|m| m.id == request.model);
66
+ let model_supported = model.is_some();
67
+
68
+ if !(model_supported || allow_claude && request.model.starts_with("claude")) {
69
+ return Err((
70
+ StatusCode::BAD_REQUEST,
71
+ Json(ChatError::ModelNotSupported(request.model).to_json()),
72
+ ));
73
+ }
74
+
75
+ let request_time = chrono::Local::now();
76
+
77
+ // 验证请求
78
+ if request.messages.is_empty() {
79
+ return Err((
80
+ StatusCode::BAD_REQUEST,
81
+ Json(ChatError::EmptyMessages.to_json()),
82
+ ));
83
+ }
84
+
85
+ // 获取并处理认证令牌
86
+ let auth_header = headers
87
+ .get(AUTHORIZATION)
88
+ .and_then(|h| h.to_str().ok())
89
+ .and_then(|h| h.strip_prefix(AUTHORIZATION_BEARER_PREFIX))
90
+ .ok_or((
91
+ StatusCode::UNAUTHORIZED,
92
+ Json(ChatError::Unauthorized.to_json()),
93
+ ))?;
94
+
95
+ // 验证认证token并获取token信息
96
+ let (auth_token, checksum) = match auth_header {
97
+ // 管理员Token验证逻辑
98
+ token if token == AUTH_TOKEN.as_str() || (*USE_SHARE && token == SHARED_AUTH_TOKEN.as_str()) => {
99
+ static CURRENT_KEY_INDEX: AtomicUsize = AtomicUsize::new(0);
100
+ let state_guard = state.lock().await;
101
+ let token_infos = &state_guard.token_infos;
102
+
103
+ // 检查是否存在可用的token
104
+ if token_infos.is_empty() {
105
+ return Err((
106
+ StatusCode::SERVICE_UNAVAILABLE,
107
+ Json(ChatError::NoTokens.to_json()),
108
+ ));
109
+ }
110
+
111
+ // 轮询选择token
112
+ let index = CURRENT_KEY_INDEX.fetch_add(1, Ordering::SeqCst) % token_infos.len();
113
+ let token_info = &token_infos[index];
114
+ (token_info.token.clone(), token_info.checksum.clone())
115
+ },
116
+
117
+ // 普通用户Token验证逻辑
118
+ token => validate_token_and_checksum(token).ok_or((
119
+ StatusCode::UNAUTHORIZED,
120
+ Json(ChatError::Unauthorized.to_json()),
121
+ ))?,
122
+ };
123
+
124
+ let current_id: u64;
125
+
126
+ // 更新请求日志
127
+ {
128
+ let state_clone = state.clone();
129
+ let mut state = state.lock().await;
130
+ state.total_requests += 1;
131
+ state.active_requests += 1;
132
+
133
+ // 查找最新的相同token的日志,检查使用情况
134
+ let need_profile_check = state
135
+ .request_logs
136
+ .iter()
137
+ .rev()
138
+ .find(|log| log.token_info.token == auth_token && log.token_info.profile.is_some())
139
+ .and_then(|log| log.token_info.profile.as_ref())
140
+ .map(|profile| {
141
+ if profile.stripe.membership_type != MembershipType::Free {
142
+ return false;
143
+ }
144
+
145
+ let is_premium = USAGE_CHECK_MODELS.contains(&request.model.as_str());
146
+ let standard = &profile.usage.standard;
147
+ let premium = &profile.usage.premium;
148
+
149
+ if is_premium {
150
+ premium
151
+ .max_requests
152
+ .map_or(false, |max| premium.num_requests >= max)
153
+ } else {
154
+ standard
155
+ .max_requests
156
+ .map_or(false, |max| standard.num_requests >= max)
157
+ }
158
+ })
159
+ .unwrap_or(false);
160
+
161
+ // 如果达到限制,直接返回未授权错误
162
+ if need_profile_check {
163
+ state.active_requests -= 1;
164
+ state.error_requests += 1;
165
+ return Err((
166
+ StatusCode::UNAUTHORIZED,
167
+ Json(ChatError::Unauthorized.to_json()),
168
+ ));
169
+ }
170
+
171
+ let next_id = state.request_logs.last().map_or(1, |log| log.id + 1);
172
+ current_id = next_id;
173
+
174
+ // 如果需要获取用户使用情况,创建后台任务获取profile
175
+ if model.map(|m| m.is_usage_check()).unwrap_or(false) {
176
+ let auth_token_clone = auth_token.clone();
177
+ let state_clone = state_clone.clone();
178
+ let log_id = next_id;
179
+
180
+ tokio::spawn(async move {
181
+ let profile = get_token_profile(&auth_token_clone).await;
182
+ let mut state = state_clone.lock().await;
183
+ // 根据id查找对应的日志
184
+ if let Some(log) = state
185
+ .request_logs
186
+ .iter_mut()
187
+ .rev()
188
+ .find(|log| log.id == log_id)
189
+ {
190
+ log.token_info.profile = profile;
191
+ }
192
+ });
193
+ }
194
+
195
+ state.request_logs.push(RequestLog {
196
+ id: next_id,
197
+ timestamp: request_time,
198
+ model: request.model.clone(),
199
+ token_info: TokenInfo {
200
+ token: auth_token.clone(),
201
+ checksum: checksum.clone(),
202
+ profile: None,
203
+ },
204
+ prompt: None,
205
+ timing: TimingInfo {
206
+ total: 0.0,
207
+ first: None,
208
+ },
209
+ stream: request.stream,
210
+ status: STATUS_PENDING,
211
+ error: None,
212
+ });
213
+
214
+ if state.request_logs.len() > REQUEST_LOGS_LIMIT {
215
+ state.request_logs.remove(0);
216
+ }
217
+ }
218
+
219
+ // 将消息转换为hex格式
220
+ let hex_data = match super::adapter::encode_chat_message(request.messages, &request.model).await
221
+ {
222
+ Ok(data) => data,
223
+ Err(e) => {
224
+ let mut state = state.lock().await;
225
+ if let Some(log) = state
226
+ .request_logs
227
+ .iter_mut()
228
+ .rev()
229
+ .find(|log| log.id == current_id)
230
+ {
231
+ log.status = STATUS_FAILED;
232
+ log.error = Some(e.to_string());
233
+ }
234
+ state.active_requests -= 1;
235
+ state.error_requests += 1;
236
+ return Err((
237
+ StatusCode::INTERNAL_SERVER_ERROR,
238
+ Json(
239
+ ChatError::RequestFailed("Failed to encode chat message".to_string()).to_json(),
240
+ ),
241
+ ));
242
+ }
243
+ };
244
+
245
+ // 构建请求客户端
246
+ let client = build_client(&auth_token, &generate_checksum_with_repair(&checksum));
247
+ let response = client.body(hex_data).send().await;
248
+
249
+ // 处理请求结果
250
+ let response = match response {
251
+ Ok(resp) => {
252
+ // 更新请求日志为成功
253
+ {
254
+ let mut state = state.lock().await;
255
+ if let Some(log) = state
256
+ .request_logs
257
+ .iter_mut()
258
+ .rev()
259
+ .find(|log| log.id == current_id)
260
+ {
261
+ log.status = STATUS_SUCCESS;
262
+ }
263
+ }
264
+ resp
265
+ }
266
+ Err(e) => {
267
+ // 更新请求日志为失败
268
+ {
269
+ let mut state = state.lock().await;
270
+ if let Some(log) = state
271
+ .request_logs
272
+ .iter_mut()
273
+ .rev()
274
+ .find(|log| log.id == current_id)
275
+ {
276
+ log.status = STATUS_FAILED;
277
+ log.error = Some(e.to_string());
278
+ }
279
+ state.active_requests -= 1;
280
+ state.error_requests += 1;
281
+ }
282
+ return Err((
283
+ StatusCode::INTERNAL_SERVER_ERROR,
284
+ Json(ChatError::RequestFailed(e.to_string()).to_json()),
285
+ ));
286
+ }
287
+ };
288
+
289
+ // 释放活动请求计数
290
+ {
291
+ let mut state = state.lock().await;
292
+ state.active_requests -= 1;
293
+ }
294
+
295
+ if request.stream {
296
+ let response_id = format!("chatcmpl-{}", Uuid::new_v4().simple());
297
+ let full_text = Arc::new(Mutex::new(String::with_capacity(1024)));
298
+ let is_start = Arc::new(AtomicBool::new(true));
299
+ let start_time = std::time::Instant::now();
300
+ let first_chunk_time = Arc::new(Mutex::new(None));
301
+
302
+ let stream = {
303
+ // 创建新的 stream
304
+ let mut stream = response.bytes_stream();
305
+
306
+ let enable_stream_check = AppConfig::get_stream_check();
307
+
308
+ if enable_stream_check {
309
+ // 检查第一个 chunk
310
+ match stream.next().await {
311
+ Some(first_chunk) => {
312
+ let chunk = first_chunk.map_err(|e| {
313
+ let error_message = format!("Failed to read response chunk: {}", e);
314
+ // 理论上,若程序正常,必定成功,因为前面判断过了
315
+ (
316
+ StatusCode::INTERNAL_SERVER_ERROR,
317
+ Json(ChatError::RequestFailed(error_message).to_json()),
318
+ )
319
+ })?;
320
+
321
+ match parse_stream_data(&chunk) {
322
+ Err(StreamError::ChatError(error)) => {
323
+ let error_respone = error.to_error_response();
324
+ // 更新请求日志为失败
325
+ {
326
+ let mut state = state.lock().await;
327
+ if let Some(log) = state
328
+ .request_logs
329
+ .iter_mut()
330
+ .rev()
331
+ .find(|log| log.id == current_id)
332
+ {
333
+ log.status = STATUS_FAILED;
334
+ log.error = Some(error_respone.native_code());
335
+ log.timing.total =
336
+ format_time_ms(start_time.elapsed().as_secs_f64());
337
+ state.error_requests += 1;
338
+ }
339
+ }
340
+ return Err((
341
+ error_respone.status_code(),
342
+ Json(error_respone.to_common()),
343
+ ));
344
+ }
345
+ Ok(_) | Err(_) => {
346
+ // 创建一个包含第一个 chunk 的 stream
347
+ Box::pin(
348
+ futures::stream::once(async move { Ok(chunk) }).chain(stream),
349
+ )
350
+ as Pin<
351
+ Box<
352
+ dyn Stream<Item = Result<Bytes, reqwest::Error>> + Send,
353
+ >,
354
+ >
355
+ }
356
+ }
357
+ }
358
+ None => {
359
+ // Box::pin(stream)
360
+ // as Pin<Box<dyn Stream<Item = Result<Bytes, reqwest::Error>> + Send>>
361
+ // 更新请求日志为失败
362
+ {
363
+ let mut state = state.lock().await;
364
+ if let Some(log) = state
365
+ .request_logs
366
+ .iter_mut()
367
+ .rev()
368
+ .find(|log| log.id == current_id)
369
+ {
370
+ log.status = STATUS_FAILED;
371
+ log.error = Some("Empty stream response".to_string());
372
+ state.error_requests += 1;
373
+ }
374
+ }
375
+ return Err((
376
+ StatusCode::INTERNAL_SERVER_ERROR,
377
+ Json(
378
+ ChatError::RequestFailed("Empty stream response".to_string())
379
+ .to_json(),
380
+ ),
381
+ ));
382
+ }
383
+ }
384
+ } else {
385
+ Box::pin(stream)
386
+ as Pin<Box<dyn Stream<Item = Result<Bytes, reqwest::Error>> + Send>>
387
+ }
388
+ }
389
+ .then({
390
+ let buffer = Arc::new(Mutex::new(Vec::new()));
391
+ let first_chunk_time = first_chunk_time.clone();
392
+ let state = state.clone();
393
+
394
+ move |chunk| {
395
+ let buffer = buffer.clone();
396
+ let response_id = response_id.clone();
397
+ let model = request.model.clone();
398
+ let is_start = is_start.clone();
399
+ let full_text = full_text.clone();
400
+ let first_chunk_time = first_chunk_time.clone();
401
+ let state = state.clone();
402
+
403
+ async move {
404
+ let chunk = chunk.unwrap_or_default();
405
+ let mut buffer_guard = buffer.lock().await;
406
+ buffer_guard.extend_from_slice(&chunk);
407
+
408
+ match parse_stream_data(&buffer_guard) {
409
+ Ok(StreamMessage::Content(texts)) => {
410
+ buffer_guard.clear();
411
+ let mut response_data = String::new();
412
+
413
+ // 记录首字时间(如果还未记录)
414
+ if let Ok(mut first_time) = first_chunk_time.try_lock() {
415
+ if first_time.is_none() {
416
+ *first_time =
417
+ Some(format_time_ms(start_time.elapsed().as_secs_f64()));
418
+ }
419
+ }
420
+
421
+ // 处理文本内容
422
+ for text in texts {
423
+ let mut text_guard = full_text.lock().await;
424
+ text_guard.push_str(&text);
425
+ let is_first = is_start.load(Ordering::SeqCst);
426
+
427
+ let response = ChatResponse {
428
+ id: response_id.clone(),
429
+ object: OBJECT_CHAT_COMPLETION_CHUNK.to_string(),
430
+ created: chrono::Utc::now().timestamp(),
431
+ model: if is_first { Some(model.clone()) } else { None },
432
+ choices: vec![Choice {
433
+ index: 0,
434
+ message: None,
435
+ delta: Some(Delta {
436
+ role: if is_first {
437
+ is_start.store(false, Ordering::SeqCst);
438
+ Some(Role::Assistant)
439
+ } else {
440
+ None
441
+ },
442
+ content: Some(text),
443
+ }),
444
+ finish_reason: None,
445
+ }],
446
+ usage: None,
447
+ };
448
+
449
+ response_data.push_str(&format!(
450
+ "data: {}\n\n",
451
+ serde_json::to_string(&response).unwrap()
452
+ ));
453
+ }
454
+
455
+ Ok::<_, Infallible>(Bytes::from(response_data))
456
+ }
457
+ Ok(StreamMessage::StreamStart) => {
458
+ buffer_guard.clear();
459
+ // 发送初始响应,包含模型信息
460
+ let response = ChatResponse {
461
+ id: response_id.clone(),
462
+ object: OBJECT_CHAT_COMPLETION_CHUNK.to_string(),
463
+ created: chrono::Utc::now().timestamp(),
464
+ model: {
465
+ is_start.store(true, Ordering::SeqCst);
466
+ Some(model.clone())
467
+ },
468
+ choices: vec![Choice {
469
+ index: 0,
470
+ message: None,
471
+ delta: Some(Delta {
472
+ role: Some(Role::Assistant),
473
+ content: Some(String::new()),
474
+ }),
475
+ finish_reason: None,
476
+ }],
477
+ usage: None,
478
+ };
479
+
480
+ Ok(Bytes::from(format!(
481
+ "data: {}\n\n",
482
+ serde_json::to_string(&response).unwrap()
483
+ )))
484
+ }
485
+ Ok(StreamMessage::StreamEnd) => {
486
+ buffer_guard.clear();
487
+ // 根据配置决定是否发送最后的 finish_reason
488
+ let include_finish_reason = AppConfig::get_stop_stream();
489
+
490
+ // 计算总时间和首次片段时间
491
+ let total_time = format_time_ms(start_time.elapsed().as_secs_f64());
492
+ let first_time = first_chunk_time.lock().await.unwrap_or(total_time);
493
+
494
+ {
495
+ let mut state = state.lock().await;
496
+ if let Some(log) = state
497
+ .request_logs
498
+ .iter_mut()
499
+ .rev()
500
+ .find(|log| log.id == current_id)
501
+ {
502
+ log.timing.total = total_time;
503
+ log.timing.first = Some(first_time);
504
+ }
505
+ }
506
+
507
+ if include_finish_reason {
508
+ let response = ChatResponse {
509
+ id: response_id.clone(),
510
+ object: OBJECT_CHAT_COMPLETION_CHUNK.to_string(),
511
+ created: chrono::Utc::now().timestamp(),
512
+ model: None,
513
+ choices: vec![Choice {
514
+ index: 0,
515
+ message: None,
516
+ delta: Some(Delta {
517
+ role: None,
518
+ content: None,
519
+ }),
520
+ finish_reason: Some(FINISH_REASON_STOP.to_string()),
521
+ }],
522
+ usage: None,
523
+ };
524
+ Ok(Bytes::from(format!(
525
+ "data: {}\n\ndata: [DONE]\n\n",
526
+ serde_json::to_string(&response).unwrap()
527
+ )))
528
+ } else {
529
+ Ok(Bytes::from("data: [DONE]\n\n"))
530
+ }
531
+ }
532
+ Ok(StreamMessage::Incomplete) => {
533
+ // 保持buffer中的数据以待下一个chunk
534
+ Ok(Bytes::new())
535
+ }
536
+ Ok(StreamMessage::Debug(debug_prompt)) => {
537
+ buffer_guard.clear();
538
+ if let Ok(mut state) = state.try_lock() {
539
+ if let Some(last_log) = state.request_logs.last_mut() {
540
+ last_log.prompt = Some(debug_prompt.clone());
541
+ }
542
+ }
543
+ Ok(Bytes::new())
544
+ }
545
+ Err(e) => {
546
+ buffer_guard.clear();
547
+ eprintln!("[警告] Stream error: {}", e);
548
+ Ok(Bytes::new())
549
+ }
550
+ }
551
+ }
552
+ }
553
+ });
554
+
555
+ Ok(Response::builder()
556
+ .header("Cache-Control", "no-cache")
557
+ .header("Connection", "keep-alive")
558
+ .header(CONTENT_TYPE, "text/event-stream")
559
+ .body(Body::from_stream(stream))
560
+ .unwrap())
561
+ } else {
562
+ // 非流式响应
563
+ let start_time = std::time::Instant::now();
564
+ let mut first_chunk_received = false;
565
+ let mut first_chunk_time = 0.0;
566
+ let mut full_text = String::with_capacity(1024);
567
+ let mut stream = response.bytes_stream();
568
+ let mut prompt = None;
569
+
570
+ let mut buffer = Vec::new();
571
+ while let Some(chunk) = stream.next().await {
572
+ let chunk = chunk.map_err(|e| {
573
+ // 更新请求日志为失败
574
+ if let Ok(mut state) = state.try_lock() {
575
+ if let Some(log) = state
576
+ .request_logs
577
+ .iter_mut()
578
+ .rev()
579
+ .find(|log| log.id == current_id)
580
+ {
581
+ log.status = STATUS_FAILED;
582
+ log.error = Some(format!("Failed to read response chunk: {}", e));
583
+ state.error_requests += 1;
584
+ }
585
+ }
586
+ (
587
+ StatusCode::INTERNAL_SERVER_ERROR,
588
+ Json(
589
+ ChatError::RequestFailed(format!("Failed to read response chunk: {}", e))
590
+ .to_json(),
591
+ ),
592
+ )
593
+ })?;
594
+
595
+ buffer.extend_from_slice(&chunk);
596
+
597
+ match parse_stream_data(&buffer) {
598
+ Ok(StreamMessage::Content(texts)) => {
599
+ if !first_chunk_received {
600
+ first_chunk_time = format_time_ms(start_time.elapsed().as_secs_f64());
601
+ first_chunk_received = true;
602
+ }
603
+ for text in texts {
604
+ full_text.push_str(&text);
605
+ }
606
+ buffer.clear();
607
+ }
608
+ Ok(StreamMessage::Incomplete) => continue,
609
+ Ok(StreamMessage::Debug(debug_prompt)) => {
610
+ prompt = Some(debug_prompt);
611
+ buffer.clear();
612
+ }
613
+ Ok(StreamMessage::StreamStart) | Ok(StreamMessage::StreamEnd) => {
614
+ buffer.clear();
615
+ }
616
+ Err(StreamError::ChatError(error)) => {
617
+ let error = error.to_error_response();
618
+ // 更新请求日志为失败
619
+ {
620
+ let mut state = state.lock().await;
621
+ if let Some(log) = state
622
+ .request_logs
623
+ .iter_mut()
624
+ .rev()
625
+ .find(|log| log.id == current_id)
626
+ {
627
+ log.status = STATUS_FAILED;
628
+ log.error = Some(error.native_code());
629
+ log.timing.total = format_time_ms(start_time.elapsed().as_secs_f64());
630
+ state.error_requests += 1;
631
+ }
632
+ }
633
+ return Err((error.status_code(), Json(error.to_common())));
634
+ }
635
+ Err(_) => {
636
+ buffer.clear();
637
+ continue;
638
+ }
639
+ }
640
+ }
641
+
642
+ // 检查响应是否为空
643
+ if full_text.is_empty() {
644
+ // 更新请求日志为失败
645
+ {
646
+ let mut state = state.lock().await;
647
+ if let Some(log) = state
648
+ .request_logs
649
+ .iter_mut()
650
+ .rev()
651
+ .find(|log| log.id == current_id)
652
+ {
653
+ log.status = STATUS_FAILED;
654
+ log.error = Some("Empty response received".to_string());
655
+ if let Some(p) = prompt {
656
+ log.prompt = Some(p);
657
+ }
658
+ state.error_requests += 1;
659
+ }
660
+ }
661
+ return Err((
662
+ StatusCode::INTERNAL_SERVER_ERROR,
663
+ Json(ChatError::RequestFailed("Empty response received".to_string()).to_json()),
664
+ ));
665
+ }
666
+
667
+ let response_data = ChatResponse {
668
+ id: format!("chatcmpl-{}", Uuid::new_v4().simple()),
669
+ object: OBJECT_CHAT_COMPLETION.to_string(),
670
+ created: chrono::Utc::now().timestamp(),
671
+ model: Some(request.model),
672
+ choices: vec![Choice {
673
+ index: 0,
674
+ message: Some(Message {
675
+ role: Role::Assistant,
676
+ content: MessageContent::Text(full_text),
677
+ }),
678
+ delta: None,
679
+ finish_reason: Some(FINISH_REASON_STOP.to_string()),
680
+ }],
681
+ usage: Some(Usage {
682
+ prompt_tokens: 0,
683
+ completion_tokens: 0,
684
+ total_tokens: 0,
685
+ }),
686
+ };
687
+
688
+ {
689
+ // 更新请求日志时间信息和状态
690
+ let total_time = format_time_ms(start_time.elapsed().as_secs_f64());
691
+ let mut state = state.lock().await;
692
+ if let Some(log) = state
693
+ .request_logs
694
+ .iter_mut()
695
+ .rev()
696
+ .find(|log| log.id == current_id)
697
+ {
698
+ log.timing.total = total_time;
699
+ log.timing.first = Some(first_chunk_time);
700
+ log.prompt = prompt;
701
+ log.status = STATUS_SUCCESS;
702
+ }
703
+ }
704
+
705
+ Ok(Response::builder()
706
+ .header(CONTENT_TYPE, "application/json")
707
+ .body(Body::from(serde_json::to_string(&response_data).unwrap()))
708
+ .unwrap())
709
+ }
710
+ }
src/chat/stream.rs ADDED
@@ -0,0 +1,209 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use super::aiserver::v1::StreamChatResponse;
2
+ use flate2::read::GzDecoder;
3
+ use prost::Message;
4
+ use std::io::Read;
5
+
6
+ use super::error::{ChatError, StreamError};
7
+
8
+ // 解压gzip数据
9
+ fn decompress_gzip(data: &[u8]) -> Option<Vec<u8>> {
10
+ let mut decoder = GzDecoder::new(data);
11
+ let mut decompressed = Vec::new();
12
+
13
+ match decoder.read_to_end(&mut decompressed) {
14
+ Ok(_) => Some(decompressed),
15
+ Err(_) => {
16
+ // println!("gzip解压失败: {}", e);
17
+ None
18
+ }
19
+ }
20
+ }
21
+
22
+ pub enum StreamMessage {
23
+ // 未完成
24
+ Incomplete,
25
+ // 调试
26
+ Debug(String),
27
+ // 流开始标志 b"\0\0\0\0\0"
28
+ StreamStart,
29
+ // 消息内容
30
+ Content(Vec<String>),
31
+ // 流结束标志 b"\x02\0\0\0\x02{}"
32
+ StreamEnd,
33
+ }
34
+
35
+ pub fn parse_stream_data(data: &[u8]) -> Result<StreamMessage, StreamError> {
36
+ if data.len() < 5 {
37
+ return Err(StreamError::DataLengthLessThan5);
38
+ }
39
+
40
+ // 检查是否为流开始标志
41
+ // if data == b"\0\0\0\0\0" {
42
+ // return Ok(StreamMessage::StreamStart);
43
+ // }
44
+
45
+ // 检查是否为流结束标志
46
+ // if data == b"\x02\0\0\0\x02{}" {
47
+ // return Ok(StreamMessage::StreamEnd);
48
+ // }
49
+
50
+ let mut messages = Vec::new();
51
+ let mut offset = 0;
52
+
53
+ while offset + 5 <= data.len() {
54
+ // 获取消息类型和长度
55
+ let msg_type = data[offset];
56
+ let msg_len = u32::from_be_bytes([
57
+ data[offset + 1],
58
+ data[offset + 2],
59
+ data[offset + 3],
60
+ data[offset + 4],
61
+ ]) as usize;
62
+
63
+ // 流开始
64
+ if msg_type == 0 && msg_len == 0 {
65
+ return Ok(StreamMessage::StreamStart);
66
+ }
67
+
68
+ // 检查剩余数据长度是否足够
69
+ if offset + 5 + msg_len > data.len() {
70
+ return Ok(StreamMessage::Incomplete);
71
+ }
72
+
73
+ let msg_data = &data[offset + 5..offset + 5 + msg_len];
74
+
75
+ match msg_type {
76
+ // 文本消息
77
+ 0 => {
78
+ if let Ok(response) = StreamChatResponse::decode(msg_data) {
79
+ // crate::debug_println!("[text] StreamChatResponse: {:?}", response);
80
+ if !response.text.is_empty() {
81
+ messages.push(response.text);
82
+ } else {
83
+ // println!("[text] StreamChatResponse: {:?}", response);
84
+ return Ok(StreamMessage::Debug(
85
+ response.filled_prompt.unwrap_or_default(),
86
+ // response.is_using_slow_request,
87
+ ));
88
+ }
89
+ }
90
+ }
91
+ // gzip压缩消息
92
+ 1 => {
93
+ if let Some(text) = decompress_gzip(msg_data) {
94
+ let response = StreamChatResponse::decode(&text[..]).unwrap_or_default();
95
+ // crate::debug_println!("[gzip] StreamChatResponse: {:?}", response);
96
+ if !response.text.is_empty() {
97
+ messages.push(response.text);
98
+ } else {
99
+ // println!("[gzip] StreamChatResponse: {:?}", response);
100
+ return Ok(StreamMessage::Debug(
101
+ response.filled_prompt.unwrap_or_default(),
102
+ // response.is_using_slow_request,
103
+ ));
104
+ }
105
+ }
106
+ }
107
+ // JSON字符串
108
+ 2 => {
109
+ if msg_len == 2 {
110
+ return Ok(StreamMessage::StreamEnd);
111
+ }
112
+ if let Ok(text) = String::from_utf8(msg_data.to_vec()) {
113
+ // println!("JSON消息: {}", text);
114
+ if let Ok(error) = serde_json::from_str::<ChatError>(&text) {
115
+ return Err(StreamError::ChatError(error));
116
+ }
117
+ // 未预计
118
+ // messages.push(text);
119
+ }
120
+ }
121
+ // 其他类型暂不处理
122
+ t => eprintln!("收到未知消息类型: {},请尝试联系开发者以获取支持", t),
123
+ }
124
+
125
+ offset += 5 + msg_len;
126
+ }
127
+
128
+ if messages.is_empty() {
129
+ Err(StreamError::EmptyMessage)
130
+ } else {
131
+ Ok(StreamMessage::Content(messages))
132
+ }
133
+ }
134
+
135
+ #[test]
136
+ fn test_parse_stream_data() {
137
+ // 使用include_str!加载测试数据文件
138
+ let stream_data = include_str!("../../tests/data/stream_data.txt");
139
+
140
+ // 将整个字符串按每两个字符分割成字节
141
+ let bytes: Vec<u8> = stream_data
142
+ .as_bytes()
143
+ .chunks(2)
144
+ .map(|chunk| {
145
+ let hex_str = std::str::from_utf8(chunk).unwrap();
146
+ u8::from_str_radix(hex_str, 16).unwrap()
147
+ })
148
+ .collect();
149
+
150
+ // 辅助函数:找到下一个消息边界
151
+ fn find_next_message_boundary(bytes: &[u8]) -> usize {
152
+ if bytes.len() < 5 {
153
+ return bytes.len();
154
+ }
155
+ let msg_len = u32::from_be_bytes([bytes[1], bytes[2], bytes[3], bytes[4]]) as usize;
156
+ 5 + msg_len
157
+ }
158
+
159
+ // 辅助函数:将字节转换为hex字符串
160
+ fn bytes_to_hex(bytes: &[u8]) -> String {
161
+ bytes.iter()
162
+ .map(|b| format!("{:02X}", b))
163
+ .collect::<Vec<String>>()
164
+ .join("")
165
+ }
166
+
167
+ // 多次解析数据
168
+ let mut offset = 0;
169
+ while offset < bytes.len() {
170
+ let remaining_bytes = &bytes[offset..];
171
+ let msg_boundary = find_next_message_boundary(remaining_bytes);
172
+ let current_msg_bytes = &remaining_bytes[..msg_boundary];
173
+ let hex_str = bytes_to_hex(current_msg_bytes);
174
+
175
+ match parse_stream_data(current_msg_bytes) {
176
+ Ok(message) => {
177
+ match message {
178
+ StreamMessage::Content(messages) => {
179
+ print!("消息内容 [hex: {}]:", hex_str);
180
+ for msg in messages {
181
+ println!(" {}", msg);
182
+ }
183
+ offset += msg_boundary;
184
+ }
185
+ StreamMessage::Debug(_) => {
186
+ // println!("调试信息 [hex: {}]: {}", hex_str, prompt);
187
+ offset += msg_boundary;
188
+ }
189
+ StreamMessage::StreamEnd => {
190
+ println!("流结束 [hex: {}]", hex_str);
191
+ break;
192
+ }
193
+ StreamMessage::StreamStart => {
194
+ println!("流开始 [hex: {}]", hex_str);
195
+ offset += msg_boundary;
196
+ }
197
+ StreamMessage::Incomplete => {
198
+ println!("数据不完整 [hex: {}]", hex_str);
199
+ break;
200
+ }
201
+ }
202
+ }
203
+ Err(e) => {
204
+ println!("解析错误 [hex: {}]: {}", hex_str, e);
205
+ break;
206
+ }
207
+ }
208
+ }
209
+ }
src/common.rs ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ pub mod models;
2
+ pub mod utils;
3
+ pub mod client;
src/common/client.rs ADDED
@@ -0,0 +1,222 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use crate::app::{
2
+ constant::{
3
+ CONTENT_TYPE_CONNECT_PROTO, CURSOR_API2_HOST, CURSOR_HOST, CURSOR_SETTINGS_URL,
4
+ HEADER_NAME_GHOST_MODE, TRUE,
5
+ },
6
+ lazy::{
7
+ CURSOR_API2_CHAT_URL, CURSOR_API2_STRIPE_URL, CURSOR_USAGE_API_URL, CURSOR_USER_API_URL,
8
+ REVERSE_PROXY_HOST, USE_PROXY,
9
+ },
10
+ };
11
+ use reqwest::header::{
12
+ ACCEPT, ACCEPT_ENCODING, ACCEPT_LANGUAGE, CACHE_CONTROL, CONNECTION, CONTENT_TYPE, COOKIE, DNT,
13
+ HOST, ORIGIN, PRAGMA, REFERER, TE, TRANSFER_ENCODING, USER_AGENT,
14
+ };
15
+ use reqwest::{Client, RequestBuilder};
16
+ use uuid::Uuid;
17
+
18
+ use super::utils::generate_hash;
19
+
20
+ macro_rules! def_const {
21
+ ($name:ident, $value:expr) => {
22
+ const $name: &'static str = $value;
23
+ };
24
+ }
25
+
26
+ def_const!(SEC_FETCH_DEST, "sec-fetch-dest");
27
+ def_const!(SEC_FETCH_MODE, "sec-fetch-mode");
28
+ def_const!(SEC_FETCH_SITE, "sec-fetch-site");
29
+ def_const!(SEC_GPC, "sec-gpc");
30
+ def_const!(PRIORITY, "priority");
31
+
32
+ def_const!(ONE, "1");
33
+ def_const!(ENCODINGS, "gzip,br");
34
+ def_const!(VALUE_ACCEPT, "*/*");
35
+ def_const!(VALUE_LANGUAGE, "zh-CN");
36
+ def_const!(EMPTY, "empty");
37
+ def_const!(CORS, "cors");
38
+ def_const!(NO_CACHE, "no-cache");
39
+ def_const!(UA_WIN, "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36");
40
+ def_const!(SAME_ORIGIN, "same-origin");
41
+ def_const!(KEEP_ALIVE, "keep-alive");
42
+ def_const!(TRAILERS, "trailers");
43
+ def_const!(U_EQ_4, "u=4");
44
+
45
+ def_const!(PROXY_HOST, "x-co");
46
+
47
+ /// 返回预构建的 Cursor API 客户端
48
+ ///
49
+ /// # 参数
50
+ ///
51
+ /// * `auth_token` - 授权令牌
52
+ /// * `checksum` - 校验和
53
+ /// * `endpoint` - API 端点路径
54
+ ///
55
+ /// # 返回
56
+ ///
57
+ /// * `reqwest::RequestBuilder` - 配置好的请求构建器
58
+ pub fn build_client(auth_token: &str, checksum: &str) -> RequestBuilder {
59
+ let trace_id = Uuid::new_v4().to_string();
60
+
61
+ let client = if *USE_PROXY {
62
+ Client::new()
63
+ .post(&*CURSOR_API2_CHAT_URL)
64
+ .header(HOST, &*REVERSE_PROXY_HOST)
65
+ .header(PROXY_HOST, CURSOR_API2_HOST)
66
+ } else {
67
+ Client::new()
68
+ .post(&*CURSOR_API2_CHAT_URL)
69
+ .header(HOST, CURSOR_API2_HOST)
70
+ };
71
+
72
+ client
73
+ .header(CONTENT_TYPE, CONTENT_TYPE_CONNECT_PROTO)
74
+ .bearer_auth(auth_token)
75
+ .header("connect-accept-encoding", ENCODINGS)
76
+ .header("connect-protocol-version", ONE)
77
+ .header(USER_AGENT, "connect-es/1.6.1")
78
+ .header("x-amzn-trace-id", format!("Root={}", trace_id))
79
+ .header("x-client-key", generate_hash())
80
+ .header("x-cursor-checksum", checksum)
81
+ .header("x-cursor-client-version", "0.42.5")
82
+ .header("x-cursor-timezone", "Asia/Shanghai")
83
+ .header(HEADER_NAME_GHOST_MODE, TRUE)
84
+ .header("x-request-id", trace_id)
85
+ .header(CONNECTION, KEEP_ALIVE)
86
+ .header(TRANSFER_ENCODING, "chunked")
87
+ }
88
+
89
+ /// 返回预构建的获取 Stripe 账户信息的 Cursor API 客户端
90
+ ///
91
+ /// # 参数
92
+ ///
93
+ /// * `auth_token` - 授权令牌
94
+ ///
95
+ /// # 返回
96
+ ///
97
+ /// * `reqwest::RequestBuilder` - 配置好的请求构建器
98
+ pub fn build_profile_client(auth_token: &str) -> RequestBuilder {
99
+ let client = if *USE_PROXY {
100
+ Client::new()
101
+ .get(&*CURSOR_API2_STRIPE_URL)
102
+ .header(HOST, &*REVERSE_PROXY_HOST)
103
+ .header(PROXY_HOST, CURSOR_API2_HOST)
104
+ } else {
105
+ Client::new()
106
+ .get(&*CURSOR_API2_STRIPE_URL)
107
+ .header(HOST, CURSOR_API2_HOST)
108
+ };
109
+
110
+ client
111
+ .header("sec-ch-ua", "\"Not-A.Brand\";v=\"99\", \"Chromium\";v=\"124\"")
112
+ .header(HEADER_NAME_GHOST_MODE, TRUE)
113
+ .header("sec-ch-ua-mobile", "?0")
114
+ .bearer_auth(auth_token)
115
+ .header(
116
+ USER_AGENT,
117
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Cursor/0.42.5 Chrome/124.0.6367.243 Electron/30.4.0 Safari/537.36",
118
+ )
119
+ .header("sec-ch-ua-platform", "\"Windows\"")
120
+ .header(ACCEPT, VALUE_ACCEPT)
121
+ .header(ORIGIN, "vscode-file://vscode-app")
122
+ .header(SEC_FETCH_SITE, "cross-site")
123
+ .header(SEC_FETCH_MODE, CORS)
124
+ .header(SEC_FETCH_DEST, EMPTY)
125
+ .header(ACCEPT_ENCODING, ENCODINGS)
126
+ .header(ACCEPT_LANGUAGE, VALUE_LANGUAGE)
127
+ .header(PRIORITY, "u=1, i")
128
+ }
129
+
130
+ /// 返回预构建的获取使用情况的 Cursor API 客户端
131
+ ///
132
+ /// # 参数
133
+ ///
134
+ /// * `user_id` - 用户 ID
135
+ /// * `auth_token` - 授权令牌
136
+ ///
137
+ /// # 返回
138
+ ///
139
+ /// * `reqwest::RequestBuilder` - 配置好的请求构建器
140
+ pub fn build_usage_client(user_id: &str, auth_token: &str) -> RequestBuilder {
141
+ let session_token = format!("{}%3A%3A{}", user_id, auth_token);
142
+
143
+ let client = if *USE_PROXY {
144
+ Client::new()
145
+ .get(&*CURSOR_USAGE_API_URL)
146
+ .header(HOST, &*REVERSE_PROXY_HOST)
147
+ .header(PROXY_HOST, CURSOR_HOST)
148
+ } else {
149
+ Client::new()
150
+ .get(&*CURSOR_USAGE_API_URL)
151
+ .header(HOST, CURSOR_HOST)
152
+ };
153
+
154
+ client
155
+ .header(USER_AGENT, UA_WIN)
156
+ .header(ACCEPT, VALUE_ACCEPT)
157
+ .header(ACCEPT_LANGUAGE, VALUE_LANGUAGE)
158
+ .header(ACCEPT_ENCODING, ENCODINGS)
159
+ .header(REFERER, CURSOR_SETTINGS_URL)
160
+ .header(DNT, ONE)
161
+ .header(SEC_GPC, ONE)
162
+ .header(SEC_FETCH_DEST, EMPTY)
163
+ .header(SEC_FETCH_MODE, CORS)
164
+ .header(SEC_FETCH_SITE, SAME_ORIGIN)
165
+ .header(CONNECTION, KEEP_ALIVE)
166
+ .header(PRAGMA, NO_CACHE)
167
+ .header(CACHE_CONTROL, NO_CACHE)
168
+ .header(TE, TRAILERS)
169
+ .header(PRIORITY, U_EQ_4)
170
+ .header(
171
+ COOKIE,
172
+ &format!("WorkosCursorSessionToken={}", session_token),
173
+ )
174
+ .query(&[("user", user_id)])
175
+ }
176
+
177
+ /// 返回预构建的获取用户信息的 Cursor API 客户端
178
+ ///
179
+ /// # 参数
180
+ ///
181
+ /// * `user_id` - 用户 ID
182
+ /// * `auth_token` - 授权令牌
183
+ ///
184
+ /// # 返回
185
+ ///
186
+ /// * `reqwest::RequestBuilder` - 配置好的请求构建器
187
+ pub fn build_userinfo_client(user_id: &str, auth_token: &str) -> RequestBuilder {
188
+ let session_token = format!("{}%3A%3A{}", user_id, auth_token);
189
+
190
+ let client = if *USE_PROXY {
191
+ Client::new()
192
+ .get(&*CURSOR_USER_API_URL)
193
+ .header(HOST, &*REVERSE_PROXY_HOST)
194
+ .header(PROXY_HOST, CURSOR_HOST)
195
+ } else {
196
+ Client::new()
197
+ .get(&*CURSOR_USER_API_URL)
198
+ .header(HOST, CURSOR_HOST)
199
+ };
200
+
201
+ client
202
+ .header(USER_AGENT, UA_WIN)
203
+ .header(ACCEPT, VALUE_ACCEPT)
204
+ .header(ACCEPT_LANGUAGE, VALUE_LANGUAGE)
205
+ .header(ACCEPT_ENCODING, ENCODINGS)
206
+ .header(REFERER, CURSOR_SETTINGS_URL)
207
+ .header(DNT, ONE)
208
+ .header(SEC_GPC, ONE)
209
+ .header(SEC_FETCH_DEST, EMPTY)
210
+ .header(SEC_FETCH_MODE, CORS)
211
+ .header(SEC_FETCH_SITE, SAME_ORIGIN)
212
+ .header(CONNECTION, KEEP_ALIVE)
213
+ .header(PRAGMA, NO_CACHE)
214
+ .header(CACHE_CONTROL, NO_CACHE)
215
+ .header(TE, TRAILERS)
216
+ .header(PRIORITY, U_EQ_4)
217
+ .header(
218
+ COOKIE,
219
+ &format!("WorkosCursorSessionToken={}", session_token),
220
+ )
221
+ .query(&[("user", user_id)])
222
+ }
src/common/models.rs ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ pub mod error;
2
+ pub mod health;
3
+ pub mod config;
4
+ pub mod userinfo;
5
+
6
+ use config::ConfigData;
7
+
8
+ use serde::Serialize;
9
+
10
+ #[derive(Serialize)]
11
+ pub enum ApiStatus {
12
+ #[serde(rename = "healthy")]
13
+ Healthy,
14
+ #[serde(rename = "success")]
15
+ Success,
16
+ #[serde(rename = "error")]
17
+ Error,
18
+ #[serde(rename = "failed")]
19
+ Failed,
20
+ }
21
+
22
+ // #[derive(Serialize)]
23
+ // #[serde(untagged)]
24
+ // pub enum ApiResponse {
25
+ // HealthCheck(HealthCheckResponse),
26
+ // ConfigData(NormalResponse<ConfigData>),
27
+ // Error(ErrorResponse),
28
+ // }
29
+
30
+ // impl ApiResponse {
31
+ // pub fn to_string(&self) -> String {
32
+ // serde_json::to_string(self).unwrap()
33
+ // }
34
+ // }
35
+
36
+ #[derive(Serialize)]
37
+ pub struct NormalResponse<T> {
38
+ pub status: ApiStatus,
39
+ #[serde(skip_serializing_if = "Option::is_none")]
40
+ pub data: Option<T>,
41
+ #[serde(skip_serializing_if = "Option::is_none")]
42
+ pub message: Option<String>,
43
+ }
44
+
45
+ impl std::fmt::Display for NormalResponse<ConfigData> {
46
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47
+ write!(f, "{}", serde_json::to_string(self).unwrap())
48
+ }
49
+ }
50
+
51
+ #[derive(Serialize)]
52
+ pub struct NormalResponseNoData {
53
+ pub status: ApiStatus,
54
+ #[serde(skip_serializing_if = "Option::is_none")]
55
+ pub message: Option<String>,
56
+ }
57
+
58
+ #[derive(Serialize)]
59
+ pub struct ErrorResponse {
60
+ // status -> 成功 / 失败
61
+ pub status: ApiStatus,
62
+ // HTTP 请求的状态码
63
+ #[serde(skip_serializing_if = "Option::is_none")]
64
+ pub code: Option<u16>,
65
+ // HTTP 请求的错误码
66
+ #[serde(skip_serializing_if = "Option::is_none")]
67
+ pub error: Option<String>,
68
+ // 错误详情
69
+ #[serde(skip_serializing_if = "Option::is_none")]
70
+ pub message: Option<String>,
71
+ }
src/common/models/config.rs ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use serde::{Deserialize, Serialize};
2
+
3
+ use crate::app::model::{PageContent, UsageCheck, VisionAbility};
4
+
5
+ #[derive(Serialize)]
6
+ pub struct ConfigData {
7
+ pub page_content: Option<PageContent>,
8
+ pub enable_stream_check: bool,
9
+ pub include_stop_stream: bool,
10
+ pub vision_ability: VisionAbility,
11
+ pub enable_slow_pool: bool,
12
+ pub enable_all_claude: bool,
13
+ pub check_usage_models: UsageCheck,
14
+ }
15
+
16
+ #[derive(Deserialize)]
17
+ pub struct ConfigUpdateRequest {
18
+ #[serde(default)]
19
+ pub action: String, // "get", "update", "reset"
20
+ #[serde(default)]
21
+ pub path: String,
22
+ #[serde(default)]
23
+ pub content: Option<PageContent>, // "default", "text", "html"
24
+ #[serde(default)]
25
+ pub enable_stream_check: Option<bool>,
26
+ #[serde(default)]
27
+ pub include_stop_stream: Option<bool>,
28
+ #[serde(default)]
29
+ pub vision_ability: Option<VisionAbility>,
30
+ #[serde(default)]
31
+ pub enable_slow_pool: Option<bool>,
32
+ #[serde(default)]
33
+ pub enable_all_claude: Option<bool>,
34
+ #[serde(default)]
35
+ pub check_usage_models: Option<UsageCheck>,
36
+ }
src/common/models/error.rs ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use super::ErrorResponse;
2
+
3
+ pub enum ChatError {
4
+ ModelNotSupported(String),
5
+ EmptyMessages,
6
+ NoTokens,
7
+ RequestFailed(String),
8
+ Unauthorized,
9
+ }
10
+
11
+ impl ChatError {
12
+ pub fn to_json(&self) -> ErrorResponse {
13
+ let (error, message) = match self {
14
+ ChatError::ModelNotSupported(model) => (
15
+ "model_not_supported",
16
+ format!("Model '{}' is not supported", model),
17
+ ),
18
+ ChatError::EmptyMessages => (
19
+ "empty_messages",
20
+ "Message array cannot be empty".to_string(),
21
+ ),
22
+ ChatError::NoTokens => ("no_tokens", "No available tokens".to_string()),
23
+ ChatError::RequestFailed(err) => ("request_failed", format!("Request failed: {}", err)),
24
+ ChatError::Unauthorized => ("unauthorized", "Invalid authorization token".to_string()),
25
+ };
26
+
27
+ ErrorResponse {
28
+ status: super::ApiStatus::Error,
29
+ code: None,
30
+ error: Some(error.to_string()),
31
+ message: Some(message),
32
+ }
33
+ }
34
+ }
src/common/models/health.rs ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use serde::Serialize;
2
+
3
+ use super::ApiStatus;
4
+
5
+ #[derive(Serialize)]
6
+ pub struct HealthCheckResponse {
7
+ pub status: ApiStatus,
8
+ pub version: &'static str,
9
+ pub uptime: i64,
10
+ #[serde(skip_serializing_if = "Option::is_none")]
11
+ pub stats: Option<SystemStats>,
12
+ pub models: Vec<&'static str>,
13
+ pub endpoints: Vec<&'static str>,
14
+ }
15
+
16
+ #[derive(Serialize)]
17
+ pub struct SystemStats {
18
+ pub started: String,
19
+ pub total_requests: u64,
20
+ pub active_requests: u64,
21
+ pub system: SystemInfo,
22
+ }
23
+
24
+ #[derive(Serialize)]
25
+ pub struct SystemInfo {
26
+ pub memory: MemoryInfo,
27
+ pub cpu: CpuInfo,
28
+ }
29
+
30
+ #[derive(Serialize)]
31
+ pub struct MemoryInfo {
32
+ pub rss: u64, // 物理内存使用量(字节)
33
+ }
34
+
35
+ #[derive(Serialize)]
36
+ pub struct CpuInfo {
37
+ pub usage: f32, // CPU 使用率(百分比)
38
+ }
src/common/models/userinfo.rs ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use chrono::{DateTime, Local};
2
+ use serde::{Deserialize, Serialize};
3
+
4
+ #[derive(Serialize)]
5
+ #[serde(untagged)]
6
+ pub enum GetUserInfo {
7
+ Usage(TokenProfile),
8
+ Error { error: String },
9
+ }
10
+
11
+ #[derive(Serialize, Clone)]
12
+ pub struct TokenProfile {
13
+ pub usage: UsageProfile,
14
+ pub user: UserProfile,
15
+ pub stripe: StripeProfile,
16
+ }
17
+
18
+ #[derive(Deserialize, Serialize, PartialEq, Clone)]
19
+ pub enum MembershipType {
20
+ #[serde(rename = "free")]
21
+ Free,
22
+ #[serde(rename = "free_trial")]
23
+ FreeTrial,
24
+ #[serde(rename = "pro")]
25
+ Pro,
26
+ #[serde(rename = "enterprise")]
27
+ Enterprise,
28
+ }
29
+
30
+ #[derive(Deserialize, Serialize, Clone)]
31
+ pub struct StripeProfile {
32
+ #[serde(rename(deserialize = "membershipType"))]
33
+ pub membership_type: MembershipType,
34
+ #[serde(
35
+ rename(deserialize = "paymentId"),
36
+ default,
37
+ skip_serializing_if = "Option::is_none"
38
+ )]
39
+ pub payment_id: Option<String>,
40
+ #[serde(rename(deserialize = "daysRemainingOnTrial"))]
41
+ pub days_remaining_on_trial: u32,
42
+ }
43
+
44
+ #[derive(Deserialize, Serialize, Clone)]
45
+ pub struct ModelUsage {
46
+ #[serde(rename(deserialize = "numRequests", serialize = "requests"))]
47
+ pub num_requests: u32,
48
+ #[serde(
49
+ rename(deserialize = "numRequestsTotal"),
50
+ default,
51
+ skip_serializing_if = "Option::is_none"
52
+ )]
53
+ pub requests_total: Option<u32>,
54
+ #[serde(rename(deserialize = "numTokens", serialize = "tokens"))]
55
+ pub num_tokens: u32,
56
+ #[serde(
57
+ rename(deserialize = "maxRequestUsage"),
58
+ skip_serializing_if = "Option::is_none"
59
+ )]
60
+ pub max_requests: Option<u32>,
61
+ #[serde(
62
+ rename(deserialize = "maxTokenUsage"),
63
+ skip_serializing_if = "Option::is_none"
64
+ )]
65
+ pub max_tokens: Option<u32>,
66
+ }
67
+
68
+ #[derive(Deserialize, Serialize, Clone)]
69
+ pub struct UsageProfile {
70
+ #[serde(rename(deserialize = "gpt-4"))]
71
+ pub premium: ModelUsage,
72
+ #[serde(rename(deserialize = "gpt-3.5-turbo"))]
73
+ pub standard: ModelUsage,
74
+ #[serde(rename(deserialize = "gpt-4-32k"))]
75
+ pub unknown: ModelUsage,
76
+ }
77
+
78
+ #[derive(Deserialize, Serialize, Clone)]
79
+ pub struct UserProfile {
80
+ pub email: String,
81
+ // pub email_verified: bool,
82
+ pub name: String,
83
+ #[serde(rename(serialize = "id"))]
84
+ pub sub: String,
85
+ pub updated_at: DateTime<Local>,
86
+ // Image link, rendered in /logs?
87
+ // pub picture: Option<String>,
88
+ }
src/common/utils.rs ADDED
@@ -0,0 +1,165 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ mod checksum;
2
+ pub use checksum::*;
3
+ mod tokens;
4
+ pub use tokens::*;
5
+
6
+ use super::models::userinfo::{StripeProfile, TokenProfile, UsageProfile, UserProfile};
7
+ use crate::app::{
8
+ constant::{FALSE, TRUE},
9
+ lazy::{TOKEN_DELIMITER, TOKEN_DELIMITER_LEN},
10
+ };
11
+
12
+ pub fn parse_bool_from_env(key: &str, default: bool) -> bool {
13
+ std::env::var(key)
14
+ .ok()
15
+ .map(|v| match v.to_lowercase().as_str() {
16
+ TRUE | "1" => true,
17
+ FALSE | "0" => false,
18
+ _ => default,
19
+ })
20
+ .unwrap_or(default)
21
+ }
22
+
23
+ pub fn parse_string_from_env(key: &str, default: &str) -> String {
24
+ std::env::var(key).unwrap_or_else(|_| default.to_string())
25
+ }
26
+
27
+ pub fn parse_ascii_char_from_env(key: &str, default: char) -> char {
28
+ std::env::var(key)
29
+ .ok()
30
+ .and_then(|v| {
31
+ let chars: Vec<char> = v.chars().collect();
32
+ if chars.len() == 1 && chars[0].is_ascii() {
33
+ Some(chars[0])
34
+ } else {
35
+ None
36
+ }
37
+ })
38
+ .unwrap_or(default)
39
+ }
40
+
41
+ pub fn parse_usize_from_env(key: &str, default: usize) -> usize {
42
+ std::env::var(key)
43
+ .ok()
44
+ .and_then(|v| v.parse().ok())
45
+ .unwrap_or(default)
46
+ }
47
+
48
+ pub async fn get_token_profile(auth_token: &str) -> Option<TokenProfile> {
49
+ let user_id = extract_user_id(auth_token)?;
50
+
51
+ // 构建请求客户端
52
+ let client = super::client::build_usage_client(&user_id, auth_token);
53
+
54
+ // 发送请求并获取响应
55
+ // let response = client.send().await.ok()?;
56
+ // let bytes = response.bytes().await?;
57
+ // println!("Raw response bytes: {:?}", bytes);
58
+ // let usage = serde_json::from_str::<UsageProfile>(&text).ok()?;
59
+ let usage = client
60
+ .send()
61
+ .await
62
+ .ok()?
63
+ .json::<UsageProfile>()
64
+ .await
65
+ .ok()?;
66
+
67
+ let user = get_user_profile(auth_token).await?;
68
+
69
+ // 从 Stripe 获取用户资料
70
+ let stripe = get_stripe_profile(auth_token).await?;
71
+
72
+ // 映射响应数据到 TokenProfile
73
+ Some(TokenProfile {
74
+ usage,
75
+ user,
76
+ stripe,
77
+ })
78
+ }
79
+
80
+ pub async fn get_stripe_profile(auth_token: &str) -> Option<StripeProfile> {
81
+ let client = super::client::build_profile_client(auth_token);
82
+ let response = client
83
+ .send()
84
+ .await
85
+ .ok()?
86
+ .json::<StripeProfile>()
87
+ .await
88
+ .ok()?;
89
+ Some(response)
90
+ }
91
+
92
+ pub async fn get_user_profile(auth_token: &str) -> Option<UserProfile> {
93
+ let user_id = extract_user_id(auth_token)?;
94
+
95
+ // 构建请求客户端
96
+ let client = super::client::build_userinfo_client(&user_id, auth_token);
97
+
98
+ // 发送请求并获取响应
99
+ let user_profile = client.send().await.ok()?.json::<UserProfile>().await.ok()?;
100
+
101
+ Some(user_profile)
102
+ }
103
+
104
+ pub fn validate_token_and_checksum(auth_token: &str) -> Option<(String, String)> {
105
+ // 找最后一个逗号
106
+ let comma_pos = auth_token.rfind(*TOKEN_DELIMITER)?;
107
+ let (token_part, checksum) = auth_token.split_at(comma_pos);
108
+ let checksum = &checksum[*TOKEN_DELIMITER_LEN..]; // 跳过逗号
109
+
110
+ // 解析 token - 为了向前兼容,忽略最后一个:或%3A前的内容
111
+ let colon_pos = token_part.rfind(':');
112
+ let encoded_colon_pos = token_part.rfind("%3A");
113
+
114
+ let token = match (colon_pos, encoded_colon_pos) {
115
+ (None, None) => token_part, // 最简单的构成: token,checksum
116
+ (Some(pos1), None) => &token_part[(pos1 + 1)..],
117
+ (None, Some(pos2)) => &token_part[(pos2 + 3)..],
118
+ (Some(pos1), Some(pos2)) => {
119
+ let pos = pos1.max(pos2);
120
+ let start = if pos == pos2 { pos + 3 } else { pos + 1 };
121
+ &token_part[start..]
122
+ }
123
+ };
124
+
125
+ // 验证 token 和 checksum 有效性
126
+ if validate_token(token) && validate_checksum(checksum) {
127
+ Some((token.to_string(), checksum.to_string()))
128
+ } else {
129
+ None
130
+ }
131
+ }
132
+
133
+ pub fn extract_token(auth_token: &str) -> Option<String> {
134
+ // 解析 token
135
+ let token_part = match auth_token.rfind(*TOKEN_DELIMITER) {
136
+ Some(pos) => &auth_token[..pos],
137
+ None => auth_token,
138
+ };
139
+
140
+ // 向前兼容
141
+ let colon_pos = token_part.rfind(':');
142
+ let encoded_colon_pos = token_part.rfind("%3A");
143
+
144
+ let token = match (colon_pos, encoded_colon_pos) {
145
+ (None, None) => token_part,
146
+ (Some(pos1), None) => &token_part[(pos1 + 1)..],
147
+ (None, Some(pos2)) => &token_part[(pos2 + 3)..],
148
+ (Some(pos1), Some(pos2)) => {
149
+ let pos = pos1.max(pos2);
150
+ let start = if pos == pos2 { pos + 3 } else { pos + 1 };
151
+ &token_part[start..]
152
+ }
153
+ };
154
+
155
+ // 验证 token 有效性
156
+ if validate_token(token) {
157
+ Some(token.to_string())
158
+ } else {
159
+ None
160
+ }
161
+ }
162
+
163
+ pub fn format_time_ms(seconds: f64) -> f64 {
164
+ (seconds * 1000.0).round() / 1000.0
165
+ }