kevin commited on
Commit
0ab3d49
·
1 Parent(s): ebbe4aa
This view is limited to 50 files because it contains too many changes.   See raw diff
.env.example ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 当前配置为默认值,请根据需要修改
2
+
3
+ # 服务器监听端口
4
+ PORT=3000
5
+
6
+ # 路由前缀,必须以 / 开头(如果不为空)
7
+ ROUTE_PREFIX=
8
+
9
+ # 最高权限的认证令牌,必填
10
+ AUTH_TOKEN=
11
+
12
+ # 共享的认证令牌,仅Chat端点权限(轮询与AUTH_TOKEN同步),无其余权限
13
+ SHARED_TOKEN=
14
+
15
+ # 启用流式响应检查,关闭则无法响应错误,代价是会对第一个块解析2次(已弃用)
16
+ # 新版本已经完成优化
17
+ # ENABLE_STREAM_CHECK=true
18
+
19
+ # 流式消息结束后发送包含"finish_reason"为"stop"的空消息块(已弃用)
20
+ # INCLUDE_STOP_REASON_STREAM=true
21
+
22
+ # 令牌文件路径(已弃用)
23
+ # TOKEN_FILE=.token
24
+
25
+ # 令牌列表文件路径
26
+ TOKEN_LIST_FILE=.tokens
27
+
28
+ # (实验性)是否启用慢速池(true/false)
29
+ ENABLE_SLOW_POOL=false
30
+
31
+ # 允许claude开头的模型请求绕过内置模型限制(true/false)
32
+ PASS_ANY_CLAUDE=false
33
+
34
+ # 图片处理能力配置
35
+ # 可选值:
36
+ # - none 或 disabled:禁用图片功能
37
+ # - base64 或 base64-only:仅支持 base64 编码的图片
38
+ # - all 或 base64-http:支持 base64 和 HTTP 图片
39
+ # 注意:启用 HTTP 支持可能会暴露服务器 IP
40
+ VISION_ABILITY=base64
41
+
42
+ # 额度检查配置
43
+ # 可选值:
44
+ # - none 或 disabled:禁用额度检查
45
+ # - default:详见 README
46
+ # - all 或 everything:额度无条件检查
47
+ # - 以,分隔的模型列表,为空时使用默认值
48
+ USAGE_CHECK=default
49
+
50
+ # 是否允许使用动态(自定义)配置的 API Key
51
+ DYNAMIC_KEY=false
52
+
53
+ # 动态 Key 的标识前缀
54
+ KEY_PREFIX=sk-
55
+
56
+ # 默认提示词
57
+ DEFAULT_INSTRUCTIONS="Respond in Chinese by default"
58
+
59
+ # 反向代理服务器主机名
60
+ REVERSE_PROXY_HOST=
61
+
62
+ # 代理地址配置说明
63
+ # - 留空或 `no`: 不使用任何代理
64
+ # - `system`: 使用系统代理(变量不存在时的默认值)
65
+ # - 代理地址: 支持以下格式
66
+ # - 多个代理: `http://localhost:7890,https://username:password@localhost:1234`
67
+ # 没有轮询,只是选择第一个格式正确的
68
+ # - 支持的协议: http, https, socks4, socks5, socks5h
69
+ PROXIES=
70
+
71
+ # 请求体大小限制(单位为MB)
72
+ # 默认为2MB (2,097,152 字节)
73
+ REQUEST_BODY_LIMIT_MB=2
74
+
75
+ # OpenAI 请求时,token 和 checksum 的分隔符
76
+ TOKEN_DELIMITER=,
77
+
78
+ # 同时兼容默认的,作为分隔符
79
+ USE_COMMA_DELIMITER=true
80
+
81
+ # 调试
82
+ DEBUG=false
83
+
84
+ # 调试文件
85
+ DEBUG_LOG_FILE=debug.log
86
+
87
+ # 日志储存条数(最大值2000)
88
+ REQUEST_LOGS_LIMIT=100
89
+
90
+ # Cursor 服务超时(秒)(最大值600)
91
+ SERVICE_TIMEOUT=30
92
+
93
+ # 包含网络引用
94
+ INCLUDE_WEB_REFERENCES=false
95
+
96
+ # 持久化日志文件路径
97
+ LOGS_FILE_PATH=logs.bin
98
+
99
+ # 持久化页面配置文件路径
100
+ PAGES_FILE_PATH=pages.bin
.gitignore ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /target
2
+ /tools/*/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
+ /.tokens
16
+ /cursor-api
17
+ /cursor-api.exe
18
+ /release
19
+
20
+ /*.py
21
+ /logs
22
+ /dev*
23
+ /build*
24
+ /*.bin
25
+ /result.txt
26
+ tools/tokenizer/
27
+
28
+ .idea
Cargo.lock ADDED
@@ -0,0 +1,2798 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 = "ahash"
22
+ version = "0.7.8"
23
+ source = "registry+https://github.com/rust-lang/crates.io-index"
24
+ checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9"
25
+ dependencies = [
26
+ "getrandom",
27
+ "once_cell",
28
+ "version_check",
29
+ ]
30
+
31
+ [[package]]
32
+ name = "aho-corasick"
33
+ version = "1.1.3"
34
+ source = "registry+https://github.com/rust-lang/crates.io-index"
35
+ checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
36
+ dependencies = [
37
+ "memchr",
38
+ ]
39
+
40
+ [[package]]
41
+ name = "alloc-no-stdlib"
42
+ version = "2.0.4"
43
+ source = "registry+https://github.com/rust-lang/crates.io-index"
44
+ checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3"
45
+
46
+ [[package]]
47
+ name = "alloc-stdlib"
48
+ version = "0.2.2"
49
+ source = "registry+https://github.com/rust-lang/crates.io-index"
50
+ checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece"
51
+ dependencies = [
52
+ "alloc-no-stdlib",
53
+ ]
54
+
55
+ [[package]]
56
+ name = "android-tzdata"
57
+ version = "0.1.1"
58
+ source = "registry+https://github.com/rust-lang/crates.io-index"
59
+ checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
60
+
61
+ [[package]]
62
+ name = "android_system_properties"
63
+ version = "0.1.5"
64
+ source = "registry+https://github.com/rust-lang/crates.io-index"
65
+ checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
66
+ dependencies = [
67
+ "libc",
68
+ ]
69
+
70
+ [[package]]
71
+ name = "anyhow"
72
+ version = "1.0.95"
73
+ source = "registry+https://github.com/rust-lang/crates.io-index"
74
+ checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04"
75
+
76
+ [[package]]
77
+ name = "async-compression"
78
+ version = "0.4.18"
79
+ source = "registry+https://github.com/rust-lang/crates.io-index"
80
+ checksum = "df895a515f70646414f4b45c0b79082783b80552b373a68283012928df56f522"
81
+ dependencies = [
82
+ "brotli",
83
+ "flate2",
84
+ "futures-core",
85
+ "memchr",
86
+ "pin-project-lite",
87
+ "tokio",
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.8.1"
105
+ source = "registry+https://github.com/rust-lang/crates.io-index"
106
+ checksum = "6d6fd624c75e18b3b4c6b9caf42b1afe24437daaee904069137d8bab077be8b8"
107
+ dependencies = [
108
+ "axum-core",
109
+ "bytes",
110
+ "form_urlencoded",
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.5.0"
139
+ source = "registry+https://github.com/rust-lang/crates.io-index"
140
+ checksum = "df1362f362fd16024ae199c1970ce98f9661bf5ef94b9808fee734bc3698b733"
141
+ dependencies = [
142
+ "bytes",
143
+ "futures-util",
144
+ "http",
145
+ "http-body",
146
+ "http-body-util",
147
+ "mime",
148
+ "pin-project-lite",
149
+ "rustversion",
150
+ "sync_wrapper",
151
+ "tower-layer",
152
+ "tower-service",
153
+ "tracing",
154
+ ]
155
+
156
+ [[package]]
157
+ name = "backtrace"
158
+ version = "0.3.74"
159
+ source = "registry+https://github.com/rust-lang/crates.io-index"
160
+ checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a"
161
+ dependencies = [
162
+ "addr2line",
163
+ "cfg-if",
164
+ "libc",
165
+ "miniz_oxide",
166
+ "object",
167
+ "rustc-demangle",
168
+ "windows-targets",
169
+ ]
170
+
171
+ [[package]]
172
+ name = "base64"
173
+ version = "0.22.1"
174
+ source = "registry+https://github.com/rust-lang/crates.io-index"
175
+ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
176
+
177
+ [[package]]
178
+ name = "bitflags"
179
+ version = "1.3.2"
180
+ source = "registry+https://github.com/rust-lang/crates.io-index"
181
+ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
182
+
183
+ [[package]]
184
+ name = "bitflags"
185
+ version = "2.8.0"
186
+ source = "registry+https://github.com/rust-lang/crates.io-index"
187
+ checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36"
188
+
189
+ [[package]]
190
+ name = "bitvec"
191
+ version = "1.0.1"
192
+ source = "registry+https://github.com/rust-lang/crates.io-index"
193
+ checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c"
194
+ dependencies = [
195
+ "funty",
196
+ "radium",
197
+ "tap",
198
+ "wyz",
199
+ ]
200
+
201
+ [[package]]
202
+ name = "block-buffer"
203
+ version = "0.10.4"
204
+ source = "registry+https://github.com/rust-lang/crates.io-index"
205
+ checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
206
+ dependencies = [
207
+ "generic-array",
208
+ ]
209
+
210
+ [[package]]
211
+ name = "brotli"
212
+ version = "7.0.0"
213
+ source = "registry+https://github.com/rust-lang/crates.io-index"
214
+ checksum = "cc97b8f16f944bba54f0433f07e30be199b6dc2bd25937444bbad560bcea29bd"
215
+ dependencies = [
216
+ "alloc-no-stdlib",
217
+ "alloc-stdlib",
218
+ "brotli-decompressor",
219
+ ]
220
+
221
+ [[package]]
222
+ name = "brotli-decompressor"
223
+ version = "4.0.1"
224
+ source = "registry+https://github.com/rust-lang/crates.io-index"
225
+ checksum = "9a45bd2e4095a8b518033b128020dd4a55aab1c0a381ba4404a472630f4bc362"
226
+ dependencies = [
227
+ "alloc-no-stdlib",
228
+ "alloc-stdlib",
229
+ ]
230
+
231
+ [[package]]
232
+ name = "bumpalo"
233
+ version = "3.16.0"
234
+ source = "registry+https://github.com/rust-lang/crates.io-index"
235
+ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
236
+
237
+ [[package]]
238
+ name = "bytecheck"
239
+ version = "0.6.12"
240
+ source = "registry+https://github.com/rust-lang/crates.io-index"
241
+ checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2"
242
+ dependencies = [
243
+ "bytecheck_derive",
244
+ "ptr_meta 0.1.4",
245
+ "simdutf8",
246
+ ]
247
+
248
+ [[package]]
249
+ name = "bytecheck_derive"
250
+ version = "0.6.12"
251
+ source = "registry+https://github.com/rust-lang/crates.io-index"
252
+ checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659"
253
+ dependencies = [
254
+ "proc-macro2",
255
+ "quote",
256
+ "syn 1.0.109",
257
+ ]
258
+
259
+ [[package]]
260
+ name = "bytemuck"
261
+ version = "1.21.0"
262
+ source = "registry+https://github.com/rust-lang/crates.io-index"
263
+ checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3"
264
+
265
+ [[package]]
266
+ name = "byteorder"
267
+ version = "1.5.0"
268
+ source = "registry+https://github.com/rust-lang/crates.io-index"
269
+ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
270
+
271
+ [[package]]
272
+ name = "byteorder-lite"
273
+ version = "0.1.0"
274
+ source = "registry+https://github.com/rust-lang/crates.io-index"
275
+ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
276
+
277
+ [[package]]
278
+ name = "bytes"
279
+ version = "1.9.0"
280
+ source = "registry+https://github.com/rust-lang/crates.io-index"
281
+ checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b"
282
+
283
+ [[package]]
284
+ name = "cc"
285
+ version = "1.2.10"
286
+ source = "registry+https://github.com/rust-lang/crates.io-index"
287
+ checksum = "13208fcbb66eaeffe09b99fffbe1af420f00a7b35aa99ad683dfc1aa76145229"
288
+ dependencies = [
289
+ "shlex",
290
+ ]
291
+
292
+ [[package]]
293
+ name = "cfg-if"
294
+ version = "1.0.0"
295
+ source = "registry+https://github.com/rust-lang/crates.io-index"
296
+ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
297
+
298
+ [[package]]
299
+ name = "chrono"
300
+ version = "0.4.39"
301
+ source = "registry+https://github.com/rust-lang/crates.io-index"
302
+ checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825"
303
+ dependencies = [
304
+ "android-tzdata",
305
+ "iana-time-zone",
306
+ "num-traits",
307
+ "rkyv 0.7.45",
308
+ "serde",
309
+ "windows-targets",
310
+ ]
311
+
312
+ [[package]]
313
+ name = "color_quant"
314
+ version = "1.1.0"
315
+ source = "registry+https://github.com/rust-lang/crates.io-index"
316
+ checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
317
+
318
+ [[package]]
319
+ name = "core-foundation"
320
+ version = "0.9.4"
321
+ source = "registry+https://github.com/rust-lang/crates.io-index"
322
+ checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
323
+ dependencies = [
324
+ "core-foundation-sys",
325
+ "libc",
326
+ ]
327
+
328
+ [[package]]
329
+ name = "core-foundation-sys"
330
+ version = "0.8.7"
331
+ source = "registry+https://github.com/rust-lang/crates.io-index"
332
+ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
333
+
334
+ [[package]]
335
+ name = "cpufeatures"
336
+ version = "0.2.16"
337
+ source = "registry+https://github.com/rust-lang/crates.io-index"
338
+ checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3"
339
+ dependencies = [
340
+ "libc",
341
+ ]
342
+
343
+ [[package]]
344
+ name = "crc32fast"
345
+ version = "1.4.2"
346
+ source = "registry+https://github.com/rust-lang/crates.io-index"
347
+ checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
348
+ dependencies = [
349
+ "cfg-if",
350
+ ]
351
+
352
+ [[package]]
353
+ name = "crypto-common"
354
+ version = "0.1.6"
355
+ source = "registry+https://github.com/rust-lang/crates.io-index"
356
+ checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
357
+ dependencies = [
358
+ "generic-array",
359
+ "typenum",
360
+ ]
361
+
362
+ [[package]]
363
+ name = "cursor-api"
364
+ version = "0.1.3-rc.4.3"
365
+ dependencies = [
366
+ "axum",
367
+ "base64",
368
+ "bytes",
369
+ "chrono",
370
+ "dotenvy",
371
+ "flate2",
372
+ "futures",
373
+ "gif",
374
+ "hex",
375
+ "image",
376
+ "memmap2",
377
+ "parking_lot",
378
+ "paste",
379
+ "prost",
380
+ "prost-build",
381
+ "rand",
382
+ "regex",
383
+ "reqwest",
384
+ "rkyv 0.7.45",
385
+ "serde",
386
+ "serde_json",
387
+ "sha2",
388
+ "sonic-rs",
389
+ "sysinfo",
390
+ "tokio",
391
+ "tokio-stream",
392
+ "tower-http",
393
+ "url",
394
+ "uuid",
395
+ ]
396
+
397
+ [[package]]
398
+ name = "digest"
399
+ version = "0.10.7"
400
+ source = "registry+https://github.com/rust-lang/crates.io-index"
401
+ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
402
+ dependencies = [
403
+ "block-buffer",
404
+ "crypto-common",
405
+ ]
406
+
407
+ [[package]]
408
+ name = "displaydoc"
409
+ version = "0.2.5"
410
+ source = "registry+https://github.com/rust-lang/crates.io-index"
411
+ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
412
+ dependencies = [
413
+ "proc-macro2",
414
+ "quote",
415
+ "syn 2.0.96",
416
+ ]
417
+
418
+ [[package]]
419
+ name = "dotenvy"
420
+ version = "0.15.7"
421
+ source = "registry+https://github.com/rust-lang/crates.io-index"
422
+ checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
423
+
424
+ [[package]]
425
+ name = "either"
426
+ version = "1.13.0"
427
+ source = "registry+https://github.com/rust-lang/crates.io-index"
428
+ checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
429
+
430
+ [[package]]
431
+ name = "encoding_rs"
432
+ version = "0.8.35"
433
+ source = "registry+https://github.com/rust-lang/crates.io-index"
434
+ checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
435
+ dependencies = [
436
+ "cfg-if",
437
+ ]
438
+
439
+ [[package]]
440
+ name = "equivalent"
441
+ version = "1.0.1"
442
+ source = "registry+https://github.com/rust-lang/crates.io-index"
443
+ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
444
+
445
+ [[package]]
446
+ name = "errno"
447
+ version = "0.3.10"
448
+ source = "registry+https://github.com/rust-lang/crates.io-index"
449
+ checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
450
+ dependencies = [
451
+ "libc",
452
+ "windows-sys 0.59.0",
453
+ ]
454
+
455
+ [[package]]
456
+ name = "fastrand"
457
+ version = "2.3.0"
458
+ source = "registry+https://github.com/rust-lang/crates.io-index"
459
+ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
460
+
461
+ [[package]]
462
+ name = "faststr"
463
+ version = "0.2.27"
464
+ source = "registry+https://github.com/rust-lang/crates.io-index"
465
+ checksum = "9154486833a83cb5d99de8c4d831314b8ae810dd4ef18d89ceb7a9c7c728dd74"
466
+ dependencies = [
467
+ "bytes",
468
+ "rkyv 0.8.10",
469
+ "serde",
470
+ "simdutf8",
471
+ ]
472
+
473
+ [[package]]
474
+ name = "fdeflate"
475
+ version = "0.3.7"
476
+ source = "registry+https://github.com/rust-lang/crates.io-index"
477
+ checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c"
478
+ dependencies = [
479
+ "simd-adler32",
480
+ ]
481
+
482
+ [[package]]
483
+ name = "fixedbitset"
484
+ version = "0.4.2"
485
+ source = "registry+https://github.com/rust-lang/crates.io-index"
486
+ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
487
+
488
+ [[package]]
489
+ name = "flate2"
490
+ version = "1.0.35"
491
+ source = "registry+https://github.com/rust-lang/crates.io-index"
492
+ checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c"
493
+ dependencies = [
494
+ "crc32fast",
495
+ "miniz_oxide",
496
+ ]
497
+
498
+ [[package]]
499
+ name = "fnv"
500
+ version = "1.0.7"
501
+ source = "registry+https://github.com/rust-lang/crates.io-index"
502
+ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
503
+
504
+ [[package]]
505
+ name = "foreign-types"
506
+ version = "0.3.2"
507
+ source = "registry+https://github.com/rust-lang/crates.io-index"
508
+ checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
509
+ dependencies = [
510
+ "foreign-types-shared",
511
+ ]
512
+
513
+ [[package]]
514
+ name = "foreign-types-shared"
515
+ version = "0.1.1"
516
+ source = "registry+https://github.com/rust-lang/crates.io-index"
517
+ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
518
+
519
+ [[package]]
520
+ name = "form_urlencoded"
521
+ version = "1.2.1"
522
+ source = "registry+https://github.com/rust-lang/crates.io-index"
523
+ checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
524
+ dependencies = [
525
+ "percent-encoding",
526
+ ]
527
+
528
+ [[package]]
529
+ name = "funty"
530
+ version = "2.0.0"
531
+ source = "registry+https://github.com/rust-lang/crates.io-index"
532
+ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
533
+
534
+ [[package]]
535
+ name = "futures"
536
+ version = "0.3.31"
537
+ source = "registry+https://github.com/rust-lang/crates.io-index"
538
+ checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
539
+ dependencies = [
540
+ "futures-channel",
541
+ "futures-core",
542
+ "futures-io",
543
+ "futures-sink",
544
+ "futures-task",
545
+ "futures-util",
546
+ ]
547
+
548
+ [[package]]
549
+ name = "futures-channel"
550
+ version = "0.3.31"
551
+ source = "registry+https://github.com/rust-lang/crates.io-index"
552
+ checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
553
+ dependencies = [
554
+ "futures-core",
555
+ "futures-sink",
556
+ ]
557
+
558
+ [[package]]
559
+ name = "futures-core"
560
+ version = "0.3.31"
561
+ source = "registry+https://github.com/rust-lang/crates.io-index"
562
+ checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
563
+
564
+ [[package]]
565
+ name = "futures-io"
566
+ version = "0.3.31"
567
+ source = "registry+https://github.com/rust-lang/crates.io-index"
568
+ checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
569
+
570
+ [[package]]
571
+ name = "futures-macro"
572
+ version = "0.3.31"
573
+ source = "registry+https://github.com/rust-lang/crates.io-index"
574
+ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
575
+ dependencies = [
576
+ "proc-macro2",
577
+ "quote",
578
+ "syn 2.0.96",
579
+ ]
580
+
581
+ [[package]]
582
+ name = "futures-sink"
583
+ version = "0.3.31"
584
+ source = "registry+https://github.com/rust-lang/crates.io-index"
585
+ checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
586
+
587
+ [[package]]
588
+ name = "futures-task"
589
+ version = "0.3.31"
590
+ source = "registry+https://github.com/rust-lang/crates.io-index"
591
+ checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
592
+
593
+ [[package]]
594
+ name = "futures-util"
595
+ version = "0.3.31"
596
+ source = "registry+https://github.com/rust-lang/crates.io-index"
597
+ checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
598
+ dependencies = [
599
+ "futures-channel",
600
+ "futures-core",
601
+ "futures-io",
602
+ "futures-macro",
603
+ "futures-sink",
604
+ "futures-task",
605
+ "memchr",
606
+ "pin-project-lite",
607
+ "pin-utils",
608
+ "slab",
609
+ ]
610
+
611
+ [[package]]
612
+ name = "generic-array"
613
+ version = "0.14.7"
614
+ source = "registry+https://github.com/rust-lang/crates.io-index"
615
+ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
616
+ dependencies = [
617
+ "typenum",
618
+ "version_check",
619
+ ]
620
+
621
+ [[package]]
622
+ name = "getrandom"
623
+ version = "0.2.15"
624
+ source = "registry+https://github.com/rust-lang/crates.io-index"
625
+ checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
626
+ dependencies = [
627
+ "cfg-if",
628
+ "libc",
629
+ "wasi",
630
+ ]
631
+
632
+ [[package]]
633
+ name = "gif"
634
+ version = "0.13.1"
635
+ source = "registry+https://github.com/rust-lang/crates.io-index"
636
+ checksum = "3fb2d69b19215e18bb912fa30f7ce15846e301408695e44e0ef719f1da9e19f2"
637
+ dependencies = [
638
+ "color_quant",
639
+ "weezl",
640
+ ]
641
+
642
+ [[package]]
643
+ name = "gimli"
644
+ version = "0.31.1"
645
+ source = "registry+https://github.com/rust-lang/crates.io-index"
646
+ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
647
+
648
+ [[package]]
649
+ name = "h2"
650
+ version = "0.4.7"
651
+ source = "registry+https://github.com/rust-lang/crates.io-index"
652
+ checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e"
653
+ dependencies = [
654
+ "atomic-waker",
655
+ "bytes",
656
+ "fnv",
657
+ "futures-core",
658
+ "futures-sink",
659
+ "http",
660
+ "indexmap",
661
+ "slab",
662
+ "tokio",
663
+ "tokio-util",
664
+ "tracing",
665
+ ]
666
+
667
+ [[package]]
668
+ name = "hashbrown"
669
+ version = "0.12.3"
670
+ source = "registry+https://github.com/rust-lang/crates.io-index"
671
+ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
672
+ dependencies = [
673
+ "ahash",
674
+ ]
675
+
676
+ [[package]]
677
+ name = "hashbrown"
678
+ version = "0.15.2"
679
+ source = "registry+https://github.com/rust-lang/crates.io-index"
680
+ checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
681
+
682
+ [[package]]
683
+ name = "heck"
684
+ version = "0.5.0"
685
+ source = "registry+https://github.com/rust-lang/crates.io-index"
686
+ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
687
+
688
+ [[package]]
689
+ name = "hex"
690
+ version = "0.4.3"
691
+ source = "registry+https://github.com/rust-lang/crates.io-index"
692
+ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
693
+
694
+ [[package]]
695
+ name = "http"
696
+ version = "1.2.0"
697
+ source = "registry+https://github.com/rust-lang/crates.io-index"
698
+ checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea"
699
+ dependencies = [
700
+ "bytes",
701
+ "fnv",
702
+ "itoa",
703
+ ]
704
+
705
+ [[package]]
706
+ name = "http-body"
707
+ version = "1.0.1"
708
+ source = "registry+https://github.com/rust-lang/crates.io-index"
709
+ checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
710
+ dependencies = [
711
+ "bytes",
712
+ "http",
713
+ ]
714
+
715
+ [[package]]
716
+ name = "http-body-util"
717
+ version = "0.1.2"
718
+ source = "registry+https://github.com/rust-lang/crates.io-index"
719
+ checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f"
720
+ dependencies = [
721
+ "bytes",
722
+ "futures-util",
723
+ "http",
724
+ "http-body",
725
+ "pin-project-lite",
726
+ ]
727
+
728
+ [[package]]
729
+ name = "httparse"
730
+ version = "1.9.5"
731
+ source = "registry+https://github.com/rust-lang/crates.io-index"
732
+ checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946"
733
+
734
+ [[package]]
735
+ name = "httpdate"
736
+ version = "1.0.3"
737
+ source = "registry+https://github.com/rust-lang/crates.io-index"
738
+ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
739
+
740
+ [[package]]
741
+ name = "hyper"
742
+ version = "1.5.2"
743
+ source = "registry+https://github.com/rust-lang/crates.io-index"
744
+ checksum = "256fb8d4bd6413123cc9d91832d78325c48ff41677595be797d90f42969beae0"
745
+ dependencies = [
746
+ "bytes",
747
+ "futures-channel",
748
+ "futures-util",
749
+ "h2",
750
+ "http",
751
+ "http-body",
752
+ "httparse",
753
+ "httpdate",
754
+ "itoa",
755
+ "pin-project-lite",
756
+ "smallvec",
757
+ "tokio",
758
+ "want",
759
+ ]
760
+
761
+ [[package]]
762
+ name = "hyper-rustls"
763
+ version = "0.27.5"
764
+ source = "registry+https://github.com/rust-lang/crates.io-index"
765
+ checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2"
766
+ dependencies = [
767
+ "futures-util",
768
+ "http",
769
+ "hyper",
770
+ "hyper-util",
771
+ "rustls",
772
+ "rustls-pki-types",
773
+ "tokio",
774
+ "tokio-rustls",
775
+ "tower-service",
776
+ ]
777
+
778
+ [[package]]
779
+ name = "hyper-tls"
780
+ version = "0.6.0"
781
+ source = "registry+https://github.com/rust-lang/crates.io-index"
782
+ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
783
+ dependencies = [
784
+ "bytes",
785
+ "http-body-util",
786
+ "hyper",
787
+ "hyper-util",
788
+ "native-tls",
789
+ "tokio",
790
+ "tokio-native-tls",
791
+ "tower-service",
792
+ ]
793
+
794
+ [[package]]
795
+ name = "hyper-util"
796
+ version = "0.1.10"
797
+ source = "registry+https://github.com/rust-lang/crates.io-index"
798
+ checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4"
799
+ dependencies = [
800
+ "bytes",
801
+ "futures-channel",
802
+ "futures-util",
803
+ "http",
804
+ "http-body",
805
+ "hyper",
806
+ "pin-project-lite",
807
+ "socket2",
808
+ "tokio",
809
+ "tower-service",
810
+ "tracing",
811
+ ]
812
+
813
+ [[package]]
814
+ name = "iana-time-zone"
815
+ version = "0.1.61"
816
+ source = "registry+https://github.com/rust-lang/crates.io-index"
817
+ checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220"
818
+ dependencies = [
819
+ "android_system_properties",
820
+ "core-foundation-sys",
821
+ "iana-time-zone-haiku",
822
+ "js-sys",
823
+ "wasm-bindgen",
824
+ "windows-core 0.52.0",
825
+ ]
826
+
827
+ [[package]]
828
+ name = "iana-time-zone-haiku"
829
+ version = "0.1.2"
830
+ source = "registry+https://github.com/rust-lang/crates.io-index"
831
+ checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
832
+ dependencies = [
833
+ "cc",
834
+ ]
835
+
836
+ [[package]]
837
+ name = "icu_collections"
838
+ version = "1.5.0"
839
+ source = "registry+https://github.com/rust-lang/crates.io-index"
840
+ checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526"
841
+ dependencies = [
842
+ "displaydoc",
843
+ "yoke",
844
+ "zerofrom",
845
+ "zerovec",
846
+ ]
847
+
848
+ [[package]]
849
+ name = "icu_locid"
850
+ version = "1.5.0"
851
+ source = "registry+https://github.com/rust-lang/crates.io-index"
852
+ checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637"
853
+ dependencies = [
854
+ "displaydoc",
855
+ "litemap",
856
+ "tinystr",
857
+ "writeable",
858
+ "zerovec",
859
+ ]
860
+
861
+ [[package]]
862
+ name = "icu_locid_transform"
863
+ version = "1.5.0"
864
+ source = "registry+https://github.com/rust-lang/crates.io-index"
865
+ checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e"
866
+ dependencies = [
867
+ "displaydoc",
868
+ "icu_locid",
869
+ "icu_locid_transform_data",
870
+ "icu_provider",
871
+ "tinystr",
872
+ "zerovec",
873
+ ]
874
+
875
+ [[package]]
876
+ name = "icu_locid_transform_data"
877
+ version = "1.5.0"
878
+ source = "registry+https://github.com/rust-lang/crates.io-index"
879
+ checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e"
880
+
881
+ [[package]]
882
+ name = "icu_normalizer"
883
+ version = "1.5.0"
884
+ source = "registry+https://github.com/rust-lang/crates.io-index"
885
+ checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f"
886
+ dependencies = [
887
+ "displaydoc",
888
+ "icu_collections",
889
+ "icu_normalizer_data",
890
+ "icu_properties",
891
+ "icu_provider",
892
+ "smallvec",
893
+ "utf16_iter",
894
+ "utf8_iter",
895
+ "write16",
896
+ "zerovec",
897
+ ]
898
+
899
+ [[package]]
900
+ name = "icu_normalizer_data"
901
+ version = "1.5.0"
902
+ source = "registry+https://github.com/rust-lang/crates.io-index"
903
+ checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516"
904
+
905
+ [[package]]
906
+ name = "icu_properties"
907
+ version = "1.5.1"
908
+ source = "registry+https://github.com/rust-lang/crates.io-index"
909
+ checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5"
910
+ dependencies = [
911
+ "displaydoc",
912
+ "icu_collections",
913
+ "icu_locid_transform",
914
+ "icu_properties_data",
915
+ "icu_provider",
916
+ "tinystr",
917
+ "zerovec",
918
+ ]
919
+
920
+ [[package]]
921
+ name = "icu_properties_data"
922
+ version = "1.5.0"
923
+ source = "registry+https://github.com/rust-lang/crates.io-index"
924
+ checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569"
925
+
926
+ [[package]]
927
+ name = "icu_provider"
928
+ version = "1.5.0"
929
+ source = "registry+https://github.com/rust-lang/crates.io-index"
930
+ checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9"
931
+ dependencies = [
932
+ "displaydoc",
933
+ "icu_locid",
934
+ "icu_provider_macros",
935
+ "stable_deref_trait",
936
+ "tinystr",
937
+ "writeable",
938
+ "yoke",
939
+ "zerofrom",
940
+ "zerovec",
941
+ ]
942
+
943
+ [[package]]
944
+ name = "icu_provider_macros"
945
+ version = "1.5.0"
946
+ source = "registry+https://github.com/rust-lang/crates.io-index"
947
+ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
948
+ dependencies = [
949
+ "proc-macro2",
950
+ "quote",
951
+ "syn 2.0.96",
952
+ ]
953
+
954
+ [[package]]
955
+ name = "idna"
956
+ version = "1.0.3"
957
+ source = "registry+https://github.com/rust-lang/crates.io-index"
958
+ checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e"
959
+ dependencies = [
960
+ "idna_adapter",
961
+ "smallvec",
962
+ "utf8_iter",
963
+ ]
964
+
965
+ [[package]]
966
+ name = "idna_adapter"
967
+ version = "1.2.0"
968
+ source = "registry+https://github.com/rust-lang/crates.io-index"
969
+ checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71"
970
+ dependencies = [
971
+ "icu_normalizer",
972
+ "icu_properties",
973
+ ]
974
+
975
+ [[package]]
976
+ name = "image"
977
+ version = "0.25.5"
978
+ source = "registry+https://github.com/rust-lang/crates.io-index"
979
+ checksum = "cd6f44aed642f18953a158afeb30206f4d50da59fbc66ecb53c66488de73563b"
980
+ dependencies = [
981
+ "bytemuck",
982
+ "byteorder-lite",
983
+ "color_quant",
984
+ "gif",
985
+ "image-webp",
986
+ "num-traits",
987
+ "png",
988
+ "zune-core",
989
+ "zune-jpeg",
990
+ ]
991
+
992
+ [[package]]
993
+ name = "image-webp"
994
+ version = "0.2.1"
995
+ source = "registry+https://github.com/rust-lang/crates.io-index"
996
+ checksum = "b77d01e822461baa8409e156015a1d91735549f0f2c17691bd2d996bef238f7f"
997
+ dependencies = [
998
+ "byteorder-lite",
999
+ "quick-error",
1000
+ ]
1001
+
1002
+ [[package]]
1003
+ name = "indexmap"
1004
+ version = "2.7.1"
1005
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1006
+ checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652"
1007
+ dependencies = [
1008
+ "equivalent",
1009
+ "hashbrown 0.15.2",
1010
+ ]
1011
+
1012
+ [[package]]
1013
+ name = "ipnet"
1014
+ version = "2.11.0"
1015
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1016
+ checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130"
1017
+
1018
+ [[package]]
1019
+ name = "itertools"
1020
+ version = "0.13.0"
1021
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1022
+ checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
1023
+ dependencies = [
1024
+ "either",
1025
+ ]
1026
+
1027
+ [[package]]
1028
+ name = "itoa"
1029
+ version = "1.0.14"
1030
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1031
+ checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
1032
+
1033
+ [[package]]
1034
+ name = "js-sys"
1035
+ version = "0.3.77"
1036
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1037
+ checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
1038
+ dependencies = [
1039
+ "once_cell",
1040
+ "wasm-bindgen",
1041
+ ]
1042
+
1043
+ [[package]]
1044
+ name = "libc"
1045
+ version = "0.2.169"
1046
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1047
+ checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
1048
+
1049
+ [[package]]
1050
+ name = "linux-raw-sys"
1051
+ version = "0.4.15"
1052
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1053
+ checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
1054
+
1055
+ [[package]]
1056
+ name = "litemap"
1057
+ version = "0.7.4"
1058
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1059
+ checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104"
1060
+
1061
+ [[package]]
1062
+ name = "lock_api"
1063
+ version = "0.4.12"
1064
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1065
+ checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
1066
+ dependencies = [
1067
+ "autocfg",
1068
+ "scopeguard",
1069
+ ]
1070
+
1071
+ [[package]]
1072
+ name = "log"
1073
+ version = "0.4.25"
1074
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1075
+ checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f"
1076
+
1077
+ [[package]]
1078
+ name = "matchit"
1079
+ version = "0.8.4"
1080
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1081
+ checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
1082
+
1083
+ [[package]]
1084
+ name = "memchr"
1085
+ version = "2.7.4"
1086
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1087
+ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
1088
+
1089
+ [[package]]
1090
+ name = "memmap2"
1091
+ version = "0.9.5"
1092
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1093
+ checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f"
1094
+ dependencies = [
1095
+ "libc",
1096
+ ]
1097
+
1098
+ [[package]]
1099
+ name = "mime"
1100
+ version = "0.3.17"
1101
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1102
+ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
1103
+
1104
+ [[package]]
1105
+ name = "miniz_oxide"
1106
+ version = "0.8.3"
1107
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1108
+ checksum = "b8402cab7aefae129c6977bb0ff1b8fd9a04eb5b51efc50a70bea51cda0c7924"
1109
+ dependencies = [
1110
+ "adler2",
1111
+ "simd-adler32",
1112
+ ]
1113
+
1114
+ [[package]]
1115
+ name = "mio"
1116
+ version = "1.0.3"
1117
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1118
+ checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd"
1119
+ dependencies = [
1120
+ "libc",
1121
+ "wasi",
1122
+ "windows-sys 0.52.0",
1123
+ ]
1124
+
1125
+ [[package]]
1126
+ name = "multimap"
1127
+ version = "0.10.0"
1128
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1129
+ checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03"
1130
+
1131
+ [[package]]
1132
+ name = "munge"
1133
+ version = "0.4.1"
1134
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1135
+ checksum = "64142d38c84badf60abf06ff9bd80ad2174306a5b11bd4706535090a30a419df"
1136
+ dependencies = [
1137
+ "munge_macro",
1138
+ ]
1139
+
1140
+ [[package]]
1141
+ name = "munge_macro"
1142
+ version = "0.4.1"
1143
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1144
+ checksum = "1bb5c1d8184f13f7d0ccbeeca0def2f9a181bce2624302793005f5ca8aa62e5e"
1145
+ dependencies = [
1146
+ "proc-macro2",
1147
+ "quote",
1148
+ "syn 2.0.96",
1149
+ ]
1150
+
1151
+ [[package]]
1152
+ name = "native-tls"
1153
+ version = "0.2.12"
1154
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1155
+ checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466"
1156
+ dependencies = [
1157
+ "libc",
1158
+ "log",
1159
+ "openssl",
1160
+ "openssl-probe",
1161
+ "openssl-sys",
1162
+ "schannel",
1163
+ "security-framework",
1164
+ "security-framework-sys",
1165
+ "tempfile",
1166
+ ]
1167
+
1168
+ [[package]]
1169
+ name = "ntapi"
1170
+ version = "0.4.1"
1171
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1172
+ checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4"
1173
+ dependencies = [
1174
+ "winapi",
1175
+ ]
1176
+
1177
+ [[package]]
1178
+ name = "num-traits"
1179
+ version = "0.2.19"
1180
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1181
+ checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
1182
+ dependencies = [
1183
+ "autocfg",
1184
+ ]
1185
+
1186
+ [[package]]
1187
+ name = "object"
1188
+ version = "0.36.7"
1189
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1190
+ checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
1191
+ dependencies = [
1192
+ "memchr",
1193
+ ]
1194
+
1195
+ [[package]]
1196
+ name = "once_cell"
1197
+ version = "1.20.2"
1198
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1199
+ checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
1200
+
1201
+ [[package]]
1202
+ name = "openssl"
1203
+ version = "0.10.68"
1204
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1205
+ checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5"
1206
+ dependencies = [
1207
+ "bitflags 2.8.0",
1208
+ "cfg-if",
1209
+ "foreign-types",
1210
+ "libc",
1211
+ "once_cell",
1212
+ "openssl-macros",
1213
+ "openssl-sys",
1214
+ ]
1215
+
1216
+ [[package]]
1217
+ name = "openssl-macros"
1218
+ version = "0.1.1"
1219
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1220
+ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
1221
+ dependencies = [
1222
+ "proc-macro2",
1223
+ "quote",
1224
+ "syn 2.0.96",
1225
+ ]
1226
+
1227
+ [[package]]
1228
+ name = "openssl-probe"
1229
+ version = "0.1.5"
1230
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1231
+ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
1232
+
1233
+ [[package]]
1234
+ name = "openssl-sys"
1235
+ version = "0.9.104"
1236
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1237
+ checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741"
1238
+ dependencies = [
1239
+ "cc",
1240
+ "libc",
1241
+ "pkg-config",
1242
+ "vcpkg",
1243
+ ]
1244
+
1245
+ [[package]]
1246
+ name = "parking_lot"
1247
+ version = "0.12.3"
1248
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1249
+ checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27"
1250
+ dependencies = [
1251
+ "lock_api",
1252
+ "parking_lot_core",
1253
+ ]
1254
+
1255
+ [[package]]
1256
+ name = "parking_lot_core"
1257
+ version = "0.9.10"
1258
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1259
+ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
1260
+ dependencies = [
1261
+ "cfg-if",
1262
+ "libc",
1263
+ "redox_syscall",
1264
+ "smallvec",
1265
+ "windows-targets",
1266
+ ]
1267
+
1268
+ [[package]]
1269
+ name = "paste"
1270
+ version = "1.0.15"
1271
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1272
+ checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
1273
+
1274
+ [[package]]
1275
+ name = "percent-encoding"
1276
+ version = "2.3.1"
1277
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1278
+ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
1279
+
1280
+ [[package]]
1281
+ name = "petgraph"
1282
+ version = "0.6.5"
1283
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1284
+ checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db"
1285
+ dependencies = [
1286
+ "fixedbitset",
1287
+ "indexmap",
1288
+ ]
1289
+
1290
+ [[package]]
1291
+ name = "pin-project-lite"
1292
+ version = "0.2.16"
1293
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1294
+ checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
1295
+
1296
+ [[package]]
1297
+ name = "pin-utils"
1298
+ version = "0.1.0"
1299
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1300
+ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
1301
+
1302
+ [[package]]
1303
+ name = "pkg-config"
1304
+ version = "0.3.31"
1305
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1306
+ checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2"
1307
+
1308
+ [[package]]
1309
+ name = "png"
1310
+ version = "0.17.16"
1311
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1312
+ checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526"
1313
+ dependencies = [
1314
+ "bitflags 1.3.2",
1315
+ "crc32fast",
1316
+ "fdeflate",
1317
+ "flate2",
1318
+ "miniz_oxide",
1319
+ ]
1320
+
1321
+ [[package]]
1322
+ name = "ppv-lite86"
1323
+ version = "0.2.20"
1324
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1325
+ checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
1326
+ dependencies = [
1327
+ "zerocopy",
1328
+ ]
1329
+
1330
+ [[package]]
1331
+ name = "prettyplease"
1332
+ version = "0.2.29"
1333
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1334
+ checksum = "6924ced06e1f7dfe3fa48d57b9f74f55d8915f5036121bef647ef4b204895fac"
1335
+ dependencies = [
1336
+ "proc-macro2",
1337
+ "syn 2.0.96",
1338
+ ]
1339
+
1340
+ [[package]]
1341
+ name = "proc-macro2"
1342
+ version = "1.0.93"
1343
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1344
+ checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
1345
+ dependencies = [
1346
+ "unicode-ident",
1347
+ ]
1348
+
1349
+ [[package]]
1350
+ name = "prost"
1351
+ version = "0.13.4"
1352
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1353
+ checksum = "2c0fef6c4230e4ccf618a35c59d7ede15dea37de8427500f50aff708806e42ec"
1354
+ dependencies = [
1355
+ "bytes",
1356
+ "prost-derive",
1357
+ ]
1358
+
1359
+ [[package]]
1360
+ name = "prost-build"
1361
+ version = "0.13.4"
1362
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1363
+ checksum = "d0f3e5beed80eb580c68e2c600937ac2c4eedabdfd5ef1e5b7ea4f3fba84497b"
1364
+ dependencies = [
1365
+ "heck",
1366
+ "itertools",
1367
+ "log",
1368
+ "multimap",
1369
+ "once_cell",
1370
+ "petgraph",
1371
+ "prettyplease",
1372
+ "prost",
1373
+ "prost-types",
1374
+ "regex",
1375
+ "syn 2.0.96",
1376
+ "tempfile",
1377
+ ]
1378
+
1379
+ [[package]]
1380
+ name = "prost-derive"
1381
+ version = "0.13.4"
1382
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1383
+ checksum = "157c5a9d7ea5c2ed2d9fb8f495b64759f7816c7eaea54ba3978f0d63000162e3"
1384
+ dependencies = [
1385
+ "anyhow",
1386
+ "itertools",
1387
+ "proc-macro2",
1388
+ "quote",
1389
+ "syn 2.0.96",
1390
+ ]
1391
+
1392
+ [[package]]
1393
+ name = "prost-types"
1394
+ version = "0.13.4"
1395
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1396
+ checksum = "cc2f1e56baa61e93533aebc21af4d2134b70f66275e0fcdf3cbe43d77ff7e8fc"
1397
+ dependencies = [
1398
+ "prost",
1399
+ ]
1400
+
1401
+ [[package]]
1402
+ name = "ptr_meta"
1403
+ version = "0.1.4"
1404
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1405
+ checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1"
1406
+ dependencies = [
1407
+ "ptr_meta_derive 0.1.4",
1408
+ ]
1409
+
1410
+ [[package]]
1411
+ name = "ptr_meta"
1412
+ version = "0.3.0"
1413
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1414
+ checksum = "fe9e76f66d3f9606f44e45598d155cb13ecf09f4a28199e48daf8c8fc937ea90"
1415
+ dependencies = [
1416
+ "ptr_meta_derive 0.3.0",
1417
+ ]
1418
+
1419
+ [[package]]
1420
+ name = "ptr_meta_derive"
1421
+ version = "0.1.4"
1422
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1423
+ checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac"
1424
+ dependencies = [
1425
+ "proc-macro2",
1426
+ "quote",
1427
+ "syn 1.0.109",
1428
+ ]
1429
+
1430
+ [[package]]
1431
+ name = "ptr_meta_derive"
1432
+ version = "0.3.0"
1433
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1434
+ checksum = "ca414edb151b4c8d125c12566ab0d74dc9cdba36fb80eb7b848c15f495fd32d1"
1435
+ dependencies = [
1436
+ "proc-macro2",
1437
+ "quote",
1438
+ "syn 2.0.96",
1439
+ ]
1440
+
1441
+ [[package]]
1442
+ name = "quick-error"
1443
+ version = "2.0.1"
1444
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1445
+ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
1446
+
1447
+ [[package]]
1448
+ name = "quote"
1449
+ version = "1.0.38"
1450
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1451
+ checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
1452
+ dependencies = [
1453
+ "proc-macro2",
1454
+ ]
1455
+
1456
+ [[package]]
1457
+ name = "radium"
1458
+ version = "0.7.0"
1459
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1460
+ checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
1461
+
1462
+ [[package]]
1463
+ name = "rancor"
1464
+ version = "0.1.0"
1465
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1466
+ checksum = "caf5f7161924b9d1cea0e4cabc97c372cea92b5f927fc13c6bca67157a0ad947"
1467
+ dependencies = [
1468
+ "ptr_meta 0.3.0",
1469
+ ]
1470
+
1471
+ [[package]]
1472
+ name = "rand"
1473
+ version = "0.8.5"
1474
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1475
+ checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
1476
+ dependencies = [
1477
+ "libc",
1478
+ "rand_chacha",
1479
+ "rand_core",
1480
+ ]
1481
+
1482
+ [[package]]
1483
+ name = "rand_chacha"
1484
+ version = "0.3.1"
1485
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1486
+ checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
1487
+ dependencies = [
1488
+ "ppv-lite86",
1489
+ "rand_core",
1490
+ ]
1491
+
1492
+ [[package]]
1493
+ name = "rand_core"
1494
+ version = "0.6.4"
1495
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1496
+ checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
1497
+ dependencies = [
1498
+ "getrandom",
1499
+ ]
1500
+
1501
+ [[package]]
1502
+ name = "redox_syscall"
1503
+ version = "0.5.8"
1504
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1505
+ checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834"
1506
+ dependencies = [
1507
+ "bitflags 2.8.0",
1508
+ ]
1509
+
1510
+ [[package]]
1511
+ name = "ref-cast"
1512
+ version = "1.0.23"
1513
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1514
+ checksum = "ccf0a6f84d5f1d581da8b41b47ec8600871962f2a528115b542b362d4b744931"
1515
+ dependencies = [
1516
+ "ref-cast-impl",
1517
+ ]
1518
+
1519
+ [[package]]
1520
+ name = "ref-cast-impl"
1521
+ version = "1.0.23"
1522
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1523
+ checksum = "bcc303e793d3734489387d205e9b186fac9c6cfacedd98cbb2e8a5943595f3e6"
1524
+ dependencies = [
1525
+ "proc-macro2",
1526
+ "quote",
1527
+ "syn 2.0.96",
1528
+ ]
1529
+
1530
+ [[package]]
1531
+ name = "regex"
1532
+ version = "1.11.1"
1533
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1534
+ checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
1535
+ dependencies = [
1536
+ "aho-corasick",
1537
+ "memchr",
1538
+ "regex-automata",
1539
+ "regex-syntax",
1540
+ ]
1541
+
1542
+ [[package]]
1543
+ name = "regex-automata"
1544
+ version = "0.4.9"
1545
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1546
+ checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
1547
+ dependencies = [
1548
+ "aho-corasick",
1549
+ "memchr",
1550
+ "regex-syntax",
1551
+ ]
1552
+
1553
+ [[package]]
1554
+ name = "regex-syntax"
1555
+ version = "0.8.5"
1556
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1557
+ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
1558
+
1559
+ [[package]]
1560
+ name = "rend"
1561
+ version = "0.4.2"
1562
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1563
+ checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c"
1564
+ dependencies = [
1565
+ "bytecheck",
1566
+ ]
1567
+
1568
+ [[package]]
1569
+ name = "rend"
1570
+ version = "0.5.2"
1571
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1572
+ checksum = "a35e8a6bf28cd121053a66aa2e6a2e3eaffad4a60012179f0e864aa5ffeff215"
1573
+
1574
+ [[package]]
1575
+ name = "reqwest"
1576
+ version = "0.12.12"
1577
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1578
+ checksum = "43e734407157c3c2034e0258f5e4473ddb361b1e85f95a66690d67264d7cd1da"
1579
+ dependencies = [
1580
+ "async-compression",
1581
+ "base64",
1582
+ "bytes",
1583
+ "encoding_rs",
1584
+ "futures-core",
1585
+ "futures-util",
1586
+ "h2",
1587
+ "http",
1588
+ "http-body",
1589
+ "http-body-util",
1590
+ "hyper",
1591
+ "hyper-rustls",
1592
+ "hyper-tls",
1593
+ "hyper-util",
1594
+ "ipnet",
1595
+ "js-sys",
1596
+ "log",
1597
+ "mime",
1598
+ "native-tls",
1599
+ "once_cell",
1600
+ "percent-encoding",
1601
+ "pin-project-lite",
1602
+ "rustls-pemfile",
1603
+ "serde",
1604
+ "serde_json",
1605
+ "serde_urlencoded",
1606
+ "sync_wrapper",
1607
+ "system-configuration",
1608
+ "tokio",
1609
+ "tokio-native-tls",
1610
+ "tokio-socks",
1611
+ "tokio-util",
1612
+ "tower",
1613
+ "tower-service",
1614
+ "url",
1615
+ "wasm-bindgen",
1616
+ "wasm-bindgen-futures",
1617
+ "wasm-streams",
1618
+ "web-sys",
1619
+ "windows-registry",
1620
+ ]
1621
+
1622
+ [[package]]
1623
+ name = "ring"
1624
+ version = "0.17.8"
1625
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1626
+ checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d"
1627
+ dependencies = [
1628
+ "cc",
1629
+ "cfg-if",
1630
+ "getrandom",
1631
+ "libc",
1632
+ "spin",
1633
+ "untrusted",
1634
+ "windows-sys 0.52.0",
1635
+ ]
1636
+
1637
+ [[package]]
1638
+ name = "rkyv"
1639
+ version = "0.7.45"
1640
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1641
+ checksum = "9008cd6385b9e161d8229e1f6549dd23c3d022f132a2ea37ac3a10ac4935779b"
1642
+ dependencies = [
1643
+ "bitvec",
1644
+ "bytecheck",
1645
+ "bytes",
1646
+ "hashbrown 0.12.3",
1647
+ "ptr_meta 0.1.4",
1648
+ "rend 0.4.2",
1649
+ "rkyv_derive 0.7.45",
1650
+ "seahash",
1651
+ "tinyvec",
1652
+ "uuid",
1653
+ ]
1654
+
1655
+ [[package]]
1656
+ name = "rkyv"
1657
+ version = "0.8.10"
1658
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1659
+ checksum = "1e147371c75553e1e2fcdb483944a8540b8438c31426279553b9a8182a9b7b65"
1660
+ dependencies = [
1661
+ "bytes",
1662
+ "hashbrown 0.15.2",
1663
+ "indexmap",
1664
+ "munge",
1665
+ "ptr_meta 0.3.0",
1666
+ "rancor",
1667
+ "rend 0.5.2",
1668
+ "rkyv_derive 0.8.10",
1669
+ "tinyvec",
1670
+ "uuid",
1671
+ ]
1672
+
1673
+ [[package]]
1674
+ name = "rkyv_derive"
1675
+ version = "0.7.45"
1676
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1677
+ checksum = "503d1d27590a2b0a3a4ca4c94755aa2875657196ecbf401a42eff41d7de532c0"
1678
+ dependencies = [
1679
+ "proc-macro2",
1680
+ "quote",
1681
+ "syn 1.0.109",
1682
+ ]
1683
+
1684
+ [[package]]
1685
+ name = "rkyv_derive"
1686
+ version = "0.8.10"
1687
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1688
+ checksum = "246b40ac189af6c675d124b802e8ef6d5246c53e17367ce9501f8f66a81abb7a"
1689
+ dependencies = [
1690
+ "proc-macro2",
1691
+ "quote",
1692
+ "syn 2.0.96",
1693
+ ]
1694
+
1695
+ [[package]]
1696
+ name = "rustc-demangle"
1697
+ version = "0.1.24"
1698
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1699
+ checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
1700
+
1701
+ [[package]]
1702
+ name = "rustix"
1703
+ version = "0.38.43"
1704
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1705
+ checksum = "a78891ee6bf2340288408954ac787aa063d8e8817e9f53abb37c695c6d834ef6"
1706
+ dependencies = [
1707
+ "bitflags 2.8.0",
1708
+ "errno",
1709
+ "libc",
1710
+ "linux-raw-sys",
1711
+ "windows-sys 0.59.0",
1712
+ ]
1713
+
1714
+ [[package]]
1715
+ name = "rustls"
1716
+ version = "0.23.21"
1717
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1718
+ checksum = "8f287924602bf649d949c63dc8ac8b235fa5387d394020705b80c4eb597ce5b8"
1719
+ dependencies = [
1720
+ "once_cell",
1721
+ "rustls-pki-types",
1722
+ "rustls-webpki",
1723
+ "subtle",
1724
+ "zeroize",
1725
+ ]
1726
+
1727
+ [[package]]
1728
+ name = "rustls-pemfile"
1729
+ version = "2.2.0"
1730
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1731
+ checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50"
1732
+ dependencies = [
1733
+ "rustls-pki-types",
1734
+ ]
1735
+
1736
+ [[package]]
1737
+ name = "rustls-pki-types"
1738
+ version = "1.10.1"
1739
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1740
+ checksum = "d2bf47e6ff922db3825eb750c4e2ff784c6ff8fb9e13046ef6a1d1c5401b0b37"
1741
+
1742
+ [[package]]
1743
+ name = "rustls-webpki"
1744
+ version = "0.102.8"
1745
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1746
+ checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9"
1747
+ dependencies = [
1748
+ "ring",
1749
+ "rustls-pki-types",
1750
+ "untrusted",
1751
+ ]
1752
+
1753
+ [[package]]
1754
+ name = "rustversion"
1755
+ version = "1.0.19"
1756
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1757
+ checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4"
1758
+
1759
+ [[package]]
1760
+ name = "ryu"
1761
+ version = "1.0.18"
1762
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1763
+ checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
1764
+
1765
+ [[package]]
1766
+ name = "schannel"
1767
+ version = "0.1.27"
1768
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1769
+ checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d"
1770
+ dependencies = [
1771
+ "windows-sys 0.59.0",
1772
+ ]
1773
+
1774
+ [[package]]
1775
+ name = "scopeguard"
1776
+ version = "1.2.0"
1777
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1778
+ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
1779
+
1780
+ [[package]]
1781
+ name = "seahash"
1782
+ version = "4.1.0"
1783
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1784
+ checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
1785
+
1786
+ [[package]]
1787
+ name = "security-framework"
1788
+ version = "2.11.1"
1789
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1790
+ checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
1791
+ dependencies = [
1792
+ "bitflags 2.8.0",
1793
+ "core-foundation",
1794
+ "core-foundation-sys",
1795
+ "libc",
1796
+ "security-framework-sys",
1797
+ ]
1798
+
1799
+ [[package]]
1800
+ name = "security-framework-sys"
1801
+ version = "2.14.0"
1802
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1803
+ checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32"
1804
+ dependencies = [
1805
+ "core-foundation-sys",
1806
+ "libc",
1807
+ ]
1808
+
1809
+ [[package]]
1810
+ name = "serde"
1811
+ version = "1.0.217"
1812
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1813
+ checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
1814
+ dependencies = [
1815
+ "serde_derive",
1816
+ ]
1817
+
1818
+ [[package]]
1819
+ name = "serde_derive"
1820
+ version = "1.0.217"
1821
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1822
+ checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
1823
+ dependencies = [
1824
+ "proc-macro2",
1825
+ "quote",
1826
+ "syn 2.0.96",
1827
+ ]
1828
+
1829
+ [[package]]
1830
+ name = "serde_json"
1831
+ version = "1.0.137"
1832
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1833
+ checksum = "930cfb6e6abf99298aaad7d29abbef7a9999a9a8806a40088f55f0dcec03146b"
1834
+ dependencies = [
1835
+ "itoa",
1836
+ "memchr",
1837
+ "ryu",
1838
+ "serde",
1839
+ ]
1840
+
1841
+ [[package]]
1842
+ name = "serde_path_to_error"
1843
+ version = "0.1.16"
1844
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1845
+ checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6"
1846
+ dependencies = [
1847
+ "itoa",
1848
+ "serde",
1849
+ ]
1850
+
1851
+ [[package]]
1852
+ name = "serde_urlencoded"
1853
+ version = "0.7.1"
1854
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1855
+ checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
1856
+ dependencies = [
1857
+ "form_urlencoded",
1858
+ "itoa",
1859
+ "ryu",
1860
+ "serde",
1861
+ ]
1862
+
1863
+ [[package]]
1864
+ name = "sha2"
1865
+ version = "0.10.8"
1866
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1867
+ checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
1868
+ dependencies = [
1869
+ "cfg-if",
1870
+ "cpufeatures",
1871
+ "digest",
1872
+ ]
1873
+
1874
+ [[package]]
1875
+ name = "shlex"
1876
+ version = "1.3.0"
1877
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1878
+ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
1879
+
1880
+ [[package]]
1881
+ name = "signal-hook-registry"
1882
+ version = "1.4.2"
1883
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1884
+ checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1"
1885
+ dependencies = [
1886
+ "libc",
1887
+ ]
1888
+
1889
+ [[package]]
1890
+ name = "simd-adler32"
1891
+ version = "0.3.7"
1892
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1893
+ checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
1894
+
1895
+ [[package]]
1896
+ name = "simdutf8"
1897
+ version = "0.1.5"
1898
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1899
+ checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
1900
+
1901
+ [[package]]
1902
+ name = "slab"
1903
+ version = "0.4.9"
1904
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1905
+ checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
1906
+ dependencies = [
1907
+ "autocfg",
1908
+ ]
1909
+
1910
+ [[package]]
1911
+ name = "smallvec"
1912
+ version = "1.13.2"
1913
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1914
+ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
1915
+
1916
+ [[package]]
1917
+ name = "socket2"
1918
+ version = "0.5.8"
1919
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1920
+ checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8"
1921
+ dependencies = [
1922
+ "libc",
1923
+ "windows-sys 0.52.0",
1924
+ ]
1925
+
1926
+ [[package]]
1927
+ name = "sonic-number"
1928
+ version = "0.1.0"
1929
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1930
+ checksum = "a8a74044c092f4f43ca7a6cfd62854cf9fb5ac8502b131347c990bf22bef1dfe"
1931
+ dependencies = [
1932
+ "cfg-if",
1933
+ ]
1934
+
1935
+ [[package]]
1936
+ name = "sonic-rs"
1937
+ version = "0.3.17"
1938
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1939
+ checksum = "0275f9f2f07d47556fe60c2759da8bc4be6083b047b491b2d476aa0bfa558eb1"
1940
+ dependencies = [
1941
+ "bumpalo",
1942
+ "bytes",
1943
+ "cfg-if",
1944
+ "faststr",
1945
+ "itoa",
1946
+ "ref-cast",
1947
+ "ryu",
1948
+ "serde",
1949
+ "simdutf8",
1950
+ "sonic-number",
1951
+ "sonic-simd",
1952
+ "thiserror 2.0.11",
1953
+ ]
1954
+
1955
+ [[package]]
1956
+ name = "sonic-simd"
1957
+ version = "0.1.0"
1958
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1959
+ checksum = "940a24e82c9a97483ef66cef06b92160a8fa5cd74042c57c10b24d99d169d2fc"
1960
+ dependencies = [
1961
+ "cfg-if",
1962
+ ]
1963
+
1964
+ [[package]]
1965
+ name = "spin"
1966
+ version = "0.9.8"
1967
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1968
+ checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
1969
+
1970
+ [[package]]
1971
+ name = "stable_deref_trait"
1972
+ version = "1.2.0"
1973
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1974
+ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
1975
+
1976
+ [[package]]
1977
+ name = "subtle"
1978
+ version = "2.6.1"
1979
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1980
+ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
1981
+
1982
+ [[package]]
1983
+ name = "syn"
1984
+ version = "1.0.109"
1985
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1986
+ checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
1987
+ dependencies = [
1988
+ "proc-macro2",
1989
+ "quote",
1990
+ "unicode-ident",
1991
+ ]
1992
+
1993
+ [[package]]
1994
+ name = "syn"
1995
+ version = "2.0.96"
1996
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1997
+ checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80"
1998
+ dependencies = [
1999
+ "proc-macro2",
2000
+ "quote",
2001
+ "unicode-ident",
2002
+ ]
2003
+
2004
+ [[package]]
2005
+ name = "sync_wrapper"
2006
+ version = "1.0.2"
2007
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2008
+ checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
2009
+ dependencies = [
2010
+ "futures-core",
2011
+ ]
2012
+
2013
+ [[package]]
2014
+ name = "synstructure"
2015
+ version = "0.13.1"
2016
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2017
+ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
2018
+ dependencies = [
2019
+ "proc-macro2",
2020
+ "quote",
2021
+ "syn 2.0.96",
2022
+ ]
2023
+
2024
+ [[package]]
2025
+ name = "sysinfo"
2026
+ version = "0.33.1"
2027
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2028
+ checksum = "4fc858248ea01b66f19d8e8a6d55f41deaf91e9d495246fd01368d99935c6c01"
2029
+ dependencies = [
2030
+ "core-foundation-sys",
2031
+ "libc",
2032
+ "memchr",
2033
+ "ntapi",
2034
+ "windows",
2035
+ ]
2036
+
2037
+ [[package]]
2038
+ name = "system-configuration"
2039
+ version = "0.6.1"
2040
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2041
+ checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b"
2042
+ dependencies = [
2043
+ "bitflags 2.8.0",
2044
+ "core-foundation",
2045
+ "system-configuration-sys",
2046
+ ]
2047
+
2048
+ [[package]]
2049
+ name = "system-configuration-sys"
2050
+ version = "0.6.0"
2051
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2052
+ checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4"
2053
+ dependencies = [
2054
+ "core-foundation-sys",
2055
+ "libc",
2056
+ ]
2057
+
2058
+ [[package]]
2059
+ name = "tap"
2060
+ version = "1.0.1"
2061
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2062
+ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
2063
+
2064
+ [[package]]
2065
+ name = "tempfile"
2066
+ version = "3.15.0"
2067
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2068
+ checksum = "9a8a559c81686f576e8cd0290cd2a24a2a9ad80c98b3478856500fcbd7acd704"
2069
+ dependencies = [
2070
+ "cfg-if",
2071
+ "fastrand",
2072
+ "getrandom",
2073
+ "once_cell",
2074
+ "rustix",
2075
+ "windows-sys 0.59.0",
2076
+ ]
2077
+
2078
+ [[package]]
2079
+ name = "thiserror"
2080
+ version = "1.0.69"
2081
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2082
+ checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
2083
+ dependencies = [
2084
+ "thiserror-impl 1.0.69",
2085
+ ]
2086
+
2087
+ [[package]]
2088
+ name = "thiserror"
2089
+ version = "2.0.11"
2090
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2091
+ checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc"
2092
+ dependencies = [
2093
+ "thiserror-impl 2.0.11",
2094
+ ]
2095
+
2096
+ [[package]]
2097
+ name = "thiserror-impl"
2098
+ version = "1.0.69"
2099
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2100
+ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
2101
+ dependencies = [
2102
+ "proc-macro2",
2103
+ "quote",
2104
+ "syn 2.0.96",
2105
+ ]
2106
+
2107
+ [[package]]
2108
+ name = "thiserror-impl"
2109
+ version = "2.0.11"
2110
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2111
+ checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2"
2112
+ dependencies = [
2113
+ "proc-macro2",
2114
+ "quote",
2115
+ "syn 2.0.96",
2116
+ ]
2117
+
2118
+ [[package]]
2119
+ name = "tinystr"
2120
+ version = "0.7.6"
2121
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2122
+ checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f"
2123
+ dependencies = [
2124
+ "displaydoc",
2125
+ "zerovec",
2126
+ ]
2127
+
2128
+ [[package]]
2129
+ name = "tinyvec"
2130
+ version = "1.8.1"
2131
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2132
+ checksum = "022db8904dfa342efe721985167e9fcd16c29b226db4397ed752a761cfce81e8"
2133
+ dependencies = [
2134
+ "tinyvec_macros",
2135
+ ]
2136
+
2137
+ [[package]]
2138
+ name = "tinyvec_macros"
2139
+ version = "0.1.1"
2140
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2141
+ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
2142
+
2143
+ [[package]]
2144
+ name = "tokio"
2145
+ version = "1.43.0"
2146
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2147
+ checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e"
2148
+ dependencies = [
2149
+ "backtrace",
2150
+ "bytes",
2151
+ "libc",
2152
+ "mio",
2153
+ "pin-project-lite",
2154
+ "signal-hook-registry",
2155
+ "socket2",
2156
+ "tokio-macros",
2157
+ "windows-sys 0.52.0",
2158
+ ]
2159
+
2160
+ [[package]]
2161
+ name = "tokio-macros"
2162
+ version = "2.5.0"
2163
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2164
+ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
2165
+ dependencies = [
2166
+ "proc-macro2",
2167
+ "quote",
2168
+ "syn 2.0.96",
2169
+ ]
2170
+
2171
+ [[package]]
2172
+ name = "tokio-native-tls"
2173
+ version = "0.3.1"
2174
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2175
+ checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
2176
+ dependencies = [
2177
+ "native-tls",
2178
+ "tokio",
2179
+ ]
2180
+
2181
+ [[package]]
2182
+ name = "tokio-rustls"
2183
+ version = "0.26.1"
2184
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2185
+ checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37"
2186
+ dependencies = [
2187
+ "rustls",
2188
+ "tokio",
2189
+ ]
2190
+
2191
+ [[package]]
2192
+ name = "tokio-socks"
2193
+ version = "0.5.2"
2194
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2195
+ checksum = "0d4770b8024672c1101b3f6733eab95b18007dbe0847a8afe341fcf79e06043f"
2196
+ dependencies = [
2197
+ "either",
2198
+ "futures-util",
2199
+ "thiserror 1.0.69",
2200
+ "tokio",
2201
+ ]
2202
+
2203
+ [[package]]
2204
+ name = "tokio-stream"
2205
+ version = "0.1.17"
2206
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2207
+ checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047"
2208
+ dependencies = [
2209
+ "futures-core",
2210
+ "pin-project-lite",
2211
+ "tokio",
2212
+ ]
2213
+
2214
+ [[package]]
2215
+ name = "tokio-util"
2216
+ version = "0.7.13"
2217
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2218
+ checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078"
2219
+ dependencies = [
2220
+ "bytes",
2221
+ "futures-core",
2222
+ "futures-sink",
2223
+ "pin-project-lite",
2224
+ "tokio",
2225
+ ]
2226
+
2227
+ [[package]]
2228
+ name = "tower"
2229
+ version = "0.5.2"
2230
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2231
+ checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9"
2232
+ dependencies = [
2233
+ "futures-core",
2234
+ "futures-util",
2235
+ "pin-project-lite",
2236
+ "sync_wrapper",
2237
+ "tokio",
2238
+ "tower-layer",
2239
+ "tower-service",
2240
+ "tracing",
2241
+ ]
2242
+
2243
+ [[package]]
2244
+ name = "tower-http"
2245
+ version = "0.6.2"
2246
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2247
+ checksum = "403fa3b783d4b626a8ad51d766ab03cb6d2dbfc46b1c5d4448395e6628dc9697"
2248
+ dependencies = [
2249
+ "bitflags 2.8.0",
2250
+ "bytes",
2251
+ "http",
2252
+ "http-body",
2253
+ "http-body-util",
2254
+ "pin-project-lite",
2255
+ "tower-layer",
2256
+ "tower-service",
2257
+ ]
2258
+
2259
+ [[package]]
2260
+ name = "tower-layer"
2261
+ version = "0.3.3"
2262
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2263
+ checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
2264
+
2265
+ [[package]]
2266
+ name = "tower-service"
2267
+ version = "0.3.3"
2268
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2269
+ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
2270
+
2271
+ [[package]]
2272
+ name = "tracing"
2273
+ version = "0.1.41"
2274
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2275
+ checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
2276
+ dependencies = [
2277
+ "log",
2278
+ "pin-project-lite",
2279
+ "tracing-core",
2280
+ ]
2281
+
2282
+ [[package]]
2283
+ name = "tracing-core"
2284
+ version = "0.1.33"
2285
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2286
+ checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c"
2287
+ dependencies = [
2288
+ "once_cell",
2289
+ ]
2290
+
2291
+ [[package]]
2292
+ name = "try-lock"
2293
+ version = "0.2.5"
2294
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2295
+ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
2296
+
2297
+ [[package]]
2298
+ name = "typenum"
2299
+ version = "1.17.0"
2300
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2301
+ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
2302
+
2303
+ [[package]]
2304
+ name = "unicode-ident"
2305
+ version = "1.0.14"
2306
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2307
+ checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
2308
+
2309
+ [[package]]
2310
+ name = "untrusted"
2311
+ version = "0.9.0"
2312
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2313
+ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
2314
+
2315
+ [[package]]
2316
+ name = "url"
2317
+ version = "2.5.4"
2318
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2319
+ checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60"
2320
+ dependencies = [
2321
+ "form_urlencoded",
2322
+ "idna",
2323
+ "percent-encoding",
2324
+ ]
2325
+
2326
+ [[package]]
2327
+ name = "utf16_iter"
2328
+ version = "1.0.5"
2329
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2330
+ checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246"
2331
+
2332
+ [[package]]
2333
+ name = "utf8_iter"
2334
+ version = "1.0.4"
2335
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2336
+ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
2337
+
2338
+ [[package]]
2339
+ name = "uuid"
2340
+ version = "1.12.1"
2341
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2342
+ checksum = "b3758f5e68192bb96cc8f9b7e2c2cfdabb435499a28499a42f8f984092adad4b"
2343
+ dependencies = [
2344
+ "getrandom",
2345
+ ]
2346
+
2347
+ [[package]]
2348
+ name = "vcpkg"
2349
+ version = "0.2.15"
2350
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2351
+ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
2352
+
2353
+ [[package]]
2354
+ name = "version_check"
2355
+ version = "0.9.5"
2356
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2357
+ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
2358
+
2359
+ [[package]]
2360
+ name = "want"
2361
+ version = "0.3.1"
2362
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2363
+ checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
2364
+ dependencies = [
2365
+ "try-lock",
2366
+ ]
2367
+
2368
+ [[package]]
2369
+ name = "wasi"
2370
+ version = "0.11.0+wasi-snapshot-preview1"
2371
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2372
+ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
2373
+
2374
+ [[package]]
2375
+ name = "wasm-bindgen"
2376
+ version = "0.2.100"
2377
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2378
+ checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
2379
+ dependencies = [
2380
+ "cfg-if",
2381
+ "once_cell",
2382
+ "rustversion",
2383
+ "wasm-bindgen-macro",
2384
+ ]
2385
+
2386
+ [[package]]
2387
+ name = "wasm-bindgen-backend"
2388
+ version = "0.2.100"
2389
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2390
+ checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
2391
+ dependencies = [
2392
+ "bumpalo",
2393
+ "log",
2394
+ "proc-macro2",
2395
+ "quote",
2396
+ "syn 2.0.96",
2397
+ "wasm-bindgen-shared",
2398
+ ]
2399
+
2400
+ [[package]]
2401
+ name = "wasm-bindgen-futures"
2402
+ version = "0.4.50"
2403
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2404
+ checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61"
2405
+ dependencies = [
2406
+ "cfg-if",
2407
+ "js-sys",
2408
+ "once_cell",
2409
+ "wasm-bindgen",
2410
+ "web-sys",
2411
+ ]
2412
+
2413
+ [[package]]
2414
+ name = "wasm-bindgen-macro"
2415
+ version = "0.2.100"
2416
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2417
+ checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
2418
+ dependencies = [
2419
+ "quote",
2420
+ "wasm-bindgen-macro-support",
2421
+ ]
2422
+
2423
+ [[package]]
2424
+ name = "wasm-bindgen-macro-support"
2425
+ version = "0.2.100"
2426
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2427
+ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
2428
+ dependencies = [
2429
+ "proc-macro2",
2430
+ "quote",
2431
+ "syn 2.0.96",
2432
+ "wasm-bindgen-backend",
2433
+ "wasm-bindgen-shared",
2434
+ ]
2435
+
2436
+ [[package]]
2437
+ name = "wasm-bindgen-shared"
2438
+ version = "0.2.100"
2439
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2440
+ checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
2441
+ dependencies = [
2442
+ "unicode-ident",
2443
+ ]
2444
+
2445
+ [[package]]
2446
+ name = "wasm-streams"
2447
+ version = "0.4.2"
2448
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2449
+ checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65"
2450
+ dependencies = [
2451
+ "futures-util",
2452
+ "js-sys",
2453
+ "wasm-bindgen",
2454
+ "wasm-bindgen-futures",
2455
+ "web-sys",
2456
+ ]
2457
+
2458
+ [[package]]
2459
+ name = "web-sys"
2460
+ version = "0.3.77"
2461
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2462
+ checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2"
2463
+ dependencies = [
2464
+ "js-sys",
2465
+ "wasm-bindgen",
2466
+ ]
2467
+
2468
+ [[package]]
2469
+ name = "weezl"
2470
+ version = "0.1.8"
2471
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2472
+ checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082"
2473
+
2474
+ [[package]]
2475
+ name = "winapi"
2476
+ version = "0.3.9"
2477
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2478
+ checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
2479
+ dependencies = [
2480
+ "winapi-i686-pc-windows-gnu",
2481
+ "winapi-x86_64-pc-windows-gnu",
2482
+ ]
2483
+
2484
+ [[package]]
2485
+ name = "winapi-i686-pc-windows-gnu"
2486
+ version = "0.4.0"
2487
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2488
+ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
2489
+
2490
+ [[package]]
2491
+ name = "winapi-x86_64-pc-windows-gnu"
2492
+ version = "0.4.0"
2493
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2494
+ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
2495
+
2496
+ [[package]]
2497
+ name = "windows"
2498
+ version = "0.57.0"
2499
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2500
+ checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143"
2501
+ dependencies = [
2502
+ "windows-core 0.57.0",
2503
+ "windows-targets",
2504
+ ]
2505
+
2506
+ [[package]]
2507
+ name = "windows-core"
2508
+ version = "0.52.0"
2509
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2510
+ checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
2511
+ dependencies = [
2512
+ "windows-targets",
2513
+ ]
2514
+
2515
+ [[package]]
2516
+ name = "windows-core"
2517
+ version = "0.57.0"
2518
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2519
+ checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d"
2520
+ dependencies = [
2521
+ "windows-implement",
2522
+ "windows-interface",
2523
+ "windows-result 0.1.2",
2524
+ "windows-targets",
2525
+ ]
2526
+
2527
+ [[package]]
2528
+ name = "windows-implement"
2529
+ version = "0.57.0"
2530
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2531
+ checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7"
2532
+ dependencies = [
2533
+ "proc-macro2",
2534
+ "quote",
2535
+ "syn 2.0.96",
2536
+ ]
2537
+
2538
+ [[package]]
2539
+ name = "windows-interface"
2540
+ version = "0.57.0"
2541
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2542
+ checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7"
2543
+ dependencies = [
2544
+ "proc-macro2",
2545
+ "quote",
2546
+ "syn 2.0.96",
2547
+ ]
2548
+
2549
+ [[package]]
2550
+ name = "windows-registry"
2551
+ version = "0.2.0"
2552
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2553
+ checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0"
2554
+ dependencies = [
2555
+ "windows-result 0.2.0",
2556
+ "windows-strings",
2557
+ "windows-targets",
2558
+ ]
2559
+
2560
+ [[package]]
2561
+ name = "windows-result"
2562
+ version = "0.1.2"
2563
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2564
+ checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8"
2565
+ dependencies = [
2566
+ "windows-targets",
2567
+ ]
2568
+
2569
+ [[package]]
2570
+ name = "windows-result"
2571
+ version = "0.2.0"
2572
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2573
+ checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e"
2574
+ dependencies = [
2575
+ "windows-targets",
2576
+ ]
2577
+
2578
+ [[package]]
2579
+ name = "windows-strings"
2580
+ version = "0.1.0"
2581
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2582
+ checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10"
2583
+ dependencies = [
2584
+ "windows-result 0.2.0",
2585
+ "windows-targets",
2586
+ ]
2587
+
2588
+ [[package]]
2589
+ name = "windows-sys"
2590
+ version = "0.52.0"
2591
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2592
+ checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
2593
+ dependencies = [
2594
+ "windows-targets",
2595
+ ]
2596
+
2597
+ [[package]]
2598
+ name = "windows-sys"
2599
+ version = "0.59.0"
2600
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2601
+ checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
2602
+ dependencies = [
2603
+ "windows-targets",
2604
+ ]
2605
+
2606
+ [[package]]
2607
+ name = "windows-targets"
2608
+ version = "0.52.6"
2609
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2610
+ checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
2611
+ dependencies = [
2612
+ "windows_aarch64_gnullvm",
2613
+ "windows_aarch64_msvc",
2614
+ "windows_i686_gnu",
2615
+ "windows_i686_gnullvm",
2616
+ "windows_i686_msvc",
2617
+ "windows_x86_64_gnu",
2618
+ "windows_x86_64_gnullvm",
2619
+ "windows_x86_64_msvc",
2620
+ ]
2621
+
2622
+ [[package]]
2623
+ name = "windows_aarch64_gnullvm"
2624
+ version = "0.52.6"
2625
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2626
+ checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
2627
+
2628
+ [[package]]
2629
+ name = "windows_aarch64_msvc"
2630
+ version = "0.52.6"
2631
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2632
+ checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
2633
+
2634
+ [[package]]
2635
+ name = "windows_i686_gnu"
2636
+ version = "0.52.6"
2637
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2638
+ checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
2639
+
2640
+ [[package]]
2641
+ name = "windows_i686_gnullvm"
2642
+ version = "0.52.6"
2643
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2644
+ checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
2645
+
2646
+ [[package]]
2647
+ name = "windows_i686_msvc"
2648
+ version = "0.52.6"
2649
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2650
+ checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
2651
+
2652
+ [[package]]
2653
+ name = "windows_x86_64_gnu"
2654
+ version = "0.52.6"
2655
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2656
+ checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
2657
+
2658
+ [[package]]
2659
+ name = "windows_x86_64_gnullvm"
2660
+ version = "0.52.6"
2661
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2662
+ checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
2663
+
2664
+ [[package]]
2665
+ name = "windows_x86_64_msvc"
2666
+ version = "0.52.6"
2667
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2668
+ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
2669
+
2670
+ [[package]]
2671
+ name = "write16"
2672
+ version = "1.0.0"
2673
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2674
+ checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936"
2675
+
2676
+ [[package]]
2677
+ name = "writeable"
2678
+ version = "0.5.5"
2679
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2680
+ checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
2681
+
2682
+ [[package]]
2683
+ name = "wyz"
2684
+ version = "0.5.1"
2685
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2686
+ checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed"
2687
+ dependencies = [
2688
+ "tap",
2689
+ ]
2690
+
2691
+ [[package]]
2692
+ name = "yoke"
2693
+ version = "0.7.5"
2694
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2695
+ checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40"
2696
+ dependencies = [
2697
+ "serde",
2698
+ "stable_deref_trait",
2699
+ "yoke-derive",
2700
+ "zerofrom",
2701
+ ]
2702
+
2703
+ [[package]]
2704
+ name = "yoke-derive"
2705
+ version = "0.7.5"
2706
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2707
+ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154"
2708
+ dependencies = [
2709
+ "proc-macro2",
2710
+ "quote",
2711
+ "syn 2.0.96",
2712
+ "synstructure",
2713
+ ]
2714
+
2715
+ [[package]]
2716
+ name = "zerocopy"
2717
+ version = "0.7.35"
2718
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2719
+ checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
2720
+ dependencies = [
2721
+ "byteorder",
2722
+ "zerocopy-derive",
2723
+ ]
2724
+
2725
+ [[package]]
2726
+ name = "zerocopy-derive"
2727
+ version = "0.7.35"
2728
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2729
+ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
2730
+ dependencies = [
2731
+ "proc-macro2",
2732
+ "quote",
2733
+ "syn 2.0.96",
2734
+ ]
2735
+
2736
+ [[package]]
2737
+ name = "zerofrom"
2738
+ version = "0.1.5"
2739
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2740
+ checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e"
2741
+ dependencies = [
2742
+ "zerofrom-derive",
2743
+ ]
2744
+
2745
+ [[package]]
2746
+ name = "zerofrom-derive"
2747
+ version = "0.1.5"
2748
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2749
+ checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808"
2750
+ dependencies = [
2751
+ "proc-macro2",
2752
+ "quote",
2753
+ "syn 2.0.96",
2754
+ "synstructure",
2755
+ ]
2756
+
2757
+ [[package]]
2758
+ name = "zeroize"
2759
+ version = "1.8.1"
2760
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2761
+ checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
2762
+
2763
+ [[package]]
2764
+ name = "zerovec"
2765
+ version = "0.10.4"
2766
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2767
+ checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079"
2768
+ dependencies = [
2769
+ "yoke",
2770
+ "zerofrom",
2771
+ "zerovec-derive",
2772
+ ]
2773
+
2774
+ [[package]]
2775
+ name = "zerovec-derive"
2776
+ version = "0.10.3"
2777
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2778
+ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
2779
+ dependencies = [
2780
+ "proc-macro2",
2781
+ "quote",
2782
+ "syn 2.0.96",
2783
+ ]
2784
+
2785
+ [[package]]
2786
+ name = "zune-core"
2787
+ version = "0.4.12"
2788
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2789
+ checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a"
2790
+
2791
+ [[package]]
2792
+ name = "zune-jpeg"
2793
+ version = "0.4.14"
2794
+ source = "registry+https://github.com/rust-lang/crates.io-index"
2795
+ checksum = "99a5bab8d7dedf81405c4bb1f2b83ea057643d9cb28778cea9eecddeedd2e028"
2796
+ dependencies = [
2797
+ "zune-core",
2798
+ ]
Cargo.toml ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [package]
2
+ name = "cursor-api"
3
+ version = "0.1.3-rc.4.3"
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.8.1", 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", "rkyv-64"] }
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
+ memmap2 = "0.9.5"
27
+ # openssl = { version = "0.10.68", features = ["vendored"] }
28
+ parking_lot = "0.12.3"
29
+ paste = "1.0.15"
30
+ prost = "0.13.4"
31
+ rand = { version = "0.8.5", default-features = false, features = ["std", "std_rng"] }
32
+ regex = { version = "1.11.1", default-features = false, features = ["std", "perf"] }
33
+ reqwest = { version = "0.12.12", default-features = false, features = ["gzip", "brotli", "json", "stream", "socks", "__tls", "charset", "default-tls", "h2", "http2", "macos-system-configuration"] }
34
+ rkyv = { version = "0.7.45", default-features = false, features = ["alloc", "std", "bytecheck", "size_64", "validation", "std"] }
35
+ serde = { version = "1.0.217", default-features = false, features = ["std", "derive"] }
36
+ serde_json = { package = "sonic-rs", version = "0.3.17" }
37
+ # serde_json = "1.0.137"
38
+ sha2 = { version = "0.10.8", default-features = false }
39
+ sysinfo = { version = "0.33.1", default-features = false, features = ["system"] }
40
+ tokio = { version = "1.43.0", features = ["rt-multi-thread", "macros", "net", "sync", "time", "fs", "signal"] }
41
+ tokio-stream = { version = "0.1.17", features = ["time"] }
42
+ tower-http = { version = "0.6.2", features = ["cors", "limit"] }
43
+ url = { version = "2.5.4", default-features = false }
44
+ uuid = { version = "1.12.1", features = ["v4"] }
45
+
46
+ [profile.release]
47
+ lto = true
48
+ codegen-units = 1
49
+ panic = 'abort'
50
+ strip = true
51
+ opt-level = 3
52
+
53
+ [features]
54
+ default = []
55
+ use-minified = []
Cross.toml ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ [target.x86_64-unknown-linux-gnu]
2
+ dockerfile = "Dockerfile.cross"
3
+
4
+ [target.aarch64-unknown-linux-gnu]
5
+ dockerfile = "Dockerfile.cross.arm64"
Cursor API.md ADDED
@@ -0,0 +1,179 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Cursor API
2
+
3
+ ## 项目说明
4
+
5
+ ### 版本声明
6
+ - 当前版本已进入稳定阶段
7
+ - 以下问题与程序无关,请勿反馈:
8
+ - 响应缺字漏字
9
+ - 首字延迟现象
10
+ - 响应出现乱码
11
+ - 性能优势:
12
+ - 达到原生客户端响应速度
13
+ - 部分场景下表现更优
14
+ - 开源协议要求:
15
+ - Fork 项目禁止以原作者名义进行宣传推广
16
+ - 禁止发布任何形式的官方声明
17
+
18
+ ![Cursor API 架构示意图](https://via.placeholder.com/800x400.png?text=Cursor+API+Architecture)
19
+
20
+ ## 快速入门
21
+
22
+ ### 密钥获取
23
+ 1. 访问 [Cursor 官网](https://www.cursor.com) 完成注册登录
24
+ 2. 开启浏览器开发者工具 (F12)
25
+ 3. 在 Application → Cookies 中定位 `WorkosCursorSessionToken`
26
+ 4. 复制第三个字段值(注意:`%3A%3A` 为 `::` 的 URL 编码形式)
27
+
28
+ ## 配置指南
29
+
30
+ ### 环境变量
31
+ | 变量名 | 类型 | 默认值 | 说明 |
32
+ |--------|------|--------|-----|
33
+ | PORT | int | 3000 | 服务端口号 |
34
+ | AUTH_TOKEN | string | 无 | 认证令牌(必需) |
35
+ | ROUTE_PREFIX | string | 无 | 路由前缀 |
36
+ | TOKEN_LIST_FILE | string | .tokens | Token 存储文件 |
37
+
38
+ 完整配置参见 [env-example](/env-example)
39
+
40
+ ### Token 文件规范
41
+ `.tokens` 文件格式:
42
+ ```plaintext
43
+ # 注释行将在下次读取时自动删除
44
+ token1,checksum1
45
+ token2,checksum2
46
+ ```
47
+
48
+ 文件管理原则:
49
+ - 系统自动维护文件内容
50
+ - 仅以下情况需要手动编辑:
51
+ - 删除特定 token
52
+ - 绑定已有 checksum 到指定 token
53
+
54
+ ## 模型支持列表
55
+ ```json
56
+ [
57
+ "claude-3.5-sonnet",
58
+ "gpt-4",
59
+ "gpt-4o",
60
+ "cursor-fast",
61
+ "gpt-4o-mini",
62
+ "deepseek-v3"
63
+ ]
64
+ ```
65
+ *注:模型列表为固定配置,暂不支持自定义扩展*
66
+
67
+ ## API 文档
68
+
69
+ ### 基础对话接口
70
+ **Endpoint**
71
+ `POST /v1/chat/completions`
72
+
73
+ **认证方式**
74
+ `Bearer Token` 三级认证机制:
75
+ 1. 环境变量 `AUTH_TOKEN`
76
+ 2. `.token` 文件轮询
77
+ 3. 直接 token,checksum 认证(v0.1.3-rc.3+)
78
+
79
+ **请求示例**
80
+ ```json
81
+ {
82
+ "model": "gpt-4",
83
+ "messages": [
84
+ {
85
+ "role": "user",
86
+ "content": "解释量子计算的基本原理"
87
+ }
88
+ ],
89
+ "stream": false
90
+ }
91
+ ```
92
+
93
+ **响应示例(非流式)**
94
+ ```json
95
+ {
96
+ "id": "chatcmpl-9Xy...",
97
+ "object": "chat.completion",
98
+ "created": 1628063500,
99
+ "model": "gpt-4",
100
+ "choices": [{
101
+ "index": 0,
102
+ "message": {
103
+ "role": "assistant",
104
+ "content": "量子计算基于量子比特..."
105
+ },
106
+ "finish_reason": "stop"
107
+ }]
108
+ }
109
+ ```
110
+
111
+ ### Token 管理接口
112
+ | 端点 | 方法 | 功能 |
113
+ |------|------|-----|
114
+ | `/tokens` | GET | Token 信息管理界面 |
115
+ | `/tokens/update` | POST | 批量更新 Token 列表 |
116
+ | `/tokens/add` | POST | 增量添加 Token |
117
+ | `/tokens/delete` | POST | 删除指定 Token |
118
+
119
+ ```mermaid
120
+ sequenceDiagram
121
+ participant Client
122
+ participant API
123
+ Client->>API: POST /tokens/add
124
+ API->>API: 验证Token有效性
125
+ API->>File: 写入.tokens
126
+ API-->>Client: 返回更新结果
127
+ ```
128
+
129
+ ## 高级功能
130
+
131
+ ### 动态密钥生成
132
+ **Endpoint**
133
+ `POST /build-key`
134
+
135
+ **优势对比**
136
+ | 特性 | 传统模式 | 动态密钥 |
137
+ |------|---------|---------|
138
+ | 密钥长度 | 较长 | 优化缩短 |
139
+ | 配置扩展 | 无 | 支持自定义 |
140
+ | 安全等级 | 基础 | 增强编码 |
141
+ | 验证效率 | 预校验耗时 | 即时验证 |
142
+
143
+ ## 系统监控
144
+
145
+ ### 健康检查
146
+ **Endpoint**
147
+ `GET /health`
148
+
149
+ **响应示例**
150
+ ```json
151
+ {
152
+ "status": "success",
153
+ "version": "1.2.0",
154
+ "uptime": 86400,
155
+ "models": ["gpt-4", "claude-3.5"],
156
+ "endpoints": ["/v1/chat", "/tokens"]
157
+ }
158
+ ```
159
+
160
+ ## 生态工具
161
+
162
+ ### 开发辅助工具
163
+ - [Token 获取工具](https://github.com/wisdgod/cursor-api/tree/main/tools/get-token)
164
+ 支持 Windows/Linux/macOS 系统
165
+ - [遥测数据重置工具](https://github.com/wisdgod/cursor-api/tree/main/tools/reset-telemetry)
166
+ 清除用户使用数据记录
167
+
168
+ ## 致谢声明
169
+ 本项目的发展离不开以下开源项目的启发:
170
+ - [zhx47/cursor-api](https://github.com/zhx47/cursor-api) - 基础架构参考
171
+ - [cursorToApi](https://github.com/luolazyandlazy/cursorToApi) - 认证机制优化方案
172
+
173
+ ---
174
+
175
+ > **项目维护说明**
176
+ > 我们欢迎社区贡献,但请注意:
177
+ > 1. 功能请求需附带使用场景说明
178
+ > 2. Bug 报告请提供复现步骤和环境信息
179
+ > 3. 重要变更需通过 CI/CD 测试流程
Dockerfile ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ARG TARGETARCH
2
+ FROM --platform=linux/${TARGETARCH} rust:1.84.0-slim-bookworm as builder
3
+
4
+ ARG TARGETARCH
5
+
6
+ WORKDIR /app
7
+ RUN apt-get update && \
8
+ apt-get install -y --no-install-recommends \
9
+ build-essential protobuf-compiler pkg-config libssl-dev nodejs npm openssl \
10
+ && rm -rf /var/lib/apt/lists/*
11
+
12
+ COPY . .
13
+ RUN case "$TARGETARCH" in \
14
+ amd64) TARGET_CPU="x86-64-v3" ;; \
15
+ arm64) TARGET_CPU="neoverse-n1" ;; \
16
+ *) echo "Unsupported architecture: $TARGETARCH" && exit 1 ;; \
17
+ esac && \
18
+ RUSTFLAGS="-C link-arg=-s -C target-cpu=$TARGET_CPU" cargo build --release && \
19
+ cp target/release/cursor-api /app/cursor-api
20
+
21
+ # 运行阶段
22
+ FROM --platform=linux/${TARGETARCH} debian:bookworm-slim
23
+
24
+ WORKDIR /app
25
+ ENV TZ=Asia/Shanghai
26
+
27
+ RUN apt-get update && \
28
+ apt-get install -y --no-install-recommends \
29
+ ca-certificates tzdata openssl \
30
+ && rm -rf /var/lib/apt/lists/*
31
+
32
+ COPY --from=builder /app/cursor-api .
33
+
34
+ ENV PORT=3000
35
+ EXPOSE ${PORT}
36
+ CMD ["./cursor-api"]
Dockerfile.cross ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Dockerfile.cross
2
+
3
+ FROM --platform=linux/amd64 rust:1.84.0-slim-bookworm
4
+
5
+ WORKDIR /app
6
+
7
+ # 安装必要的软件包
8
+ RUN apt-get update && \
9
+ apt-get install -y --no-install-recommends \
10
+ build-essential \
11
+ pkg-config \
12
+ libssl-dev \
13
+ protobuf-compiler \
14
+ openssl \
15
+ && rm -rf /var/lib/apt/lists/*
16
+
17
+ # 设置环境变量 (如果需要)
18
+ # ENV RUSTFLAGS="-C link-arg=-s"
19
+
20
+ # 设置 PROTOC 环境变量 (因为你的 build.rs 需要)
21
+ ENV PROTOC=/usr/bin/protoc
22
+
23
+ # 安装特定版本的 protoc (如果你需要特定版本,例如 29.3;否则可以删除这部分)
24
+ # ENV PROTOC_VERSION=29.3
25
+ # ENV PROTOC_ZIP=protoc-${PROTOC_VERSION}-linux-x86_64.zip
26
+ # RUN wget https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOC_VERSION}/${PROTOC_ZIP} -O /tmp/${PROTOC_ZIP} && \
27
+ # unzip /tmp/${PROTOC_ZIP} -d /usr && \
28
+ # rm /tmp/${PROTOC_ZIP}
29
+
30
+ # 验证安装
31
+ RUN protoc --version
Dockerfile.cross.arm64 ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Dockerfile.cross
2
+
3
+ FROM --platform=linux/arm64 rust:1.84.0-slim-bookworm
4
+
5
+ WORKDIR /app
6
+
7
+ # 安装必要的软件包
8
+ RUN apt-get update && \
9
+ apt-get install -y --no-install-recommends \
10
+ build-essential \
11
+ pkg-config \
12
+ libssl-dev \
13
+ protobuf-compiler \
14
+ openssl \
15
+ && rm -rf /var/lib/apt/lists/*
16
+
17
+ # 设置环境变量 (如果需要)
18
+ # ENV RUSTFLAGS="-C link-arg=-s"
19
+
20
+ # 设置 PROTOC 环境变量 (因为你的 build.rs 需要)
21
+ ENV PROTOC=/usr/bin/protoc
22
+
23
+ # 安装特定版本的 protoc (如果你需要特定版本,例如 29.3;否则可以删除这部分)
24
+ # ENV PROTOC_VERSION=29.3
25
+ # ENV PROTOC_ZIP=protoc-${PROTOC_VERSION}-linux-x86_64.zip
26
+ # RUN wget https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOC_VERSION}/${PROTOC_ZIP} -O /tmp/${PROTOC_ZIP} && \
27
+ # unzip /tmp/${PROTOC_ZIP} -d /usr && \
28
+ # rm /tmp/${PROTOC_ZIP}
29
+
30
+ # 验证安装
31
+ RUN protoc --version
README.md CHANGED
@@ -5,6 +5,7 @@ colorFrom: purple
5
  colorTo: gray
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: gray
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
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,157 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 MarkdownIt = require('markdown-it');
7
+ const fs = require('fs');
8
+ const path = require('path');
9
+
10
+ // 配置选项
11
+ const options = {
12
+ collapseWhitespace: true,
13
+ removeComments: true,
14
+ removeEmptyAttributes: true,
15
+ removeOptionalTags: true,
16
+ removeRedundantAttributes: true,
17
+ removeScriptTypeAttributes: true,
18
+ removeStyleLinkTypeAttributes: true,
19
+ minifyCSS: true,
20
+ minifyJS: true,
21
+ processScripts: ['application/json'],
22
+ };
23
+
24
+ // CSS 压缩选项
25
+ const cssOptions = {
26
+ level: 2
27
+ };
28
+
29
+ // 处理文件
30
+ async function minifyFile(inputPath, outputPath) {
31
+ try {
32
+ let ext = path.extname(inputPath).toLowerCase();
33
+ if (ext === '.md') ext = '.html';
34
+ const filename = path.basename(inputPath);
35
+ let content = fs.readFileSync(inputPath, 'utf8');
36
+ let minified;
37
+
38
+ // 特殊处理 readme.html
39
+ if (filename.toLowerCase() === 'readme.md') {
40
+ const md = new MarkdownIt({
41
+ html: true,
42
+ linkify: true,
43
+ typographer: true
44
+ });
45
+ const readmeMdPath = path.join(__dirname, '..', 'README.md');
46
+ const markdownContent = fs.readFileSync(readmeMdPath, 'utf8');
47
+ // 添加基本的 markdown 样式
48
+ const htmlContent = `
49
+ <!DOCTYPE html>
50
+ <html>
51
+ <head>
52
+ <meta charset="UTF-8">
53
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
54
+ <title>README</title>
55
+ <style>
56
+ body {
57
+ max-width: 800px;
58
+ margin: 0 auto;
59
+ padding: 20px;
60
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
61
+ line-height: 1.6;
62
+ }
63
+ pre {
64
+ background-color: #f6f8fa;
65
+ padding: 16px;
66
+ border-radius: 6px;
67
+ overflow: auto;
68
+ }
69
+ code {
70
+ background-color: #f6f8fa;
71
+ padding: 0.2em 0.4em;
72
+ border-radius: 3px;
73
+ }
74
+ img {
75
+ max-width: 100%;
76
+ }
77
+ table {
78
+ border-collapse: collapse;
79
+ width: 100%;
80
+ }
81
+ table td, table th {
82
+ border: 1px solid #dfe2e5;
83
+ padding: 6px 13px;
84
+ }
85
+ blockquote {
86
+ border-left: 4px solid #dfe2e5;
87
+ margin: 0;
88
+ padding: 0 1em;
89
+ color: #6a737d;
90
+ }
91
+ </style>
92
+ </head>
93
+ <body>
94
+ ${md.render(markdownContent)}
95
+ </body>
96
+ </html>
97
+ `;
98
+ content = htmlContent;
99
+ }
100
+
101
+ switch (ext) {
102
+ case '.html':
103
+ minified = await minifyHtml(content, options);
104
+ break;
105
+ case '.js':
106
+ const result = await minifyJs(content);
107
+ minified = result.code;
108
+ break;
109
+ case '.css':
110
+ minified = new CleanCSS(cssOptions).minify(content).styles;
111
+ break;
112
+ default:
113
+ throw new Error(`Unsupported file type: ${ext}`);
114
+ }
115
+
116
+ fs.writeFileSync(outputPath, minified);
117
+ console.log(`✓ Minified ${path.basename(inputPath)} -> ${path.basename(outputPath)}`);
118
+ } catch (err) {
119
+ console.error(`✗ Error processing ${inputPath}:`, err);
120
+ process.exit(1);
121
+ }
122
+ }
123
+
124
+ // 主函数
125
+ async function main() {
126
+ // 获取命令行参数,跳过前两个参数(node和脚本路径)
127
+ const files = process.argv.slice(2);
128
+
129
+ if (files.length === 0) {
130
+ console.error('No input files specified');
131
+ process.exit(1);
132
+ }
133
+
134
+ const staticDir = path.join(__dirname, '..', 'static');
135
+
136
+ for (const file of files) {
137
+ // 特殊处理 README.md 的输入路径
138
+ let inputPath;
139
+ let outputPath;
140
+
141
+ if (file.toLowerCase() === 'readme.md') {
142
+ inputPath = path.join(__dirname, '..', 'README.md');
143
+ outputPath = path.join(staticDir, 'readme.min.html');
144
+ } else {
145
+ inputPath = path.join(staticDir, file);
146
+ const ext = path.extname(file);
147
+ outputPath = path.join(
148
+ staticDir,
149
+ file.replace(ext, `.min${ext}`)
150
+ );
151
+ }
152
+
153
+ await minifyFile(inputPath, outputPath);
154
+ }
155
+ }
156
+
157
+ main();
scripts/package-lock.json ADDED
@@ -0,0 +1,321 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ "markdown-it": "^14.1.0",
14
+ "terser": "^5.37.0"
15
+ },
16
+ "engines": {
17
+ "node": ">=14.0.0"
18
+ }
19
+ },
20
+ "node_modules/@jridgewell/gen-mapping": {
21
+ "version": "0.3.8",
22
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
23
+ "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==",
24
+ "license": "MIT",
25
+ "dependencies": {
26
+ "@jridgewell/set-array": "^1.2.1",
27
+ "@jridgewell/sourcemap-codec": "^1.4.10",
28
+ "@jridgewell/trace-mapping": "^0.3.24"
29
+ },
30
+ "engines": {
31
+ "node": ">=6.0.0"
32
+ }
33
+ },
34
+ "node_modules/@jridgewell/resolve-uri": {
35
+ "version": "3.1.2",
36
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
37
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
38
+ "license": "MIT",
39
+ "engines": {
40
+ "node": ">=6.0.0"
41
+ }
42
+ },
43
+ "node_modules/@jridgewell/set-array": {
44
+ "version": "1.2.1",
45
+ "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
46
+ "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
47
+ "license": "MIT",
48
+ "engines": {
49
+ "node": ">=6.0.0"
50
+ }
51
+ },
52
+ "node_modules/@jridgewell/source-map": {
53
+ "version": "0.3.6",
54
+ "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz",
55
+ "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==",
56
+ "license": "MIT",
57
+ "dependencies": {
58
+ "@jridgewell/gen-mapping": "^0.3.5",
59
+ "@jridgewell/trace-mapping": "^0.3.25"
60
+ }
61
+ },
62
+ "node_modules/@jridgewell/sourcemap-codec": {
63
+ "version": "1.5.0",
64
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
65
+ "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
66
+ "license": "MIT"
67
+ },
68
+ "node_modules/@jridgewell/trace-mapping": {
69
+ "version": "0.3.25",
70
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
71
+ "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
72
+ "license": "MIT",
73
+ "dependencies": {
74
+ "@jridgewell/resolve-uri": "^3.1.0",
75
+ "@jridgewell/sourcemap-codec": "^1.4.14"
76
+ }
77
+ },
78
+ "node_modules/acorn": {
79
+ "version": "8.14.0",
80
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
81
+ "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
82
+ "license": "MIT",
83
+ "bin": {
84
+ "acorn": "bin/acorn"
85
+ },
86
+ "engines": {
87
+ "node": ">=0.4.0"
88
+ }
89
+ },
90
+ "node_modules/argparse": {
91
+ "version": "2.0.1",
92
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
93
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
94
+ "license": "Python-2.0"
95
+ },
96
+ "node_modules/buffer-from": {
97
+ "version": "1.1.2",
98
+ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
99
+ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
100
+ "license": "MIT"
101
+ },
102
+ "node_modules/camel-case": {
103
+ "version": "4.1.2",
104
+ "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz",
105
+ "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==",
106
+ "license": "MIT",
107
+ "dependencies": {
108
+ "pascal-case": "^3.1.2",
109
+ "tslib": "^2.0.3"
110
+ }
111
+ },
112
+ "node_modules/clean-css": {
113
+ "version": "5.3.3",
114
+ "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz",
115
+ "integrity": "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==",
116
+ "license": "MIT",
117
+ "dependencies": {
118
+ "source-map": "~0.6.0"
119
+ },
120
+ "engines": {
121
+ "node": ">= 10.0"
122
+ }
123
+ },
124
+ "node_modules/commander": {
125
+ "version": "10.0.1",
126
+ "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz",
127
+ "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==",
128
+ "license": "MIT",
129
+ "engines": {
130
+ "node": ">=14"
131
+ }
132
+ },
133
+ "node_modules/dot-case": {
134
+ "version": "3.0.4",
135
+ "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz",
136
+ "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==",
137
+ "license": "MIT",
138
+ "dependencies": {
139
+ "no-case": "^3.0.4",
140
+ "tslib": "^2.0.3"
141
+ }
142
+ },
143
+ "node_modules/entities": {
144
+ "version": "4.5.0",
145
+ "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
146
+ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
147
+ "license": "BSD-2-Clause",
148
+ "engines": {
149
+ "node": ">=0.12"
150
+ },
151
+ "funding": {
152
+ "url": "https://github.com/fb55/entities?sponsor=1"
153
+ }
154
+ },
155
+ "node_modules/html-minifier-terser": {
156
+ "version": "7.2.0",
157
+ "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-7.2.0.tgz",
158
+ "integrity": "sha512-tXgn3QfqPIpGl9o+K5tpcj3/MN4SfLtsx2GWwBC3SSd0tXQGyF3gsSqad8loJgKZGM3ZxbYDd5yhiBIdWpmvLA==",
159
+ "license": "MIT",
160
+ "dependencies": {
161
+ "camel-case": "^4.1.2",
162
+ "clean-css": "~5.3.2",
163
+ "commander": "^10.0.0",
164
+ "entities": "^4.4.0",
165
+ "param-case": "^3.0.4",
166
+ "relateurl": "^0.2.7",
167
+ "terser": "^5.15.1"
168
+ },
169
+ "bin": {
170
+ "html-minifier-terser": "cli.js"
171
+ },
172
+ "engines": {
173
+ "node": "^14.13.1 || >=16.0.0"
174
+ }
175
+ },
176
+ "node_modules/linkify-it": {
177
+ "version": "5.0.0",
178
+ "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz",
179
+ "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==",
180
+ "license": "MIT",
181
+ "dependencies": {
182
+ "uc.micro": "^2.0.0"
183
+ }
184
+ },
185
+ "node_modules/lower-case": {
186
+ "version": "2.0.2",
187
+ "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz",
188
+ "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==",
189
+ "license": "MIT",
190
+ "dependencies": {
191
+ "tslib": "^2.0.3"
192
+ }
193
+ },
194
+ "node_modules/markdown-it": {
195
+ "version": "14.1.0",
196
+ "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz",
197
+ "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==",
198
+ "license": "MIT",
199
+ "dependencies": {
200
+ "argparse": "^2.0.1",
201
+ "entities": "^4.4.0",
202
+ "linkify-it": "^5.0.0",
203
+ "mdurl": "^2.0.0",
204
+ "punycode.js": "^2.3.1",
205
+ "uc.micro": "^2.1.0"
206
+ },
207
+ "bin": {
208
+ "markdown-it": "bin/markdown-it.mjs"
209
+ }
210
+ },
211
+ "node_modules/mdurl": {
212
+ "version": "2.0.0",
213
+ "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz",
214
+ "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==",
215
+ "license": "MIT"
216
+ },
217
+ "node_modules/no-case": {
218
+ "version": "3.0.4",
219
+ "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz",
220
+ "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==",
221
+ "license": "MIT",
222
+ "dependencies": {
223
+ "lower-case": "^2.0.2",
224
+ "tslib": "^2.0.3"
225
+ }
226
+ },
227
+ "node_modules/param-case": {
228
+ "version": "3.0.4",
229
+ "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz",
230
+ "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==",
231
+ "license": "MIT",
232
+ "dependencies": {
233
+ "dot-case": "^3.0.4",
234
+ "tslib": "^2.0.3"
235
+ }
236
+ },
237
+ "node_modules/pascal-case": {
238
+ "version": "3.1.2",
239
+ "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz",
240
+ "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==",
241
+ "license": "MIT",
242
+ "dependencies": {
243
+ "no-case": "^3.0.4",
244
+ "tslib": "^2.0.3"
245
+ }
246
+ },
247
+ "node_modules/punycode.js": {
248
+ "version": "2.3.1",
249
+ "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz",
250
+ "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==",
251
+ "license": "MIT",
252
+ "engines": {
253
+ "node": ">=6"
254
+ }
255
+ },
256
+ "node_modules/relateurl": {
257
+ "version": "0.2.7",
258
+ "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz",
259
+ "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==",
260
+ "license": "MIT",
261
+ "engines": {
262
+ "node": ">= 0.10"
263
+ }
264
+ },
265
+ "node_modules/source-map": {
266
+ "version": "0.6.1",
267
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
268
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
269
+ "license": "BSD-3-Clause",
270
+ "engines": {
271
+ "node": ">=0.10.0"
272
+ }
273
+ },
274
+ "node_modules/source-map-support": {
275
+ "version": "0.5.21",
276
+ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
277
+ "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
278
+ "license": "MIT",
279
+ "dependencies": {
280
+ "buffer-from": "^1.0.0",
281
+ "source-map": "^0.6.0"
282
+ }
283
+ },
284
+ "node_modules/terser": {
285
+ "version": "5.37.0",
286
+ "resolved": "https://registry.npmjs.org/terser/-/terser-5.37.0.tgz",
287
+ "integrity": "sha512-B8wRRkmre4ERucLM/uXx4MOV5cbnOlVAqUst+1+iLKPI0dOgFO28f84ptoQt9HEI537PMzfYa/d+GEPKTRXmYA==",
288
+ "license": "BSD-2-Clause",
289
+ "dependencies": {
290
+ "@jridgewell/source-map": "^0.3.3",
291
+ "acorn": "^8.8.2",
292
+ "commander": "^2.20.0",
293
+ "source-map-support": "~0.5.20"
294
+ },
295
+ "bin": {
296
+ "terser": "bin/terser"
297
+ },
298
+ "engines": {
299
+ "node": ">=10"
300
+ }
301
+ },
302
+ "node_modules/terser/node_modules/commander": {
303
+ "version": "2.20.3",
304
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
305
+ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
306
+ "license": "MIT"
307
+ },
308
+ "node_modules/tslib": {
309
+ "version": "2.8.1",
310
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
311
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
312
+ "license": "0BSD"
313
+ },
314
+ "node_modules/uc.micro": {
315
+ "version": "2.1.0",
316
+ "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",
317
+ "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==",
318
+ "license": "MIT"
319
+ }
320
+ }
321
+ }
scripts/package.json ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ "markdown-it": "^14.1.0",
12
+ "terser": "^5.37.0"
13
+ }
14
+ }
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}"
serve.ts ADDED
@@ -0,0 +1 @@
 
 
1
+ Deno.serve(async(r:Request)=>{const rs=(s:number,m:string)=>new Response(m,{status:s,headers:{"Access-Control-Allow-Origin":"*"}});const h=r.headers.get("x-co");if(!h)return rs(400,"Missing header");const a=["api2.cursor.sh","www.cursor.com"];if(!a.includes(h))return rs(403,"Host denied");const u=new URL(r.url),p=["/aiserver.v1.AiService/StreamChat","/aiserver.v1.AiService/StreamChatWeb","/auth/full_stripe_profile","/api/usage","/api/auth/me"];if(!p.includes(u.pathname))return rs(404,"Path invalid");const hd=new Headers(r.headers);hd.delete("x-co");hd.set("Host",h);try{const f=await fetch(`https://${h}${u.pathname}${u.search}`,{method:r.method,headers:hd,body:r.body});const fh=new Headers(f.headers);fh.set("Access-Control-Allow-Origin","*");return new Response(f.body,{status:f.status,headers:fh})}catch(e){return rs(500,"Server error")}});
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,159 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use super::{constant::AUTHORIZATION_BEARER_PREFIX, lazy::AUTH_TOKEN, model::AppConfig};
2
+ use crate::common::model::{
3
+ config::{ConfigData, ConfigUpdateRequest},
4
+ ApiStatus, ErrorResponse, NormalResponse,
5
+ };
6
+ use axum::{
7
+ http::{header::AUTHORIZATION, HeaderMap, StatusCode},
8
+ Json,
9
+ };
10
+
11
+ // 定义处理更新操作的宏
12
+ macro_rules! handle_updates {
13
+ ($request:expr, $($field:ident => $update_fn:expr),* $(,)?) => {
14
+ $(
15
+ if let Some(value) = $request.$field {
16
+ $update_fn(value);
17
+ }
18
+ )*
19
+ };
20
+ }
21
+
22
+ // 定义处理重置操作的宏
23
+ macro_rules! handle_resets {
24
+ ($request:expr, $($field:ident => $reset_fn:expr),* $(,)?) => {
25
+ $(
26
+ if $request.$field.is_some() {
27
+ $reset_fn();
28
+ }
29
+ )*
30
+ };
31
+ }
32
+
33
+ pub async fn handle_config_update(
34
+ headers: HeaderMap,
35
+ Json(request): Json<ConfigUpdateRequest>,
36
+ ) -> Result<Json<NormalResponse<ConfigData>>, (StatusCode, Json<ErrorResponse>)> {
37
+ let auth_header = headers
38
+ .get(AUTHORIZATION)
39
+ .and_then(|h| h.to_str().ok())
40
+ .and_then(|h| h.strip_prefix(AUTHORIZATION_BEARER_PREFIX))
41
+ .ok_or((
42
+ StatusCode::UNAUTHORIZED,
43
+ Json(ErrorResponse {
44
+ status: ApiStatus::Failed,
45
+ code: Some(401),
46
+ error: Some("未提供认证令牌".to_string()),
47
+ message: None,
48
+ }),
49
+ ))?;
50
+
51
+ if auth_header != AUTH_TOKEN.as_str() {
52
+ return Err((
53
+ StatusCode::UNAUTHORIZED,
54
+ Json(ErrorResponse {
55
+ status: ApiStatus::Failed,
56
+ code: Some(401),
57
+ error: Some("无效的认证令牌".to_string()),
58
+ message: None,
59
+ }),
60
+ ));
61
+ }
62
+
63
+ match request.action.as_str() {
64
+ "get" => Ok(Json(NormalResponse {
65
+ status: ApiStatus::Success,
66
+ data: Some(ConfigData {
67
+ page_content: AppConfig::get_page_content(&request.path),
68
+ vision_ability: AppConfig::get_vision_ability(),
69
+ enable_slow_pool: AppConfig::get_slow_pool(),
70
+ enable_all_claude: AppConfig::get_allow_claude(),
71
+ usage_check_models: AppConfig::get_usage_check(),
72
+ enable_dynamic_key: AppConfig::get_dynamic_key(),
73
+ share_token: AppConfig::get_share_token(),
74
+ proxies: AppConfig::get_proxies(),
75
+ include_web_references: AppConfig::get_web_refs(),
76
+ }),
77
+ message: None,
78
+ })),
79
+
80
+ "update" => {
81
+ // 处理页面内容更新
82
+ if !request.path.is_empty() && request.content.is_some() {
83
+ let content = request.content.unwrap();
84
+ if let Err(e) = AppConfig::update_page_content(&request.path, content) {
85
+ return Err((
86
+ StatusCode::INTERNAL_SERVER_ERROR,
87
+ Json(ErrorResponse {
88
+ status: ApiStatus::Failed,
89
+ code: Some(500),
90
+ error: Some(format!("更新页面内容失败: {}", e)),
91
+ message: None,
92
+ }),
93
+ ));
94
+ }
95
+ }
96
+
97
+ handle_updates!(request,
98
+ vision_ability => AppConfig::update_vision_ability,
99
+ enable_slow_pool => AppConfig::update_slow_pool,
100
+ enable_all_claude => AppConfig::update_allow_claude,
101
+ usage_check_models => AppConfig::update_usage_check,
102
+ enable_dynamic_key => AppConfig::update_dynamic_key,
103
+ share_token => AppConfig::update_share_token,
104
+ proxies => AppConfig::update_proxies,
105
+ include_web_references => AppConfig::update_web_refs,
106
+ );
107
+
108
+ Ok(Json(NormalResponse {
109
+ status: ApiStatus::Success,
110
+ data: None,
111
+ message: Some("配置已更新".to_string()),
112
+ }))
113
+ }
114
+
115
+ "reset" => {
116
+ // 重置页面内容
117
+ if !request.path.is_empty() {
118
+ if let Err(e) = AppConfig::reset_page_content(&request.path) {
119
+ return Err((
120
+ StatusCode::INTERNAL_SERVER_ERROR,
121
+ Json(ErrorResponse {
122
+ status: ApiStatus::Failed,
123
+ code: Some(500),
124
+ error: Some(format!("重置页面内容失败: {}", e)),
125
+ message: None,
126
+ }),
127
+ ));
128
+ }
129
+ }
130
+
131
+ handle_resets!(request,
132
+ vision_ability => AppConfig::reset_vision_ability,
133
+ enable_slow_pool => AppConfig::reset_slow_pool,
134
+ enable_all_claude => AppConfig::reset_allow_claude,
135
+ usage_check_models => AppConfig::reset_usage_check,
136
+ enable_dynamic_key => AppConfig::reset_dynamic_key,
137
+ share_token => AppConfig::reset_share_token,
138
+ proxies => AppConfig::reset_proxies,
139
+ include_web_references => AppConfig::reset_web_refs,
140
+ );
141
+
142
+ Ok(Json(NormalResponse {
143
+ status: ApiStatus::Success,
144
+ data: None,
145
+ message: Some("配置已重置".to_string()),
146
+ }))
147
+ }
148
+
149
+ _ => Err((
150
+ StatusCode::BAD_REQUEST,
151
+ Json(ErrorResponse {
152
+ status: ApiStatus::Failed,
153
+ code: Some(400),
154
+ error: Some("无效的操作类型".to_string()),
155
+ message: None,
156
+ }),
157
+ )),
158
+ }
159
+ }
src/app/constant.rs ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ macro_rules! def_pub_const {
2
+ ($name:ident, $value:expr) => {
3
+ pub const $name: &'static str = $value;
4
+ };
5
+ }
6
+
7
+ pub const COMMA: char = ',';
8
+
9
+ def_pub_const!(PKG_VERSION, env!("CARGO_PKG_VERSION"));
10
+ // def_pub_const!(PKG_NAME, env!("CARGO_PKG_NAME"));
11
+ // def_pub_const!(PKG_DESCRIPTION, env!("CARGO_PKG_DESCRIPTION"));
12
+ // def_pub_const!(PKG_AUTHORS, env!("CARGO_PKG_AUTHORS"));
13
+ // def_pub_const!(PKG_REPOSITORY, env!("CARGO_PKG_REPOSITORY"));
14
+
15
+ def_pub_const!(EMPTY_STRING, "");
16
+
17
+ def_pub_const!(COMMA_STRING, ",");
18
+
19
+ def_pub_const!(ROUTE_ROOT_PATH, "/");
20
+ def_pub_const!(ROUTE_HEALTH_PATH, "/health");
21
+ def_pub_const!(ROUTE_GET_HASH, "/get-hash");
22
+ def_pub_const!(ROUTE_GET_CHECKSUM, "/get-checksum");
23
+ def_pub_const!(ROUTE_GET_TIMESTAMP_HEADER, "/get-tsheader");
24
+ def_pub_const!(ROUTE_USER_INFO_PATH, "/userinfo");
25
+ def_pub_const!(ROUTE_API_PATH, "/api");
26
+ def_pub_const!(ROUTE_LOGS_PATH, "/logs");
27
+ def_pub_const!(ROUTE_CONFIG_PATH, "/config");
28
+ def_pub_const!(ROUTE_TOKENS_PATH, "/tokens");
29
+ def_pub_const!(ROUTE_TOKENS_GET_PATH, "/tokens/get");
30
+ def_pub_const!(ROUTE_TOKENS_RELOAD_PATH, "/tokens/reload");
31
+ def_pub_const!(ROUTE_TOKENS_UPDATE_PATH, "/tokens/update");
32
+ def_pub_const!(ROUTE_TOKENS_ADD_PATH, "/tokens/add");
33
+ def_pub_const!(ROUTE_TOKENS_DELETE_PATH, "/tokens/delete");
34
+ def_pub_const!(ROUTE_ENV_EXAMPLE_PATH, "/env-example");
35
+ def_pub_const!(ROUTE_STATIC_PATH, "/static/{path}");
36
+ def_pub_const!(ROUTE_SHARED_STYLES_PATH, "/static/shared-styles.css");
37
+ def_pub_const!(ROUTE_SHARED_JS_PATH, "/static/shared.js");
38
+ def_pub_const!(ROUTE_ABOUT_PATH, "/about");
39
+ def_pub_const!(ROUTE_README_PATH, "/readme");
40
+ def_pub_const!(ROUTE_BASIC_CALIBRATION_PATH, "/basic-calibration");
41
+ def_pub_const!(ROUTE_BUILD_KEY_PATH, "/build-key");
42
+
43
+ def_pub_const!(DEFAULT_TOKEN_LIST_FILE_NAME, ".tokens");
44
+
45
+ def_pub_const!(STATUS_PENDING, "pending");
46
+ def_pub_const!(STATUS_SUCCESS, "success");
47
+ def_pub_const!(STATUS_FAILED, "failed");
48
+
49
+ def_pub_const!(HEADER_NAME_GHOST_MODE, "x-ghost-mode");
50
+
51
+ def_pub_const!(TRUE, "true");
52
+ def_pub_const!(FALSE, "false");
53
+
54
+ // def_pub_const!(CONTENT_TYPE_PROTO, "application/proto");
55
+ def_pub_const!(CONTENT_TYPE_CONNECT_PROTO, "application/connect+proto");
56
+ def_pub_const!(CONTENT_TYPE_TEXT_HTML_WITH_UTF8, "text/html;charset=utf-8");
57
+ def_pub_const!(
58
+ CONTENT_TYPE_TEXT_PLAIN_WITH_UTF8,
59
+ "text/plain;charset=utf-8"
60
+ );
61
+ def_pub_const!(CONTENT_TYPE_TEXT_CSS_WITH_UTF8, "text/css;charset=utf-8");
62
+ def_pub_const!(
63
+ CONTENT_TYPE_TEXT_JS_WITH_UTF8,
64
+ "text/javascript;charset=utf-8"
65
+ );
66
+
67
+ def_pub_const!(AUTHORIZATION_BEARER_PREFIX, "Bearer ");
68
+
69
+ def_pub_const!(CURSOR_API2_HOST, "api2.cursor.sh");
70
+ def_pub_const!(CURSOR_HOST, "www.cursor.com");
71
+ def_pub_const!(CURSOR_SETTINGS_URL, "https://www.cursor.com/settings");
72
+
73
+ def_pub_const!(OBJECT_CHAT_COMPLETION, "chat.completion");
74
+ def_pub_const!(OBJECT_CHAT_COMPLETION_CHUNK, "chat.completion.chunk");
75
+
76
+ // def_pub_const!(CURSOR_API2_STREAM_CHAT, "StreamChat");
77
+ // def_pub_const!(CURSOR_API2_GET_USER_INFO, "GetUserInfo");
78
+
79
+ def_pub_const!(FINISH_REASON_STOP, "stop");
80
+
81
+ def_pub_const!(ERR_INVALID_PATH, "无效的路径");
82
+
83
+ // def_pub_const!(ERR_CHECKSUM_NO_GOOD, "checksum no good");
src/app/lazy.rs ADDED
@@ -0,0 +1,189 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use super::constant::{
2
+ COMMA, CURSOR_API2_HOST, CURSOR_HOST, DEFAULT_TOKEN_LIST_FILE_NAME, EMPTY_STRING,
3
+ };
4
+ use crate::common::utils::{
5
+ parse_ascii_char_from_env, parse_bool_from_env, parse_string_from_env, parse_usize_from_env,
6
+ };
7
+ use std::sync::LazyLock;
8
+ use tokio::sync::{Mutex, OnceCell};
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_LIST_FILE, env: "TOKEN_LIST_FILE", default: DEFAULT_TOKEN_LIST_FILE_NAME);
36
+ def_pub_static!(ROUTE_MODELS_PATH, format!("{}/v1/models", *ROUTE_PREFIX));
37
+ def_pub_static!(
38
+ ROUTE_CHAT_PATH,
39
+ format!("{}/v1/chat/completions", *ROUTE_PREFIX)
40
+ );
41
+
42
+ pub static START_TIME: LazyLock<chrono::DateTime<chrono::Local>> =
43
+ LazyLock::new(chrono::Local::now);
44
+
45
+ pub fn get_start_time() -> chrono::DateTime<chrono::Local> {
46
+ *START_TIME
47
+ }
48
+
49
+ def_pub_static!(DEFAULT_INSTRUCTIONS, env: "DEFAULT_INSTRUCTIONS", default: "Respond in Chinese by default");
50
+
51
+ def_pub_static!(REVERSE_PROXY_HOST, env: "REVERSE_PROXY_HOST", default: EMPTY_STRING);
52
+
53
+ const DEFAULT_KEY_PREFIX: &str = "sk-";
54
+
55
+ pub static KEY_PREFIX: LazyLock<String> = LazyLock::new(|| {
56
+ let value = parse_string_from_env("KEY_PREFIX", DEFAULT_KEY_PREFIX)
57
+ .trim()
58
+ .to_string();
59
+ if value.is_empty() {
60
+ DEFAULT_KEY_PREFIX.to_string()
61
+ } else {
62
+ value
63
+ }
64
+ });
65
+
66
+ pub static KEY_PREFIX_LEN: LazyLock<usize> = LazyLock::new(|| KEY_PREFIX.len());
67
+
68
+ pub static TOKEN_DELIMITER: LazyLock<char> = LazyLock::new(|| {
69
+ let delimiter = parse_ascii_char_from_env("TOKEN_DELIMITER", COMMA);
70
+ if delimiter.is_ascii_alphabetic()
71
+ || delimiter.is_ascii_digit()
72
+ || delimiter == '+'
73
+ || delimiter == '/'
74
+ {
75
+ COMMA
76
+ } else {
77
+ delimiter
78
+ }
79
+ });
80
+
81
+ pub static USE_COMMA_DELIMITER: LazyLock<bool> = LazyLock::new(|| {
82
+ let enable = parse_bool_from_env("USE_COMMA_DELIMITER", true);
83
+ if enable && *TOKEN_DELIMITER == COMMA {
84
+ false
85
+ } else {
86
+ enable
87
+ }
88
+ });
89
+
90
+ pub static USE_REVERSE_PROXY: LazyLock<bool> = LazyLock::new(|| !REVERSE_PROXY_HOST.is_empty());
91
+
92
+ macro_rules! def_cursor_api_url {
93
+ ($name:ident, $api_host:expr, $path:expr) => {
94
+ pub static $name: LazyLock<String> = LazyLock::new(|| {
95
+ let host = if *USE_REVERSE_PROXY {
96
+ &*REVERSE_PROXY_HOST
97
+ } else {
98
+ $api_host
99
+ };
100
+ format!("https://{}{}", host, $path)
101
+ });
102
+ };
103
+ }
104
+
105
+ def_cursor_api_url!(
106
+ CURSOR_API2_CHAT_URL,
107
+ CURSOR_API2_HOST,
108
+ "/aiserver.v1.AiService/StreamChat"
109
+ );
110
+
111
+ def_cursor_api_url!(
112
+ CURSOR_API2_CHAT_WEB_URL,
113
+ CURSOR_API2_HOST,
114
+ "/aiserver.v1.AiService/StreamChatWeb"
115
+ );
116
+
117
+ def_cursor_api_url!(
118
+ CURSOR_API2_STRIPE_URL,
119
+ CURSOR_API2_HOST,
120
+ "/auth/full_stripe_profile"
121
+ );
122
+
123
+ def_cursor_api_url!(CURSOR_USAGE_API_URL, CURSOR_HOST, "/api/usage");
124
+
125
+ def_cursor_api_url!(CURSOR_USER_API_URL, CURSOR_HOST, "/api/auth/me");
126
+
127
+ pub(super) static LOGS_FILE_PATH: LazyLock<String> =
128
+ LazyLock::new(|| parse_string_from_env("LOGS_FILE_PATH", "logs.bin"));
129
+
130
+ pub(super) static PAGES_FILE_PATH: LazyLock<String> =
131
+ LazyLock::new(|| parse_string_from_env("PAGES_FILE_PATH", "pages.bin"));
132
+
133
+ pub static DEBUG: LazyLock<bool> = LazyLock::new(|| parse_bool_from_env("DEBUG", false));
134
+
135
+ // 使用环境变量 "DEBUG_LOG_FILE" 来指定日志文件路径,默认值为 "debug.log"
136
+ static DEBUG_LOG_FILE: LazyLock<String> =
137
+ LazyLock::new(|| parse_string_from_env("DEBUG_LOG_FILE", "debug.log"));
138
+
139
+ // 使用 OnceCell 结合 Mutex 来异步初始化 LOG_FILE
140
+ static LOG_FILE: OnceCell<Mutex<tokio::fs::File>> = OnceCell::const_new();
141
+
142
+ pub(crate) async fn get_log_file() -> &'static Mutex<tokio::fs::File> {
143
+ LOG_FILE
144
+ .get_or_init(|| async {
145
+ Mutex::new(
146
+ tokio::fs::OpenOptions::new()
147
+ .create(true)
148
+ .append(true)
149
+ .open(&*DEBUG_LOG_FILE)
150
+ .await
151
+ .expect("无法打开日志文件"),
152
+ )
153
+ })
154
+ .await
155
+ }
156
+
157
+ #[macro_export]
158
+ macro_rules! debug_println {
159
+ ($($arg:tt)*) => {
160
+ if *crate::app::lazy::DEBUG {
161
+ let time = chrono::Local::now().format("%Y-%m-%d %H:%M:%S").to_string();
162
+ let log_message = format!("{} - {}", time, format!($($arg)*));
163
+ use tokio::io::AsyncWriteExt as _;
164
+
165
+ // 使用 tokio 的 spawn 在后台异步写入日志
166
+ tokio::spawn(async move {
167
+ let log_file = crate::app::lazy::get_log_file().await;
168
+ // 使用 MutexGuard 获取可变引用
169
+ let mut file = log_file.lock().await;
170
+ if let Err(err) = file.write_all(log_message.as_bytes()).await {
171
+ eprintln!("写入日志文件失败: {}", err);
172
+ }
173
+ if let Err(err) = file.write_all(b"\n").await {
174
+ eprintln!("写入换行符失败: {}", err);
175
+ }
176
+ // 可以选择在写入失败时 panic,或者忽略
177
+ // panic!("写入日志文件失败: {}", err);
178
+ });
179
+ }
180
+ };
181
+ }
182
+
183
+ pub static REQUEST_LOGS_LIMIT: LazyLock<usize> =
184
+ LazyLock::new(|| std::cmp::min(parse_usize_from_env("REQUEST_LOGS_LIMIT", 100), 2000));
185
+
186
+ pub static SERVICE_TIMEOUT: LazyLock<u64> = LazyLock::new(|| {
187
+ let timeout = parse_usize_from_env("SERVICE_TIMEOUT", 30);
188
+ u64::try_from(timeout).map(|t| t.min(600)).unwrap_or(30)
189
+ });
src/app/model.rs ADDED
@@ -0,0 +1,486 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use crate::{
2
+ app::constant::{
3
+ EMPTY_STRING, ERR_INVALID_PATH, ROUTE_ABOUT_PATH, ROUTE_API_PATH, ROUTE_BUILD_KEY_PATH,
4
+ ROUTE_CONFIG_PATH, ROUTE_LOGS_PATH, ROUTE_README_PATH, ROUTE_ROOT_PATH,
5
+ ROUTE_SHARED_JS_PATH, ROUTE_SHARED_STYLES_PATH, ROUTE_TOKENS_PATH,
6
+ },
7
+ chat::model::Message,
8
+ common::{
9
+ client::rebuild_http_client,
10
+ model::{userinfo::TokenProfile, ApiStatus},
11
+ utils::{generate_checksum_with_repair, parse_bool_from_env, parse_string_from_env},
12
+ },
13
+ };
14
+ use parking_lot::RwLock;
15
+ use rkyv::{Archive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize};
16
+ use serde::{Deserialize, Serialize};
17
+ use std::sync::LazyLock;
18
+
19
+ mod usage_check;
20
+ pub use usage_check::UsageCheck;
21
+ mod config;
22
+ mod proxies;
23
+ pub use proxies::Proxies;
24
+ mod build_key;
25
+ pub use build_key::*;
26
+
27
+ use super::constant::{STATUS_FAILED, STATUS_PENDING, STATUS_SUCCESS};
28
+
29
+ // 页面内容类型枚举
30
+ #[derive(Clone, Serialize, Deserialize, Archive, RkyvDeserialize, RkyvSerialize)]
31
+ #[serde(tag = "type", content = "content")]
32
+ pub enum PageContent {
33
+ #[serde(rename = "default")]
34
+ Default, // 默认行为
35
+ #[serde(rename = "text")]
36
+ Text(String), // 纯文本
37
+ #[serde(rename = "html")]
38
+ Html(String), // HTML 内容
39
+ }
40
+
41
+ impl Default for PageContent {
42
+ fn default() -> Self {
43
+ Self::Default
44
+ }
45
+ }
46
+
47
+ // 静态配置
48
+ #[derive(Default, Clone)]
49
+ pub struct AppConfig {
50
+ vision_ability: VisionAbility,
51
+ slow_pool: bool,
52
+ allow_claude: bool,
53
+ pages: Pages,
54
+ usage_check: UsageCheck,
55
+ dynamic_key: bool,
56
+ share_token: String,
57
+ is_share: bool,
58
+ proxies: Proxies,
59
+ web_refs: bool,
60
+ }
61
+
62
+ #[derive(Serialize, Deserialize, Clone, Copy, PartialEq)]
63
+ pub enum VisionAbility {
64
+ #[serde(rename = "none", alias = "disabled")]
65
+ None,
66
+ #[serde(rename = "base64", alias = "base64-only")]
67
+ Base64,
68
+ #[serde(rename = "all", alias = "base64-http")]
69
+ All,
70
+ }
71
+
72
+ impl VisionAbility {
73
+ pub fn from_str(s: &str) -> Self {
74
+ match s.to_lowercase().as_str() {
75
+ "none" | "disabled" => Self::None,
76
+ "base64" | "base64-only" => Self::Base64,
77
+ "all" | "base64-http" => Self::All,
78
+ _ => Self::default(),
79
+ }
80
+ }
81
+
82
+ pub fn is_none(&self) -> bool {
83
+ matches!(self, VisionAbility::None)
84
+ }
85
+ }
86
+
87
+ impl Default for VisionAbility {
88
+ fn default() -> Self {
89
+ Self::Base64
90
+ }
91
+ }
92
+
93
+ #[derive(Clone, Default, Archive, RkyvDeserialize, RkyvSerialize)]
94
+ pub struct Pages {
95
+ pub root_content: PageContent,
96
+ pub logs_content: PageContent,
97
+ pub config_content: PageContent,
98
+ pub tokeninfo_content: PageContent,
99
+ pub shared_styles_content: PageContent,
100
+ pub shared_js_content: PageContent,
101
+ pub about_content: PageContent,
102
+ pub readme_content: PageContent,
103
+ pub api_content: PageContent,
104
+ pub build_key_content: PageContent,
105
+ }
106
+
107
+ // 运行时状态
108
+ pub struct AppState {
109
+ pub total_requests: u64,
110
+ pub active_requests: u64,
111
+ pub error_requests: u64,
112
+ pub request_logs: Vec<RequestLog>,
113
+ pub token_infos: Vec<TokenInfo>,
114
+ }
115
+
116
+ // 全局配置实例
117
+ pub static APP_CONFIG: LazyLock<RwLock<AppConfig>> =
118
+ LazyLock::new(|| RwLock::new(AppConfig::default()));
119
+
120
+ macro_rules! config_methods {
121
+ ($($field:ident: $type:ty, $default:expr;)*) => {
122
+ $(
123
+ paste::paste! {
124
+ pub fn [<get_ $field>]() -> $type
125
+ where
126
+ $type: Copy + PartialEq,
127
+ {
128
+ APP_CONFIG.read().$field
129
+ }
130
+
131
+ pub fn [<update_ $field>](value: $type)
132
+ where
133
+ $type: Copy + PartialEq,
134
+ {
135
+ let current = Self::[<get_ $field>]();
136
+ if current != value {
137
+ APP_CONFIG.write().$field = value;
138
+ }
139
+ }
140
+
141
+ pub fn [<reset_ $field>]()
142
+ where
143
+ $type: Copy + PartialEq,
144
+ {
145
+ let default_value = $default;
146
+ let current = Self::[<get_ $field>]();
147
+ if current != default_value {
148
+ APP_CONFIG.write().$field = default_value;
149
+ }
150
+ }
151
+ }
152
+ )*
153
+ };
154
+ }
155
+
156
+ macro_rules! config_methods_clone {
157
+ ($($field:ident: $type:ty, $default:expr;)*) => {
158
+ $(
159
+ paste::paste! {
160
+ pub fn [<get_ $field>]() -> $type
161
+ where
162
+ $type: Clone + PartialEq,
163
+ {
164
+ APP_CONFIG.read().$field.clone()
165
+ }
166
+
167
+ pub fn [<update_ $field>](value: $type)
168
+ where
169
+ $type: Clone + PartialEq,
170
+ {
171
+ let current = Self::[<get_ $field>]();
172
+ if current != value {
173
+ APP_CONFIG.write().$field = value;
174
+ }
175
+ }
176
+
177
+ pub fn [<reset_ $field>]()
178
+ where
179
+ $type: Clone + PartialEq,
180
+ {
181
+ let default_value = $default;
182
+ let current = Self::[<get_ $field>]();
183
+ if current != default_value {
184
+ APP_CONFIG.write().$field = default_value;
185
+ }
186
+ }
187
+ }
188
+ )*
189
+ };
190
+ }
191
+
192
+ impl AppConfig {
193
+ pub fn init() {
194
+ let mut config = APP_CONFIG.write();
195
+ config.vision_ability =
196
+ VisionAbility::from_str(&parse_string_from_env("VISION_ABILITY", EMPTY_STRING));
197
+ config.slow_pool = parse_bool_from_env("ENABLE_SLOW_POOL", false);
198
+ config.allow_claude = parse_bool_from_env("PASS_ANY_CLAUDE", false);
199
+ config.usage_check =
200
+ UsageCheck::from_str(&parse_string_from_env("USAGE_CHECK", EMPTY_STRING));
201
+ config.dynamic_key = parse_bool_from_env("DYNAMIC_KEY", false);
202
+ config.share_token = parse_string_from_env("SHARED_TOKEN", EMPTY_STRING);
203
+ config.is_share = !config.share_token.is_empty();
204
+ config.proxies = match std::env::var("PROXIES") {
205
+ Ok(proxies) => Proxies::from_str(proxies.as_str()),
206
+ Err(_) => Proxies::default(),
207
+ };
208
+ config.web_refs = parse_bool_from_env("INCLUDE_WEB_REFERENCES", false)
209
+ }
210
+
211
+ config_methods! {
212
+ slow_pool: bool, false;
213
+ allow_claude: bool, false;
214
+ dynamic_key: bool, false;
215
+ web_refs: bool, false;
216
+ }
217
+
218
+ config_methods_clone! {
219
+ vision_ability: VisionAbility, VisionAbility::default();
220
+ usage_check: UsageCheck, UsageCheck::default();
221
+ }
222
+
223
+ pub fn get_share_token() -> String {
224
+ APP_CONFIG.read().share_token.clone()
225
+ }
226
+
227
+ pub fn update_share_token(value: String) {
228
+ let current = Self::get_share_token();
229
+ if current != value {
230
+ let mut config = APP_CONFIG.write();
231
+ config.share_token = value;
232
+ config.is_share = !config.share_token.is_empty();
233
+ }
234
+ }
235
+
236
+ pub fn reset_share_token() {
237
+ let current = Self::get_share_token();
238
+ if !current.is_empty() {
239
+ let mut config = APP_CONFIG.write();
240
+ config.share_token = String::new();
241
+ config.is_share = false;
242
+ }
243
+ }
244
+
245
+ pub fn get_proxies() -> Proxies {
246
+ APP_CONFIG.read().proxies.clone()
247
+ }
248
+
249
+ pub fn update_proxies(value: Proxies) {
250
+ let current = Self::get_proxies();
251
+ if current != value {
252
+ let mut config = APP_CONFIG.write();
253
+ config.proxies = value;
254
+ rebuild_http_client();
255
+ }
256
+ }
257
+
258
+ pub fn reset_proxies() {
259
+ let default_value = Proxies::default();
260
+ let current = Self::get_proxies();
261
+ if current != default_value {
262
+ let mut config = APP_CONFIG.write();
263
+ config.proxies = default_value;
264
+ rebuild_http_client();
265
+ }
266
+ }
267
+
268
+ pub fn get_page_content(path: &str) -> Option<PageContent> {
269
+ match path {
270
+ ROUTE_ROOT_PATH => Some(APP_CONFIG.read().pages.root_content.clone()),
271
+ ROUTE_LOGS_PATH => Some(APP_CONFIG.read().pages.logs_content.clone()),
272
+ ROUTE_CONFIG_PATH => Some(APP_CONFIG.read().pages.config_content.clone()),
273
+ ROUTE_TOKENS_PATH => Some(APP_CONFIG.read().pages.tokeninfo_content.clone()),
274
+ ROUTE_SHARED_STYLES_PATH => Some(APP_CONFIG.read().pages.shared_styles_content.clone()),
275
+ ROUTE_SHARED_JS_PATH => Some(APP_CONFIG.read().pages.shared_js_content.clone()),
276
+ ROUTE_ABOUT_PATH => Some(APP_CONFIG.read().pages.about_content.clone()),
277
+ ROUTE_README_PATH => Some(APP_CONFIG.read().pages.readme_content.clone()),
278
+ ROUTE_API_PATH => Some(APP_CONFIG.read().pages.api_content.clone()),
279
+ ROUTE_BUILD_KEY_PATH => Some(APP_CONFIG.read().pages.build_key_content.clone()),
280
+ _ => None,
281
+ }
282
+ }
283
+
284
+ pub fn update_page_content(path: &str, content: PageContent) -> Result<(), &'static str> {
285
+ let mut config = APP_CONFIG.write();
286
+ match path {
287
+ ROUTE_ROOT_PATH => config.pages.root_content = content,
288
+ ROUTE_LOGS_PATH => config.pages.logs_content = content,
289
+ ROUTE_CONFIG_PATH => config.pages.config_content = content,
290
+ ROUTE_TOKENS_PATH => config.pages.tokeninfo_content = content,
291
+ ROUTE_SHARED_STYLES_PATH => config.pages.shared_styles_content = content,
292
+ ROUTE_SHARED_JS_PATH => config.pages.shared_js_content = content,
293
+ ROUTE_ABOUT_PATH => config.pages.about_content = content,
294
+ ROUTE_README_PATH => config.pages.readme_content = content,
295
+ ROUTE_API_PATH => config.pages.api_content = content,
296
+ ROUTE_BUILD_KEY_PATH => config.pages.build_key_content = content,
297
+ _ => return Err(ERR_INVALID_PATH),
298
+ }
299
+ Ok(())
300
+ }
301
+
302
+ pub fn reset_page_content(path: &str) -> Result<(), &'static str> {
303
+ let mut config = APP_CONFIG.write();
304
+ match path {
305
+ ROUTE_ROOT_PATH => config.pages.root_content = PageContent::default(),
306
+ ROUTE_LOGS_PATH => config.pages.logs_content = PageContent::default(),
307
+ ROUTE_CONFIG_PATH => config.pages.config_content = PageContent::default(),
308
+ ROUTE_TOKENS_PATH => config.pages.tokeninfo_content = PageContent::default(),
309
+ ROUTE_SHARED_STYLES_PATH => config.pages.shared_styles_content = PageContent::default(),
310
+ ROUTE_SHARED_JS_PATH => config.pages.shared_js_content = PageContent::default(),
311
+ ROUTE_ABOUT_PATH => config.pages.about_content = PageContent::default(),
312
+ ROUTE_README_PATH => config.pages.readme_content = PageContent::default(),
313
+ ROUTE_API_PATH => config.pages.api_content = PageContent::default(),
314
+ ROUTE_BUILD_KEY_PATH => config.pages.build_key_content = PageContent::default(),
315
+ _ => return Err(ERR_INVALID_PATH),
316
+ }
317
+ Ok(())
318
+ }
319
+
320
+ pub fn is_share() -> bool {
321
+ APP_CONFIG.read().is_share
322
+ }
323
+ }
324
+
325
+ impl AppState {
326
+ pub fn new(token_infos: Vec<TokenInfo>) -> Self {
327
+ // 尝试加载保存的日志
328
+ let request_logs = tokio::task::block_in_place(|| {
329
+ tokio::runtime::Handle::current()
330
+ .block_on(async { Self::load_saved_logs().await.unwrap_or_default() })
331
+ });
332
+
333
+ Self {
334
+ total_requests: request_logs.len() as u64,
335
+ active_requests: 0,
336
+ error_requests: request_logs
337
+ .iter()
338
+ .filter(|log| matches!(log.status, LogStatus::Failed))
339
+ .count() as u64,
340
+ request_logs,
341
+ token_infos,
342
+ }
343
+ }
344
+
345
+ pub fn update_checksum(&mut self) {
346
+ for token_info in self.token_infos.iter_mut() {
347
+ token_info.checksum = generate_checksum_with_repair(&token_info.checksum);
348
+ }
349
+ }
350
+ }
351
+
352
+ #[derive(Clone, Archive, RkyvDeserialize, RkyvSerialize)]
353
+ pub enum LogStatus {
354
+ Pending,
355
+ Success,
356
+ Failed,
357
+ }
358
+
359
+ impl Serialize for LogStatus {
360
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
361
+ where
362
+ S: serde::Serializer,
363
+ {
364
+ serializer.serialize_str(self.as_str_name())
365
+ }
366
+ }
367
+
368
+ impl LogStatus {
369
+ pub fn as_str_name(&self) -> &'static str {
370
+ match self {
371
+ Self::Pending => STATUS_PENDING,
372
+ Self::Success => STATUS_SUCCESS,
373
+ Self::Failed => STATUS_FAILED,
374
+ }
375
+ }
376
+
377
+ pub fn from_str_name(s: &str) -> Option<Self> {
378
+ match s {
379
+ STATUS_PENDING => Some(Self::Pending),
380
+ STATUS_SUCCESS => Some(Self::Success),
381
+ STATUS_FAILED => Some(Self::Failed),
382
+ _ => None,
383
+ }
384
+ }
385
+ }
386
+
387
+ // 请求日志
388
+ #[derive(Serialize, Clone, Archive, RkyvDeserialize, RkyvSerialize)]
389
+ pub struct RequestLog {
390
+ pub id: u64,
391
+ pub timestamp: chrono::DateTime<chrono::Local>,
392
+ pub model: String,
393
+ pub token_info: TokenInfo,
394
+ #[serde(skip_serializing_if = "Option::is_none")]
395
+ pub prompt: Option<String>,
396
+ pub timing: TimingInfo,
397
+ pub stream: bool,
398
+ pub status: LogStatus,
399
+ #[serde(skip_serializing_if = "Option::is_none")]
400
+ pub error: Option<String>,
401
+ }
402
+
403
+ #[derive(Serialize, Clone, Archive, RkyvDeserialize, RkyvSerialize)]
404
+ pub struct TimingInfo {
405
+ pub total: f64, // 总用时(秒)
406
+ #[serde(skip_serializing_if = "Option::is_none")]
407
+ pub first: Option<f64>, // 首字时间(秒)
408
+ }
409
+
410
+ // 聊天请求
411
+ #[derive(Deserialize)]
412
+ pub struct ChatRequest {
413
+ pub model: String,
414
+ pub messages: Vec<Message>,
415
+ #[serde(default)]
416
+ pub stream: bool,
417
+ }
418
+
419
+ // 用于存储 token 信息
420
+ #[derive(Serialize, Clone, Archive, RkyvDeserialize, RkyvSerialize)]
421
+ pub struct TokenInfo {
422
+ pub token: String,
423
+ pub checksum: String,
424
+ #[serde(skip_serializing_if = "Option::is_none")]
425
+ pub profile: Option<TokenProfile>,
426
+ }
427
+
428
+ // TokenUpdateRequest 结构体
429
+ #[derive(Deserialize)]
430
+ pub struct TokenUpdateRequest {
431
+ pub tokens: String,
432
+ }
433
+
434
+ #[derive(Deserialize)]
435
+ pub struct TokenAddRequestTokenInfo {
436
+ pub token: String,
437
+ #[serde(default)]
438
+ pub checksum: Option<String>,
439
+ }
440
+
441
+ // TokensDeleteRequest 结构体
442
+ #[derive(Deserialize)]
443
+ pub struct TokensDeleteRequest {
444
+ #[serde(default)]
445
+ pub tokens: Vec<String>,
446
+ #[serde(default)]
447
+ pub expectation: TokensDeleteResponseExpectation,
448
+ }
449
+
450
+ #[derive(Deserialize, Default)]
451
+ #[serde(rename_all = "snake_case")]
452
+ pub enum TokensDeleteResponseExpectation {
453
+ #[default]
454
+ Simple,
455
+ UpdatedTokens,
456
+ FailedTokens,
457
+ Detailed,
458
+ }
459
+
460
+ impl TokensDeleteResponseExpectation {
461
+ pub fn needs_updated_tokens(&self) -> bool {
462
+ matches!(
463
+ self,
464
+ TokensDeleteResponseExpectation::UpdatedTokens
465
+ | TokensDeleteResponseExpectation::Detailed
466
+ )
467
+ }
468
+
469
+ pub fn needs_failed_tokens(&self) -> bool {
470
+ matches!(
471
+ self,
472
+ TokensDeleteResponseExpectation::FailedTokens
473
+ | TokensDeleteResponseExpectation::Detailed
474
+ )
475
+ }
476
+ }
477
+
478
+ // TokensDeleteResponse 结构体
479
+ #[derive(Serialize)]
480
+ pub struct TokensDeleteResponse {
481
+ pub status: ApiStatus,
482
+ #[serde(skip_serializing_if = "Option::is_none")]
483
+ pub updated_tokens: Option<Vec<String>>,
484
+ #[serde(skip_serializing_if = "Option::is_none")]
485
+ pub failed_tokens: Option<Vec<String>>,
486
+ }
src/app/model/build_key.rs ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use serde::{Deserialize, Serialize};
2
+
3
+ use crate::{app::constant::COMMA, chat::constant::AVAILABLE_MODELS};
4
+
5
+ #[derive(Deserialize)]
6
+ pub struct BuildKeyRequest {
7
+ pub auth_token: String,
8
+ #[serde(default)]
9
+ pub disable_vision: Option<bool>,
10
+ #[serde(default)]
11
+ pub enable_slow_pool: Option<bool>,
12
+ #[serde(default)]
13
+ pub usage_check_models: Option<UsageCheckModelConfig>,
14
+ #[serde(default)]
15
+ pub include_web_references: Option<bool>,
16
+ }
17
+ pub struct UsageCheckModelConfig {
18
+ pub model_type: UsageCheckModelType,
19
+ pub model_ids: Vec<&'static str>,
20
+ }
21
+
22
+ impl<'de> Deserialize<'de> for UsageCheckModelConfig {
23
+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
24
+ where
25
+ D: serde::Deserializer<'de>,
26
+ {
27
+ #[derive(Deserialize)]
28
+ struct Helper {
29
+ #[serde(rename = "type")]
30
+ model_type: UsageCheckModelType,
31
+ #[serde(default)]
32
+ model_ids: String,
33
+ }
34
+
35
+ let helper = Helper::deserialize(deserializer)?;
36
+
37
+ let model_ids = if helper.model_ids.is_empty() {
38
+ Vec::new()
39
+ } else {
40
+ helper
41
+ .model_ids
42
+ .split(COMMA)
43
+ .filter_map(|model| {
44
+ let model = model.trim();
45
+ AVAILABLE_MODELS
46
+ .iter()
47
+ .find(|m| m.id == model)
48
+ .map(|m| m.id)
49
+ })
50
+ .collect()
51
+ };
52
+
53
+ Ok(UsageCheckModelConfig {
54
+ model_type: helper.model_type,
55
+ model_ids,
56
+ })
57
+ }
58
+ }
59
+
60
+ #[derive(Deserialize)]
61
+ #[serde(rename_all = "lowercase")]
62
+ pub enum UsageCheckModelType {
63
+ Default,
64
+ Disabled,
65
+ All,
66
+ Custom,
67
+ }
68
+
69
+ #[derive(Serialize)]
70
+ #[serde(rename_all = "lowercase")]
71
+ pub enum BuildKeyResponse {
72
+ Key(String),
73
+ Error(String),
74
+ }
src/app/model/config.rs ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use memmap2::{MmapMut, MmapOptions};
2
+ use rkyv::{archived_root, Deserialize as _};
3
+ use std::fs::OpenOptions;
4
+
5
+ use crate::app::lazy::{LOGS_FILE_PATH, PAGES_FILE_PATH};
6
+
7
+ use super::{AppConfig, AppState, Pages, RequestLog, APP_CONFIG};
8
+
9
+ impl AppState {
10
+ // 保存日志的方法
11
+ pub(crate) async fn save_logs(&self) -> Result<(), Box<dyn std::error::Error>> {
12
+ // 序列化日志
13
+ let bytes = rkyv::to_bytes::<_, 256>(&self.request_logs)?;
14
+
15
+ // 创建或打开文件
16
+ let file = OpenOptions::new()
17
+ .read(true)
18
+ .write(true)
19
+ .create(true)
20
+ .open(LOGS_FILE_PATH.as_str())?;
21
+
22
+ // 添加大小检查
23
+ if bytes.len() > usize::MAX / 2 {
24
+ return Err("日志数据过大".into());
25
+ }
26
+
27
+ // 设置文件大小
28
+ file.set_len(bytes.len() as u64)?;
29
+
30
+ // 创建可写入的内存映射
31
+ let mut mmap = unsafe { MmapMut::map_mut(&file)? };
32
+
33
+ // 写入数据
34
+ mmap.copy_from_slice(&bytes);
35
+
36
+ // 同步到磁盘
37
+ mmap.flush()?;
38
+
39
+ Ok(())
40
+ }
41
+
42
+ // 加载日志的方法
43
+ pub(super) async fn load_saved_logs() -> Result<Vec<RequestLog>, Box<dyn std::error::Error>> {
44
+ let file = match OpenOptions::new().read(true).open(LOGS_FILE_PATH.as_str()) {
45
+ Ok(file) => file,
46
+ Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
47
+ return Ok(Vec::new());
48
+ }
49
+ Err(e) => return Err(Box::new(e)),
50
+ };
51
+
52
+ // 添加文件大小检查
53
+ if file.metadata()?.len() > usize::MAX as u64 {
54
+ return Err("日志文件过大".into());
55
+ }
56
+
57
+ // 创建只读内存映射
58
+ let mmap = unsafe { MmapOptions::new().map(&file)? };
59
+
60
+ // 验证并反序列化数据
61
+ let archived = unsafe { archived_root::<Vec<RequestLog>>(&mmap) };
62
+ Ok(archived.deserialize(&mut rkyv::Infallible)?)
63
+ }
64
+ }
65
+
66
+ impl AppConfig {
67
+ pub fn save_config() -> Result<(), Box<dyn std::error::Error>> {
68
+ let pages = APP_CONFIG.read().pages.clone();
69
+ let bytes = rkyv::to_bytes::<_, 256>(&pages)?;
70
+
71
+ let file = OpenOptions::new()
72
+ .read(true)
73
+ .write(true)
74
+ .create(true)
75
+ .open(PAGES_FILE_PATH.as_str())?;
76
+
77
+ // 添加大小检查
78
+ if bytes.len() > usize::MAX / 2 {
79
+ return Err("配置数据过大".into());
80
+ }
81
+
82
+ file.set_len(bytes.len() as u64)?;
83
+
84
+ let mut mmap = unsafe { MmapMut::map_mut(&file)? };
85
+ mmap.copy_from_slice(&bytes);
86
+ mmap.flush()?;
87
+
88
+ Ok(())
89
+ }
90
+
91
+ pub fn load_saved_config() -> Result<(), Box<dyn std::error::Error>> {
92
+ let file = match OpenOptions::new().read(true).open(PAGES_FILE_PATH.as_str()) {
93
+ Ok(file) => file,
94
+ Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
95
+ return Ok(());
96
+ }
97
+ Err(e) => return Err(Box::new(e)),
98
+ };
99
+
100
+ // 添加文件大小检查
101
+ if file.metadata()?.len() > usize::MAX as u64 {
102
+ return Err("配置文件过大".into());
103
+ }
104
+
105
+ let mmap = unsafe { MmapOptions::new().map(&file)? };
106
+
107
+ let archived = unsafe { archived_root::<Pages>(&mmap) };
108
+ let pages = archived.deserialize(&mut rkyv::Infallible)?;
109
+ let mut config = APP_CONFIG.write();
110
+ config.pages = pages;
111
+
112
+ Ok(())
113
+ }
114
+ }
src/app/model/proxies.rs ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use reqwest::{Client, Proxy};
2
+ use serde::{Serialize, Serializer};
3
+ use serde::{Deserialize, Deserializer};
4
+ // use rkyv::{Archive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize};
5
+
6
+ use crate::app::constant::COMMA_STRING;
7
+
8
+ #[derive(Clone, Default, PartialEq)]
9
+ pub enum Proxies {
10
+ No,
11
+ #[default]
12
+ System,
13
+ List(Vec<String>),
14
+ }
15
+
16
+ impl Serialize for Proxies {
17
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
18
+ where
19
+ S: Serializer,
20
+ {
21
+ match self {
22
+ Proxies::No => serializer.serialize_str(""),
23
+ Proxies::System => serializer.serialize_str("system"),
24
+ Proxies::List(urls) => serializer.serialize_str(&urls.join(COMMA_STRING)),
25
+ }
26
+ }
27
+ }
28
+
29
+ impl<'de> Deserialize<'de> for Proxies {
30
+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
31
+ where
32
+ D: Deserializer<'de>,
33
+ {
34
+ let s = <String as serde::Deserialize>::deserialize(deserializer)?;
35
+ Ok(Proxies::from_str(&s))
36
+ }
37
+ }
38
+
39
+ impl Proxies {
40
+ /// 从字符串创建 Proxies
41
+ ///
42
+ /// # Arguments
43
+ /// * `s` - 代理字符串:
44
+ /// - "" 或 "no": 不使用代理
45
+ /// - "system": 使用系统代理
46
+ /// - 其他: 尝试解析为代理列表,无效则返回 System
47
+ pub fn from_str(s: &str) -> Self {
48
+ match s.trim() {
49
+ "" | "no" => Self::No,
50
+ "system" => Self::System,
51
+ urls => {
52
+ let valid_proxies: Vec<String> = urls
53
+ .split(',')
54
+ .filter_map(|url| {
55
+ let trimmed = url.trim();
56
+ (!trimmed.is_empty() && Proxy::all(trimmed).is_ok())
57
+ .then(|| trimmed.to_string())
58
+ })
59
+ .collect();
60
+
61
+ if valid_proxies.is_empty() {
62
+ Self::default()
63
+ } else {
64
+ Self::List(valid_proxies)
65
+ }
66
+ }
67
+ }
68
+ }
69
+
70
+ pub fn get_client(&self) -> Client {
71
+ match self {
72
+ Proxies::No => Client::builder().no_proxy().build().unwrap(),
73
+ Proxies::System => Client::new(),
74
+ Proxies::List(list) => {
75
+ // 使用第一个代理(已经确保是有效的)
76
+ let proxy = Proxy::all(list[0].clone()).unwrap();
77
+ Client::builder().proxy(proxy).build().unwrap()
78
+ }
79
+ }
80
+ }
81
+ }
src/app/model/usage_check.rs ADDED
@@ -0,0 +1,172 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use crate::{
2
+ app::constant::{COMMA, COMMA_STRING},
3
+ chat::{config::key_config, constant::AVAILABLE_MODELS},
4
+ };
5
+ use serde::{Deserialize, Serialize};
6
+ // use rkyv::{Archive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize};
7
+
8
+ #[derive(Clone, PartialEq)]
9
+ pub enum UsageCheck {
10
+ None,
11
+ Default,
12
+ All,
13
+ Custom(Vec<&'static str>),
14
+ }
15
+
16
+ impl UsageCheck {
17
+ pub fn from_proto(model: Option<&key_config::UsageCheckModel>) -> Option<Self> {
18
+ model.map(|model| {
19
+ use key_config::usage_check_model::Type;
20
+ match Type::try_from(model.r#type).unwrap_or(Type::Default) {
21
+ Type::Default | Type::Disabled => Self::None,
22
+ Type::All => Self::All,
23
+ Type::Custom => {
24
+ let models: Vec<&'static str> = model
25
+ .model_ids
26
+ .iter()
27
+ .filter_map(|id| AVAILABLE_MODELS.iter().find(|m| m.id == id).map(|m| m.id))
28
+ .collect();
29
+ if models.is_empty() {
30
+ Self::None
31
+ } else {
32
+ Self::Custom(models)
33
+ }
34
+ }
35
+ }
36
+ })
37
+ }
38
+
39
+ // pub fn to_proto(&self) -> key_config::UsageCheckModel {
40
+ // use key_config::usage_check_model::Type;
41
+ // match self {
42
+ // Self::None => key_config::UsageCheckModel {
43
+ // r#type: Type::Disabled.into(),
44
+ // model_ids: vec![],
45
+ // },
46
+ // Self::Default => key_config::UsageCheckModel {
47
+ // r#type: Type::Default.into(),
48
+ // model_ids: vec![],
49
+ // },
50
+ // Self::All => key_config::UsageCheckModel {
51
+ // r#type: Type::All.into(),
52
+ // model_ids: vec![],
53
+ // },
54
+ // Self::Custom(models) => key_config::UsageCheckModel {
55
+ // r#type: Type::Custom.into(),
56
+ // model_ids: models.iter().map(|&s| s.to_string()).collect(),
57
+ // },
58
+ // }
59
+ // }
60
+ }
61
+
62
+ impl Default for UsageCheck {
63
+ fn default() -> Self {
64
+ Self::Default
65
+ }
66
+ }
67
+
68
+ impl Serialize for UsageCheck {
69
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
70
+ where
71
+ S: serde::Serializer,
72
+ {
73
+ use serde::ser::SerializeStruct;
74
+ let mut state = serializer.serialize_struct("UsageCheck", 1)?;
75
+ match self {
76
+ UsageCheck::None => {
77
+ state.serialize_field("type", "none")?;
78
+ }
79
+ UsageCheck::Default => {
80
+ state.serialize_field("type", "default")?;
81
+ }
82
+ UsageCheck::All => {
83
+ state.serialize_field("type", "all")?;
84
+ }
85
+ UsageCheck::Custom(models) => {
86
+ state.serialize_field("type", "list")?;
87
+ state.serialize_field("content", &models.join(COMMA_STRING))?;
88
+ }
89
+ }
90
+ state.end()
91
+ }
92
+ }
93
+
94
+ impl<'de> Deserialize<'de> for UsageCheck {
95
+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
96
+ where
97
+ D: serde::Deserializer<'de>,
98
+ {
99
+ #[derive(Deserialize)]
100
+ #[serde(tag = "type", content = "content")]
101
+ enum UsageCheckHelper {
102
+ #[serde(rename = "none")]
103
+ None,
104
+ #[serde(rename = "default")]
105
+ Default,
106
+ #[serde(rename = "all")]
107
+ All,
108
+ #[serde(rename = "list")]
109
+ Custom(String),
110
+ }
111
+
112
+ let helper = <UsageCheckHelper as serde::Deserialize>::deserialize(deserializer)?;
113
+ Ok(match helper {
114
+ UsageCheckHelper::None => UsageCheck::None,
115
+ UsageCheckHelper::Default => UsageCheck::Default,
116
+ UsageCheckHelper::All => UsageCheck::All,
117
+ UsageCheckHelper::Custom(list) => {
118
+ if list.is_empty() {
119
+ return Ok(UsageCheck::None);
120
+ }
121
+
122
+ let models: Vec<&'static str> = list
123
+ .split(COMMA)
124
+ .filter_map(|model| {
125
+ let model = model.trim();
126
+ AVAILABLE_MODELS
127
+ .iter()
128
+ .find(|m| m.id == model)
129
+ .map(|m| m.id)
130
+ })
131
+ .collect();
132
+
133
+ if models.is_empty() {
134
+ UsageCheck::None
135
+ } else {
136
+ UsageCheck::Custom(models)
137
+ }
138
+ }
139
+ })
140
+ }
141
+ }
142
+
143
+ impl UsageCheck {
144
+ pub fn from_str(s: &str) -> Self {
145
+ match s.trim().to_lowercase().as_str() {
146
+ "none" | "disabled" => Self::None,
147
+ "default" => Self::Default,
148
+ "all" | "everything" => Self::All,
149
+ list => {
150
+ if list.is_empty() {
151
+ return Self::default();
152
+ }
153
+ let models: Vec<&'static str> = list
154
+ .split(COMMA)
155
+ .filter_map(|model| {
156
+ let model = model.trim();
157
+ AVAILABLE_MODELS
158
+ .iter()
159
+ .find(|m| m.id == model)
160
+ .map(|m| m.id)
161
+ })
162
+ .collect();
163
+
164
+ if models.is_empty() {
165
+ Self::default()
166
+ } else {
167
+ Self::Custom(models)
168
+ }
169
+ }
170
+ }
171
+ }
172
+ }
src/chat.rs ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ pub mod adapter;
2
+ pub mod aiserver;
3
+ pub mod config;
4
+ pub mod constant;
5
+ pub mod error;
6
+ // pub mod middleware;
7
+ pub mod model;
8
+ pub mod route;
9
+ pub mod service;
10
+ pub mod stream;
src/chat/adapter.rs ADDED
@@ -0,0 +1,473 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use base64::{engine::general_purpose::STANDARD as BASE64, Engine as _};
2
+ use image::guess_format;
3
+ use prost::Message as _;
4
+ use reqwest::Client;
5
+ use uuid::Uuid;
6
+
7
+ use crate::{
8
+ app::{
9
+ constant::EMPTY_STRING,
10
+ lazy::DEFAULT_INSTRUCTIONS,
11
+ model::{AppConfig, VisionAbility},
12
+ },
13
+ common::client::HTTP_CLIENT,
14
+ };
15
+
16
+ use super::{
17
+ aiserver::v1::{
18
+ conversation_message, image_proto, AzureState, ChatExternalLink, ConversationMessage, ExplicitContext, GetChatRequest, ImageProto, ModelDetails
19
+ },
20
+ constant::{ERR_UNSUPPORTED_GIF, ERR_UNSUPPORTED_IMAGE_FORMAT, LONG_CONTEXT_MODELS},
21
+ model::{Message, MessageContent, Role},
22
+ };
23
+
24
+ async fn process_chat_inputs(
25
+ inputs: Vec<Message>,
26
+ disable_vision: bool,
27
+ ) -> (String, Vec<ConversationMessage>, Vec<String>) {
28
+ // 收集 system 指令
29
+ let instructions = inputs
30
+ .iter()
31
+ .filter(|input| input.role == Role::System)
32
+ .map(|input| match &input.content {
33
+ MessageContent::Text(text) => text.clone(),
34
+ MessageContent::Vision(contents) => contents
35
+ .iter()
36
+ .filter_map(|content| {
37
+ if content.content_type == "text" {
38
+ content.text.clone()
39
+ } else {
40
+ None
41
+ }
42
+ })
43
+ .collect::<Vec<String>>()
44
+ .join("\n"),
45
+ })
46
+ .collect::<Vec<String>>()
47
+ .join("\n\n");
48
+
49
+ // 使用默认指令或收集到的指令
50
+ let instructions = if instructions.is_empty() {
51
+ DEFAULT_INSTRUCTIONS.clone()
52
+ } else {
53
+ instructions
54
+ };
55
+
56
+ // 过滤出 user 和 assistant 对话
57
+ let mut chat_inputs: Vec<Message> = inputs
58
+ .into_iter()
59
+ .filter(|input| input.role == Role::User || input.role == Role::Assistant)
60
+ .collect();
61
+
62
+ // 处理空对话情况
63
+ if chat_inputs.is_empty() {
64
+ return (
65
+ instructions,
66
+ vec![ConversationMessage {
67
+ text: EMPTY_STRING.into(),
68
+ r#type: conversation_message::MessageType::Human as i32,
69
+ attached_code_chunks: vec![],
70
+ codebase_context_chunks: vec![],
71
+ commits: vec![],
72
+ pull_requests: vec![],
73
+ git_diffs: vec![],
74
+ assistant_suggested_diffs: vec![],
75
+ interpreter_results: vec![],
76
+ images: vec![],
77
+ attached_folders: vec![],
78
+ approximate_lint_errors: vec![],
79
+ bubble_id: Uuid::new_v4().to_string(),
80
+ server_bubble_id: None,
81
+ attached_folders_new: vec![],
82
+ lints: vec![],
83
+ user_responses_to_suggested_code_blocks: vec![],
84
+ relevant_files: vec![],
85
+ tool_results: vec![],
86
+ notepads: vec![],
87
+ is_capability_iteration: Some(false),
88
+ capabilities: vec![],
89
+ edit_trail_contexts: vec![],
90
+ suggested_code_blocks: vec![],
91
+ diffs_for_compressing_files: vec![],
92
+ multi_file_linter_errors: vec![],
93
+ diff_histories: vec![],
94
+ recently_viewed_files: vec![],
95
+ recent_locations_history: vec![],
96
+ is_agentic: false,
97
+ file_diff_trajectories: vec![],
98
+ conversation_summary: None,
99
+ }],
100
+ vec![],
101
+ );
102
+ }
103
+
104
+ // 处理 WebReferences 开头的 assistant 消息
105
+ chat_inputs = chat_inputs
106
+ .into_iter()
107
+ .map(|mut input| {
108
+ if let (Role::Assistant, MessageContent::Text(text)) = (&input.role, &input.content) {
109
+ if text.starts_with("WebReferences:") {
110
+ if let Some(pos) = text.find("\n\n") {
111
+ input.content = MessageContent::Text(text[pos + 2..].to_owned());
112
+ }
113
+ }
114
+ }
115
+ input
116
+ })
117
+ .collect();
118
+
119
+ // 如果第一条是 assistant,插入空的 user 消息
120
+ if chat_inputs
121
+ .first()
122
+ .map_or(false, |input| input.role == Role::Assistant)
123
+ {
124
+ chat_inputs.insert(
125
+ 0,
126
+ Message {
127
+ role: Role::User,
128
+ content: MessageContent::Text(EMPTY_STRING.into()),
129
+ },
130
+ );
131
+ }
132
+
133
+ // 处理连续相同角色的情况
134
+ let mut i = 1;
135
+ while i < chat_inputs.len() {
136
+ if chat_inputs[i].role == chat_inputs[i - 1].role {
137
+ let insert_role = if chat_inputs[i].role == Role::User {
138
+ Role::Assistant
139
+ } else {
140
+ Role::User
141
+ };
142
+ chat_inputs.insert(
143
+ i,
144
+ Message {
145
+ role: insert_role,
146
+ content: MessageContent::Text(EMPTY_STRING.into()),
147
+ },
148
+ );
149
+ }
150
+ i += 1;
151
+ }
152
+
153
+ // 确保最后一条是 user
154
+ if chat_inputs
155
+ .last()
156
+ .map_or(false, |input| input.role == Role::Assistant)
157
+ {
158
+ chat_inputs.push(Message {
159
+ role: Role::User,
160
+ content: MessageContent::Text(EMPTY_STRING.into()),
161
+ });
162
+ }
163
+
164
+ // 转换为 proto messages
165
+ let mut messages = Vec::new();
166
+ for input in chat_inputs {
167
+ let (text, images) = match input.content {
168
+ MessageContent::Text(text) => (text, vec![]),
169
+ MessageContent::Vision(contents) => {
170
+ let mut text_parts = Vec::new();
171
+ let mut images = Vec::new();
172
+
173
+ for content in contents {
174
+ match content.content_type.as_str() {
175
+ "text" => {
176
+ if let Some(text) = content.text {
177
+ text_parts.push(text);
178
+ }
179
+ }
180
+ "image_url" => {
181
+ if !disable_vision {
182
+ if let Some(image_url) = &content.image_url {
183
+ let url = image_url.url.clone();
184
+ let client = HTTP_CLIENT.read().clone();
185
+ let result = tokio::spawn(async move {
186
+ fetch_image_data(&url, client).await
187
+ });
188
+ if let Ok(Ok((image_data, dimensions))) = result.await {
189
+ images.push(ImageProto {
190
+ data: image_data,
191
+ dimension: dimensions,
192
+ });
193
+ }
194
+ }
195
+ }
196
+ }
197
+ _ => {}
198
+ }
199
+ }
200
+ (text_parts.join("\n"), images)
201
+ }
202
+ };
203
+
204
+ messages.push(ConversationMessage {
205
+ text,
206
+ r#type: if input.role == Role::User {
207
+ conversation_message::MessageType::Human as i32
208
+ } else {
209
+ conversation_message::MessageType::Ai as i32
210
+ },
211
+ attached_code_chunks: vec![],
212
+ codebase_context_chunks: vec![],
213
+ commits: vec![],
214
+ pull_requests: vec![],
215
+ git_diffs: vec![],
216
+ assistant_suggested_diffs: vec![],
217
+ interpreter_results: vec![],
218
+ images,
219
+ attached_folders: vec![],
220
+ approximate_lint_errors: vec![],
221
+ bubble_id: Uuid::new_v4().to_string(),
222
+ server_bubble_id: None,
223
+ attached_folders_new: vec![],
224
+ lints: vec![],
225
+ user_responses_to_suggested_code_blocks: vec![],
226
+ relevant_files: vec![],
227
+ tool_results: vec![],
228
+ notepads: vec![],
229
+ is_capability_iteration: None,
230
+ capabilities: vec![],
231
+ edit_trail_contexts: vec![],
232
+ suggested_code_blocks: vec![],
233
+ diffs_for_compressing_files: vec![],
234
+ multi_file_linter_errors: vec![],
235
+ diff_histories: vec![],
236
+ recently_viewed_files: vec![],
237
+ recent_locations_history: vec![],
238
+ is_agentic: false,
239
+ file_diff_trajectories: vec![],
240
+ conversation_summary: None,
241
+ });
242
+ }
243
+
244
+ let mut urls = Vec::new();
245
+ if let Some(last_msg) = messages.last() {
246
+ if last_msg.r#type == conversation_message::MessageType::Human as i32 {
247
+ let text = &last_msg.text;
248
+ let mut chars = text.chars().peekable();
249
+
250
+ while let Some(c) = chars.next() {
251
+ if c == '@' {
252
+ let mut url = String::new();
253
+ while let Some(&next_char) = chars.peek() {
254
+ if next_char.is_whitespace() {
255
+ break;
256
+ }
257
+ url.push(chars.next().unwrap());
258
+ }
259
+ if let Ok(parsed_url) = url::Url::parse(&url) {
260
+ if parsed_url.scheme() == "http" || parsed_url.scheme() == "https" {
261
+ urls.push(url);
262
+ }
263
+ }
264
+ }
265
+ }
266
+ }
267
+ }
268
+
269
+ (instructions, messages, urls)
270
+ }
271
+
272
+ async fn fetch_image_data(
273
+ url: &str,
274
+ client: Client,
275
+ ) -> Result<(Vec<u8>, Option<image_proto::Dimension>), Box<dyn std::error::Error + Send + Sync>> {
276
+ // 在进入异步操作前获取并释放锁
277
+ let vision_ability = AppConfig::get_vision_ability();
278
+
279
+ match vision_ability {
280
+ VisionAbility::None => Err("图片功能已禁用".into()),
281
+
282
+ VisionAbility::Base64 => {
283
+ if !url.starts_with("data:image/") {
284
+ return Err("仅支持 base64 编码的图片".into());
285
+ }
286
+ process_base64_image(url)
287
+ }
288
+
289
+ VisionAbility::All => {
290
+ if url.starts_with("data:image/") {
291
+ process_base64_image(url)
292
+ } else {
293
+ process_http_image(url, client).await
294
+ }
295
+ }
296
+ }
297
+ }
298
+
299
+ // 处理 base64 编码的图片
300
+ fn process_base64_image(
301
+ url: &str,
302
+ ) -> Result<(Vec<u8>, Option<image_proto::Dimension>), Box<dyn std::error::Error + Send + Sync>> {
303
+ let parts: Vec<&str> = url.split("base64,").collect();
304
+ if parts.len() != 2 {
305
+ return Err("无效的 base64 图片格式".into());
306
+ }
307
+
308
+ // 检查图片格式
309
+ let format = parts[0].to_lowercase();
310
+ if !format.contains("png")
311
+ && !format.contains("jpeg")
312
+ && !format.contains("jpg")
313
+ && !format.contains("webp")
314
+ && !format.contains("gif")
315
+ {
316
+ return Err(ERR_UNSUPPORTED_IMAGE_FORMAT.into());
317
+ }
318
+
319
+ let image_data = BASE64.decode(parts[1])?;
320
+
321
+ // 检查是否为动态 GIF
322
+ if format.contains("gif") {
323
+ if let Ok(frames) = gif::DecodeOptions::new().read_info(std::io::Cursor::new(&image_data)) {
324
+ if frames.into_iter().count() > 1 {
325
+ return Err(ERR_UNSUPPORTED_GIF.into());
326
+ }
327
+ }
328
+ }
329
+
330
+ // 获取图片尺寸
331
+ let dimensions = if let Ok(img) = image::load_from_memory(&image_data) {
332
+ Some(image_proto::Dimension {
333
+ width: img.width() as i32,
334
+ height: img.height() as i32,
335
+ })
336
+ } else {
337
+ None
338
+ };
339
+
340
+ Ok((image_data, dimensions))
341
+ }
342
+
343
+ // 处理 HTTP 图片 URL
344
+ async fn process_http_image(
345
+ url: &str,
346
+ client: Client,
347
+ ) -> Result<(Vec<u8>, Option<image_proto::Dimension>), Box<dyn std::error::Error + Send + Sync>> {
348
+ let response = client.get(url).send().await?;
349
+ let image_data = response.bytes().await?.to_vec();
350
+ let format = guess_format(&image_data)?;
351
+
352
+ // 检查图片格式
353
+ match format {
354
+ image::ImageFormat::Png | image::ImageFormat::Jpeg | image::ImageFormat::WebP => {
355
+ // 这些格式都支持
356
+ }
357
+ image::ImageFormat::Gif => {
358
+ if let Ok(frames) =
359
+ gif::DecodeOptions::new().read_info(std::io::Cursor::new(&image_data))
360
+ {
361
+ if frames.into_iter().count() > 1 {
362
+ return Err(ERR_UNSUPPORTED_GIF.into());
363
+ }
364
+ }
365
+ }
366
+ _ => return Err(ERR_UNSUPPORTED_IMAGE_FORMAT.into()),
367
+ }
368
+
369
+ // 获取图片尺寸
370
+ let dimensions = if let Ok(img) = image::load_from_memory_with_format(&image_data, format) {
371
+ Some(image_proto::Dimension {
372
+ width: img.width() as i32,
373
+ height: img.height() as i32,
374
+ })
375
+ } else {
376
+ None
377
+ };
378
+
379
+ Ok((image_data, dimensions))
380
+ }
381
+
382
+ pub async fn encode_chat_message(
383
+ inputs: Vec<Message>,
384
+ model_name: &str,
385
+ disable_vision: bool,
386
+ enable_slow_pool: bool,
387
+ is_search: bool,
388
+ ) -> Result<Vec<u8>, Box<dyn std::error::Error + Send + Sync>> {
389
+ // 在进入异步操作前获取并释放锁
390
+ let enable_slow_pool = {
391
+ if enable_slow_pool {
392
+ Some(true)
393
+ } else {
394
+ None
395
+ }
396
+ };
397
+
398
+ let (instructions, messages, urls) = process_chat_inputs(inputs, disable_vision).await;
399
+
400
+ let explicit_context = if !instructions.trim().is_empty() {
401
+ Some(ExplicitContext {
402
+ context: instructions,
403
+ repo_context: None,
404
+ })
405
+ } else {
406
+ None
407
+ };
408
+
409
+ let base_uuid = rand::random::<u16>();
410
+ let external_links = urls.into_iter().enumerate().map(|(i, url)| {
411
+ let uuid = base_uuid.wrapping_add(i as u16);
412
+ ChatExternalLink {
413
+ url,
414
+ uuid: uuid.to_string(),
415
+ }
416
+ }).collect();
417
+
418
+ let chat = GetChatRequest {
419
+ current_file: None,
420
+ conversation: messages,
421
+ repositories: vec![],
422
+ explicit_context,
423
+ workspace_root_path: None,
424
+ code_blocks: vec![],
425
+ model_details: Some(ModelDetails {
426
+ model_name: Some(model_name.to_string()),
427
+ api_key: None,
428
+ enable_ghost_mode: None,
429
+ azure_state: Some(AzureState {
430
+ api_key: String::new(),
431
+ base_url: String::new(),
432
+ deployment: String::new(),
433
+ use_azure: false,
434
+ }),
435
+ enable_slow_pool,
436
+ openai_api_base_url: None,
437
+ }),
438
+ documentation_identifiers: vec![],
439
+ request_id: Uuid::new_v4().to_string(),
440
+ linter_errors: None,
441
+ summary: None,
442
+ summary_up_until_index: None,
443
+ allow_long_file_scan: Some(false),
444
+ is_bash: Some(false),
445
+ conversation_id: Uuid::new_v4().to_string(),
446
+ can_handle_filenames_after_language_ids: Some(true),
447
+ use_web: if is_search {
448
+ Some("full_search".to_string())
449
+ } else {
450
+ None
451
+ },
452
+ quotes: vec![],
453
+ debug_info: None,
454
+ workspace_id: None,
455
+ external_links,
456
+ commit_notes: vec![],
457
+ long_context_mode: Some(LONG_CONTEXT_MODELS.contains(&model_name)),
458
+ is_eval: Some(false),
459
+ desired_max_tokens: None,
460
+ context_ast: None,
461
+ is_composer: None,
462
+ runnable_code_blocks: Some(false),
463
+ should_cache: Some(false),
464
+ };
465
+
466
+ let mut encoded = Vec::new();
467
+ chat.encode(&mut encoded)?;
468
+
469
+ let len_prefix = format!("{:010x}", encoded.len()).to_uppercase();
470
+ let content = hex::encode_upper(&encoded);
471
+
472
+ Ok(hex::decode(len_prefix + &content)?)
473
+ }
src/chat/aiserver.rs ADDED
@@ -0,0 +1 @@
 
 
1
+ pub mod v1;
src/chat/aiserver/v1.rs ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ include!(concat!(env!("OUT_DIR"), "/aiserver.v1.rs"));
2
+ use error_details::Error;
3
+
4
+ impl ErrorDetails {
5
+ pub fn status_code(&self) -> u16 {
6
+ match Error::try_from(self.error) {
7
+ Ok(error) => match error {
8
+ Error::Unspecified => 500,
9
+ Error::BadApiKey
10
+ | Error::InvalidAuthId
11
+ | Error::AuthTokenNotFound
12
+ | Error::AuthTokenExpired
13
+ | Error::Unauthorized => 401,
14
+ Error::NotLoggedIn
15
+ | Error::NotHighEnoughPermissions
16
+ | Error::AgentRequiresLogin
17
+ | Error::ProUserOnly
18
+ | Error::TaskNoPermissions => 403,
19
+ Error::NotFound
20
+ | Error::UserNotFound
21
+ | Error::TaskUuidNotFound
22
+ | Error::AgentEngineNotFound
23
+ | Error::GitgraphNotFound
24
+ | Error::FileNotFound => 404,
25
+ Error::FreeUserRateLimitExceeded
26
+ | Error::ProUserRateLimitExceeded
27
+ | Error::OpenaiRateLimitExceeded
28
+ | Error::OpenaiAccountLimitExceeded
29
+ | Error::GenericRateLimitExceeded
30
+ | Error::Gpt4VisionPreviewRateLimit
31
+ | Error::ApiKeyRateLimit => 429,
32
+ Error::BadRequest
33
+ | Error::BadModelName
34
+ | Error::SlashEditFileTooLong
35
+ | Error::FileUnsupported
36
+ | Error::ClaudeImageTooLarge => 400,
37
+ Error::Deprecated
38
+ | Error::FreeUserUsageLimit
39
+ | Error::ProUserUsageLimit
40
+ | Error::ResourceExhausted
41
+ | Error::Openai
42
+ | Error::MaxTokens
43
+ | Error::ApiKeyNotSupported
44
+ | Error::UserAbortedRequest
45
+ | Error::CustomMessage
46
+ | Error::OutdatedClient
47
+ | Error::Debounced
48
+ | Error::RepositoryServiceRepositoryIsNotInitialized => 500,
49
+ },
50
+ Err(_) => 500,
51
+ }
52
+ }
53
+
54
+ // pub fn is_expected(&self) -> bool {
55
+ // self.is_expected.unwrap_or_default()
56
+ // }
57
+ }
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/config.rs ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use crate::AppConfig;
2
+
3
+ include!(concat!(env!("OUT_DIR"), "/key.rs"));
4
+
5
+ impl KeyConfig {
6
+ pub fn new_with_global() -> Self {
7
+ Self {
8
+ auth_token: None,
9
+ disable_vision: Some(AppConfig::get_vision_ability().is_none()),
10
+ enable_slow_pool: Some(AppConfig::get_slow_pool()),
11
+ usage_check_models: None,
12
+ include_web_references: Some(AppConfig::get_web_refs()),
13
+ }
14
+ }
15
+
16
+ pub fn copy_without_auth_token(&self, config: &mut Self) {
17
+ if self.disable_vision.is_some() {
18
+ config.disable_vision = self.disable_vision;
19
+ }
20
+ if self.enable_slow_pool.is_some() {
21
+ config.enable_slow_pool = self.enable_slow_pool;
22
+ }
23
+ if self.usage_check_models.is_some() {
24
+ config.usage_check_models = self.usage_check_models.clone();
25
+ }
26
+ if self.include_web_references.is_some() {
27
+ config.include_web_references = self.include_web_references;
28
+ }
29
+ }
30
+ }
src/chat/config/key.proto ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ syntax = "proto3";
2
+
3
+ package key;
4
+
5
+ // 动态配置的 API KEY
6
+ message KeyConfig {
7
+ // 认证令牌信息
8
+ message TokenInfo {
9
+ string sub = 1; // 用户标识符
10
+ int64 exp = 2; // 过期时间(Unix 时间戳)
11
+ string randomness = 3; // 随机字符串
12
+ string signature = 4; // 签名
13
+ bytes machine_id = 5; // 机器ID的SHA256哈希值
14
+ bytes mac_id = 6; // MAC地址的SHA256哈希值
15
+ }
16
+
17
+ // 认证令牌(必需)
18
+ TokenInfo auth_token = 1;
19
+
20
+ // 是否禁用图片处理能力
21
+ optional bool disable_vision = 4;
22
+
23
+ // 是否启用慢速池
24
+ optional bool enable_slow_pool = 5;
25
+
26
+ // 使用量检查模型规则
27
+ message UsageCheckModel {
28
+ // 检查类型
29
+ enum Type {
30
+ TYPE_DEFAULT = 0; // 未指定
31
+ TYPE_DISABLED = 1; // 禁用
32
+ TYPE_ALL = 2; // 全部
33
+ TYPE_CUSTOM = 3; // 自定义列表
34
+ }
35
+ Type type = 1; // 检查类型
36
+ repeated string model_ids = 2; // 模型 ID 列表,当 type 为 TYPE_CUSTOM 时生效
37
+ }
38
+ // 使用量检查模型规则
39
+ optional UsageCheckModel usage_check_models = 6;
40
+
41
+ // 包含网络引用
42
+ optional bool include_web_references = 7;
43
+
44
+ // 密码SHA256哈希值
45
+ // bytes secret = 2;
46
+ }
src/chat/constant.rs ADDED
@@ -0,0 +1,204 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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!(
10
+ ERR_UNSUPPORTED_IMAGE_FORMAT,
11
+ "不支持的图片格式,仅支持 PNG、JPEG、WEBP 和非动态 GIF"
12
+ );
13
+ def_pub_const!(ERR_NODATA, "No data");
14
+
15
+ const MODEL_OBJECT: &str = "model";
16
+ const CREATED: &i64 = &1706659200;
17
+
18
+ def_pub_const!(ANTHROPIC, "anthropic");
19
+ def_pub_const!(CURSOR, "cursor");
20
+ def_pub_const!(GOOGLE, "google");
21
+ def_pub_const!(OPENAI, "openai");
22
+ def_pub_const!(DEEPSEEK, "deepseek");
23
+
24
+ def_pub_const!(CLAUDE_3_5_SONNET, "claude-3.5-sonnet");
25
+ def_pub_const!(GPT_4, "gpt-4");
26
+ def_pub_const!(GPT_4O, "gpt-4o");
27
+ def_pub_const!(CLAUDE_3_OPUS, "claude-3-opus");
28
+ def_pub_const!(CURSOR_FAST, "cursor-fast");
29
+ def_pub_const!(CURSOR_SMALL, "cursor-small");
30
+ def_pub_const!(GPT_3_5_TURBO, "gpt-3.5-turbo");
31
+ def_pub_const!(GPT_4_TURBO_2024_04_09, "gpt-4-turbo-2024-04-09");
32
+ def_pub_const!(GPT_4O_128K, "gpt-4o-128k");
33
+ def_pub_const!(GEMINI_1_5_FLASH_500K, "gemini-1.5-flash-500k");
34
+ def_pub_const!(CLAUDE_3_HAIKU_200K, "claude-3-haiku-200k");
35
+ def_pub_const!(CLAUDE_3_5_SONNET_200K, "claude-3-5-sonnet-200k");
36
+ def_pub_const!(CLAUDE_3_5_SONNET_20241022, "claude-3-5-sonnet-20241022");
37
+ def_pub_const!(GPT_4O_MINI, "gpt-4o-mini");
38
+ def_pub_const!(O1_MINI, "o1-mini");
39
+ def_pub_const!(O1_PREVIEW, "o1-preview");
40
+ def_pub_const!(O1, "o1");
41
+ def_pub_const!(CLAUDE_3_5_HAIKU, "claude-3.5-haiku");
42
+ def_pub_const!(GEMINI_EXP_1206, "gemini-exp-1206");
43
+ def_pub_const!(
44
+ GEMINI_2_0_FLASH_THINKING_EXP,
45
+ "gemini-2.0-flash-thinking-exp"
46
+ );
47
+ def_pub_const!(GEMINI_2_0_FLASH_EXP, "gemini-2.0-flash-exp");
48
+ def_pub_const!(DEEPSEEK_V3, "deepseek-v3");
49
+ def_pub_const!(DEEPSEEK_R1, "deepseek-r1");
50
+
51
+ // #[derive(Clone, PartialEq, rkyv::Archive, rkyv::Deserialize, rkyv::Serialize)]
52
+ // pub enum ModelType {
53
+ // Claude35Sonnet,
54
+ // Gpt4,
55
+ // Gpt4o,
56
+ // Claude3Opus,
57
+ // CursorFast,
58
+ // CursorSmall,
59
+ // Gpt35Turbo,
60
+ // Gpt4Turbo202404,
61
+ // Gpt4o128k,
62
+ // Gemini15Flash500k,
63
+ // Claude3Haiku200k,
64
+ // Claude35Sonnet200k,
65
+ // Claude35Sonnet20241022,
66
+ // Gpt4oMini,
67
+ // O1Mini,
68
+ // O1Preview,
69
+ // O1,
70
+ // Claude35Haiku,
71
+ // GeminiExp1206,
72
+ // Gemini20FlashThinkingExp,
73
+ // Gemini20FlashExp,
74
+ // DeepseekV3,
75
+ // DeepseekR1,
76
+ // }
77
+
78
+ macro_rules! create_model {
79
+ ($($id:expr, $owner:expr),* $(,)?) => {
80
+ pub const AVAILABLE_MODELS: [Model; count!($( ($id, $owner) )*)] = [
81
+ $(
82
+ Model {
83
+ id: $id,
84
+ created: CREATED,
85
+ object: MODEL_OBJECT,
86
+ owned_by: $owner,
87
+ },
88
+ )*
89
+ ];
90
+ };
91
+ }
92
+
93
+ macro_rules! count {
94
+ () => (0);
95
+ (($id:expr, $owner:expr) $( ($id2:expr, $owner2:expr) )*) => (1 + count!($( ($id2, $owner2) )*));
96
+ }
97
+
98
+ // impl ModelType {
99
+ // pub fn as_str_name(&self) -> &'static str {
100
+ // match self {
101
+ // ModelType::Claude35Sonnet => CLAUDE_3_5_SONNET,
102
+ // ModelType::Gpt4 => GPT_4,
103
+ // ModelType::Gpt4o => GPT_4O,
104
+ // ModelType::Claude3Opus => CLAUDE_3_OPUS,
105
+ // ModelType::CursorFast => CURSOR_FAST,
106
+ // ModelType::CursorSmall => CURSOR_SMALL,
107
+ // ModelType::Gpt35Turbo => GPT_3_5_TURBO,
108
+ // ModelType::Gpt4Turbo202404 => GPT_4_TURBO_2024_04_09,
109
+ // ModelType::Gpt4o128k => GPT_4O_128K,
110
+ // ModelType::Gemini15Flash500k => GEMINI_1_5_FLASH_500K,
111
+ // ModelType::Claude3Haiku200k => CLAUDE_3_HAIKU_200K,
112
+ // ModelType::Claude35Sonnet200k => CLAUDE_3_5_SONNET_200K,
113
+ // ModelType::Claude35Sonnet20241022 => CLAUDE_3_5_SONNET_20241022,
114
+ // ModelType::Gpt4oMini => GPT_4O_MINI,
115
+ // ModelType::O1Mini => O1_MINI,
116
+ // ModelType::O1Preview => O1_PREVIEW,
117
+ // ModelType::O1 => O1,
118
+ // ModelType::Claude35Haiku => CLAUDE_3_5_HAIKU,
119
+ // ModelType::GeminiExp1206 => GEMINI_EXP_1206,
120
+ // ModelType::Gemini20FlashThinkingExp => GEMINI_2_0_FLASH_THINKING_EXP,
121
+ // ModelType::Gemini20FlashExp => GEMINI_2_0_FLASH_EXP,
122
+ // ModelType::DeepseekV3 => DEEPSEEK_V3,
123
+ // ModelType::DeepseekR1 => DEEPSEEK_R1,
124
+ // }
125
+ // }
126
+
127
+ // pub fn from_str_name(id :&str) -> Option<ModelType> {
128
+ // match id {
129
+ // CLAUDE_3_5_SONNET => Some(ModelType::Claude35Sonnet),
130
+ // GPT_4 => Some(ModelType::Gpt4),
131
+ // GPT_4O => Some(ModelType::Gpt4o),
132
+ // CLAUDE_3_OPUS => Some(ModelType::Claude3Opus),
133
+ // CURSOR_FAST => Some(ModelType::CursorFast),
134
+ // CURSOR_SMALL => Some(ModelType::CursorSmall),
135
+ // GPT_3_5_TURBO => Some(ModelType::Gpt35Turbo),
136
+ // GPT_4_TURBO_2024_04_09 => Some(ModelType::Gpt4Turbo202404),
137
+ // GPT_4O_128K => Some(ModelType::Gpt4o128k),
138
+ // GEMINI_1_5_FLASH_500K => Some(ModelType::Gemini15Flash500k),
139
+ // CLAUDE_3_HAIKU_200K => Some(ModelType::Claude3Haiku200k),
140
+ // CLAUDE_3_5_SONNET_200K => Some(ModelType::Claude35Sonnet200k),
141
+ // CLAUDE_3_5_SONNET_20241022 => Some(ModelType::Claude35Sonnet20241022),
142
+ // GPT_4O_MINI => Some(ModelType::Gpt4oMini),
143
+ // O1_MINI => Some(ModelType::O1Mini),
144
+ // O1_PREVIEW => Some(ModelType::O1Preview),
145
+ // O1 => Some(ModelType::O1),
146
+ // CLAUDE_3_5_HAIKU => Some(ModelType::Claude35Haiku),
147
+ // GEMINI_EXP_1206 => Some(ModelType::GeminiExp1206),
148
+ // GEMINI_2_0_FLASH_THINKING_EXP => Some(ModelType::Gemini20FlashThinkingExp),
149
+ // GEMINI_2_0_FLASH_EXP => Some(ModelType::Gemini20FlashExp),
150
+ // DEEPSEEK_V3 => Some(ModelType::DeepseekV3),
151
+ // DEEPSEEK_R1 => Some(ModelType::DeepseekR1),
152
+ // _ => None,
153
+ // }
154
+ // }
155
+ // }
156
+
157
+ create_model!(
158
+ CLAUDE_3_5_SONNET, ANTHROPIC,
159
+ GPT_4, OPENAI,
160
+ GPT_4O, OPENAI,
161
+ CLAUDE_3_OPUS, ANTHROPIC,
162
+ CURSOR_FAST, CURSOR,
163
+ CURSOR_SMALL, CURSOR,
164
+ GPT_3_5_TURBO, OPENAI,
165
+ GPT_4_TURBO_2024_04_09, OPENAI,
166
+ GPT_4O_128K, OPENAI,
167
+ GEMINI_1_5_FLASH_500K, GOOGLE,
168
+ CLAUDE_3_HAIKU_200K, ANTHROPIC,
169
+ CLAUDE_3_5_SONNET_200K, ANTHROPIC,
170
+ CLAUDE_3_5_SONNET_20241022, ANTHROPIC,
171
+ GPT_4O_MINI, OPENAI,
172
+ O1_MINI, OPENAI,
173
+ O1_PREVIEW, OPENAI,
174
+ O1, OPENAI,
175
+ CLAUDE_3_5_HAIKU, ANTHROPIC,
176
+ GEMINI_EXP_1206, GOOGLE,
177
+ GEMINI_2_0_FLASH_THINKING_EXP, GOOGLE,
178
+ GEMINI_2_0_FLASH_EXP, GOOGLE,
179
+ DEEPSEEK_V3, DEEPSEEK,
180
+ DEEPSEEK_R1, DEEPSEEK,
181
+ );
182
+
183
+ pub const USAGE_CHECK_MODELS: [&str; 11] = [
184
+ CLAUDE_3_5_SONNET_20241022,
185
+ CLAUDE_3_5_SONNET,
186
+ GEMINI_EXP_1206,
187
+ GPT_4,
188
+ GPT_4_TURBO_2024_04_09,
189
+ GPT_4O,
190
+ CLAUDE_3_5_HAIKU,
191
+ GPT_4O_128K,
192
+ GEMINI_1_5_FLASH_500K,
193
+ CLAUDE_3_HAIKU_200K,
194
+ CLAUDE_3_5_SONNET_200K,
195
+ ];
196
+
197
+ pub const LONG_CONTEXT_MODELS: [&str; 4] = [
198
+ GPT_4O_128K,
199
+ GEMINI_1_5_FLASH_500K,
200
+ CLAUDE_3_HAIKU_200K,
201
+ CLAUDE_3_5_SONNET_200K,
202
+ ];
203
+
204
+ // include!("constant/models.rs");
src/chat/constant/models.rs ADDED
@@ -0,0 +1,118 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ pub struct DefaultModel {
2
+ pub default_on: bool,
3
+ pub is_long_context_only: Option<bool>,
4
+ pub name: &'static str,
5
+ }
6
+
7
+ pub const AVAILABLE_MODELS2: [DefaultModel; 22] = [
8
+ DefaultModel {
9
+ default_on: true,
10
+ is_long_context_only: Some(false),
11
+ name: CLAUDE_3_5_SONNET,
12
+ },
13
+ DefaultModel {
14
+ default_on: false,
15
+ is_long_context_only: None,
16
+ name: GPT_4,
17
+ },
18
+ DefaultModel {
19
+ default_on: true,
20
+ is_long_context_only: None,
21
+ name: GPT_4O,
22
+ },
23
+ DefaultModel {
24
+ default_on: false,
25
+ is_long_context_only: None,
26
+ name: CLAUDE_3_OPUS,
27
+ },
28
+ DefaultModel {
29
+ default_on: false,
30
+ is_long_context_only: None,
31
+ name: CURSOR_FAST,
32
+ },
33
+ DefaultModel {
34
+ default_on: false,
35
+ is_long_context_only: None,
36
+ name: CURSOR_SMALL,
37
+ },
38
+ DefaultModel {
39
+ default_on: false,
40
+ is_long_context_only: None,
41
+ name: GPT_3_5_TURBO,
42
+ },
43
+ DefaultModel {
44
+ default_on: false,
45
+ is_long_context_only: None,
46
+ name: GPT_4_TURBO_2024_04_09,
47
+ },
48
+ DefaultModel {
49
+ default_on: true,
50
+ is_long_context_only: Some(true),
51
+ name: GPT_4O_128K,
52
+ },
53
+ DefaultModel {
54
+ default_on: true,
55
+ is_long_context_only: Some(true),
56
+ name: GEMINI_1_5_FLASH_500K,
57
+ },
58
+ DefaultModel {
59
+ default_on: true,
60
+ is_long_context_only: Some(true),
61
+ name: CLAUDE_3_HAIKU_200K,
62
+ },
63
+ DefaultModel {
64
+ default_on: true,
65
+ is_long_context_only: Some(true),
66
+ name: CLAUDE_3_5_SONNET_200K,
67
+ },
68
+ DefaultModel {
69
+ default_on: false,
70
+ is_long_context_only: Some(false),
71
+ name: CLAUDE_3_5_SONNET_20241022,
72
+ },
73
+ DefaultModel {
74
+ default_on: true,
75
+ is_long_context_only: Some(false),
76
+ name: GPT_4O_MINI,
77
+ },
78
+ DefaultModel {
79
+ default_on: true,
80
+ is_long_context_only: Some(false),
81
+ name: O1_MINI,
82
+ },
83
+ DefaultModel {
84
+ default_on: true,
85
+ is_long_context_only: Some(false),
86
+ name: O1_PREVIEW,
87
+ },
88
+ DefaultModel {
89
+ default_on: true,
90
+ is_long_context_only: Some(false),
91
+ name: O1,
92
+ },
93
+ DefaultModel {
94
+ default_on: false,
95
+ is_long_context_only: Some(false),
96
+ name: CLAUDE_3_5_HAIKU,
97
+ },
98
+ DefaultModel {
99
+ default_on: false,
100
+ is_long_context_only: None,
101
+ name: GEMINI_EXP_1206,
102
+ },
103
+ DefaultModel {
104
+ default_on: false,
105
+ is_long_context_only: None,
106
+ name: GEMINI_2_0_FLASH_THINKING_EXP,
107
+ },
108
+ DefaultModel {
109
+ default_on: false,
110
+ is_long_context_only: None,
111
+ name: GEMINI_2_0_FLASH_EXP,
112
+ },
113
+ DefaultModel {
114
+ default_on: false,
115
+ is_long_context_only: None,
116
+ name: DEEPSEEK_V3,
117
+ },
118
+ ];
src/chat/error.rs ADDED
@@ -0,0 +1,139 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use super::aiserver::v1::ErrorDetails;
2
+ use crate::common::model::{ApiStatus, ErrorResponse as CommonErrorResponse};
3
+ use base64::{engine::general_purpose::STANDARD_NO_PAD, Engine as _};
4
+ use prost::Message as _;
5
+ use reqwest::StatusCode;
6
+ use serde::{Deserialize, Serialize};
7
+
8
+ #[derive(Deserialize)]
9
+ pub struct ChatError {
10
+ error: ErrorBody,
11
+ }
12
+
13
+ #[derive(Deserialize)]
14
+ pub struct ErrorBody {
15
+ code: String,
16
+ // message: String, always: Error
17
+ details: Vec<ErrorDetail>,
18
+ }
19
+
20
+ #[derive(Deserialize)]
21
+ pub struct ErrorDetail {
22
+ // #[serde(rename = "type")]
23
+ // error_type: String, always: aiserver.v1.ErrorDetails
24
+ // debug: ErrorDebug,
25
+ value: String,
26
+ }
27
+
28
+ // #[derive(Deserialize)]
29
+ // pub struct ErrorDebug {
30
+ // error: String,
31
+ // details: ErrorDetails,
32
+ // // #[serde(rename = "isExpected")]
33
+ // // is_expected: Option<bool>,
34
+ // }
35
+
36
+ // #[derive(Deserialize)]
37
+ // pub struct ErrorDetails {
38
+ // title: String,
39
+ // detail: String,
40
+ // // #[serde(rename = "isRetryable")]
41
+ // // is_retryable: Option<bool>,
42
+ // }
43
+
44
+ impl ChatError {
45
+ pub fn to_error_response(self) -> ErrorResponse {
46
+ if self.error.details.is_empty() {
47
+ return ErrorResponse {
48
+ status: 500,
49
+ code: "unknown".to_string(),
50
+ error: None,
51
+ };
52
+ }
53
+
54
+ let error_details = self.error.details.first().and_then(|detail| {
55
+ STANDARD_NO_PAD
56
+ .decode(&detail.value)
57
+ .ok()
58
+ .map(bytes::Bytes::from)
59
+ .and_then(|buf| ErrorDetails::decode(buf).ok())
60
+ });
61
+
62
+ let status = error_details
63
+ .as_ref()
64
+ .map(|details| details.status_code())
65
+ .unwrap_or(500);
66
+
67
+ ErrorResponse {
68
+ status,
69
+ code: self.error.code,
70
+ error: error_details
71
+ .and_then(|details| details.details)
72
+ .map(|custom_details| Error {
73
+ message: custom_details.title,
74
+ details: custom_details.detail,
75
+ }),
76
+ }
77
+ }
78
+ }
79
+
80
+ #[derive(Serialize)]
81
+ pub struct ErrorResponse {
82
+ pub status: u16,
83
+ pub code: String,
84
+ #[serde(skip_serializing_if = "Option::is_none")]
85
+ pub error: Option<Error>,
86
+ }
87
+
88
+ #[derive(Serialize)]
89
+ pub struct Error {
90
+ pub message: String,
91
+ pub details: String,
92
+ // pub value: String,
93
+ }
94
+
95
+ impl ErrorResponse {
96
+ // pub fn to_json(&self) -> serde_json::Value {
97
+ // serde_json::to_value(self).unwrap()
98
+ // }
99
+
100
+ pub fn status_code(&self) -> StatusCode {
101
+ StatusCode::from_u16(self.status).unwrap()
102
+ }
103
+
104
+ pub fn native_code(&self) -> String {
105
+ self.error.as_ref().map_or_else(
106
+ || self.code.replace("_", " "),
107
+ |error| error.message.clone(),
108
+ )
109
+ }
110
+
111
+ pub fn to_common(self) -> CommonErrorResponse {
112
+ CommonErrorResponse {
113
+ status: ApiStatus::Error,
114
+ code: Some(self.status),
115
+ error: self
116
+ .error
117
+ .as_ref()
118
+ .map(|error| error.message.clone())
119
+ .or(Some(self.code.clone())),
120
+ message: self.error.as_ref().map(|error| error.details.clone()),
121
+ }
122
+ }
123
+ }
124
+
125
+ pub enum StreamError {
126
+ ChatError(ChatError),
127
+ DataLengthLessThan5,
128
+ EmptyStream,
129
+ }
130
+
131
+ impl std::fmt::Display for StreamError {
132
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
133
+ match self {
134
+ StreamError::ChatError(error) => write!(f, "{}", error.error.code),
135
+ StreamError::DataLengthLessThan5 => write!(f, "data length less than 5"),
136
+ StreamError::EmptyStream => write!(f, "empty stream"),
137
+ }
138
+ }
139
+ }
src/chat/middleware.rs ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ mod auth;
2
+ pub use auth::*;
src/chat/middleware/auth.rs ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use crate::app::{constant::AUTHORIZATION_BEARER_PREFIX, lazy::AUTH_TOKEN};
2
+ use axum::{
3
+ body::Body,
4
+ http::{header::AUTHORIZATION, Request, StatusCode},
5
+ middleware::Next,
6
+ response::Response,
7
+ };
8
+
9
+ // 认证中间件函数
10
+ pub async fn auth_middleware(request: Request<Body>, next: Next) -> Result<Response, StatusCode> {
11
+ let auth_header = request
12
+ .headers()
13
+ .get(AUTHORIZATION)
14
+ .and_then(|h| h.to_str().ok())
15
+ .and_then(|h| h.strip_prefix(AUTHORIZATION_BEARER_PREFIX))
16
+ .ok_or(StatusCode::UNAUTHORIZED)?;
17
+
18
+ if auth_header != AUTH_TOKEN.as_str() {
19
+ return Err(StatusCode::UNAUTHORIZED);
20
+ }
21
+
22
+ Ok(next.run(request).await)
23
+ }
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 super::constant::USAGE_CHECK_MODELS;
90
+ use crate::app::model::{AppConfig, UsageCheck};
91
+
92
+ impl Model {
93
+ pub fn is_usage_check(&self, usage_check: Option<UsageCheck>) -> bool {
94
+ match usage_check.unwrap_or(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,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 tokens;
6
+ pub use tokens::{
7
+ handle_add_tokens, handle_basic_calibration, handle_delete_tokens, handle_get_checksum,
8
+ handle_get_hash, handle_get_timestamp_header, handle_get_tokens, handle_reload_tokens,
9
+ handle_tokens_page, handle_update_tokens,
10
+ };
11
+ mod profile;
12
+ pub use profile::handle_user_info;
13
+ mod config;
14
+ pub use config::{
15
+ handle_about, handle_build_key, handle_build_key_page, handle_config_page, handle_env_example,
16
+ handle_readme, handle_static,
17
+ };
18
+ mod api;
19
+ 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,204 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use crate::{
2
+ app::{
3
+ constant::{
4
+ AUTHORIZATION_BEARER_PREFIX, CONTENT_TYPE_TEXT_CSS_WITH_UTF8, CONTENT_TYPE_TEXT_HTML_WITH_UTF8, CONTENT_TYPE_TEXT_JS_WITH_UTF8, CONTENT_TYPE_TEXT_PLAIN_WITH_UTF8, ROUTE_ABOUT_PATH, ROUTE_BUILD_KEY_PATH, ROUTE_CONFIG_PATH, ROUTE_README_PATH, ROUTE_SHARED_JS_PATH, ROUTE_SHARED_STYLES_PATH
5
+ },
6
+ lazy::{AUTH_TOKEN, KEY_PREFIX},
7
+ model::{AppConfig, BuildKeyRequest, BuildKeyResponse, PageContent, UsageCheckModelType},
8
+ },
9
+ chat::config::{key_config, KeyConfig},
10
+ common::utils::{to_base64, token_to_tokeninfo},
11
+ };
12
+ use axum::{
13
+ body::Body,
14
+ extract::Path,
15
+ http::{
16
+ header::{AUTHORIZATION, CONTENT_TYPE, LOCATION},
17
+ HeaderMap, StatusCode,
18
+ },
19
+ response::{IntoResponse, Response},
20
+ Json,
21
+ };
22
+ use prost::Message as _;
23
+
24
+ pub async fn handle_env_example() -> impl IntoResponse {
25
+ Response::builder()
26
+ .header(CONTENT_TYPE, CONTENT_TYPE_TEXT_PLAIN_WITH_UTF8)
27
+ .body(include_str!("../../../.env.example").to_string())
28
+ .unwrap()
29
+ }
30
+
31
+ // 配置页面处理函数
32
+ pub async fn handle_config_page() -> impl IntoResponse {
33
+ match AppConfig::get_page_content(ROUTE_CONFIG_PATH).unwrap_or_default() {
34
+ PageContent::Default => Response::builder()
35
+ .header(CONTENT_TYPE, CONTENT_TYPE_TEXT_HTML_WITH_UTF8)
36
+ .body(include_str!("../../../static/config.min.html").to_string())
37
+ .unwrap(),
38
+ PageContent::Text(content) => Response::builder()
39
+ .header(CONTENT_TYPE, CONTENT_TYPE_TEXT_PLAIN_WITH_UTF8)
40
+ .body(content.clone())
41
+ .unwrap(),
42
+ PageContent::Html(content) => Response::builder()
43
+ .header(CONTENT_TYPE, CONTENT_TYPE_TEXT_HTML_WITH_UTF8)
44
+ .body(content.clone())
45
+ .unwrap(),
46
+ }
47
+ }
48
+
49
+ pub async fn handle_static(Path(path): Path<String>) -> impl IntoResponse {
50
+ match path.as_str() {
51
+ "shared-styles.css" => {
52
+ match AppConfig::get_page_content(ROUTE_SHARED_STYLES_PATH).unwrap_or_default() {
53
+ PageContent::Default => Response::builder()
54
+ .header(CONTENT_TYPE, CONTENT_TYPE_TEXT_CSS_WITH_UTF8)
55
+ .body(include_str!("../../../static/shared-styles.min.css").to_string())
56
+ .unwrap(),
57
+ PageContent::Text(content) | PageContent::Html(content) => Response::builder()
58
+ .header(CONTENT_TYPE, CONTENT_TYPE_TEXT_CSS_WITH_UTF8)
59
+ .body(content.clone())
60
+ .unwrap(),
61
+ }
62
+ }
63
+ "shared.js" => {
64
+ match AppConfig::get_page_content(ROUTE_SHARED_JS_PATH).unwrap_or_default() {
65
+ PageContent::Default => Response::builder()
66
+ .header(CONTENT_TYPE, CONTENT_TYPE_TEXT_JS_WITH_UTF8)
67
+ .body(include_str!("../../../static/shared.min.js").to_string())
68
+ .unwrap(),
69
+ PageContent::Text(content) | PageContent::Html(content) => Response::builder()
70
+ .header(CONTENT_TYPE, CONTENT_TYPE_TEXT_JS_WITH_UTF8)
71
+ .body(content.clone())
72
+ .unwrap(),
73
+ }
74
+ }
75
+ _ => Response::builder()
76
+ .status(StatusCode::NOT_FOUND)
77
+ .body("Not found".to_string())
78
+ .unwrap(),
79
+ }
80
+ }
81
+
82
+ pub async fn handle_readme() -> impl IntoResponse {
83
+ match AppConfig::get_page_content(ROUTE_README_PATH).unwrap_or_default() {
84
+ PageContent::Default => Response::builder()
85
+ .header(CONTENT_TYPE, CONTENT_TYPE_TEXT_HTML_WITH_UTF8)
86
+ .body(include_str!("../../../static/readme.min.html").to_string())
87
+ .unwrap(),
88
+ PageContent::Text(content) => Response::builder()
89
+ .header(CONTENT_TYPE, CONTENT_TYPE_TEXT_PLAIN_WITH_UTF8)
90
+ .body(content.clone())
91
+ .unwrap(),
92
+ PageContent::Html(content) => Response::builder()
93
+ .header(CONTENT_TYPE, CONTENT_TYPE_TEXT_HTML_WITH_UTF8)
94
+ .body(content.clone())
95
+ .unwrap(),
96
+ }
97
+ }
98
+
99
+ pub async fn handle_about() -> impl IntoResponse {
100
+ match AppConfig::get_page_content(ROUTE_ABOUT_PATH).unwrap_or_default() {
101
+ PageContent::Default => Response::builder()
102
+ .status(StatusCode::TEMPORARY_REDIRECT)
103
+ .header(LOCATION, ROUTE_README_PATH)
104
+ .body(Body::empty())
105
+ .unwrap(),
106
+ PageContent::Text(content) => Response::builder()
107
+ .header(CONTENT_TYPE, CONTENT_TYPE_TEXT_PLAIN_WITH_UTF8)
108
+ .body(Body::from(content.clone()))
109
+ .unwrap(),
110
+ PageContent::Html(content) => Response::builder()
111
+ .header(CONTENT_TYPE, CONTENT_TYPE_TEXT_HTML_WITH_UTF8)
112
+ .body(Body::from(content.clone()))
113
+ .unwrap(),
114
+ }
115
+ }
116
+
117
+ pub async fn handle_build_key_page() -> impl IntoResponse {
118
+ match AppConfig::get_page_content(ROUTE_BUILD_KEY_PATH).unwrap_or_default() {
119
+ PageContent::Default => Response::builder()
120
+ .header(CONTENT_TYPE, CONTENT_TYPE_TEXT_HTML_WITH_UTF8)
121
+ .body(include_str!("../../../static/build_key.min.html").to_string())
122
+ .unwrap(),
123
+ PageContent::Text(content) => Response::builder()
124
+ .header(CONTENT_TYPE, CONTENT_TYPE_TEXT_PLAIN_WITH_UTF8)
125
+ .body(content.clone())
126
+ .unwrap(),
127
+ PageContent::Html(content) => Response::builder()
128
+ .header(CONTENT_TYPE, CONTENT_TYPE_TEXT_HTML_WITH_UTF8)
129
+ .body(content.clone())
130
+ .unwrap(),
131
+ }
132
+ }
133
+
134
+ pub async fn handle_build_key(
135
+ headers: HeaderMap,
136
+ Json(request): Json<BuildKeyRequest>,
137
+ ) -> (StatusCode, Json<BuildKeyResponse>) {
138
+ // 验证认证令牌
139
+ if AppConfig::is_share() {
140
+ let auth_header = headers
141
+ .get(AUTHORIZATION)
142
+ .and_then(|h| h.to_str().ok())
143
+ .and_then(|h| h.strip_prefix(AUTHORIZATION_BEARER_PREFIX));
144
+
145
+ if auth_header.map_or(true, |h| h != AppConfig::get_share_token().as_str() && h != AUTH_TOKEN.as_str()) {
146
+ return (
147
+ StatusCode::UNAUTHORIZED,
148
+ Json(BuildKeyResponse::Error("Unauthorized".to_owned())),
149
+ );
150
+ }
151
+ }
152
+
153
+ // 验证并解析 auth_token
154
+ let token_info = match token_to_tokeninfo(&request.auth_token) {
155
+ Some(info) => info,
156
+ None => {
157
+ return (
158
+ StatusCode::BAD_REQUEST,
159
+ Json(BuildKeyResponse::Error("Invalid auth token".to_owned())),
160
+ )
161
+ }
162
+ };
163
+
164
+ // 构建 proto 消息
165
+ let mut key_config = KeyConfig {
166
+ auth_token: Some(token_info),
167
+ disable_vision: request.disable_vision,
168
+ enable_slow_pool: request.enable_slow_pool,
169
+ usage_check_models: None,
170
+ include_web_references: request.include_web_references,
171
+ };
172
+
173
+ if let Some(usage_check_models) = request.usage_check_models {
174
+ let usage_check = key_config::UsageCheckModel {
175
+ r#type: match usage_check_models.model_type {
176
+ UsageCheckModelType::Default => {
177
+ key_config::usage_check_model::Type::Default as i32
178
+ }
179
+ UsageCheckModelType::Disabled => {
180
+ key_config::usage_check_model::Type::Disabled as i32
181
+ }
182
+ UsageCheckModelType::All => key_config::usage_check_model::Type::All as i32,
183
+ UsageCheckModelType::Custom => key_config::usage_check_model::Type::Custom as i32,
184
+ },
185
+ model_ids: if matches!(usage_check_models.model_type, UsageCheckModelType::Custom) {
186
+ usage_check_models
187
+ .model_ids
188
+ .iter()
189
+ .map(|s| s.to_string())
190
+ .collect()
191
+ } else {
192
+ Vec::new()
193
+ },
194
+ };
195
+ key_config.usage_check_models = Some(usage_check);
196
+ }
197
+
198
+ // 序列化
199
+ let encoded = key_config.encode_to_vec();
200
+
201
+ let key = format!("{}{}", *KEY_PREFIX, to_base64(&encoded));
202
+
203
+ (StatusCode::OK, Json(BuildKeyResponse::Key(key)))
204
+ }
src/chat/route/health.rs ADDED
@@ -0,0 +1,140 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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_BUILD_KEY_PATH, ROUTE_CONFIG_PATH,
7
+ ROUTE_ENV_EXAMPLE_PATH, ROUTE_GET_CHECKSUM, ROUTE_GET_HASH, ROUTE_GET_TIMESTAMP_HEADER,
8
+ ROUTE_HEALTH_PATH, ROUTE_LOGS_PATH, ROUTE_README_PATH, ROUTE_ROOT_PATH,
9
+ ROUTE_STATIC_PATH, ROUTE_TOKENS_ADD_PATH, ROUTE_TOKENS_DELETE_PATH,
10
+ ROUTE_TOKENS_GET_PATH, ROUTE_TOKENS_PATH, ROUTE_TOKENS_UPDATE_PATH,
11
+ ROUTE_USER_INFO_PATH,
12
+ },
13
+ lazy::{get_start_time, AUTH_TOKEN, ROUTE_CHAT_PATH, ROUTE_MODELS_PATH},
14
+ model::{AppConfig, AppState, PageContent},
15
+ },
16
+ chat::constant::AVAILABLE_MODELS,
17
+ common::model::{
18
+ health::{CpuInfo, HealthCheckResponse, MemoryInfo, SystemInfo, SystemStats},
19
+ ApiStatus,
20
+ },
21
+ };
22
+ use axum::{
23
+ body::Body,
24
+ extract::State,
25
+ http::{
26
+ header::{CONTENT_TYPE, LOCATION},
27
+ HeaderMap, StatusCode,
28
+ },
29
+ response::{IntoResponse, Response},
30
+ Json,
31
+ };
32
+ use chrono::Local;
33
+ use reqwest::header::AUTHORIZATION;
34
+ use std::sync::Arc;
35
+ use sysinfo::{CpuRefreshKind, MemoryRefreshKind, RefreshKind, System};
36
+ use tokio::sync::Mutex;
37
+
38
+ pub async fn handle_root() -> impl IntoResponse {
39
+ match AppConfig::get_page_content(ROUTE_ROOT_PATH).unwrap_or_default() {
40
+ PageContent::Default => Response::builder()
41
+ .status(StatusCode::TEMPORARY_REDIRECT)
42
+ .header(LOCATION, ROUTE_HEALTH_PATH)
43
+ .body(Body::empty())
44
+ .unwrap(),
45
+ PageContent::Text(content) => Response::builder()
46
+ .header(CONTENT_TYPE, CONTENT_TYPE_TEXT_PLAIN_WITH_UTF8)
47
+ .body(Body::from(content.clone()))
48
+ .unwrap(),
49
+ PageContent::Html(content) => Response::builder()
50
+ .header(CONTENT_TYPE, CONTENT_TYPE_TEXT_HTML_WITH_UTF8)
51
+ .body(Body::from(content.clone()))
52
+ .unwrap(),
53
+ }
54
+ }
55
+
56
+ pub async fn handle_health(
57
+ State(state): State<Arc<Mutex<AppState>>>,
58
+ headers: HeaderMap,
59
+ ) -> Json<HealthCheckResponse> {
60
+ let start_time = get_start_time();
61
+ let uptime = (Local::now() - start_time).num_seconds();
62
+
63
+ // 先检查 headers 是否包含有效的认证信息
64
+ let stats = if headers
65
+ .get(AUTHORIZATION)
66
+ .and_then(|h| h.to_str().ok())
67
+ .and_then(|h| h.strip_prefix(AUTHORIZATION_BEARER_PREFIX))
68
+ .map_or(false, |token| token == AUTH_TOKEN.as_str())
69
+ {
70
+ // 只有在需要系统信息时才创建实例
71
+ let mut sys = System::new_with_specifics(
72
+ RefreshKind::nothing()
73
+ .with_memory(MemoryRefreshKind::everything())
74
+ .with_cpu(CpuRefreshKind::everything()),
75
+ );
76
+
77
+ std::thread::sleep(sysinfo::MINIMUM_CPU_UPDATE_INTERVAL);
78
+
79
+ // 刷新 CPU 和内存信息
80
+ sys.refresh_memory();
81
+ sys.refresh_cpu_usage();
82
+
83
+ let pid = std::process::id() as usize;
84
+ let process = sys.process(pid.into());
85
+
86
+ // 获取内存信息
87
+ let memory = process.map(|p| p.memory()).unwrap_or(0);
88
+
89
+ // 获取 CPU 使用率
90
+ let cpu_usage = sys.global_cpu_usage();
91
+
92
+ let state = state.lock().await;
93
+
94
+ Some(SystemStats {
95
+ started: start_time.to_string(),
96
+ total_requests: state.total_requests,
97
+ active_requests: state.active_requests,
98
+ system: SystemInfo {
99
+ memory: MemoryInfo {
100
+ rss: memory, // 物理内存使用量(字节)
101
+ },
102
+ cpu: CpuInfo {
103
+ usage: cpu_usage, // CPU 使用率(百分比)
104
+ },
105
+ },
106
+ })
107
+ } else {
108
+ None
109
+ };
110
+
111
+ Json(HealthCheckResponse {
112
+ status: ApiStatus::Healthy,
113
+ version: PKG_VERSION,
114
+ uptime,
115
+ stats,
116
+ models: AVAILABLE_MODELS.iter().map(|m| m.id).collect::<Vec<_>>(),
117
+ endpoints: vec![
118
+ ROUTE_CHAT_PATH.as_str(),
119
+ ROUTE_MODELS_PATH.as_str(),
120
+ ROUTE_TOKENS_PATH,
121
+ ROUTE_TOKENS_GET_PATH,
122
+ ROUTE_TOKENS_UPDATE_PATH,
123
+ ROUTE_TOKENS_ADD_PATH,
124
+ ROUTE_TOKENS_DELETE_PATH,
125
+ ROUTE_LOGS_PATH,
126
+ ROUTE_ENV_EXAMPLE_PATH,
127
+ ROUTE_CONFIG_PATH,
128
+ ROUTE_STATIC_PATH,
129
+ ROUTE_ABOUT_PATH,
130
+ ROUTE_README_PATH,
131
+ ROUTE_API_PATH,
132
+ ROUTE_GET_HASH,
133
+ ROUTE_GET_CHECKSUM,
134
+ ROUTE_GET_TIMESTAMP_HEADER,
135
+ ROUTE_BASIC_CALIBRATION_PATH,
136
+ ROUTE_USER_INFO_PATH,
137
+ ROUTE_BUILD_KEY_PATH,
138
+ ],
139
+ })
140
+ }
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::{model::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::{model::userinfo::GetUserInfo, utils::{extract_token, get_token_profile}},
4
+ };
5
+ use axum::Json;
6
+
7
+ use super::tokens::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/tokens.rs ADDED
@@ -0,0 +1,481 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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_TOKENS_PATH,
6
+ },
7
+ lazy::{AUTH_TOKEN, TOKEN_LIST_FILE},
8
+ model::{
9
+ AppConfig, AppState, PageContent, TokenAddRequestTokenInfo, TokenInfo,
10
+ TokenUpdateRequest, TokensDeleteRequest, TokensDeleteResponse,
11
+ },
12
+ },
13
+ common::{
14
+ model::{error::ChatError, ApiStatus, ErrorResponse},
15
+ utils::{
16
+ extract_time, extract_time_ks, extract_user_id, generate_checksum_with_default,
17
+ generate_checksum_with_repair, generate_hash, generate_timestamp_header, load_tokens,
18
+ parse_token, validate_token, validate_token_and_checksum, write_tokens,
19
+ },
20
+ },
21
+ };
22
+ use axum::{
23
+ extract::{Query, State},
24
+ http::{
25
+ header::{AUTHORIZATION, CONTENT_TYPE},
26
+ HeaderMap,
27
+ },
28
+ response::{IntoResponse, Response},
29
+ Json,
30
+ };
31
+ use reqwest::StatusCode;
32
+ use serde::{Deserialize, Serialize};
33
+ use std::sync::Arc;
34
+ use tokio::sync::Mutex;
35
+
36
+ pub async fn handle_get_hash() -> Response {
37
+ let hash = generate_hash();
38
+
39
+ let mut headers = HeaderMap::new();
40
+ headers.insert(
41
+ CONTENT_TYPE,
42
+ CONTENT_TYPE_TEXT_PLAIN_WITH_UTF8.parse().unwrap(),
43
+ );
44
+
45
+ (headers, hash).into_response()
46
+ }
47
+
48
+ #[derive(Deserialize)]
49
+ pub struct ChecksumQuery {
50
+ #[serde(default)]
51
+ pub checksum: Option<String>,
52
+ }
53
+
54
+ pub async fn handle_get_checksum(Query(query): Query<ChecksumQuery>) -> Response {
55
+ let checksum = match query.checksum {
56
+ None => generate_checksum_with_default(),
57
+ Some(checksum) => generate_checksum_with_repair(&checksum),
58
+ };
59
+
60
+ let mut headers = HeaderMap::new();
61
+ headers.insert(
62
+ CONTENT_TYPE,
63
+ CONTENT_TYPE_TEXT_PLAIN_WITH_UTF8.parse().unwrap(),
64
+ );
65
+
66
+ (headers, checksum).into_response()
67
+ }
68
+
69
+ pub async fn handle_get_timestamp_header() -> Response {
70
+ let timestamp_header = generate_timestamp_header();
71
+
72
+ let mut headers = HeaderMap::new();
73
+ headers.insert(
74
+ CONTENT_TYPE,
75
+ CONTENT_TYPE_TEXT_PLAIN_WITH_UTF8.parse().unwrap(),
76
+ );
77
+
78
+ (headers, timestamp_header).into_response()
79
+ }
80
+
81
+ pub async fn handle_get_tokens(
82
+ State(state): State<Arc<Mutex<AppState>>>,
83
+ headers: HeaderMap,
84
+ ) -> Result<Json<TokenInfoResponse>, StatusCode> {
85
+ // 验证 AUTH_TOKEN
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(StatusCode::UNAUTHORIZED)?;
91
+
92
+ if auth_header != AUTH_TOKEN.as_str() {
93
+ return Err(StatusCode::UNAUTHORIZED);
94
+ }
95
+
96
+ let tokens = state.lock().await.token_infos.clone();
97
+ let tokens_count = tokens.len();
98
+
99
+ Ok(Json(TokenInfoResponse {
100
+ status: ApiStatus::Success,
101
+ tokens: Some(tokens),
102
+ tokens_count,
103
+ message: None,
104
+ }))
105
+ }
106
+
107
+ #[derive(Serialize)]
108
+ pub struct TokenInfoResponse {
109
+ pub status: ApiStatus,
110
+ #[serde(skip_serializing_if = "Option::is_none")]
111
+ pub tokens: Option<Vec<TokenInfo>>,
112
+ pub tokens_count: usize,
113
+ #[serde(skip_serializing_if = "Option::is_none")]
114
+ pub message: Option<String>,
115
+ }
116
+
117
+ pub async fn handle_reload_tokens(
118
+ State(state): State<Arc<Mutex<AppState>>>,
119
+ headers: HeaderMap,
120
+ ) -> Result<Json<TokenInfoResponse>, StatusCode> {
121
+ // 验证 AUTH_TOKEN
122
+ let auth_header = headers
123
+ .get(AUTHORIZATION)
124
+ .and_then(|h| h.to_str().ok())
125
+ .and_then(|h| h.strip_prefix(AUTHORIZATION_BEARER_PREFIX))
126
+ .ok_or(StatusCode::UNAUTHORIZED)?;
127
+
128
+ if auth_header != AUTH_TOKEN.as_str() {
129
+ return Err(StatusCode::UNAUTHORIZED);
130
+ }
131
+
132
+ // 重新加载 tokens
133
+ let tokens = load_tokens();
134
+ let tokens_count = tokens.len();
135
+
136
+ // 更新应用状态
137
+ {
138
+ let mut state = state.lock().await;
139
+ state.token_infos = tokens;
140
+ }
141
+
142
+ Ok(Json(TokenInfoResponse {
143
+ status: ApiStatus::Success,
144
+ tokens: None,
145
+ tokens_count,
146
+ message: Some("Token list has been reloaded".to_string()),
147
+ }))
148
+ }
149
+
150
+ pub async fn handle_update_tokens(
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_list_file = TOKEN_LIST_FILE.as_str();
167
+
168
+ std::fs::write(&token_list_file, &request.tokens)
169
+ .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
170
+
171
+ // 重新加载 tokens
172
+ let token_infos = load_tokens();
173
+ let tokens_count = token_infos.len();
174
+
175
+ // 更新应用状态
176
+ {
177
+ let mut state = state.lock().await;
178
+ state.token_infos = token_infos;
179
+ }
180
+
181
+ Ok(Json(TokenInfoResponse {
182
+ status: ApiStatus::Success,
183
+ tokens: None,
184
+ tokens_count,
185
+ message: Some("Token files have been updated and reloaded".to_string()),
186
+ }))
187
+ }
188
+
189
+ pub async fn handle_add_tokens(
190
+ State(state): State<Arc<Mutex<AppState>>>,
191
+ headers: HeaderMap,
192
+ Json(request): Json<Vec<TokenAddRequestTokenInfo>>,
193
+ ) -> Result<Json<TokenInfoResponse>, (StatusCode, Json<ErrorResponse>)> {
194
+ // 验证 AUTH_TOKEN
195
+ let auth_header = headers
196
+ .get(AUTHORIZATION)
197
+ .and_then(|h| h.to_str().ok())
198
+ .and_then(|h| h.strip_prefix(AUTHORIZATION_BEARER_PREFIX))
199
+ .ok_or((
200
+ StatusCode::UNAUTHORIZED,
201
+ Json(ChatError::Unauthorized.to_json()),
202
+ ))?;
203
+
204
+ if auth_header != AUTH_TOKEN.as_str() {
205
+ return Err((
206
+ StatusCode::UNAUTHORIZED,
207
+ Json(ChatError::Unauthorized.to_json()),
208
+ ));
209
+ }
210
+
211
+ let token_list_file = TOKEN_LIST_FILE.as_str();
212
+
213
+ // 获取当前的 tokens 并创建新的 token_infos
214
+ let mut token_infos = {
215
+ let state = state.lock().await;
216
+ state.token_infos.clone()
217
+ };
218
+
219
+ // 创建现有token的集合
220
+ let existing_tokens: std::collections::HashSet<_> =
221
+ token_infos.iter().map(|info| info.token.as_str()).collect();
222
+
223
+ // 预分配容量
224
+ let mut new_tokens = Vec::with_capacity(request.len());
225
+
226
+ // 处理新的tokens
227
+ for token_info in request {
228
+ let parsed_token = parse_token(&token_info.token);
229
+ if !existing_tokens.contains(parsed_token.as_str()) && validate_token(&parsed_token) {
230
+ new_tokens.push(TokenInfo {
231
+ token: parsed_token,
232
+ // 如果提供了checksum就使用提供的,否则生成新的
233
+ checksum: token_info
234
+ .checksum
235
+ .as_deref()
236
+ .map(generate_checksum_with_repair)
237
+ .unwrap_or_else(generate_checksum_with_default),
238
+ profile: None,
239
+ });
240
+ }
241
+ }
242
+
243
+ // 如果有新tokens才进行后续操作
244
+ if !new_tokens.is_empty() {
245
+ // 预分配足够的容量
246
+ token_infos.reserve(new_tokens.len());
247
+ token_infos.extend(new_tokens);
248
+
249
+ // 写入文件
250
+ write_tokens(&token_infos, token_list_file).map_err(|_| {
251
+ (
252
+ StatusCode::INTERNAL_SERVER_ERROR,
253
+ Json(ErrorResponse {
254
+ status: ApiStatus::Error,
255
+ code: None,
256
+ error: Some("Failed to update token list file".to_string()),
257
+ message: Some("无法更新token list文件".to_string()),
258
+ }),
259
+ )
260
+ })?;
261
+
262
+ // 获取最终的tokens数量(在更新状态之前)
263
+ let tokens_count = token_infos.len();
264
+
265
+ // 更新应用状态
266
+ {
267
+ let mut state = state.lock().await;
268
+ state.token_infos = token_infos;
269
+ }
270
+
271
+ Ok(Json(TokenInfoResponse {
272
+ status: ApiStatus::Success,
273
+ tokens: None,
274
+ tokens_count,
275
+ message: Some("New tokens have been added and reloaded".to_string()),
276
+ }))
277
+ } else {
278
+ // 如果没有新tokens,使用原始数量
279
+ let tokens_count = token_infos.len();
280
+
281
+ Ok(Json(TokenInfoResponse {
282
+ status: ApiStatus::Success,
283
+ tokens: None,
284
+ tokens_count,
285
+ message: Some("No new tokens were added".to_string()),
286
+ }))
287
+ }
288
+ }
289
+
290
+ pub async fn handle_delete_tokens(
291
+ State(state): State<Arc<Mutex<AppState>>>,
292
+ headers: HeaderMap,
293
+ Json(request): Json<TokensDeleteRequest>,
294
+ ) -> Result<Json<TokensDeleteResponse>, (StatusCode, Json<ErrorResponse>)> {
295
+ // 验证 AUTH_TOKEN
296
+ let auth_header = headers
297
+ .get(AUTHORIZATION)
298
+ .and_then(|h| h.to_str().ok())
299
+ .and_then(|h| h.strip_prefix(AUTHORIZATION_BEARER_PREFIX))
300
+ .ok_or((
301
+ StatusCode::UNAUTHORIZED,
302
+ Json(ChatError::Unauthorized.to_json()),
303
+ ))?;
304
+
305
+ if auth_header != AUTH_TOKEN.as_str() {
306
+ return Err((
307
+ StatusCode::UNAUTHORIZED,
308
+ Json(ChatError::Unauthorized.to_json()),
309
+ ));
310
+ }
311
+
312
+ let token_infos = state.lock().await.token_infos.clone();
313
+ let original_count = token_infos.len(); // 提前存储原始长度
314
+
315
+ // 获取token_list文件路径
316
+ let token_list_file = TOKEN_LIST_FILE.as_str();
317
+
318
+ // 创建要删除的tokens的HashSet,提高查找效率
319
+ let tokens_to_delete: std::collections::HashSet<_> = request.tokens.iter().collect();
320
+
321
+ // 如果需要的话计算 failed_tokens
322
+ let failed_tokens = if request.expectation.needs_failed_tokens() {
323
+ Some(
324
+ request
325
+ .tokens
326
+ .iter()
327
+ .filter(|token| !token_infos.iter().any(|info| &info.token == *token))
328
+ .cloned()
329
+ .collect::<Vec<String>>(),
330
+ )
331
+ } else {
332
+ None
333
+ };
334
+
335
+ // 预分配容量并过滤掉要删除的tokens
336
+ let estimated_capacity = original_count.saturating_sub(tokens_to_delete.len());
337
+ let mut filtered_token_infos = Vec::with_capacity(estimated_capacity);
338
+
339
+ // 一次性过滤tokens
340
+ for info in token_infos {
341
+ if !tokens_to_delete.contains(&info.token) {
342
+ filtered_token_infos.push(info);
343
+ }
344
+ }
345
+
346
+ // 如果有tokens被删除才进行更新操作
347
+ if filtered_token_infos.len() < original_count {
348
+ // 写入文件
349
+ write_tokens(&filtered_token_infos, token_list_file).map_err(|_| {
350
+ (
351
+ StatusCode::INTERNAL_SERVER_ERROR,
352
+ Json(ErrorResponse {
353
+ status: ApiStatus::Error,
354
+ code: None,
355
+ error: Some("Failed to update token list file".to_string()),
356
+ message: Some("无法更新token list文件".to_string()),
357
+ }),
358
+ )
359
+ })?;
360
+
361
+ // 如果需要的话计算 updated_tokens
362
+ let updated_tokens = if request.expectation.needs_updated_tokens() {
363
+ Some(
364
+ filtered_token_infos
365
+ .iter()
366
+ .map(|info| info.token.clone())
367
+ .collect(),
368
+ )
369
+ } else {
370
+ None
371
+ };
372
+
373
+ // 更新状态
374
+ {
375
+ let mut state = state.lock().await;
376
+ state.token_infos = filtered_token_infos;
377
+ }
378
+
379
+ Ok(Json(TokensDeleteResponse {
380
+ status: ApiStatus::Success,
381
+ updated_tokens,
382
+ failed_tokens,
383
+ }))
384
+ } else {
385
+ // 如果没有tokens被删除
386
+ Ok(Json(TokensDeleteResponse {
387
+ status: ApiStatus::Success,
388
+ updated_tokens: if request.expectation.needs_updated_tokens() {
389
+ Some(
390
+ filtered_token_infos
391
+ .iter()
392
+ .map(|info| info.token.clone())
393
+ .collect(),
394
+ )
395
+ } else {
396
+ None
397
+ },
398
+ failed_tokens,
399
+ }))
400
+ }
401
+ }
402
+
403
+ pub async fn handle_tokens_page() -> impl IntoResponse {
404
+ match AppConfig::get_page_content(ROUTE_TOKENS_PATH).unwrap_or_default() {
405
+ PageContent::Default => Response::builder()
406
+ .header(CONTENT_TYPE, CONTENT_TYPE_TEXT_HTML_WITH_UTF8)
407
+ .body(include_str!("../../../static/tokens.min.html").to_string())
408
+ .unwrap(),
409
+ PageContent::Text(content) => Response::builder()
410
+ .header(CONTENT_TYPE, CONTENT_TYPE_TEXT_PLAIN_WITH_UTF8)
411
+ .body(content.clone())
412
+ .unwrap(),
413
+ PageContent::Html(content) => Response::builder()
414
+ .header(CONTENT_TYPE, CONTENT_TYPE_TEXT_HTML_WITH_UTF8)
415
+ .body(content.clone())
416
+ .unwrap(),
417
+ }
418
+ }
419
+
420
+ #[derive(Deserialize)]
421
+ pub struct TokenRequest {
422
+ pub token: Option<String>,
423
+ }
424
+
425
+ #[derive(Serialize)]
426
+ pub struct BasicCalibrationResponse {
427
+ pub status: ApiStatus,
428
+ pub message: Option<String>,
429
+ #[serde(skip_serializing_if = "Option::is_none")]
430
+ pub user_id: Option<String>,
431
+ #[serde(skip_serializing_if = "Option::is_none")]
432
+ pub create_at: Option<String>,
433
+ #[serde(skip_serializing_if = "Option::is_none")]
434
+ pub checksum_time: Option<u64>,
435
+ }
436
+
437
+ pub async fn handle_basic_calibration(
438
+ Json(request): Json<TokenRequest>,
439
+ ) -> Json<BasicCalibrationResponse> {
440
+ // 从请求头中获取并验证 auth token
441
+ let auth_token = match request.token {
442
+ Some(token) => token,
443
+ None => {
444
+ return Json(BasicCalibrationResponse {
445
+ status: ApiStatus::Error,
446
+ message: Some("未提供授权令牌".to_string()),
447
+ user_id: None,
448
+ create_at: None,
449
+ checksum_time: None,
450
+ })
451
+ }
452
+ };
453
+
454
+ // 校验 token 和 checksum
455
+ let (token, checksum) = match validate_token_and_checksum(&auth_token) {
456
+ Some(parts) => parts,
457
+ None => {
458
+ return Json(BasicCalibrationResponse {
459
+ status: ApiStatus::Error,
460
+ message: Some("无效令牌或无效校验和".to_string()),
461
+ user_id: None,
462
+ create_at: None,
463
+ checksum_time: None,
464
+ })
465
+ }
466
+ };
467
+
468
+ // 提取用户ID和创建时间
469
+ let user_id = extract_user_id(&token);
470
+ let create_at = extract_time(&token).map(|dt| dt.to_string());
471
+ let checksum_time = extract_time_ks(&checksum[..8]);
472
+
473
+ // 返回校验结果
474
+ Json(BasicCalibrationResponse {
475
+ status: ApiStatus::Success,
476
+ message: Some("校验成功".to_string()),
477
+ user_id,
478
+ create_at,
479
+ checksum_time,
480
+ })
481
+ }
src/chat/service.rs ADDED
@@ -0,0 +1,766 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use crate::{
2
+ app::{
3
+ constant::{
4
+ AUTHORIZATION_BEARER_PREFIX, FINISH_REASON_STOP, OBJECT_CHAT_COMPLETION,
5
+ OBJECT_CHAT_COMPLETION_CHUNK,
6
+ },
7
+ lazy::{AUTH_TOKEN, KEY_PREFIX, KEY_PREFIX_LEN, REQUEST_LOGS_LIMIT, SERVICE_TIMEOUT},
8
+ model::{
9
+ AppConfig, AppState, ChatRequest, LogStatus, RequestLog, TimingInfo, TokenInfo,
10
+ UsageCheck,
11
+ },
12
+ },
13
+ chat::{
14
+ config::KeyConfig,
15
+ constant::{AVAILABLE_MODELS, USAGE_CHECK_MODELS},
16
+ error::StreamError,
17
+ model::{
18
+ ChatResponse, Choice, Delta, Message, MessageContent, ModelsResponse, Role, Usage,
19
+ },
20
+ stream::{StreamDecoder, StreamMessage},
21
+ },
22
+ common::{
23
+ client::build_client,
24
+ model::{error::ChatError, userinfo::MembershipType, ApiStatus, ErrorResponse},
25
+ utils::{
26
+ format_time_ms, from_base64, get_token_profile, tokeninfo_to_token,
27
+ validate_token_and_checksum, TrimNewlines as _,
28
+ },
29
+ },
30
+ };
31
+ use axum::{
32
+ body::Body,
33
+ extract::State,
34
+ http::{
35
+ header::{AUTHORIZATION, CONTENT_TYPE},
36
+ HeaderMap, StatusCode,
37
+ },
38
+ response::Response,
39
+ Json,
40
+ };
41
+ use bytes::Bytes;
42
+ use futures::StreamExt;
43
+ use prost::Message as _;
44
+ use std::sync::atomic::{AtomicUsize, Ordering};
45
+ use std::{
46
+ convert::Infallible,
47
+ sync::{atomic::AtomicBool, Arc},
48
+ };
49
+ use tokio::sync::Mutex;
50
+ use uuid::Uuid;
51
+
52
+ // 模型列表处理
53
+ pub async fn handle_models() -> Json<ModelsResponse> {
54
+ Json(ModelsResponse {
55
+ object: "list",
56
+ data: &AVAILABLE_MODELS,
57
+ })
58
+ }
59
+
60
+ // 聊天处理函数的签名
61
+ pub async fn handle_chat(
62
+ State(state): State<Arc<Mutex<AppState>>>,
63
+ headers: HeaderMap,
64
+ Json(request): Json<ChatRequest>,
65
+ ) -> Result<Response<Body>, (StatusCode, Json<ErrorResponse>)> {
66
+ let allow_claude = AppConfig::get_allow_claude();
67
+
68
+ let is_search = request.model.ends_with("-online");
69
+ let model_name = if is_search {
70
+ request.model[..request.model.len() - 7].to_string()
71
+ } else {
72
+ request.model.clone()
73
+ };
74
+
75
+ // 验证模型是否支持并获取模型信息
76
+ let model = AVAILABLE_MODELS.iter().find(|m| m.id == model_name);
77
+ let model_supported = model.is_some();
78
+
79
+ if !(model_supported || allow_claude && request.model.starts_with("claude")) {
80
+ return Err((
81
+ StatusCode::BAD_REQUEST,
82
+ Json(ChatError::ModelNotSupported(request.model).to_json()),
83
+ ));
84
+ }
85
+
86
+ let request_time = chrono::Local::now();
87
+
88
+ // 验证请求
89
+ if request.messages.is_empty() {
90
+ return Err((
91
+ StatusCode::BAD_REQUEST,
92
+ Json(ChatError::EmptyMessages.to_json()),
93
+ ));
94
+ }
95
+
96
+ // 获取并处理认证令牌
97
+ let auth_header = headers
98
+ .get(AUTHORIZATION)
99
+ .and_then(|h| h.to_str().ok())
100
+ .and_then(|h| h.strip_prefix(AUTHORIZATION_BEARER_PREFIX))
101
+ .ok_or((
102
+ StatusCode::UNAUTHORIZED,
103
+ Json(ChatError::Unauthorized.to_json()),
104
+ ))?;
105
+
106
+ let mut current_config = KeyConfig::new_with_global();
107
+
108
+ // 验证认证token并获取token信息
109
+ let (auth_token, checksum) = match auth_header {
110
+ // 管理员Token验证逻辑
111
+ token
112
+ if token == AUTH_TOKEN.as_str()
113
+ || (AppConfig::is_share() && token == AppConfig::get_share_token().as_str()) =>
114
+ {
115
+ static CURRENT_KEY_INDEX: AtomicUsize = AtomicUsize::new(0);
116
+ let state_guard = state.lock().await;
117
+ let token_infos = &state_guard.token_infos;
118
+
119
+ // 检查是否存在可用的token
120
+ if token_infos.is_empty() {
121
+ return Err((
122
+ StatusCode::SERVICE_UNAVAILABLE,
123
+ Json(ChatError::NoTokens.to_json()),
124
+ ));
125
+ }
126
+
127
+ // 轮询选择token
128
+ let index = CURRENT_KEY_INDEX.fetch_add(1, Ordering::SeqCst) % token_infos.len();
129
+ let token_info = &token_infos[index];
130
+ (token_info.token.clone(), token_info.checksum.clone())
131
+ }
132
+
133
+ token if AppConfig::get_dynamic_key() && token.starts_with(&*KEY_PREFIX) => {
134
+ from_base64(&token[*KEY_PREFIX_LEN..])
135
+ .and_then(|decoded_bytes| KeyConfig::decode(&decoded_bytes[..]).ok())
136
+ .and_then(|key_config| {
137
+ key_config.copy_without_auth_token(&mut current_config);
138
+ key_config.auth_token
139
+ })
140
+ .and_then(|token_info| tokeninfo_to_token(&token_info))
141
+ .ok_or((
142
+ StatusCode::UNAUTHORIZED,
143
+ Json(ChatError::Unauthorized.to_json()),
144
+ ))?
145
+ }
146
+
147
+ // 普通用户Token验证逻辑
148
+ token => validate_token_and_checksum(token).ok_or((
149
+ StatusCode::UNAUTHORIZED,
150
+ Json(ChatError::Unauthorized.to_json()),
151
+ ))?,
152
+ };
153
+
154
+ let current_config = current_config;
155
+
156
+ let current_id: u64;
157
+
158
+ // 更新请求日志
159
+ {
160
+ let state_clone = state.clone();
161
+ let mut state = state.lock().await;
162
+ state.total_requests += 1;
163
+ state.active_requests += 1;
164
+
165
+ // 查找最新的相同token的日志,检查使用情况
166
+ let need_profile_check = state
167
+ .request_logs
168
+ .iter()
169
+ .rev()
170
+ .find(|log| log.token_info.token == auth_token && log.token_info.profile.is_some())
171
+ .and_then(|log| log.token_info.profile.as_ref())
172
+ .map(|profile| {
173
+ if profile.stripe.membership_type != MembershipType::Free {
174
+ return false;
175
+ }
176
+
177
+ let is_premium = USAGE_CHECK_MODELS.contains(&model_name.as_str());
178
+ let standard = &profile.usage.standard;
179
+ let premium = &profile.usage.premium;
180
+
181
+ if is_premium {
182
+ premium
183
+ .max_requests
184
+ .map_or(false, |max| premium.num_requests >= max)
185
+ } else {
186
+ standard
187
+ .max_requests
188
+ .map_or(false, |max| standard.num_requests >= max)
189
+ }
190
+ })
191
+ .unwrap_or(false);
192
+
193
+ // 如果达到限制,直接返回未授权错误
194
+ if need_profile_check {
195
+ state.active_requests -= 1;
196
+ state.error_requests += 1;
197
+ return Err((
198
+ StatusCode::UNAUTHORIZED,
199
+ Json(ChatError::Unauthorized.to_json()),
200
+ ));
201
+ }
202
+
203
+ let next_id = state.request_logs.last().map_or(1, |log| log.id + 1);
204
+ current_id = next_id;
205
+
206
+ // 如果需要获取用户使用情况,创建后台任务获取profile
207
+ if model
208
+ .map(|m| {
209
+ m.is_usage_check(UsageCheck::from_proto(
210
+ current_config.usage_check_models.as_ref(),
211
+ ))
212
+ })
213
+ .unwrap_or(false)
214
+ {
215
+ let auth_token_clone = auth_token.clone();
216
+ let state_clone = state_clone.clone();
217
+ let log_id = next_id;
218
+
219
+ tokio::spawn(async move {
220
+ let profile = get_token_profile(&auth_token_clone).await;
221
+ let mut state = state_clone.lock().await;
222
+
223
+ // 先找到所有需要更新的位置的索引
224
+ let token_info_idx = state
225
+ .token_infos
226
+ .iter()
227
+ .position(|info| info.token == auth_token_clone);
228
+
229
+ let log_idx = state.request_logs.iter().rposition(|log| log.id == log_id);
230
+
231
+ // 根据索引更新
232
+ match (token_info_idx, log_idx) {
233
+ (Some(t_idx), Some(l_idx)) => {
234
+ state.token_infos[t_idx].profile = profile.clone();
235
+ state.request_logs[l_idx].token_info.profile = profile;
236
+ }
237
+ (Some(t_idx), None) => {
238
+ state.token_infos[t_idx].profile = profile;
239
+ }
240
+ (None, Some(l_idx)) => {
241
+ state.request_logs[l_idx].token_info.profile = profile;
242
+ }
243
+ (None, None) => {}
244
+ }
245
+ });
246
+ }
247
+
248
+ state.request_logs.push(RequestLog {
249
+ id: next_id,
250
+ timestamp: request_time,
251
+ model: request.model.clone(),
252
+ token_info: TokenInfo {
253
+ token: auth_token.clone(),
254
+ checksum: checksum.clone(),
255
+ profile: None,
256
+ },
257
+ prompt: None,
258
+ timing: TimingInfo {
259
+ total: 0.0,
260
+ first: None,
261
+ },
262
+ stream: request.stream,
263
+ status: LogStatus::Pending,
264
+ error: None,
265
+ });
266
+
267
+ if state.request_logs.len() > *REQUEST_LOGS_LIMIT {
268
+ state.request_logs.remove(0);
269
+ }
270
+ }
271
+
272
+ // 将消息转换为hex格式
273
+ let hex_data = match super::adapter::encode_chat_message(
274
+ request.messages,
275
+ &model_name,
276
+ current_config.disable_vision(),
277
+ current_config.enable_slow_pool(),
278
+ is_search,
279
+ )
280
+ .await
281
+ {
282
+ Ok(data) => data,
283
+ Err(e) => {
284
+ let mut state = state.lock().await;
285
+ if let Some(log) = state
286
+ .request_logs
287
+ .iter_mut()
288
+ .rev()
289
+ .find(|log| log.id == current_id)
290
+ {
291
+ log.status = LogStatus::Failed;
292
+ log.error = Some(e.to_string());
293
+ }
294
+ state.active_requests -= 1;
295
+ state.error_requests += 1;
296
+ return Err((
297
+ StatusCode::INTERNAL_SERVER_ERROR,
298
+ Json(
299
+ ChatError::RequestFailed("Failed to encode chat message".to_string()).to_json(),
300
+ ),
301
+ ));
302
+ }
303
+ };
304
+
305
+ // 构建请求客户端
306
+ let client = build_client(&auth_token, &checksum, is_search);
307
+ // 添加超时设置
308
+ let response = tokio::time::timeout(
309
+ std::time::Duration::from_secs(*SERVICE_TIMEOUT),
310
+ client.body(hex_data).send(),
311
+ )
312
+ .await;
313
+
314
+ // 处理请求结果
315
+ let response = match response {
316
+ Ok(inner_response) => match inner_response {
317
+ Ok(resp) => {
318
+ // 更新请求日志为成功
319
+ {
320
+ let mut state = state.lock().await;
321
+ if let Some(log) = state
322
+ .request_logs
323
+ .iter_mut()
324
+ .rev()
325
+ .find(|log| log.id == current_id)
326
+ {
327
+ log.status = LogStatus::Success;
328
+ }
329
+ }
330
+ resp
331
+ }
332
+ Err(e) => {
333
+ // 更新请求日志为失败
334
+ {
335
+ let mut state = state.lock().await;
336
+ if let Some(log) = state
337
+ .request_logs
338
+ .iter_mut()
339
+ .rev()
340
+ .find(|log| log.id == current_id)
341
+ {
342
+ log.status = LogStatus::Failed;
343
+ log.error = Some(e.to_string());
344
+ }
345
+ state.active_requests -= 1;
346
+ state.error_requests += 1;
347
+ }
348
+ return Err((
349
+ StatusCode::INTERNAL_SERVER_ERROR,
350
+ Json(ChatError::RequestFailed(e.to_string()).to_json()),
351
+ ));
352
+ }
353
+ },
354
+ Err(_) => {
355
+ // 处理超时错误
356
+ {
357
+ let mut state = state.lock().await;
358
+ if let Some(log) = state
359
+ .request_logs
360
+ .iter_mut()
361
+ .rev()
362
+ .find(|log| log.id == current_id)
363
+ {
364
+ log.status = LogStatus::Failed;
365
+ log.error = Some("Request timeout".to_string());
366
+ }
367
+ state.active_requests -= 1;
368
+ state.error_requests += 1;
369
+ }
370
+ return Err((
371
+ StatusCode::GATEWAY_TIMEOUT,
372
+ Json(ChatError::RequestFailed("Request timeout".to_string()).to_json()),
373
+ ));
374
+ }
375
+ };
376
+
377
+ // 释放活动请求计数
378
+ {
379
+ let mut state = state.lock().await;
380
+ state.active_requests -= 1;
381
+ }
382
+
383
+ let convert_web_ref = current_config.include_web_references();
384
+
385
+ if request.stream {
386
+ let response_id = format!("chatcmpl-{}", Uuid::new_v4().simple());
387
+ let is_start = Arc::new(AtomicBool::new(true));
388
+ let start_time = std::time::Instant::now();
389
+ let first_chunk_time = Arc::new(Mutex::new(None::<f64>));
390
+ let decoder = Arc::new(Mutex::new(StreamDecoder::new()));
391
+
392
+ // 定义消息处理器的上下文结构体
393
+ struct MessageProcessContext<'a> {
394
+ response_id: &'a str,
395
+ model: &'a str,
396
+ is_start: &'a AtomicBool,
397
+ first_chunk_time: &'a Mutex<Option<f64>>,
398
+ start_time: std::time::Instant,
399
+ state: &'a Mutex<AppState>,
400
+ current_id: u64,
401
+ }
402
+
403
+ // 处理消息并生成响应数据的辅助函数
404
+ async fn process_messages(
405
+ messages: Vec<StreamMessage>,
406
+ ctx: &MessageProcessContext<'_>,
407
+ ) -> String {
408
+ let mut response_data = String::new();
409
+
410
+ for message in messages {
411
+ match message {
412
+ StreamMessage::Content(text) => {
413
+ let is_first = ctx.is_start.load(Ordering::SeqCst);
414
+ if is_first {
415
+ if let Ok(mut first_time) = ctx.first_chunk_time.try_lock() {
416
+ *first_time = Some(ctx.start_time.elapsed().as_secs_f64());
417
+ }
418
+ }
419
+
420
+ let response = ChatResponse {
421
+ id: ctx.response_id.to_string(),
422
+ object: OBJECT_CHAT_COMPLETION_CHUNK.to_string(),
423
+ created: chrono::Utc::now().timestamp(),
424
+ model: if is_first {
425
+ Some(ctx.model.to_string())
426
+ } else {
427
+ None
428
+ },
429
+ choices: vec![Choice {
430
+ index: 0,
431
+ message: None,
432
+ delta: Some(Delta {
433
+ role: if is_first {
434
+ Some(Role::Assistant)
435
+ } else {
436
+ None
437
+ },
438
+ content: if is_first {
439
+ ctx.is_start.store(false, Ordering::SeqCst);
440
+ Some(text.trim_leading_newlines())
441
+ } else {
442
+ Some(text)
443
+ },
444
+ }),
445
+ finish_reason: None,
446
+ }],
447
+ usage: None,
448
+ };
449
+
450
+ response_data.push_str(&format!(
451
+ "data: {}\n\n",
452
+ serde_json::to_string(&response).unwrap()
453
+ ));
454
+ }
455
+ StreamMessage::StreamEnd => {
456
+ // 计算总时间和首次片段时间
457
+ let total_time = ctx.start_time.elapsed().as_secs_f64();
458
+ let first_time = ctx.first_chunk_time.lock().await.unwrap_or(total_time);
459
+
460
+ {
461
+ let mut state = ctx.state.lock().await;
462
+ if let Some(log) = state
463
+ .request_logs
464
+ .iter_mut()
465
+ .rev()
466
+ .find(|log| log.id == ctx.current_id)
467
+ {
468
+ log.timing.total = format_time_ms(total_time);
469
+ log.timing.first = Some(format_time_ms(first_time));
470
+ }
471
+ }
472
+
473
+ let response = ChatResponse {
474
+ id: ctx.response_id.to_string(),
475
+ object: OBJECT_CHAT_COMPLETION_CHUNK.to_string(),
476
+ created: chrono::Utc::now().timestamp(),
477
+ model: None,
478
+ choices: vec![Choice {
479
+ index: 0,
480
+ message: None,
481
+ delta: Some(Delta {
482
+ role: None,
483
+ content: None,
484
+ }),
485
+ finish_reason: Some(FINISH_REASON_STOP.to_string()),
486
+ }],
487
+ usage: None,
488
+ };
489
+ response_data.push_str(&format!(
490
+ "data: {}\n\ndata: [DONE]\n\n",
491
+ serde_json::to_string(&response).unwrap()
492
+ ));
493
+ }
494
+ StreamMessage::Debug(debug_prompt) => {
495
+ if let Ok(mut state) = ctx.state.try_lock() {
496
+ if let Some(log) = state
497
+ .request_logs
498
+ .iter_mut()
499
+ .rev()
500
+ .find(|log| log.id == ctx.current_id)
501
+ {
502
+ log.prompt = Some(debug_prompt);
503
+ }
504
+ }
505
+ }
506
+ _ => {} // 忽略其他消息类型
507
+ }
508
+ }
509
+
510
+ response_data
511
+ }
512
+
513
+ // 首先处理stream直到获得第一个结果
514
+ let mut stream = response.bytes_stream();
515
+ while !decoder.lock().await.is_first_result_ready() {
516
+ match stream.next().await {
517
+ Some(Ok(chunk)) => {
518
+ if let Err(StreamError::ChatError(error)) =
519
+ decoder.lock().await.decode(&chunk, convert_web_ref)
520
+ {
521
+ let error_response = error.to_error_response();
522
+ // 更新请求日志为失败
523
+ {
524
+ let mut state = state.lock().await;
525
+ if let Some(log) = state
526
+ .request_logs
527
+ .iter_mut()
528
+ .rev()
529
+ .find(|log| log.id == current_id)
530
+ {
531
+ log.status = LogStatus::Failed;
532
+ log.error = Some(error_response.native_code());
533
+ log.timing.total =
534
+ format_time_ms(start_time.elapsed().as_secs_f64());
535
+ state.error_requests += 1;
536
+ }
537
+ }
538
+ return Err((
539
+ error_response.status_code(),
540
+ Json(error_response.to_common()),
541
+ ));
542
+ }
543
+ }
544
+ Some(Err(e)) => {
545
+ let error_message = format!("Failed to read response chunk: {}", e);
546
+ return Err((
547
+ StatusCode::INTERNAL_SERVER_ERROR,
548
+ Json(ChatError::RequestFailed(error_message).to_json()),
549
+ ));
550
+ }
551
+ None => {
552
+ // 更新请求日志为失败
553
+ {
554
+ let mut state = state.lock().await;
555
+ if let Some(log) = state
556
+ .request_logs
557
+ .iter_mut()
558
+ .rev()
559
+ .find(|log| log.id == current_id)
560
+ {
561
+ log.status = LogStatus::Failed;
562
+ log.error = Some("Empty stream response".to_string());
563
+ state.error_requests += 1;
564
+ }
565
+ }
566
+ return Err((
567
+ StatusCode::INTERNAL_SERVER_ERROR,
568
+ Json(
569
+ ChatError::RequestFailed("Empty stream response".to_string()).to_json(),
570
+ ),
571
+ ));
572
+ }
573
+ }
574
+ }
575
+
576
+ // 处理后续的stream
577
+ let stream = stream.then({
578
+ let decoder = decoder.clone();
579
+ let response_id = response_id.clone();
580
+ let model = request.model.clone();
581
+ let is_start = is_start.clone();
582
+ let first_chunk_time = first_chunk_time.clone();
583
+ let state = state.clone();
584
+
585
+ move |chunk| {
586
+ let decoder = decoder.clone();
587
+ let response_id = response_id.clone();
588
+ let model = model.clone();
589
+ let is_start = is_start.clone();
590
+ let first_chunk_time = first_chunk_time.clone();
591
+ let state = state.clone();
592
+
593
+ async move {
594
+ let chunk = chunk.unwrap_or_default();
595
+
596
+ let ctx = MessageProcessContext {
597
+ response_id: &response_id,
598
+ model: &model,
599
+ is_start: &is_start,
600
+ first_chunk_time: &first_chunk_time,
601
+ start_time,
602
+ state: &state,
603
+ current_id,
604
+ };
605
+
606
+ // 使用decoder处理chunk
607
+ let messages = match decoder.lock().await.decode(&chunk, convert_web_ref) {
608
+ Ok(msgs) => msgs,
609
+ Err(e) => {
610
+ eprintln!("[警告] Stream error: {}", e);
611
+ return Ok::<_, Infallible>(Bytes::new());
612
+ }
613
+ };
614
+
615
+ let mut response_data = String::new();
616
+
617
+ if let Some(first_msg) = decoder.lock().await.take_first_result() {
618
+ let first_response = process_messages(first_msg, &ctx).await;
619
+ response_data.push_str(&first_response);
620
+ }
621
+
622
+ let current_response = process_messages(messages, &ctx).await;
623
+ if !current_response.is_empty() {
624
+ response_data.push_str(&current_response);
625
+ }
626
+
627
+ Ok(Bytes::from(response_data))
628
+ }
629
+ }
630
+ });
631
+
632
+ Ok(Response::builder()
633
+ .header("Cache-Control", "no-cache")
634
+ .header("Connection", "keep-alive")
635
+ .header(CONTENT_TYPE, "text/event-stream")
636
+ .body(Body::from_stream(stream))
637
+ .unwrap())
638
+ } else {
639
+ // 非流式响应
640
+ let start_time = std::time::Instant::now();
641
+ let mut first_chunk_time = None::<f64>;
642
+ let mut decoder = StreamDecoder::new();
643
+ let mut full_text = String::with_capacity(1024);
644
+ let mut stream = response.bytes_stream();
645
+
646
+ // 逐个处理chunks
647
+ while let Some(chunk) = stream.next().await {
648
+ let chunk = chunk.map_err(|e| {
649
+ let error_message = format!("Failed to read response chunk: {}", e);
650
+ (
651
+ StatusCode::INTERNAL_SERVER_ERROR,
652
+ Json(ChatError::RequestFailed(error_message).to_json()),
653
+ )
654
+ })?;
655
+
656
+ // 立即处理当前chunk
657
+ match decoder.decode(&chunk, convert_web_ref) {
658
+ Ok(messages) => {
659
+ for message in messages {
660
+ match message {
661
+ StreamMessage::Content(text) => {
662
+ if first_chunk_time.is_none() {
663
+ first_chunk_time = Some(start_time.elapsed().as_secs_f64());
664
+ }
665
+ full_text.push_str(&text);
666
+ }
667
+ StreamMessage::Debug(debug_prompt) => {
668
+ if let Ok(mut state) = state.try_lock() {
669
+ if let Some(log) = state
670
+ .request_logs
671
+ .iter_mut()
672
+ .rev()
673
+ .find(|log| log.id == current_id)
674
+ {
675
+ log.prompt = Some(debug_prompt);
676
+ }
677
+ }
678
+ }
679
+ _ => {}
680
+ }
681
+ }
682
+ }
683
+ Err(StreamError::ChatError(error)) => {
684
+ let error_response = error.to_error_response();
685
+ return Err((
686
+ error_response.status_code(),
687
+ Json(error_response.to_common()),
688
+ ));
689
+ }
690
+ Err(e) => {
691
+ let error_response = ErrorResponse {
692
+ status: ApiStatus::Error,
693
+ code: Some(500),
694
+ error: Some(e.to_string()),
695
+ message: None,
696
+ };
697
+ return Err((StatusCode::INTERNAL_SERVER_ERROR, Json(error_response)));
698
+ }
699
+ }
700
+ }
701
+
702
+ // 检查响应是否为空
703
+ if full_text.is_empty() {
704
+ // 更新请求日志为失败
705
+ {
706
+ let mut state = state.lock().await;
707
+ if let Some(log) = state
708
+ .request_logs
709
+ .iter_mut()
710
+ .rev()
711
+ .find(|log| log.id == current_id)
712
+ {
713
+ log.status = LogStatus::Failed;
714
+ log.error = Some("Empty response received".to_string());
715
+ state.error_requests += 1;
716
+ }
717
+ }
718
+ return Err((
719
+ StatusCode::INTERNAL_SERVER_ERROR,
720
+ Json(ChatError::RequestFailed("Empty response received".to_string()).to_json()),
721
+ ));
722
+ }
723
+
724
+ let response_data = ChatResponse {
725
+ id: format!("chatcmpl-{}", Uuid::new_v4().simple()),
726
+ object: OBJECT_CHAT_COMPLETION.to_string(),
727
+ created: chrono::Utc::now().timestamp(),
728
+ model: Some(request.model),
729
+ choices: vec![Choice {
730
+ index: 0,
731
+ message: Some(Message {
732
+ role: Role::Assistant,
733
+ content: MessageContent::Text(full_text.trim_leading_newlines()),
734
+ }),
735
+ delta: None,
736
+ finish_reason: Some(FINISH_REASON_STOP.to_string()),
737
+ }],
738
+ usage: Some(Usage {
739
+ prompt_tokens: 0,
740
+ completion_tokens: 0,
741
+ total_tokens: 0,
742
+ }),
743
+ };
744
+
745
+ {
746
+ // 更新请求日志时间信息和状态
747
+ let total_time = format_time_ms(start_time.elapsed().as_secs_f64());
748
+ let mut state = state.lock().await;
749
+ if let Some(log) = state
750
+ .request_logs
751
+ .iter_mut()
752
+ .rev()
753
+ .find(|log| log.id == current_id)
754
+ {
755
+ log.timing.total = total_time;
756
+ log.timing.first = first_chunk_time;
757
+ log.status = LogStatus::Success;
758
+ }
759
+ }
760
+
761
+ Ok(Response::builder()
762
+ .header(CONTENT_TYPE, "application/json")
763
+ .body(Body::from(serde_json::to_string(&response_data).unwrap()))
764
+ .unwrap())
765
+ }
766
+ }
src/chat/stream.rs ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ mod decoder;
2
+ pub use decoder::*;
src/chat/stream/decoder.rs ADDED
@@ -0,0 +1,406 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use crate::chat::{
2
+ aiserver::v1::StreamChatResponse,
3
+ error::{ChatError, StreamError},
4
+ };
5
+ use flate2::read::GzDecoder;
6
+ use prost::Message;
7
+ use std::{collections::BTreeMap, io::Read};
8
+
9
+ // 解压gzip数据
10
+ fn decompress_gzip(data: &[u8]) -> Option<Vec<u8>> {
11
+ let mut decoder = GzDecoder::new(data);
12
+ let mut decompressed = Vec::new();
13
+
14
+ match decoder.read_to_end(&mut decompressed) {
15
+ Ok(_) => Some(decompressed),
16
+ Err(_) => {
17
+ // println!("gzip解压失败: {}", e);
18
+ None
19
+ }
20
+ }
21
+ }
22
+
23
+ pub trait ToMarkdown {
24
+ fn to_markdown(&self) -> String;
25
+ }
26
+
27
+ impl ToMarkdown for BTreeMap<String, String> {
28
+ fn to_markdown(&self) -> String {
29
+ if self.is_empty() {
30
+ return String::new();
31
+ }
32
+
33
+ let mut result = String::from("WebReferences:\n");
34
+ for (i, (url, title)) in self.iter().enumerate() {
35
+ result.push_str(&format!("{}. [{}]({})\n", i + 1, title, url));
36
+ }
37
+ result.push_str("\n");
38
+ result
39
+ }
40
+ }
41
+
42
+ #[derive(PartialEq, Clone, Debug)]
43
+ pub enum StreamMessage {
44
+ // 调试
45
+ Debug(String),
46
+ // 网络引用
47
+ WebReference(BTreeMap<String, String>),
48
+ // 内容开始标志
49
+ ContentStart,
50
+ // 消息内容
51
+ Content(String),
52
+ // 流结束标志
53
+ StreamEnd,
54
+ }
55
+
56
+ impl StreamMessage {
57
+ fn convert_web_ref_to_content(self) -> Self {
58
+ match self {
59
+ StreamMessage::WebReference(refs) => StreamMessage::Content(refs.to_markdown()),
60
+ other => other,
61
+ }
62
+ }
63
+ }
64
+
65
+ pub struct StreamDecoder {
66
+ buffer: Vec<u8>,
67
+ first_result: Option<Vec<StreamMessage>>,
68
+ first_result_ready: bool,
69
+ first_result_taken: bool,
70
+ }
71
+
72
+ impl StreamDecoder {
73
+ pub fn new() -> Self {
74
+ Self {
75
+ buffer: Vec::new(),
76
+ first_result: None,
77
+ first_result_ready: false,
78
+ first_result_taken: false,
79
+ }
80
+ }
81
+
82
+ pub fn take_first_result(&mut self) -> Option<Vec<StreamMessage>> {
83
+ if !self.buffer.is_empty() {
84
+ return None;
85
+ }
86
+ if self.first_result.is_some() {
87
+ self.first_result_taken = true;
88
+ }
89
+ self.first_result.take()
90
+ }
91
+
92
+ #[cfg(test)]
93
+ fn is_incomplete(&self) -> bool {
94
+ !self.buffer.is_empty()
95
+ }
96
+
97
+ pub fn is_first_result_ready(&self) -> bool {
98
+ self.first_result_ready
99
+ }
100
+
101
+ pub fn decode(&mut self, data: &[u8], convert_web_ref: bool) -> Result<Vec<StreamMessage>, StreamError> {
102
+ self.buffer.extend_from_slice(data);
103
+
104
+ if self.buffer.len() < 5 {
105
+ if self.buffer.len() == 0 {
106
+ return Err(StreamError::EmptyStream);
107
+ }
108
+ crate::debug_println!("数据长度小于5字节,当前数据: {}", hex::encode(&self.buffer));
109
+ return Err(StreamError::DataLengthLessThan5);
110
+ }
111
+
112
+ let mut messages = Vec::new();
113
+ let mut offset = 0;
114
+
115
+ while offset + 5 <= self.buffer.len() {
116
+ let msg_type = self.buffer[offset];
117
+ let msg_len = u32::from_be_bytes([
118
+ self.buffer[offset + 1],
119
+ self.buffer[offset + 2],
120
+ self.buffer[offset + 3],
121
+ self.buffer[offset + 4],
122
+ ]) as usize;
123
+
124
+ if msg_len == 0 {
125
+ offset += 5;
126
+ messages.push(StreamMessage::ContentStart);
127
+ continue;
128
+ }
129
+
130
+ if offset + 5 + msg_len > self.buffer.len() {
131
+ break;
132
+ }
133
+
134
+ let msg_data = &self.buffer[offset + 5..offset + 5 + msg_len];
135
+
136
+ match self.process_message(msg_type, msg_data)? {
137
+ Some(msg) => {
138
+ if convert_web_ref {
139
+ messages.push(msg.convert_web_ref_to_content());
140
+ } else {
141
+ messages.push(msg);
142
+ }
143
+ }
144
+ _ => {}
145
+ }
146
+
147
+ offset += 5 + msg_len;
148
+ }
149
+
150
+ self.buffer.drain(..offset);
151
+
152
+ if !self.first_result_taken && !messages.is_empty() {
153
+ if self.first_result.is_none() {
154
+ self.first_result = Some(messages.clone());
155
+ } else if !self.first_result_ready {
156
+ self.first_result.as_mut().unwrap().extend(messages.clone());
157
+ }
158
+ }
159
+ if !self.first_result_ready {
160
+ self.first_result_ready = self.first_result.is_some() && self.buffer.is_empty() && !self.first_result_taken;
161
+ }
162
+ Ok(messages)
163
+ }
164
+
165
+ fn process_message(
166
+ &self,
167
+ msg_type: u8,
168
+ msg_data: &[u8],
169
+ ) -> Result<Option<StreamMessage>, StreamError> {
170
+ match msg_type {
171
+ 0 => self.handle_text_message(msg_data),
172
+ 1 => self.handle_gzip_message(msg_data),
173
+ 2 => self.handle_json_message(msg_data),
174
+ 3 => self.handle_gzip_json_message(msg_data),
175
+ t => {
176
+ eprintln!("收到未知消息类型: {},请尝试联系开发者以获取支持", t);
177
+ crate::debug_println!("消息类型: {},消息内容: {}", t, hex::encode(msg_data));
178
+ Ok(None)
179
+ }
180
+ }
181
+ }
182
+
183
+ fn handle_text_message(&self, msg_data: &[u8]) -> Result<Option<StreamMessage>, StreamError> {
184
+ if let Ok(response) = StreamChatResponse::decode(msg_data) {
185
+ // crate::debug_println!("[text] StreamChatResponse [hex: {}]: {:?}", hex::encode(msg_data), response);
186
+ if !response.text.is_empty() {
187
+ Ok(Some(StreamMessage::Content(response.text)))
188
+ } else if let Some(filled_prompt) = response.filled_prompt {
189
+ Ok(Some(StreamMessage::Debug(filled_prompt)))
190
+ } else if let Some(web_citation) = response.web_citation {
191
+ let mut refs = BTreeMap::new();
192
+ for reference in web_citation.references {
193
+ refs.insert(reference.url, reference.title);
194
+ }
195
+ Ok(Some(StreamMessage::WebReference(refs)))
196
+ } else {
197
+ Ok(None)
198
+ }
199
+ } else {
200
+ Ok(None)
201
+ }
202
+ }
203
+
204
+ fn handle_gzip_message(&self, msg_data: &[u8]) -> Result<Option<StreamMessage>, StreamError> {
205
+ if let Some(text) = decompress_gzip(msg_data) {
206
+ if let Ok(response) = StreamChatResponse::decode(&text[..]) {
207
+ // crate::debug_println!("[gzip] StreamChatResponse [hex: {}]: {:?}", hex::encode(msg_data), response);
208
+ if !response.text.is_empty() {
209
+ Ok(Some(StreamMessage::Content(response.text)))
210
+ } else if let Some(filled_prompt) = response.filled_prompt {
211
+ Ok(Some(StreamMessage::Debug(filled_prompt)))
212
+ } else if let Some(web_citation) = response.web_citation {
213
+ let mut refs = BTreeMap::new();
214
+ for reference in web_citation.references {
215
+ refs.insert(reference.url, reference.title);
216
+ }
217
+ Ok(Some(StreamMessage::WebReference(refs)))
218
+ } else {
219
+ Ok(None)
220
+ }
221
+ } else {
222
+ Ok(None)
223
+ }
224
+ } else {
225
+ Ok(None)
226
+ }
227
+ }
228
+
229
+ fn handle_json_message(&self, msg_data: &[u8]) -> Result<Option<StreamMessage>, StreamError> {
230
+ if msg_data.len() == 2 {
231
+ return Ok(Some(StreamMessage::StreamEnd));
232
+ }
233
+ if let Ok(text) = String::from_utf8(msg_data.to_vec()) {
234
+ // println!("JSON消息: {}", text);
235
+ if let Ok(error) = serde_json::from_str::<ChatError>(&text) {
236
+ return Err(StreamError::ChatError(error));
237
+ }
238
+ }
239
+ Ok(None)
240
+ }
241
+
242
+ fn handle_gzip_json_message(
243
+ &self,
244
+ msg_data: &[u8],
245
+ ) -> Result<Option<StreamMessage>, StreamError> {
246
+ if let Some(text) = decompress_gzip(msg_data) {
247
+ if text.len() == 2 {
248
+ return Ok(Some(StreamMessage::StreamEnd));
249
+ }
250
+ if let Ok(text) = String::from_utf8(text) {
251
+ // println!("JSON消息: {}", text);
252
+ if let Ok(error) = serde_json::from_str::<ChatError>(&text) {
253
+ return Err(StreamError::ChatError(error));
254
+ }
255
+ }
256
+ }
257
+ Ok(None)
258
+ }
259
+ }
260
+
261
+ #[cfg(test)]
262
+ mod tests {
263
+ use super::*;
264
+
265
+ #[test]
266
+ fn test_single_chunk() {
267
+ // 使用include_str!加载测试数据文件
268
+ let stream_data = include_str!("../../../tests/data/stream_data.txt");
269
+
270
+ // 将整个字符串按每两个字符分割成字节
271
+ let bytes: Vec<u8> = stream_data
272
+ .as_bytes()
273
+ .chunks(2)
274
+ .map(|chunk| {
275
+ let hex_str = std::str::from_utf8(chunk).unwrap();
276
+ u8::from_str_radix(hex_str, 16).unwrap()
277
+ })
278
+ .collect();
279
+
280
+ // 创建解码器
281
+ let mut decoder = StreamDecoder::new();
282
+
283
+ match decoder.decode(&bytes, false) {
284
+ Ok(messages) => {
285
+ for message in messages {
286
+ match message {
287
+ StreamMessage::StreamEnd => {
288
+ println!("流结束");
289
+ break;
290
+ }
291
+ StreamMessage::Content(msg) => {
292
+ println!("消息内容: {}", msg);
293
+ }
294
+ StreamMessage::WebReference(refs) => {
295
+ println!("网页引用:");
296
+ for (i, (url, title)) in refs.iter().enumerate() {
297
+ println!("{}. {} - {}", i, url, title);
298
+ }
299
+ }
300
+ StreamMessage::Debug(prompt) => {
301
+ println!("调试信息: {}", prompt);
302
+ }
303
+ StreamMessage::ContentStart => {
304
+ println!("流开始");
305
+ }
306
+ }
307
+ }
308
+ }
309
+ Err(e) => {
310
+ println!("解析错误: {}", e);
311
+ }
312
+ }
313
+ if decoder.is_incomplete() {
314
+ println!("数据不完整");
315
+ }
316
+ }
317
+
318
+ #[test]
319
+ fn test_multiple_chunks() {
320
+ // 使用include_str!加载测试数据文件
321
+ let stream_data = include_str!("../../../tests/data/stream_data.txt");
322
+
323
+ // 将整个字符串按每两个字符分割成字节
324
+ let bytes: Vec<u8> = stream_data
325
+ .as_bytes()
326
+ .chunks(2)
327
+ .map(|chunk| {
328
+ let hex_str = std::str::from_utf8(chunk).unwrap();
329
+ u8::from_str_radix(hex_str, 16).unwrap()
330
+ })
331
+ .collect();
332
+
333
+ // 创建解码器
334
+ let mut decoder = StreamDecoder::new();
335
+
336
+ // 辅助函数:找到下一个消息边界
337
+ fn find_next_message_boundary(bytes: &[u8]) -> usize {
338
+ if bytes.len() < 5 {
339
+ return bytes.len();
340
+ }
341
+ let msg_len = u32::from_be_bytes([bytes[1], bytes[2], bytes[3], bytes[4]]) as usize;
342
+ 5 + msg_len
343
+ }
344
+
345
+ // 辅助函数:将字节转换为hex字符串
346
+ fn bytes_to_hex(bytes: &[u8]) -> String {
347
+ bytes
348
+ .iter()
349
+ .map(|b| format!("{:02X}", b))
350
+ .collect::<Vec<String>>()
351
+ .join("")
352
+ }
353
+
354
+ // 多次解析数据
355
+ let mut offset = 0;
356
+ let mut should_break = false;
357
+
358
+ while offset < bytes.len() {
359
+ let remaining_bytes = &bytes[offset..];
360
+ let msg_boundary = find_next_message_boundary(remaining_bytes);
361
+ let current_msg_bytes = &remaining_bytes[..msg_boundary];
362
+ let hex_str = bytes_to_hex(current_msg_bytes);
363
+
364
+ match decoder.decode(current_msg_bytes, false) {
365
+ Ok(messages) => {
366
+ for message in messages {
367
+ match message {
368
+ StreamMessage::StreamEnd => {
369
+ println!("流结束 [hex: {}]", hex_str);
370
+ should_break = true;
371
+ break;
372
+ }
373
+ StreamMessage::Content(msg) => {
374
+ println!("消息内容 [hex: {}]: {}", hex_str, msg);
375
+ }
376
+ StreamMessage::WebReference(refs) => {
377
+ println!("网页引用 [hex: {}]:", hex_str);
378
+ for (i, (url, title)) in refs.iter().enumerate() {
379
+ println!("{}. {} - {}", i, url, title);
380
+ }
381
+ }
382
+ StreamMessage::Debug(prompt) => {
383
+ println!("调试信息 [hex: {}]: {}", hex_str, prompt);
384
+ }
385
+ StreamMessage::ContentStart => {
386
+ println!("流开始 [hex: {}]", hex_str);
387
+ }
388
+ }
389
+ }
390
+ if should_break {
391
+ break;
392
+ }
393
+ if decoder.is_incomplete() {
394
+ println!("数据不完整 [hex: {}]", hex_str);
395
+ break;
396
+ }
397
+ offset += msg_boundary;
398
+ }
399
+ Err(e) => {
400
+ println!("解析错误 [hex: {}]: {}", hex_str, e);
401
+ break;
402
+ }
403
+ }
404
+ }
405
+ }
406
+ }