mirror of
https://github.com/wisdgod/cursor-api.git
synced 2025-10-29 01:22:34 +08:00
0.1.3-rc.5.2.4
This commit is contained in:
3
.cargo/config.toml
Normal file
3
.cargo/config.toml
Normal file
@@ -0,0 +1,3 @@
|
||||
[unstable]
|
||||
profile-rustflags = true
|
||||
trim-paths = true
|
||||
12
.github/workflows/docker.yml
vendored
12
.github/workflows/docker.yml
vendored
@@ -36,14 +36,14 @@ jobs:
|
||||
echo "version=v${VERSION}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v3.3.0
|
||||
uses: docker/login-action@v3.4.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5.6.1
|
||||
uses: docker/metadata-action@v5.7.0
|
||||
with:
|
||||
images: ${{ env.IMAGE_NAME }}
|
||||
tags: |
|
||||
@@ -53,20 +53,20 @@ jobs:
|
||||
type=raw,value=${{ github.ref_name }},enable=${{ github.event_name == 'push' }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3.8.0
|
||||
uses: docker/setup-buildx-action@v3.10.0
|
||||
with:
|
||||
driver-opts: |
|
||||
image=moby/buildkit:latest
|
||||
network=host
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v6.11.0
|
||||
uses: docker/build-push-action@v6.15.0
|
||||
env:
|
||||
DOCKER_BUILD_RECORD_UPLOAD: false
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
platforms: linux/amd64,linux/arm64v8
|
||||
platforms: linux/amd64,linux/arm64
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
@@ -93,4 +93,4 @@ jobs:
|
||||
with:
|
||||
name: cursor-api-binaries
|
||||
path: artifacts/
|
||||
retention-days: 7
|
||||
retention-days: 7
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -9,7 +9,6 @@
|
||||
node_modules
|
||||
.DS_Store
|
||||
/.vscode
|
||||
/.cargo
|
||||
/.token
|
||||
/.token-list
|
||||
/.tokens
|
||||
@@ -25,3 +24,4 @@ node_modules
|
||||
/result.txt
|
||||
tools/tokenizer/
|
||||
/diff
|
||||
/Cargo.lock
|
||||
|
||||
3030
Cargo.lock
generated
3030
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
16
Cargo.toml
16
Cargo.toml
@@ -1,6 +1,8 @@
|
||||
cargo-features = ["profile-rustflags", "trim-paths"]
|
||||
|
||||
[package]
|
||||
name = "cursor-api"
|
||||
version = "0.1.3-rc.5.2.3"
|
||||
version = "0.1.3-rc.5.2.4"
|
||||
edition = "2024"
|
||||
authors = ["wisdgod <nav@wisdgod.com>"]
|
||||
description = "OpenAI format compatibility layer for the Cursor API"
|
||||
@@ -19,7 +21,7 @@ bytes = "^1.10"
|
||||
chrono = { version = "^0.4", default-features = false, features = ["std", "clock", "now", "serde", "rkyv-64"] }
|
||||
chrono-tz = { version = "^0.10", features = ["serde"] }
|
||||
dotenvy = "^0.15"
|
||||
flate2 = { version = "^1.0", default-features = false, features = ["rust_backend"] }
|
||||
flate2 = { version = "1", default-features = false, features = ["rust_backend"] }
|
||||
futures = { version = "^0.3", default-features = false, features = ["std"] }
|
||||
gif = { version = "^0.13", default-features = false, features = ["std"] }
|
||||
hex = { version = "^0.4", default-features = false, features = ["std"] }
|
||||
@@ -33,13 +35,13 @@ prost = "^0.13"
|
||||
prost-types = "^0.13"
|
||||
rand = { version = "^0.9", default-features = false, features = ["thread_rng"] }
|
||||
regex = { version = "^1.11", default-features = false, features = ["std", "perf"] }
|
||||
reqwest = { version = "^0.12", default-features = false, features = ["gzip", "brotli", "json", "stream", "socks", "__tls", "charset", "rustls-tls-webpki-roots", "macos-system-configuration"] }
|
||||
reqwest = { version = "^0.12", default-features = false, features = ["gzip", "brotli", "json", "stream", "socks", "__tls", "charset", "rustls-tls-native-roots", "macos-system-configuration"] }
|
||||
rkyv = { version = "^0.7", default-features = false, features = ["alloc", "std", "bytecheck", "size_64", "validation", "std"] }
|
||||
serde = { version = "^1.0", default-features = false, features = ["std", "derive"] }
|
||||
serde_json = { package = "sonic-rs", version = "^0.3" }
|
||||
serde = { version = "^1.0", default-features = false, features = ["std", "derive", "rc"] }
|
||||
serde_json = { package = "sonic-rs", version = "^0.4" }
|
||||
# serde_json = "^1.0"
|
||||
sha2 = { version = "^0.10", default-features = false }
|
||||
sysinfo = { version = "^0.33", default-features = false, features = ["system"] }
|
||||
sysinfo = { version = "^0.34", default-features = false, features = ["system"] }
|
||||
tokio = { version = "^1.43", features = ["rt-multi-thread", "macros", "net", "sync", "time", "fs", "signal"] }
|
||||
# tokio-stream = { version = "^0.1", features = ["time"] }
|
||||
tower-http = { version = "^0.6", features = ["cors", "limit"] }
|
||||
@@ -52,6 +54,8 @@ codegen-units = 1
|
||||
panic = 'abort'
|
||||
strip = true
|
||||
opt-level = 3
|
||||
trim-paths = "all"
|
||||
rustflags = ["-Cdebuginfo=0", "-Zthreads=8"]
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
@@ -112,9 +112,9 @@ token2,checksum2
|
||||
| 端点 | 方法 | 功能 |
|
||||
|------|------|-----|
|
||||
| `/tokens` | GET | Token 信息管理界面 |
|
||||
| `/tokens/update` | POST | 批量更新 Token 列表 |
|
||||
| `/tokens/set` | POST | 批量更新 Token 列表 |
|
||||
| `/tokens/add` | POST | 增量添加 Token |
|
||||
| `/tokens/delete` | POST | 删除指定 Token |
|
||||
| `/tokens/del` | POST | 删除指定 Token |
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
ARG TARGETARCH
|
||||
FROM --platform=linux/${TARGETARCH} rust:1-slim-bookworm as builder
|
||||
FROM --platform=linux/${TARGETARCH} rustlang/rust:nightly-bookworm-slim as builder
|
||||
|
||||
ARG TARGETARCH
|
||||
|
||||
@@ -10,7 +10,7 @@ RUN apt-get update && \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY . .
|
||||
RUN case "$TARGETARCH" in amd64) TARGET_CPU="x86-64-v2" ;; arm64) TARGET_CPU="neoverse-n1" ;; *) echo "Unsupported architecture: $TARGETARCH" && exit 1 ;; esac && RUSTFLAGS="-C link-arg=-s -C target-cpu=$TARGET_CPU" cargo build --release && cp target/release/cursor-api /app/cursor-api
|
||||
RUN case "$TARGETARCH" in amd64) TARGET_CPU="x86-64-v2" ;; arm64) TARGET_CPU="neoverse-n1" ;; *) echo "Unsupported architecture: $TARGETARCH" && exit 1 ;; esac && RUSTFLAGS="-C link-arg=-s -C target-cpu=$TARGET_CPU" cargo +nightly build --release && cp target/release/cursor-api /app/cursor-api
|
||||
|
||||
# 运行阶段
|
||||
ARG TARGETARCH
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Dockerfile.cross
|
||||
|
||||
FROM --platform=linux/amd64 rust:1-slim-bookworm
|
||||
FROM --platform=linux/amd64 rustlang/rust:nightly-bookworm-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
|
||||
104
README.md
104
README.md
@@ -73,6 +73,7 @@ o1
|
||||
claude-3.5-haiku
|
||||
gemini-2.0-pro-exp
|
||||
gemini-2.5-pro-exp-03-25
|
||||
gemini-2.5-pro-max
|
||||
gemini-2.0-flash-thinking-exp
|
||||
gemini-2.0-flash
|
||||
deepseek-v3
|
||||
@@ -209,6 +210,7 @@ data: [DONE]
|
||||
{
|
||||
"token": "string",
|
||||
"checksum": "string",
|
||||
"status": "enabled" | "disabled",
|
||||
"profile": { // 可能存在
|
||||
"usage": {
|
||||
"premium": {
|
||||
@@ -246,16 +248,18 @@ data: [DONE]
|
||||
"days_remaining_on_trial": number
|
||||
}
|
||||
},
|
||||
"tags": ["string"]
|
||||
"tags": {
|
||||
"string": null | "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"tokens_count": number
|
||||
}
|
||||
```
|
||||
|
||||
#### 更新Token信息
|
||||
#### 设置Token信息
|
||||
|
||||
* 接口地址: `/tokens/update`
|
||||
* 接口地址: `/tokens/set`
|
||||
* 请求方法: POST
|
||||
* 认证方式: Bearer Token
|
||||
* 请求格式:
|
||||
@@ -265,6 +269,7 @@ data: [DONE]
|
||||
{
|
||||
"token": "string",
|
||||
"checksum": "string",
|
||||
"status": "enabled" | "disabled",
|
||||
"profile": {
|
||||
"usage": {
|
||||
"premium": {
|
||||
@@ -302,7 +307,9 @@ data: [DONE]
|
||||
"days_remaining_on_trial": number
|
||||
}
|
||||
},
|
||||
"tags": ["string"]
|
||||
"tags": {
|
||||
"string": null | "string"
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
@@ -332,7 +339,10 @@ data: [DONE]
|
||||
"checksum": "string" // 可选,如果不提供将自动生成
|
||||
}
|
||||
],
|
||||
"tags": ["string"]
|
||||
"tags": {
|
||||
"string": null | "string"
|
||||
},
|
||||
"status": "enabled" | "disabled"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -348,7 +358,7 @@ data: [DONE]
|
||||
|
||||
#### 删除Token
|
||||
|
||||
* 接口地址: `/tokens/delete`
|
||||
* 接口地址: `/tokens/del`
|
||||
* 请求方法: POST
|
||||
* 认证方式: Bearer Token
|
||||
* 请求格式:
|
||||
@@ -376,9 +386,9 @@ data: [DONE]
|
||||
- failed_tokens: 返回未找到的token列表
|
||||
- detailed: 返回完整信息(包括updated_tokens和failed_tokens)
|
||||
|
||||
#### 更新Tokens标签
|
||||
#### 设置Tokens标签
|
||||
|
||||
* 接口地址: `/tokens/tags/update`
|
||||
* 接口地址: `/tokens/tags/set`
|
||||
* 请求方法: POST
|
||||
* 认证方式: Bearer Token
|
||||
* 请求格式:
|
||||
@@ -386,7 +396,9 @@ data: [DONE]
|
||||
```json
|
||||
{
|
||||
"tokens": ["string"],
|
||||
"tags": ["string"] // index 0: 时区标识符(独有); index 1: 代理名称
|
||||
"tags": {
|
||||
"string": null | "string" // 键可以为 timezone: 时区标识符 或 proxy: 代理名称
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -421,6 +433,51 @@ data: [DONE]
|
||||
}
|
||||
```
|
||||
|
||||
#### 升级Tokens
|
||||
|
||||
* 接口地址: `/tokens/upgrade`
|
||||
* 请求方法: POST
|
||||
* 认证方式: Bearer Token
|
||||
* 请求格式:
|
||||
|
||||
```json
|
||||
[
|
||||
"string" // tokens
|
||||
]
|
||||
```
|
||||
|
||||
* 响应格式:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"message": "string" // "已升级?个令牌, ?个令牌升级失败"
|
||||
}
|
||||
```
|
||||
|
||||
#### 设置Tokens Status
|
||||
|
||||
* 接口地址: `/tokens/status/set`
|
||||
* 请求方法: POST
|
||||
* 认证方式: Bearer Token
|
||||
* 请求格式:
|
||||
|
||||
```json
|
||||
{
|
||||
"tokens": ["string"],
|
||||
"status": "enabled" | "disabled"
|
||||
}
|
||||
```
|
||||
|
||||
* 响应格式:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"message": "string" // "已设置?个令牌状态, ?个令牌设置失败"
|
||||
}
|
||||
```
|
||||
|
||||
#### 构建API Key
|
||||
|
||||
* 接口地址: `/build-key`
|
||||
@@ -512,9 +569,9 @@ data: [DONE]
|
||||
}
|
||||
```
|
||||
|
||||
#### 更新代理配置
|
||||
#### 设置代理配置
|
||||
|
||||
* 接口地址: `/proxies/update`
|
||||
* 接口地址: `/proxies/set`
|
||||
* 请求方法: POST
|
||||
* 请求格式:
|
||||
|
||||
@@ -565,7 +622,7 @@ data: [DONE]
|
||||
|
||||
#### 删除代理
|
||||
|
||||
* 接口地址: `/proxies/delete`
|
||||
* 接口地址: `/proxies/del`
|
||||
* 请求方法: POST
|
||||
* 请求格式:
|
||||
|
||||
@@ -997,6 +1054,29 @@ string
|
||||
}
|
||||
```
|
||||
|
||||
#### 获取更新令牌
|
||||
|
||||
* 接口地址: `/token-upgrade`
|
||||
* 请求方法: POST
|
||||
* 认证方式: 请求体中包含token
|
||||
* 请求格式:
|
||||
|
||||
```json
|
||||
{
|
||||
"token": "string"
|
||||
}
|
||||
```
|
||||
|
||||
* 响应格式:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "success" | "failure" | "error",
|
||||
"message": "string",
|
||||
"result": "string" // optional
|
||||
}
|
||||
```
|
||||
|
||||
#### 基础校准
|
||||
|
||||
* 接口地址: `/basic-calibration`
|
||||
|
||||
37
build.rs
37
build.rs
@@ -13,6 +13,7 @@ use std::io::Result;
|
||||
use std::io::{Read, Write};
|
||||
#[cfg(not(any(feature = "use-minified")))]
|
||||
use std::path::Path;
|
||||
#[cfg(not(any(feature = "use-minified")))]
|
||||
use std::path::PathBuf;
|
||||
#[cfg(not(any(feature = "use-minified")))]
|
||||
use std::process::Command;
|
||||
@@ -153,7 +154,7 @@ fn minify_assets() -> Result<()> {
|
||||
}
|
||||
|
||||
println!("cargo:warning=Minifying {} files...", files_to_update.len());
|
||||
println!("cargo:warning={}", files_to_update.join("\n"));
|
||||
println!("cargo:warning={}", files_to_update.join(" "));
|
||||
|
||||
// 运行压缩脚本
|
||||
let status = Command::new("node")
|
||||
@@ -231,21 +232,21 @@ fn main() -> Result<()> {
|
||||
|
||||
// Proto 文件处理
|
||||
// println!("cargo:rerun-if-changed=src/core/aiserver/v1/lite.proto");
|
||||
println!("cargo:rerun-if-changed=src/core/config/key.proto");
|
||||
// println!("cargo:rerun-if-changed=src/core/config/key.proto");
|
||||
// 获取环境变量 PROTOC
|
||||
let protoc_path = match std::env::var_os("PROTOC") {
|
||||
Some(path) => PathBuf::from(path),
|
||||
None => {
|
||||
println!("cargo:warning=PROTOC environment variable not set, using default protoc.");
|
||||
// 如果 PROTOC 未设置,则返回一个空的 PathBuf,prost-build 会尝试使用默认的 protoc
|
||||
PathBuf::new()
|
||||
}
|
||||
};
|
||||
let mut config = prost_build::Config::new();
|
||||
// 如果 protoc_path 不为空,则配置使用指定的 protoc
|
||||
if !protoc_path.as_os_str().is_empty() {
|
||||
config.protoc_executable(protoc_path);
|
||||
}
|
||||
// let protoc_path = match std::env::var_os("PROTOC") {
|
||||
// Some(path) => PathBuf::from(path),
|
||||
// None => {
|
||||
// println!("cargo:warning=PROTOC environment variable not set, using default protoc.");
|
||||
// // 如果 PROTOC 未设置,则返回一个空的 PathBuf,prost-build 会尝试使用默认的 protoc
|
||||
// PathBuf::new()
|
||||
// }
|
||||
// };
|
||||
// let mut config = prost_build::Config::new();
|
||||
// // 如果 protoc_path 不为空,则配置使用指定的 protoc
|
||||
// if !protoc_path.as_os_str().is_empty() {
|
||||
// config.protoc_executable(protoc_path);
|
||||
// }
|
||||
// config.type_attribute(".", "#[derive(serde::Serialize, serde::Deserialize)]");
|
||||
// config.enum_attribute(".aiserver.v1", "#[allow(clippy::enum_variant_names)]");
|
||||
// config
|
||||
@@ -254,9 +255,9 @@ fn main() -> Result<()> {
|
||||
// &["src/core/aiserver/v1/"],
|
||||
// )
|
||||
// .unwrap();
|
||||
config
|
||||
.compile_protos(&["src/core/config/key.proto"], &["src/core/config/"])
|
||||
.unwrap();
|
||||
// config
|
||||
// .compile_protos(&["src/core/config/key.proto"], &["src/core/config/"])
|
||||
// .unwrap();
|
||||
|
||||
// 静态资源文件处理
|
||||
println!("cargo:rerun-if-changed=scripts/minify.js");
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use super::{constant::AUTHORIZATION_BEARER_PREFIX, lazy::AUTH_TOKEN, model::AppConfig};
|
||||
use crate::common::model::{
|
||||
ApiStatus, ErrorResponse, NormalResponse,
|
||||
@@ -43,7 +45,7 @@ pub async fn handle_config_update(
|
||||
Json(ErrorResponse {
|
||||
status: ApiStatus::Failure,
|
||||
code: Some(401),
|
||||
error: Some("未提供认证令牌".to_string()),
|
||||
error: Some(Cow::Borrowed("未提供认证令牌")),
|
||||
message: None,
|
||||
}),
|
||||
))?;
|
||||
@@ -54,7 +56,7 @@ pub async fn handle_config_update(
|
||||
Json(ErrorResponse {
|
||||
status: ApiStatus::Failure,
|
||||
code: Some(401),
|
||||
error: Some("无效的认证令牌".to_string()),
|
||||
error: Some(Cow::Borrowed("无效的认证令牌")),
|
||||
message: None,
|
||||
}),
|
||||
));
|
||||
@@ -86,7 +88,7 @@ pub async fn handle_config_update(
|
||||
Json(ErrorResponse {
|
||||
status: ApiStatus::Failure,
|
||||
code: Some(500),
|
||||
error: Some(format!("更新页面内容失败: {e}")),
|
||||
error: Some(Cow::Owned(format!("更新页面内容失败: {e}"))),
|
||||
message: None,
|
||||
}),
|
||||
));
|
||||
@@ -107,7 +109,7 @@ pub async fn handle_config_update(
|
||||
Ok(Json(NormalResponse {
|
||||
status: ApiStatus::Success,
|
||||
data: None,
|
||||
message: Some("配置已更新".to_string()),
|
||||
message: Some(Cow::Borrowed("配置已更新")),
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -120,7 +122,7 @@ pub async fn handle_config_update(
|
||||
Json(ErrorResponse {
|
||||
status: ApiStatus::Failure,
|
||||
code: Some(500),
|
||||
error: Some(format!("重置页面内容失败: {e}")),
|
||||
error: Some(Cow::Owned(format!("重置页面内容失败: {e}"))),
|
||||
message: None,
|
||||
}),
|
||||
));
|
||||
@@ -140,7 +142,7 @@ pub async fn handle_config_update(
|
||||
Ok(Json(NormalResponse {
|
||||
status: ApiStatus::Success,
|
||||
data: None,
|
||||
message: Some("配置已重置".to_string()),
|
||||
message: Some(Cow::Borrowed("配置已重置")),
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -149,7 +151,7 @@ pub async fn handle_config_update(
|
||||
Json(ErrorResponse {
|
||||
status: ApiStatus::Failure,
|
||||
code: Some(400),
|
||||
error: Some("无效的操作类型".to_string()),
|
||||
error: Some(Cow::Borrowed("无效的操作类型")),
|
||||
message: None,
|
||||
}),
|
||||
)),
|
||||
|
||||
@@ -43,18 +43,20 @@ def_pub_const!(
|
||||
ROUTE_CONFIG_PATH => "/config",
|
||||
ROUTE_TOKENS_PATH => "/tokens",
|
||||
ROUTE_TOKENS_GET_PATH => "/tokens/get",
|
||||
ROUTE_TOKENS_UPDATE_PATH => "/tokens/update",
|
||||
ROUTE_TOKENS_SET_PATH => "/tokens/set",
|
||||
ROUTE_TOKENS_ADD_PATH => "/tokens/add",
|
||||
ROUTE_TOKENS_DELETE_PATH => "/tokens/delete",
|
||||
ROUTE_TOKENS_DELETE_PATH => "/tokens/del",
|
||||
ROUTE_TOKENS_TAGS_GET_PATH => "/tokens/tags/get",
|
||||
ROUTE_TOKENS_TAGS_UPDATE_PATH => "/tokens/tags/update",
|
||||
ROUTE_TOKENS_TAGS_SET_PATH => "/tokens/tags/set",
|
||||
ROUTE_TOKENS_BY_TAG_GET_PATH => "/tokens/by-tag/get",
|
||||
ROUTE_TOKENS_PROFILE_UPDATE_PATH => "/tokens/profile/update",
|
||||
ROUTE_TOKENS_UPGRADE_PATH => "/tokens/upgrade",
|
||||
ROUTE_TOKENS_STATUS_SET_PATH => "/tokens/status/set",
|
||||
ROUTE_PROXIES_PATH => "/proxies",
|
||||
ROUTE_PROXIES_GET_PATH => "/proxies/get",
|
||||
ROUTE_PROXIES_UPDATE_PATH => "/proxies/update",
|
||||
ROUTE_PROXIES_SET_PATH => "/proxies/set",
|
||||
ROUTE_PROXIES_ADD_PATH => "/proxies/add",
|
||||
ROUTE_PROXIES_DELETE_PATH => "/proxies/delete",
|
||||
ROUTE_PROXIES_DELETE_PATH => "/proxies/del",
|
||||
ROUTE_PROXIES_SET_GENERAL_PATH => "/proxies/set-general",
|
||||
ROUTE_ENV_EXAMPLE_PATH => "/env-example",
|
||||
ROUTE_STATIC_PATH => "/static/{path}",
|
||||
@@ -63,7 +65,8 @@ def_pub_const!(
|
||||
ROUTE_ABOUT_PATH => "/about",
|
||||
ROUTE_README_PATH => "/readme",
|
||||
ROUTE_BASIC_CALIBRATION_PATH => "/basic-calibration",
|
||||
ROUTE_BUILD_KEY_PATH => "/build-key"
|
||||
ROUTE_BUILD_KEY_PATH => "/build-key",
|
||||
ROUTE_TOKEN_UPGRADE_PATH => "/token-upgrade"
|
||||
);
|
||||
|
||||
// def_pub_const!(DEFAULT_TOKEN_LIST_FILE_NAME => ".tokens");
|
||||
|
||||
@@ -48,6 +48,7 @@ def_pub_static!(
|
||||
format!("{}/v1/chat/completions", *ROUTE_PREFIX),
|
||||
_
|
||||
);
|
||||
def_pub_static!(ROUTE_MESSAGES_PATH, format!("{}/v1/messages", *ROUTE_PREFIX), _);
|
||||
|
||||
static START_TIME: OnceLock<chrono::DateTime<chrono::Local>> = OnceLock::new();
|
||||
|
||||
@@ -84,7 +85,11 @@ def_pub_static!(DEFAULT_INSTRUCTIONS, env: "DEFAULT_INSTRUCTIONS", default: "Res
|
||||
const USE_OFFICIAL_CLAUDE_PROMPTS: LazyLock<bool> =
|
||||
LazyLock::new(|| parse_bool_from_env("USE_OFFICIAL_CLAUDE_PROMPTS", false));
|
||||
|
||||
pub fn get_default_instructions(model: &str, image_support: bool) -> String {
|
||||
pub fn get_default_instructions(
|
||||
now_with_tz: Option<chrono::DateTime<chrono_tz::Tz>>,
|
||||
model: &str,
|
||||
image_support: bool,
|
||||
) -> String {
|
||||
let mut instructions = "";
|
||||
if *USE_OFFICIAL_CLAUDE_PROMPTS {
|
||||
if let Some(rest) = model.strip_prefix("claude-3") {
|
||||
@@ -111,7 +116,12 @@ pub fn get_default_instructions(model: &str, image_support: bool) -> String {
|
||||
}
|
||||
instructions.replace(
|
||||
"{{currentDateTime}}",
|
||||
&now_in_general_timezone().to_rfc3339_opts(chrono::SecondsFormat::Millis, true),
|
||||
&if let Some(now) = now_with_tz {
|
||||
now
|
||||
} else {
|
||||
now_in_general_timezone()
|
||||
}
|
||||
.to_rfc3339_opts(chrono::SecondsFormat::Millis, true),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -226,6 +236,14 @@ def_cursor_api_url!(cursor_usage_api_url, CURSOR_HOST, "/api/usage");
|
||||
|
||||
def_cursor_api_url!(cursor_user_api_url, CURSOR_HOST, "/api/auth/me");
|
||||
|
||||
def_cursor_api_url!(
|
||||
cursor_token_upgrade_url,
|
||||
CURSOR_HOST,
|
||||
"/api/auth/loginDeepCallbackControl"
|
||||
);
|
||||
|
||||
def_cursor_api_url!(cursor_token_poll_url, CURSOR_API2_HOST, "/auth/poll");
|
||||
|
||||
static DATA_DIR: LazyLock<PathBuf> = LazyLock::new(|| {
|
||||
let data_dir = parse_string_from_env("DATA_DIR", "data");
|
||||
let path = std::env::current_exe()
|
||||
|
||||
110
src/app/model.rs
110
src/app/model.rs
@@ -1,4 +1,4 @@
|
||||
use std::sync::LazyLock;
|
||||
use std::{collections::HashMap, sync::LazyLock};
|
||||
|
||||
use crate::{
|
||||
common::{
|
||||
@@ -277,12 +277,30 @@ impl ErrorInfo {
|
||||
pub struct TokenInfo {
|
||||
pub token: String,
|
||||
pub checksum: String,
|
||||
#[serde(default)]
|
||||
pub status: TokenStatus,
|
||||
#[serde(skip_serializing, default = "generate_client_key")]
|
||||
pub client_key: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub profile: Option<TokenProfile>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub tags: Option<Vec<String>>,
|
||||
pub tags: Option<HashMap<String, Option<String>>>,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Copy, Serialize, Deserialize, Archive, RkyvSerialize, RkyvDeserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
#[repr(u8)]
|
||||
pub enum TokenStatus {
|
||||
#[default]
|
||||
Enabled,
|
||||
Disabled,
|
||||
}
|
||||
|
||||
impl TokenInfo {
|
||||
#[inline(always)]
|
||||
pub fn is_enabled(&self) -> bool {
|
||||
matches!(self.status, TokenStatus::Enabled)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
@@ -293,56 +311,50 @@ fn generate_client_key() -> Option<String> {
|
||||
impl TokenInfo {
|
||||
/// 获取适用于此 token 的 HTTP 客户端
|
||||
///
|
||||
/// 如果 tags 中包含 "proxy" 标签,会尝试使用其后一个标签作为代理 URL
|
||||
/// 例如: tags = ["proxy", "http://localhost:8080"] 将使用 http://localhost:8080 作为代理
|
||||
/// 如果 tags 中包含 "proxy" 键值对对象,会使用其值作为代理 URL
|
||||
/// 例如: tags = ["a", {"proxy": "http://localhost:8080"}, "d"] 将使用 http://localhost:8080 作为代理
|
||||
///
|
||||
/// 如果没有找到有效的代理配置,将返回默认客户端
|
||||
pub fn get_client(&self) -> Client {
|
||||
// if let Some(tags) = &self.tags {
|
||||
// // 查找 "proxy" 标签的位置
|
||||
// if let Some(proxy_index) = tags.iter().position(|tag| tag == "proxy") {
|
||||
// // 检查是否存在下一个标签作为代理 URL
|
||||
// if proxy_index + 1 < tags.len() {
|
||||
// // 获取代理 URL 并尝试创建对应的客户端
|
||||
// return ProxyPool::get_client(&tags[proxy_index + 1]);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// // 如果没有找到有效的代理配置,返回默认客户端
|
||||
// ProxyPool::get_general_client()
|
||||
if let Some(tags) = &self.tags {
|
||||
ProxyPool::get_client_or_general(tags.get(1).map(|s| s.as_str()))
|
||||
ProxyPool::get_client_or_general(
|
||||
tags.get("proxy")
|
||||
.and_then(|v| v.as_ref().map(String::as_str)),
|
||||
)
|
||||
} else {
|
||||
ProxyPool::get_general_client()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn timezone_name(&self) -> &'static str {
|
||||
/// 获取此 token 关联的时区
|
||||
///
|
||||
/// 如果 tags 中包含 "timezone" 键值对对象,会尝试使用其值作为时区标识
|
||||
/// 例如: tags = ["a", {"timezone": "Asia/Shanghai"}, "d"] 将使用上海时区
|
||||
/// 如果无法解析时区或未设置,将返回系统默认时区
|
||||
#[inline]
|
||||
fn get_timezone(&self) -> chrono_tz::Tz {
|
||||
use std::str::FromStr as _;
|
||||
if let Some(Some(Ok(tz))) = self.tags.as_ref().map(|tags| {
|
||||
tags.first()
|
||||
.filter(|s| !s.is_empty())
|
||||
.map(|s| chrono_tz::Tz::from_str(s.as_str()))
|
||||
}) {
|
||||
tz.name()
|
||||
} else {
|
||||
super::lazy::GENERAL_TIMEZONE.name()
|
||||
if let Some(tags) = self.tags.as_ref() {
|
||||
if let Some(Some(tz_str)) = tags.get("timezone") {
|
||||
if let Ok(tz) = chrono_tz::Tz::from_str(tz_str) {
|
||||
return tz;
|
||||
}
|
||||
}
|
||||
}
|
||||
*super::lazy::GENERAL_TIMEZONE
|
||||
}
|
||||
|
||||
// pub fn now(&self) -> chrono::DateTime<chrono_tz::Tz> {
|
||||
// use std::str::FromStr as _;
|
||||
// if let Some(Some(Ok(tz))) = self.tags.as_ref().map(|tags| {
|
||||
// tags.get(0)
|
||||
// .filter(|s| !s.is_empty())
|
||||
// .map(|s| chrono_tz::Tz::from_str(s.as_str()))
|
||||
// }) {
|
||||
// use chrono::TimeZone as _;
|
||||
// tz.from_utc_datetime(&chrono::Utc::now().naive_utc())
|
||||
// } else {
|
||||
// super::lazy::now_in_general_timezone()
|
||||
// }
|
||||
// }
|
||||
/// 返回关联的时区名称
|
||||
pub fn timezone_name(&self) -> &'static str {
|
||||
self.get_timezone().name()
|
||||
}
|
||||
|
||||
/// 获取当前时区的当前时间
|
||||
pub fn now(&self) -> chrono::DateTime<chrono_tz::Tz> {
|
||||
use chrono::TimeZone as _;
|
||||
self.get_timezone()
|
||||
.from_utc_datetime(&chrono::Utc::now().naive_utc())
|
||||
}
|
||||
}
|
||||
|
||||
// TokenUpdateRequest 结构体
|
||||
@@ -352,7 +364,9 @@ pub type TokenUpdateRequest = Vec<TokenInfo>;
|
||||
pub struct TokenAddRequest {
|
||||
pub tokens: Vec<TokenAddRequestTokenInfo>,
|
||||
#[serde(default)]
|
||||
pub tags: Option<Vec<String>>,
|
||||
pub tags: Option<HashMap<String, Option<String>>>,
|
||||
#[serde(default)]
|
||||
pub status: TokenStatus,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@@ -421,7 +435,7 @@ pub struct TokenInfoResponse {
|
||||
#[derive(Deserialize)]
|
||||
pub struct TokenTagsUpdateRequest {
|
||||
pub tokens: Vec<String>,
|
||||
pub tags: Vec<String>,
|
||||
pub tags: Option<HashMap<String, Option<String>>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
@@ -430,12 +444,8 @@ pub struct CommonResponse {
|
||||
pub message: Option<String>,
|
||||
}
|
||||
|
||||
// impl CommonResponse {
|
||||
// pub fn into_normal_response(self) -> NormalResponse<()> {
|
||||
// NormalResponse {
|
||||
// status: self.status,
|
||||
// data: None,
|
||||
// message: self.message,
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
#[derive(Deserialize)]
|
||||
pub struct TokenStatusSetRequest {
|
||||
pub tokens: Vec<String>,
|
||||
pub status: TokenStatus,
|
||||
}
|
||||
|
||||
@@ -70,5 +70,5 @@ pub enum UsageCheckModelType {
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum BuildKeyResponse {
|
||||
Key(String),
|
||||
Error(String),
|
||||
Error(&'static str),
|
||||
}
|
||||
|
||||
@@ -127,10 +127,10 @@ impl AppConfig {
|
||||
long_context: bool, false;
|
||||
dynamic_key: bool, false;
|
||||
web_refs: bool, false;
|
||||
vision_ability: VisionAbility, VisionAbility::default();
|
||||
}
|
||||
|
||||
config_methods_clone! {
|
||||
vision_ability: VisionAbility, VisionAbility::default();
|
||||
usage_check: UsageCheck, UsageCheck::default();
|
||||
}
|
||||
|
||||
@@ -138,9 +138,12 @@ impl AppConfig {
|
||||
APP_CONFIG.read().share_token.clone()
|
||||
}
|
||||
|
||||
pub fn share_token_eq(s: &str) -> bool {
|
||||
APP_CONFIG.read().share_token == s
|
||||
}
|
||||
|
||||
pub fn update_share_token(value: String) {
|
||||
let current = Self::get_share_token();
|
||||
if current != value {
|
||||
if Self::share_token_eq(&value) {
|
||||
let mut config = APP_CONFIG.write();
|
||||
config.share_token = value;
|
||||
config.is_share = !config.share_token.is_empty();
|
||||
@@ -148,8 +151,7 @@ impl AppConfig {
|
||||
}
|
||||
|
||||
pub fn reset_share_token() {
|
||||
let current = Self::get_share_token();
|
||||
if !current.is_empty() {
|
||||
if !APP_CONFIG.read().share_token.is_empty() {
|
||||
let mut config = APP_CONFIG.write();
|
||||
config.share_token = String::new();
|
||||
config.is_share = false;
|
||||
|
||||
@@ -164,17 +164,16 @@ impl Proxies {
|
||||
}
|
||||
|
||||
// 5. 设置通用客户端
|
||||
if let Some(proxy) = self.proxies.get(&self.general) {
|
||||
if let Some(client) = pool.clients.get(proxy) {
|
||||
pool.general = Some(client.clone());
|
||||
} else {
|
||||
// 这不应该发生
|
||||
unreachable!()
|
||||
}
|
||||
} else {
|
||||
// 这不应该发生
|
||||
unreachable!()
|
||||
}
|
||||
pool.general = Some(
|
||||
pool.clients
|
||||
.get(
|
||||
self.proxies
|
||||
.get(&self.general)
|
||||
.expect("General proxy not found in proxy list"),
|
||||
)
|
||||
.expect("Client for general proxy not found in client pool")
|
||||
.clone(),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -2,7 +2,10 @@ use crate::common::utils::{generate_checksum_with_repair, generate_hash};
|
||||
use memmap2::{MmapMut, MmapOptions};
|
||||
use rkyv::{Archive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{collections::HashSet, fs::OpenOptions};
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
fs::OpenOptions,
|
||||
};
|
||||
|
||||
use super::{
|
||||
super::lazy::{LOGS_FILE_PATH, TOKENS_FILE_PATH},
|
||||
@@ -70,7 +73,7 @@ impl TokenManager {
|
||||
let mut tags = HashSet::new();
|
||||
for token in &tokens {
|
||||
if let Some(token_tags) = &token.tags {
|
||||
tags.extend(token_tags.iter().cloned());
|
||||
tags.extend(token_tags.keys().cloned());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,16 +81,16 @@ impl TokenManager {
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn update_global_tags(&mut self, new_tags: &[String]) {
|
||||
pub fn update_global_tags(&mut self, new_tags: &HashMap<String, Option<String>>) {
|
||||
// 将新标签添加到全局标签集合中
|
||||
self.tags.extend(new_tags.iter().cloned());
|
||||
self.tags.extend(new_tags.keys().cloned());
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn update_tokens_tags(
|
||||
&mut self,
|
||||
tokens: Vec<String>,
|
||||
new_tags: Vec<String>,
|
||||
tokens: &[String],
|
||||
new_tags: Option<HashMap<String, Option<String>>>,
|
||||
) -> Result<(), &'static str> {
|
||||
// 创建tokens的HashSet用于快速查找
|
||||
let tokens_set: HashSet<_> = tokens.iter().collect();
|
||||
@@ -95,7 +98,7 @@ impl TokenManager {
|
||||
// 更新指定tokens的标签
|
||||
for token_info in &mut self.tokens {
|
||||
if tokens_set.contains(&token_info.token) {
|
||||
token_info.tags = Some(new_tags.clone());
|
||||
token_info.tags = new_tags.clone();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,8 +106,8 @@ impl TokenManager {
|
||||
self.tags = self
|
||||
.tokens
|
||||
.iter()
|
||||
.filter_map(|t| t.tags.clone())
|
||||
.flatten()
|
||||
.filter_map(|t| t.tags.as_ref())
|
||||
.flat_map(|tags| tags.keys().cloned())
|
||||
.collect();
|
||||
|
||||
Ok(())
|
||||
@@ -122,7 +125,7 @@ impl TokenManager {
|
||||
.filter(|t| {
|
||||
t.tags
|
||||
.as_ref()
|
||||
.is_some_and(|tags| tags.iter().any(|t| t == tag))
|
||||
.is_some_and(|tags| tags.keys().any(|t| t == tag))
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
@@ -5,14 +5,16 @@ use crate::app::{
|
||||
},
|
||||
lazy::{
|
||||
PRI_REVERSE_PROXY_HOST, PUB_REVERSE_PROXY_HOST, USE_PRI_REVERSE_PROXY,
|
||||
USE_PUB_REVERSE_PROXY, cursor_api2_stripe_url, cursor_usage_api_url, cursor_user_api_url,
|
||||
USE_PUB_REVERSE_PROXY, cursor_api2_stripe_url, cursor_token_poll_url,
|
||||
cursor_token_upgrade_url, cursor_usage_api_url, cursor_user_api_url,
|
||||
},
|
||||
};
|
||||
use reqwest::{
|
||||
Client, RequestBuilder,
|
||||
header::{
|
||||
ACCEPT, ACCEPT_ENCODING, ACCEPT_LANGUAGE, CACHE_CONTROL, CONNECTION, CONTENT_TYPE, COOKIE,
|
||||
DNT, HOST, ORIGIN, PRAGMA, REFERER, TE, USER_AGENT,
|
||||
ACCEPT, ACCEPT_ENCODING, ACCEPT_LANGUAGE, CACHE_CONTROL, CONNECTION, CONTENT_LENGTH,
|
||||
CONTENT_TYPE, COOKIE, DNT, HOST, ORIGIN, PRAGMA, REFERER, TE, TRANSFER_ENCODING,
|
||||
USER_AGENT,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -43,9 +45,32 @@ def_const!(SAME_ORIGIN, "same-origin");
|
||||
def_const!(KEEP_ALIVE, "keep-alive");
|
||||
def_const!(TRAILERS, "trailers");
|
||||
def_const!(U_EQ_4, "u=4");
|
||||
def_const!(U_EQ_0, "u=0");
|
||||
|
||||
def_const!(PROXY_HOST, "x-co");
|
||||
|
||||
#[inline]
|
||||
fn get_client_and_host_post<'a>(
|
||||
client: &Client,
|
||||
url: &'a str,
|
||||
is_pri: bool,
|
||||
real_host: &'a str,
|
||||
) -> (RequestBuilder, &'a str) {
|
||||
if is_pri && *USE_PRI_REVERSE_PROXY {
|
||||
(
|
||||
client.post(url).header(PROXY_HOST, real_host),
|
||||
PRI_REVERSE_PROXY_HOST.as_str(),
|
||||
)
|
||||
} else if !is_pri && *USE_PUB_REVERSE_PROXY {
|
||||
(
|
||||
client.post(url).header(PROXY_HOST, real_host),
|
||||
PUB_REVERSE_PROXY_HOST.as_str(),
|
||||
)
|
||||
} else {
|
||||
(client.post(url), real_host)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct AiServiceRequest<'a> {
|
||||
pub(crate) client: Client,
|
||||
pub(crate) auth_token: &'a str,
|
||||
@@ -70,28 +95,8 @@ pub(crate) struct AiServiceRequest<'a> {
|
||||
///
|
||||
/// * `reqwest::RequestBuilder` - 配置好的请求构建器
|
||||
pub fn build_request(req: AiServiceRequest) -> RequestBuilder {
|
||||
#[inline]
|
||||
fn get_client_and_host<'a>(
|
||||
client: &Client,
|
||||
url: &'a str,
|
||||
is_pri: bool,
|
||||
real_host: &'a str,
|
||||
) -> (RequestBuilder, &'a str) {
|
||||
if is_pri && *USE_PRI_REVERSE_PROXY {
|
||||
(
|
||||
client.post(url).header(PROXY_HOST, real_host),
|
||||
PRI_REVERSE_PROXY_HOST.as_str(),
|
||||
)
|
||||
} else if !is_pri && *USE_PUB_REVERSE_PROXY {
|
||||
(
|
||||
client.post(url).header(PROXY_HOST, real_host),
|
||||
PUB_REVERSE_PROXY_HOST.as_str(),
|
||||
)
|
||||
} else {
|
||||
(client.post(url), real_host)
|
||||
}
|
||||
}
|
||||
let (client, host) = get_client_and_host(&req.client, req.url, req.is_pri, CURSOR_API2_HOST);
|
||||
let (client, host) =
|
||||
get_client_and_host_post(&req.client, req.url, req.is_pri, CURSOR_API2_HOST);
|
||||
|
||||
client
|
||||
.header(
|
||||
@@ -114,8 +119,8 @@ pub fn build_request(req: AiServiceRequest) -> RequestBuilder {
|
||||
.header(HEADER_NAME_GHOST_MODE, TRUE)
|
||||
.header("x-request-id", req.trace_id)
|
||||
.header(HOST, host)
|
||||
.header("Connection", KEEP_ALIVE)
|
||||
.header("Transfer-Encoding", "chunked")
|
||||
.header(CONNECTION, KEEP_ALIVE)
|
||||
.header(TRANSFER_ENCODING, "chunked")
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -194,8 +199,6 @@ pub fn build_usage_request(
|
||||
auth_token: &str,
|
||||
is_pri: bool,
|
||||
) -> RequestBuilder {
|
||||
let session_token = format!("{}%3A%3A{}", user_id, auth_token);
|
||||
|
||||
let (client, host) =
|
||||
get_client_and_host(client, cursor_usage_api_url(is_pri), is_pri, CURSOR_HOST);
|
||||
|
||||
@@ -218,7 +221,7 @@ pub fn build_usage_request(
|
||||
.header(PRIORITY, U_EQ_4)
|
||||
.header(
|
||||
COOKIE,
|
||||
&format!("WorkosCursorSessionToken={}", session_token),
|
||||
format!("WorkosCursorSessionToken={user_id}%3A%3A{auth_token}"),
|
||||
)
|
||||
.query(&[("user", user_id)])
|
||||
}
|
||||
@@ -239,8 +242,6 @@ pub fn build_userinfo_request(
|
||||
auth_token: &str,
|
||||
is_pri: bool,
|
||||
) -> RequestBuilder {
|
||||
let session_token = format!("{}%3A%3A{}", user_id, auth_token);
|
||||
|
||||
let (client, host) =
|
||||
get_client_and_host(client, cursor_user_api_url(is_pri), is_pri, CURSOR_HOST);
|
||||
|
||||
@@ -263,7 +264,77 @@ pub fn build_userinfo_request(
|
||||
.header(PRIORITY, U_EQ_4)
|
||||
.header(
|
||||
COOKIE,
|
||||
&format!("WorkosCursorSessionToken={}", session_token),
|
||||
format!("WorkosCursorSessionToken={user_id}%3A%3A{auth_token}"),
|
||||
)
|
||||
.query(&[("user", user_id)])
|
||||
}
|
||||
|
||||
pub fn build_token_upgrade_request(
|
||||
client: &Client,
|
||||
uuid: &str,
|
||||
challenge: &str,
|
||||
user_id: &str,
|
||||
auth_token: &str,
|
||||
is_pri: bool,
|
||||
) -> RequestBuilder {
|
||||
let (client, host) = get_client_and_host_post(
|
||||
client,
|
||||
cursor_token_upgrade_url(is_pri),
|
||||
is_pri,
|
||||
CURSOR_HOST,
|
||||
);
|
||||
|
||||
let body = format!("{{\"uuid\":\"{uuid}\",\"challenge\":\"{challenge}\"}}");
|
||||
|
||||
client
|
||||
.header(HOST, host)
|
||||
.header(USER_AGENT, UA_WIN)
|
||||
.header(ACCEPT, VALUE_ACCEPT)
|
||||
.header(ACCEPT_LANGUAGE, VALUE_LANGUAGE)
|
||||
.header(ACCEPT_ENCODING, ENCODINGS)
|
||||
.header(
|
||||
REFERER,
|
||||
format!(
|
||||
"https://cursor.com/loginDeepControl?challenge={challenge}&uuid={uuid}&mode=login"
|
||||
),
|
||||
)
|
||||
.header(CONTENT_TYPE, "application/json")
|
||||
.header(CONTENT_LENGTH, body.len())
|
||||
.header(DNT, ONE)
|
||||
.header(SEC_GPC, ONE)
|
||||
.header(SEC_FETCH_DEST, EMPTY)
|
||||
.header(SEC_FETCH_MODE, CORS)
|
||||
.header(SEC_FETCH_SITE, SAME_ORIGIN)
|
||||
.header(CONNECTION, KEEP_ALIVE)
|
||||
.header(PRAGMA, NO_CACHE)
|
||||
.header(CACHE_CONTROL, NO_CACHE)
|
||||
.header(TE, TRAILERS)
|
||||
.header(PRIORITY, U_EQ_0)
|
||||
.header(
|
||||
COOKIE,
|
||||
format!("WorkosCursorSessionToken={user_id}%3A%3A{auth_token}"),
|
||||
)
|
||||
.body(body)
|
||||
}
|
||||
|
||||
pub fn build_token_poll_request(
|
||||
client: &Client,
|
||||
uuid: &str,
|
||||
verifier: &str,
|
||||
is_pri: bool,
|
||||
) -> RequestBuilder {
|
||||
let (client, host) = get_client_and_host(
|
||||
client,
|
||||
cursor_token_poll_url(is_pri),
|
||||
is_pri,
|
||||
CURSOR_API2_HOST,
|
||||
);
|
||||
client
|
||||
.header(HOST, host)
|
||||
.header(ACCEPT_ENCODING, "gzip, deflate")
|
||||
.header(ACCEPT_LANGUAGE, "en-US")
|
||||
.header(USER_AGENT, "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Cursor/0.48.2 Chrome/132.0.6834.210 Electron/34.3.4 Safari/537.36")
|
||||
.header(ORIGIN, "vscode-file://vscode-app")
|
||||
.header(HEADER_NAME_GHOST_MODE, TRUE)
|
||||
.header(ACCEPT, "*/*")
|
||||
.query(&[("uuid", uuid), ("verifier", verifier)])
|
||||
}
|
||||
|
||||
@@ -5,19 +5,18 @@ pub mod token;
|
||||
pub mod tri;
|
||||
pub mod userinfo;
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
use config::ConfigData;
|
||||
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum ApiStatus {
|
||||
#[serde(rename = "healthy")]
|
||||
Healthy,
|
||||
#[serde(rename = "success")]
|
||||
Success,
|
||||
#[serde(rename = "error")]
|
||||
Error,
|
||||
#[serde(rename = "failure")]
|
||||
Failure,
|
||||
}
|
||||
|
||||
@@ -41,7 +40,7 @@ pub struct NormalResponse<T> {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub data: Option<T>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub message: Option<String>,
|
||||
pub message: Option<Cow<'static, str>>,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for NormalResponse<ConfigData> {
|
||||
@@ -66,8 +65,8 @@ pub struct ErrorResponse {
|
||||
pub code: Option<u16>,
|
||||
// HTTP 请求的错误码
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub error: Option<String>,
|
||||
pub error: Option<Cow<'static, str>>,
|
||||
// 错误详情
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub message: Option<String>,
|
||||
pub message: Option<Cow<'static, str>>,
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use super::ErrorResponse;
|
||||
|
||||
pub enum ChatError {
|
||||
@@ -27,8 +29,8 @@ impl ChatError {
|
||||
ErrorResponse {
|
||||
status: super::ApiStatus::Error,
|
||||
code: None,
|
||||
error: Some(error.to_string()),
|
||||
message: Some(message),
|
||||
error: Some(Cow::Borrowed(error)),
|
||||
message: Some(Cow::Owned(message)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ pub struct HealthCheckResponse {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub stats: Option<SystemStats>,
|
||||
pub models: Vec<&'static str>,
|
||||
pub endpoints: Vec<&'static str>,
|
||||
pub endpoints: &'static [&'static str],
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
|
||||
@@ -31,6 +31,7 @@ use crate::{
|
||||
config::key_config,
|
||||
constant::{
|
||||
ANTHROPIC, CREATED, CURSOR, DEEPSEEK, GOOGLE, MODEL_OBJECT, OPENAI, UNKNOWN, XAI,
|
||||
calculate_display_name_v3,
|
||||
},
|
||||
model::{Model, Usage},
|
||||
},
|
||||
@@ -72,6 +73,13 @@ pub fn parse_usize_from_env(key: &str, default: usize) -> usize {
|
||||
.unwrap_or(default)
|
||||
}
|
||||
|
||||
pub fn now_secs() -> u64 {
|
||||
std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.expect("system time before Unix epoch")
|
||||
.as_secs()
|
||||
}
|
||||
|
||||
pub trait TrimNewlines {
|
||||
fn trim_leading_newlines(self) -> Self;
|
||||
}
|
||||
@@ -231,7 +239,7 @@ pub async fn get_available_models(
|
||||
Some('g') => match chars.next() {
|
||||
Some('p') => OPENAI, // g + p → "gp" (gpt)
|
||||
Some('e') => GOOGLE, // g + e → "ge" (gemini)
|
||||
Some('r') => XAI, // g + r → "ge" (grok)
|
||||
Some('r') => XAI, // g + r → "gr" (grok)
|
||||
_ => UNKNOWN,
|
||||
},
|
||||
Some('o') => match chars.next() {
|
||||
@@ -252,9 +260,11 @@ pub async fn get_available_models(
|
||||
_ => UNKNOWN,
|
||||
}
|
||||
};
|
||||
let display_name = calculate_display_name_v3(&model.name);
|
||||
|
||||
Model {
|
||||
id: crate::leak::intern_string(model.name),
|
||||
display_name: crate::leak::intern_string(display_name),
|
||||
created: CREATED,
|
||||
object: MODEL_OBJECT,
|
||||
owned_by,
|
||||
@@ -425,7 +435,11 @@ pub fn token_to_tokeninfo(
|
||||
// 构建 TokenInfo
|
||||
Some(key_config::TokenInfo {
|
||||
sub: payload.sub,
|
||||
exp: payload.exp,
|
||||
start: match payload.time.parse::<i64>() {
|
||||
Ok(n) => n,
|
||||
Err(_) => return None,
|
||||
},
|
||||
end: payload.exp,
|
||||
randomness: payload.randomness,
|
||||
signature: parts[2].to_string(),
|
||||
machine_id: machine_id_hash,
|
||||
@@ -439,9 +453,9 @@ pub fn tokeninfo_to_token(mut info: key_config::TokenInfo) -> Option<(String, St
|
||||
// 构建 payload
|
||||
let payload = TokenPayload {
|
||||
sub: std::mem::take(&mut info.sub),
|
||||
exp: info.exp,
|
||||
exp: info.end,
|
||||
randomness: std::mem::take(&mut info.randomness),
|
||||
time: (info.exp - 2592000000).to_string(), // exp - 30000天
|
||||
time: info.start.to_string(), // exp - 30000天
|
||||
iss: ISSUER.to_string(),
|
||||
scope: SCOPE.to_string(),
|
||||
aud: AUDIENCE.to_string(),
|
||||
@@ -466,7 +480,7 @@ pub fn tokeninfo_to_token(mut info: key_config::TokenInfo) -> Option<(String, St
|
||||
|
||||
// 组合 token
|
||||
Some((
|
||||
format!("{}.{}.{}", HEADER_B64, payload_b64, info.signature),
|
||||
format!("{HEADER_B64}.{payload_b64}.{}", info.signature),
|
||||
generate_checksum(&device_id, mac_addr.as_deref()),
|
||||
client,
|
||||
))
|
||||
@@ -497,3 +511,74 @@ pub fn encode_message(
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// 生成 PKCE code_verifier 和对应的 code_challenge (S256 method).
|
||||
/// 返回一个包含 (verifier, challenge) 的元组。
|
||||
fn generate_pkce_pair() -> (String, String) {
|
||||
use rand::TryRngCore as _;
|
||||
use sha2::Digest as _;
|
||||
|
||||
// 1. 生成 code_verifier 的原始随机字节 (32 bytes is recommended)
|
||||
let mut verifier_bytes = [0u8; 32];
|
||||
|
||||
// 使用 OsRng 填充字节。如果失败(极其罕见),则直接 panic
|
||||
rand::rngs::OsRng
|
||||
.try_fill_bytes(&mut verifier_bytes)
|
||||
.expect("获取系统安全随机数失败,这是一个严重错误!");
|
||||
|
||||
// 2. 将随机字节编码为 URL 安全 Base64 字符串,这就是 code_verifier
|
||||
let code_verifier = URL_SAFE_NO_PAD.encode(verifier_bytes);
|
||||
|
||||
// 3. 计算 code_verifier 字符串的 SHA-256 哈希值
|
||||
let hash_result = sha2::Sha256::digest(code_verifier.as_bytes());
|
||||
|
||||
// 4. 将哈希结果编码为 URL 安全 Base64 字符串,这就是 code_challenge
|
||||
let code_challenge = URL_SAFE_NO_PAD.encode(hash_result);
|
||||
|
||||
// 5. 同时返回 verifier 和 challenge
|
||||
(code_verifier, code_challenge)
|
||||
}
|
||||
|
||||
pub async fn get_new_token(client: Client, auth_token: &str, is_pri: bool) -> Option<String> {
|
||||
#[derive(serde::Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PollResponse {
|
||||
pub access_token: String,
|
||||
// pub refresh_token: String,
|
||||
// pub challenge: String,
|
||||
// pub auth_id: String,
|
||||
// pub uuid: String,
|
||||
}
|
||||
|
||||
let (verifier, challenge) = generate_pkce_pair();
|
||||
let user_id = extract_user_id(auth_token)?;
|
||||
let uuid = uuid::Uuid::new_v4().to_string();
|
||||
|
||||
let request = super::client::build_token_upgrade_request(
|
||||
&client, &uuid, &challenge, &user_id, auth_token, is_pri,
|
||||
);
|
||||
|
||||
let response = request.send().await.ok()?;
|
||||
if response.status() != reqwest::StatusCode::OK {
|
||||
return None;
|
||||
}
|
||||
|
||||
for _ in 0..5 {
|
||||
let request = super::client::build_token_poll_request(&client, &uuid, &verifier, is_pri);
|
||||
let response = request.send().await.ok()?;
|
||||
|
||||
match response.status() {
|
||||
reqwest::StatusCode::OK => {
|
||||
let poll_response = response.json::<PollResponse>().await.ok()?;
|
||||
return Some(poll_response.access_token);
|
||||
}
|
||||
reqwest::StatusCode::NOT_FOUND => {
|
||||
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||
continue;
|
||||
}
|
||||
_ => return None,
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
@@ -32,11 +32,7 @@ fn deobfuscate_bytes(bytes: &mut [u8]) {
|
||||
}
|
||||
|
||||
pub fn generate_timestamp_header() -> String {
|
||||
let timestamp = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs()
|
||||
/ 1_000;
|
||||
let timestamp = super::now_secs() / 1_000;
|
||||
|
||||
let mut timestamp_bytes = vec![
|
||||
((timestamp >> 8) & 0xFF) as u8,
|
||||
@@ -55,8 +51,8 @@ pub fn generate_timestamp_header() -> String {
|
||||
pub fn generate_checksum(device_id: &str, mac_addr: Option<&str>) -> String {
|
||||
let encoded = generate_timestamp_header();
|
||||
match mac_addr {
|
||||
Some(mac) => format!("{}{}/{}", encoded, device_id, mac),
|
||||
None => format!("{}{}", encoded, device_id),
|
||||
Some(mac) => format!("{encoded}{device_id}/{mac}"),
|
||||
None => format!("{encoded}{device_id}"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,20 +105,20 @@ pub fn generate_checksum_with_repair(checksum: &str) -> String {
|
||||
72 => format!(
|
||||
"{}{}/{}",
|
||||
generate_timestamp_header(),
|
||||
unsafe { std::str::from_utf8_unchecked(&bytes[8..]) },
|
||||
unsafe { std::str::from_utf8_unchecked(bytes.get_unchecked(8..)) },
|
||||
generate_hash()
|
||||
),
|
||||
129 => format!(
|
||||
"{}{}/{}",
|
||||
generate_timestamp_header(),
|
||||
unsafe { std::str::from_utf8_unchecked(&bytes[..64]) },
|
||||
unsafe { std::str::from_utf8_unchecked(&bytes[65..]) }
|
||||
unsafe { std::str::from_utf8_unchecked(bytes.get_unchecked(..64)) },
|
||||
unsafe { std::str::from_utf8_unchecked(bytes.get_unchecked(65..)) }
|
||||
),
|
||||
137 => format!(
|
||||
"{}{}/{}",
|
||||
generate_timestamp_header(),
|
||||
unsafe { std::str::from_utf8_unchecked(&bytes[8..72]) },
|
||||
unsafe { std::str::from_utf8_unchecked(&bytes[73..]) }
|
||||
unsafe { std::str::from_utf8_unchecked(bytes.get_unchecked(8..72)) },
|
||||
unsafe { std::str::from_utf8_unchecked(bytes.get_unchecked(73..)) }
|
||||
),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
@@ -137,17 +133,21 @@ pub fn extract_time_ks(timestamp_base64: &str) -> Option<u64> {
|
||||
|
||||
deobfuscate_bytes(&mut timestamp_bytes);
|
||||
|
||||
if timestamp_bytes[0] != timestamp_bytes[4] || timestamp_bytes[1] != timestamp_bytes[5] {
|
||||
return None;
|
||||
}
|
||||
unsafe {
|
||||
if timestamp_bytes.get_unchecked(0) != timestamp_bytes.get_unchecked(4)
|
||||
|| timestamp_bytes.get_unchecked(1) != timestamp_bytes.get_unchecked(5)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
// 使用后四位还原 timestamp
|
||||
Some(
|
||||
((timestamp_bytes[2] as u64) << 24)
|
||||
| ((timestamp_bytes[3] as u64) << 16)
|
||||
| ((timestamp_bytes[4] as u64) << 8)
|
||||
| (timestamp_bytes[5] as u64),
|
||||
)
|
||||
// 使用后四位还原 timestamp
|
||||
Some(
|
||||
((*timestamp_bytes.get_unchecked(2) as u64) << 24)
|
||||
| ((*timestamp_bytes.get_unchecked(3) as u64) << 16)
|
||||
| ((*timestamp_bytes.get_unchecked(4) as u64) << 8)
|
||||
| (*timestamp_bytes.get_unchecked(5) as u64),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn validate_checksum(checksum: &str) -> bool {
|
||||
@@ -183,16 +183,9 @@ pub fn validate_checksum(checksum: &str) -> bool {
|
||||
}
|
||||
|
||||
// 统一时间戳验证(无需分层)
|
||||
let time_valid = extract_time_ks(&checksum[..8]).is_some();
|
||||
let time_valid = extract_time_ks(unsafe { checksum.get_unchecked(..8) }).is_some();
|
||||
|
||||
// 附加MAC哈希长度校验(仅137字符需要)
|
||||
let mac_hash_valid = if len == 137 {
|
||||
checksum[73..].len() == 64 // 确保MAC哈希长度为64
|
||||
} else {
|
||||
true // 72字符无需此检查
|
||||
};
|
||||
|
||||
time_valid && mac_hash_valid
|
||||
time_valid
|
||||
}
|
||||
|
||||
/// 从校验通过的checksum中提取哈希值(需先通过validate_checksum验证)
|
||||
@@ -207,14 +200,14 @@ pub fn extract_hashes(checksum: &str) -> Option<(Vec<u8>, Vec<u8>)> {
|
||||
match checksum.len() {
|
||||
72 => {
|
||||
// 格式:8字节时间戳 + 64字节设备哈希
|
||||
let device_hash = hex::decode(&checksum[8..]).ok()?; // 8..72
|
||||
let device_hash = hex::decode(unsafe { checksum.get_unchecked(8..) }).ok()?; // 8..72
|
||||
Some((device_hash, Vec::new()))
|
||||
}
|
||||
137 => {
|
||||
// 格式:8时间戳 + 64设备哈希 + '/' + 64MAC哈希
|
||||
// 直接按固定位置切割(validate_checksum已确保索引72是'/')
|
||||
let device_hash = hex::decode(&checksum[8..72]).ok()?;
|
||||
let mac_hash = hex::decode(&checksum[73..]).ok()?; // 73..137
|
||||
let device_hash = hex::decode(unsafe { checksum.get_unchecked(8..72) }).ok()?;
|
||||
let mac_hash = hex::decode(unsafe { checksum.get_unchecked(73..) }).ok()?; // 73..137
|
||||
Some((device_hash, mac_hash))
|
||||
}
|
||||
// validate_checksum已过滤其他长度,此处应为不可达代码
|
||||
|
||||
@@ -123,10 +123,10 @@ pub fn validate_token(token: &str) -> bool {
|
||||
}
|
||||
|
||||
// 验证过期时间
|
||||
let current_time = chrono::Utc::now().timestamp();
|
||||
if current_time > payload.exp {
|
||||
return false;
|
||||
}
|
||||
// let current_time = chrono::Utc::now().timestamp();
|
||||
// if current_time > payload.exp {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// 验证发行者
|
||||
if payload.iss != ISSUER {
|
||||
@@ -183,8 +183,14 @@ pub fn extract_user_id(token: &str) -> Option<String> {
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize)]
|
||||
pub struct JwtTime {
|
||||
pub iat: DateTime<Local>,
|
||||
pub exp: DateTime<Local>,
|
||||
}
|
||||
|
||||
// 从 JWT token 中提取 time 字段
|
||||
pub fn extract_time(token: &str) -> Option<DateTime<Local>> {
|
||||
pub fn extract_time(token: &str) -> Option<JwtTime> {
|
||||
// JWT token 由3部分组成,用 . 分隔
|
||||
let parts: Vec<&str> = token.split('.').collect();
|
||||
if parts.len() != 3 {
|
||||
@@ -197,6 +203,8 @@ pub fn extract_time(token: &str) -> Option<DateTime<Local>> {
|
||||
Err(_) => return None,
|
||||
};
|
||||
|
||||
drop(parts);
|
||||
|
||||
// 将 payload 转换为字符串
|
||||
let payload_str = match String::from_utf8(payload) {
|
||||
Ok(s) => s,
|
||||
@@ -209,10 +217,12 @@ pub fn extract_time(token: &str) -> Option<DateTime<Local>> {
|
||||
Err(_) => return None,
|
||||
};
|
||||
|
||||
// 提取时间戳并转换为本地时间
|
||||
payload
|
||||
let iat = payload
|
||||
.time
|
||||
.parse::<i64>()
|
||||
.ok()
|
||||
.and_then(|timestamp| Local.timestamp_opt(timestamp, 0).single())
|
||||
.and_then(|ts| Local.timestamp_opt(ts, 0).single())?;
|
||||
let exp = Local.timestamp_opt(payload.exp, 0).single()?;
|
||||
|
||||
Some(JwtTime { iat, exp })
|
||||
}
|
||||
|
||||
@@ -88,6 +88,7 @@ fn parse_web_references(text: &str) -> Vec<WebReference> {
|
||||
|
||||
async fn process_chat_inputs(
|
||||
inputs: Vec<Message>,
|
||||
now_with_tz: Option<chrono::DateTime<chrono_tz::Tz>>,
|
||||
disable_vision: bool,
|
||||
model: &str,
|
||||
) -> (String, Vec<ConversationMessage>, Vec<String>) {
|
||||
@@ -115,7 +116,7 @@ async fn process_chat_inputs(
|
||||
// 使用默认指令或收集到的指令
|
||||
let image_support = !disable_vision && SUPPORTED_IMAGE_MODELS.contains(&model);
|
||||
let instructions = if instructions.is_empty() {
|
||||
get_default_instructions(model, image_support)
|
||||
get_default_instructions(now_with_tz, model, image_support)
|
||||
} else {
|
||||
instructions
|
||||
};
|
||||
@@ -491,15 +492,14 @@ async fn process_http_image(
|
||||
|
||||
pub async fn encode_chat_message(
|
||||
inputs: Vec<Message>,
|
||||
now_with_tz: Option<chrono::DateTime<chrono_tz::Tz>>,
|
||||
model: &str,
|
||||
disable_vision: bool,
|
||||
enable_slow_pool: bool,
|
||||
is_search: bool,
|
||||
) -> Result<Vec<u8>, Box<dyn std::error::Error + Send + Sync>> {
|
||||
// 在进入异步操作前获取并释放锁
|
||||
let enable_slow_pool = { if enable_slow_pool { Some(true) } else { None } };
|
||||
|
||||
let (instructions, messages, urls) = process_chat_inputs(inputs, disable_vision, model).await;
|
||||
let (instructions, messages, urls) =
|
||||
process_chat_inputs(inputs, now_with_tz, disable_vision, model).await;
|
||||
|
||||
let explicit_context = if !instructions.trim().is_empty() {
|
||||
Some(ExplicitContext {
|
||||
@@ -525,6 +525,8 @@ pub async fn encode_chat_message(
|
||||
})
|
||||
.collect();
|
||||
|
||||
let long_context = AppConfig::get_long_context() || LONG_CONTEXT_MODELS.contains(&model);
|
||||
|
||||
let chat = GetChatRequest {
|
||||
current_file: None,
|
||||
conversation: messages,
|
||||
@@ -542,7 +544,7 @@ pub async fn encode_chat_message(
|
||||
deployment: String::new(),
|
||||
use_azure: false,
|
||||
}),
|
||||
enable_slow_pool,
|
||||
enable_slow_pool: if enable_slow_pool { Some(true) } else { None },
|
||||
openai_api_base_url: None,
|
||||
}),
|
||||
documentation_identifiers: vec![],
|
||||
@@ -564,11 +566,9 @@ pub async fn encode_chat_message(
|
||||
workspace_id: None,
|
||||
external_links,
|
||||
commit_notes: vec![],
|
||||
long_context_mode: Some(
|
||||
AppConfig::get_long_context() || LONG_CONTEXT_MODELS.contains(&model),
|
||||
),
|
||||
long_context_mode: Some(long_context),
|
||||
is_eval: Some(false),
|
||||
desired_max_tokens: None,
|
||||
desired_max_tokens: if long_context { Some(200_000) } else { None },
|
||||
context_ast: None,
|
||||
is_composer: None,
|
||||
runnable_code_blocks: Some(false),
|
||||
|
||||
@@ -25,17 +25,7 @@ pub struct GitDiff {
|
||||
/// Nested message and enum types in `GitDiff`.
|
||||
pub mod git_diff {
|
||||
/// aiserver.v1.GitDiff.DiffType
|
||||
#[derive(
|
||||
Clone,
|
||||
Copy,
|
||||
Debug,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Hash,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
::prost::Enumeration
|
||||
)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
|
||||
#[repr(i32)]
|
||||
pub enum DiffType {
|
||||
Unspecified = 0,
|
||||
@@ -182,17 +172,7 @@ pub mod diagnostic {
|
||||
pub range: ::core::option::Option<super::CursorRange>,
|
||||
}
|
||||
/// aiserver.v1.Diagnostic.DiagnosticSeverity
|
||||
#[derive(
|
||||
Clone,
|
||||
Copy,
|
||||
Debug,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Hash,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
::prost::Enumeration
|
||||
)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
|
||||
#[repr(i32)]
|
||||
pub enum DiagnosticSeverity {
|
||||
Unspecified = 0,
|
||||
@@ -410,17 +390,7 @@ pub struct ErrorDetails {
|
||||
/// Nested message and enum types in `ErrorDetails`.
|
||||
pub mod error_details {
|
||||
/// aiserver.v1.ErrorDetails.Error
|
||||
#[derive(
|
||||
Clone,
|
||||
Copy,
|
||||
Debug,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Hash,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
::prost::Enumeration
|
||||
)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
|
||||
#[repr(i32)]
|
||||
pub enum Error {
|
||||
Unspecified = 0,
|
||||
@@ -509,9 +479,7 @@ pub mod error_details {
|
||||
Self::GenericRateLimitExceeded => "ERROR_GENERIC_RATE_LIMIT_EXCEEDED",
|
||||
Self::SlashEditFileTooLong => "ERROR_SLASH_EDIT_FILE_TOO_LONG",
|
||||
Self::FileUnsupported => "ERROR_FILE_UNSUPPORTED",
|
||||
Self::Gpt4VisionPreviewRateLimit => {
|
||||
"ERROR_GPT_4_VISION_PREVIEW_RATE_LIMIT"
|
||||
}
|
||||
Self::Gpt4VisionPreviewRateLimit => "ERROR_GPT_4_VISION_PREVIEW_RATE_LIMIT",
|
||||
Self::CustomMessage => "ERROR_CUSTOM_MESSAGE",
|
||||
Self::OutdatedClient => "ERROR_OUTDATED_CLIENT",
|
||||
Self::ClaudeImageTooLarge => "ERROR_CLAUDE_IMAGE_TOO_LARGE",
|
||||
@@ -526,9 +494,7 @@ pub mod error_details {
|
||||
Self::Unauthorized => "ERROR_UNAUTHORIZED",
|
||||
Self::ConversationTooLong => "ERROR_CONVERSATION_TOO_LONG",
|
||||
Self::UsagePricingRequired => "ERROR_USAGE_PRICING_REQUIRED",
|
||||
Self::UsagePricingRequiredChangeable => {
|
||||
"ERROR_USAGE_PRICING_REQUIRED_CHANGEABLE"
|
||||
}
|
||||
Self::UsagePricingRequiredChangeable => "ERROR_USAGE_PRICING_REQUIRED_CHANGEABLE",
|
||||
}
|
||||
}
|
||||
/// Creates an enum from field names used in the ProtoBuf definition.
|
||||
@@ -539,20 +505,14 @@ pub mod error_details {
|
||||
"ERROR_BAD_USER_API_KEY" => Some(Self::BadUserApiKey),
|
||||
"ERROR_NOT_LOGGED_IN" => Some(Self::NotLoggedIn),
|
||||
"ERROR_INVALID_AUTH_ID" => Some(Self::InvalidAuthId),
|
||||
"ERROR_NOT_HIGH_ENOUGH_PERMISSIONS" => {
|
||||
Some(Self::NotHighEnoughPermissions)
|
||||
}
|
||||
"ERROR_NOT_HIGH_ENOUGH_PERMISSIONS" => Some(Self::NotHighEnoughPermissions),
|
||||
"ERROR_AGENT_REQUIRES_LOGIN" => Some(Self::AgentRequiresLogin),
|
||||
"ERROR_BAD_MODEL_NAME" => Some(Self::BadModelName),
|
||||
"ERROR_NOT_FOUND" => Some(Self::NotFound),
|
||||
"ERROR_DEPRECATED" => Some(Self::Deprecated),
|
||||
"ERROR_USER_NOT_FOUND" => Some(Self::UserNotFound),
|
||||
"ERROR_FREE_USER_RATE_LIMIT_EXCEEDED" => {
|
||||
Some(Self::FreeUserRateLimitExceeded)
|
||||
}
|
||||
"ERROR_PRO_USER_RATE_LIMIT_EXCEEDED" => {
|
||||
Some(Self::ProUserRateLimitExceeded)
|
||||
}
|
||||
"ERROR_FREE_USER_RATE_LIMIT_EXCEEDED" => Some(Self::FreeUserRateLimitExceeded),
|
||||
"ERROR_PRO_USER_RATE_LIMIT_EXCEEDED" => Some(Self::ProUserRateLimitExceeded),
|
||||
"ERROR_FREE_USER_USAGE_LIMIT" => Some(Self::FreeUserUsageLimit),
|
||||
"ERROR_PRO_USER_USAGE_LIMIT" => Some(Self::ProUserUsageLimit),
|
||||
"ERROR_RESOURCE_EXHAUSTED" => Some(Self::ResourceExhausted),
|
||||
@@ -560,9 +520,7 @@ pub mod error_details {
|
||||
"ERROR_AUTH_TOKEN_EXPIRED" => Some(Self::AuthTokenExpired),
|
||||
"ERROR_OPENAI" => Some(Self::Openai),
|
||||
"ERROR_OPENAI_RATE_LIMIT_EXCEEDED" => Some(Self::OpenaiRateLimitExceeded),
|
||||
"ERROR_OPENAI_ACCOUNT_LIMIT_EXCEEDED" => {
|
||||
Some(Self::OpenaiAccountLimitExceeded)
|
||||
}
|
||||
"ERROR_OPENAI_ACCOUNT_LIMIT_EXCEEDED" => Some(Self::OpenaiAccountLimitExceeded),
|
||||
"ERROR_TASK_UUID_NOT_FOUND" => Some(Self::TaskUuidNotFound),
|
||||
"ERROR_TASK_NO_PERMISSIONS" => Some(Self::TaskNoPermissions),
|
||||
"ERROR_AGENT_ENGINE_NOT_FOUND" => Some(Self::AgentEngineNotFound),
|
||||
@@ -571,14 +529,10 @@ pub mod error_details {
|
||||
"ERROR_API_KEY_NOT_SUPPORTED" => Some(Self::ApiKeyNotSupported),
|
||||
"ERROR_USER_ABORTED_REQUEST" => Some(Self::UserAbortedRequest),
|
||||
"ERROR_TIMEOUT" => Some(Self::Timeout),
|
||||
"ERROR_GENERIC_RATE_LIMIT_EXCEEDED" => {
|
||||
Some(Self::GenericRateLimitExceeded)
|
||||
}
|
||||
"ERROR_GENERIC_RATE_LIMIT_EXCEEDED" => Some(Self::GenericRateLimitExceeded),
|
||||
"ERROR_SLASH_EDIT_FILE_TOO_LONG" => Some(Self::SlashEditFileTooLong),
|
||||
"ERROR_FILE_UNSUPPORTED" => Some(Self::FileUnsupported),
|
||||
"ERROR_GPT_4_VISION_PREVIEW_RATE_LIMIT" => {
|
||||
Some(Self::Gpt4VisionPreviewRateLimit)
|
||||
}
|
||||
"ERROR_GPT_4_VISION_PREVIEW_RATE_LIMIT" => Some(Self::Gpt4VisionPreviewRateLimit),
|
||||
"ERROR_CUSTOM_MESSAGE" => Some(Self::CustomMessage),
|
||||
"ERROR_OUTDATED_CLIENT" => Some(Self::OutdatedClient),
|
||||
"ERROR_CLAUDE_IMAGE_TOO_LARGE" => Some(Self::ClaudeImageTooLarge),
|
||||
@@ -609,9 +563,8 @@ pub struct CustomErrorDetails {
|
||||
#[prost(string, tag = "2")]
|
||||
pub detail: ::prost::alloc::string::String,
|
||||
#[prost(bool, optional, tag = "3")]
|
||||
pub allow_command_links_potentially_unsafe_please_only_use_for_handwritten_trusted_markdown: ::core::option::Option<
|
||||
bool,
|
||||
>,
|
||||
pub allow_command_links_potentially_unsafe_please_only_use_for_handwritten_trusted_markdown:
|
||||
::core::option::Option<bool>,
|
||||
#[prost(bool, optional, tag = "4")]
|
||||
pub is_retryable: ::core::option::Option<bool>,
|
||||
#[prost(bool, optional, tag = "5")]
|
||||
@@ -687,17 +640,7 @@ pub struct CodeChunk {
|
||||
/// Nested message and enum types in `CodeChunk`.
|
||||
pub mod code_chunk {
|
||||
/// aiserver.v1.CodeChunk.Intent
|
||||
#[derive(
|
||||
Clone,
|
||||
Copy,
|
||||
Debug,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Hash,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
::prost::Enumeration
|
||||
)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
|
||||
#[repr(i32)]
|
||||
pub enum Intent {
|
||||
Unspecified = 0,
|
||||
@@ -727,17 +670,7 @@ pub mod code_chunk {
|
||||
}
|
||||
}
|
||||
/// aiserver.v1.CodeChunk.SummarizationStrategy
|
||||
#[derive(
|
||||
Clone,
|
||||
Copy,
|
||||
Debug,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Hash,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
::prost::Enumeration
|
||||
)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
|
||||
#[repr(i32)]
|
||||
pub enum SummarizationStrategy {
|
||||
NoneUnspecified = 0,
|
||||
@@ -852,9 +785,8 @@ pub struct ToolResultError {
|
||||
#[prost(string, tag = "2")]
|
||||
pub model_visible_error_message: ::prost::alloc::string::String,
|
||||
#[prost(string, optional, tag = "3")]
|
||||
pub actual_error_message_only_send_from_client_to_server_never_the_other_way_around_because_that_may_be_a_security_risk: ::core::option::Option<
|
||||
::prost::alloc::string::String,
|
||||
>,
|
||||
pub actual_error_message_only_send_from_client_to_server_never_the_other_way_around_because_that_may_be_a_security_risk:
|
||||
::core::option::Option<::prost::alloc::string::String>,
|
||||
}
|
||||
/// aiserver.v1.ClientSideToolV2Result
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
@@ -971,15 +903,7 @@ pub mod edit_file_result {
|
||||
}
|
||||
/// aiserver.v1.EditFileResult.FileDiff.Editor
|
||||
#[derive(
|
||||
Clone,
|
||||
Copy,
|
||||
Debug,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Hash,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
::prost::Enumeration
|
||||
Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration,
|
||||
)]
|
||||
#[repr(i32)]
|
||||
pub enum Editor {
|
||||
@@ -1101,9 +1025,8 @@ pub struct RipgrepSearchResultInternal {
|
||||
#[prost(bool, optional, tag = "3")]
|
||||
pub limit_hit: ::core::option::Option<bool>,
|
||||
#[prost(message, repeated, tag = "4")]
|
||||
pub messages: ::prost::alloc::vec::Vec<
|
||||
ripgrep_search_result_internal::ITextSearchCompleteMessage,
|
||||
>,
|
||||
pub messages:
|
||||
::prost::alloc::vec::Vec<ripgrep_search_result_internal::ITextSearchCompleteMessage>,
|
||||
#[prost(oneof = "ripgrep_search_result_internal::Stats", tags = "5, 6")]
|
||||
pub stats: ::core::option::Option<ripgrep_search_result_internal::Stats>,
|
||||
}
|
||||
@@ -1205,15 +1128,7 @@ pub mod ripgrep_search_result_internal {
|
||||
pub mod i_file_search_stats {
|
||||
/// aiserver.v1.RipgrepSearchResultInternal.IFileSearchStats.FileSearchProviderType
|
||||
#[derive(
|
||||
Clone,
|
||||
Copy,
|
||||
Debug,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Hash,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
::prost::Enumeration
|
||||
Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration,
|
||||
)]
|
||||
#[repr(i32)]
|
||||
pub enum FileSearchProviderType {
|
||||
@@ -1229,9 +1144,7 @@ pub mod ripgrep_search_result_internal {
|
||||
pub fn as_str_name(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Unspecified => "FILE_SEARCH_PROVIDER_TYPE_UNSPECIFIED",
|
||||
Self::FileSearchProvider => {
|
||||
"FILE_SEARCH_PROVIDER_TYPE_FILE_SEARCH_PROVIDER"
|
||||
}
|
||||
Self::FileSearchProvider => "FILE_SEARCH_PROVIDER_TYPE_FILE_SEARCH_PROVIDER",
|
||||
Self::SearchProcess => "FILE_SEARCH_PROVIDER_TYPE_SEARCH_PROCESS",
|
||||
}
|
||||
}
|
||||
@@ -1242,9 +1155,7 @@ pub mod ripgrep_search_result_internal {
|
||||
"FILE_SEARCH_PROVIDER_TYPE_FILE_SEARCH_PROVIDER" => {
|
||||
Some(Self::FileSearchProvider)
|
||||
}
|
||||
"FILE_SEARCH_PROVIDER_TYPE_SEARCH_PROCESS" => {
|
||||
Some(Self::SearchProcess)
|
||||
}
|
||||
"FILE_SEARCH_PROVIDER_TYPE_SEARCH_PROCESS" => Some(Self::SearchProcess),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -1269,15 +1180,7 @@ pub mod ripgrep_search_result_internal {
|
||||
pub mod i_text_search_stats {
|
||||
/// aiserver.v1.RipgrepSearchResultInternal.ITextSearchStats.TextSearchProviderType
|
||||
#[derive(
|
||||
Clone,
|
||||
Copy,
|
||||
Debug,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Hash,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
::prost::Enumeration
|
||||
Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration,
|
||||
)]
|
||||
#[repr(i32)]
|
||||
pub enum TextSearchProviderType {
|
||||
@@ -1294,9 +1197,7 @@ pub mod ripgrep_search_result_internal {
|
||||
pub fn as_str_name(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Unspecified => "TEXT_SEARCH_PROVIDER_TYPE_UNSPECIFIED",
|
||||
Self::TextSearchProvider => {
|
||||
"TEXT_SEARCH_PROVIDER_TYPE_TEXT_SEARCH_PROVIDER"
|
||||
}
|
||||
Self::TextSearchProvider => "TEXT_SEARCH_PROVIDER_TYPE_TEXT_SEARCH_PROVIDER",
|
||||
Self::SearchProcess => "TEXT_SEARCH_PROVIDER_TYPE_SEARCH_PROCESS",
|
||||
Self::AiTextSearchProvider => {
|
||||
"TEXT_SEARCH_PROVIDER_TYPE_AI_TEXT_SEARCH_PROVIDER"
|
||||
@@ -1310,9 +1211,7 @@ pub mod ripgrep_search_result_internal {
|
||||
"TEXT_SEARCH_PROVIDER_TYPE_TEXT_SEARCH_PROVIDER" => {
|
||||
Some(Self::TextSearchProvider)
|
||||
}
|
||||
"TEXT_SEARCH_PROVIDER_TYPE_SEARCH_PROCESS" => {
|
||||
Some(Self::SearchProcess)
|
||||
}
|
||||
"TEXT_SEARCH_PROVIDER_TYPE_SEARCH_PROCESS" => Some(Self::SearchProcess),
|
||||
"TEXT_SEARCH_PROVIDER_TYPE_AI_TEXT_SEARCH_PROVIDER" => {
|
||||
Some(Self::AiTextSearchProvider)
|
||||
}
|
||||
@@ -1356,17 +1255,7 @@ pub mod ripgrep_search_result_internal {
|
||||
pub post_process_time: i32,
|
||||
}
|
||||
/// aiserver.v1.RipgrepSearchResultInternal.TextSearchCompleteMessageType
|
||||
#[derive(
|
||||
Clone,
|
||||
Copy,
|
||||
Debug,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Hash,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
::prost::Enumeration
|
||||
)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
|
||||
#[repr(i32)]
|
||||
pub enum TextSearchCompleteMessageType {
|
||||
Unspecified = 0,
|
||||
@@ -1388,29 +1277,15 @@ pub mod ripgrep_search_result_internal {
|
||||
/// Creates an enum from field names used in the ProtoBuf definition.
|
||||
pub fn from_str_name(value: &str) -> ::core::option::Option<Self> {
|
||||
match value {
|
||||
"TEXT_SEARCH_COMPLETE_MESSAGE_TYPE_UNSPECIFIED" => {
|
||||
Some(Self::Unspecified)
|
||||
}
|
||||
"TEXT_SEARCH_COMPLETE_MESSAGE_TYPE_INFORMATION" => {
|
||||
Some(Self::Information)
|
||||
}
|
||||
"TEXT_SEARCH_COMPLETE_MESSAGE_TYPE_UNSPECIFIED" => Some(Self::Unspecified),
|
||||
"TEXT_SEARCH_COMPLETE_MESSAGE_TYPE_INFORMATION" => Some(Self::Information),
|
||||
"TEXT_SEARCH_COMPLETE_MESSAGE_TYPE_WARNING" => Some(Self::Warning),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
/// aiserver.v1.RipgrepSearchResultInternal.SearchCompletionExitCode
|
||||
#[derive(
|
||||
Clone,
|
||||
Copy,
|
||||
Debug,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Hash,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
::prost::Enumeration
|
||||
)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
|
||||
#[repr(i32)]
|
||||
pub enum SearchCompletionExitCode {
|
||||
Unspecified = 0,
|
||||
@@ -1426,9 +1301,7 @@ pub mod ripgrep_search_result_internal {
|
||||
match self {
|
||||
Self::Unspecified => "SEARCH_COMPLETION_EXIT_CODE_UNSPECIFIED",
|
||||
Self::Normal => "SEARCH_COMPLETION_EXIT_CODE_NORMAL",
|
||||
Self::NewSearchStarted => {
|
||||
"SEARCH_COMPLETION_EXIT_CODE_NEW_SEARCH_STARTED"
|
||||
}
|
||||
Self::NewSearchStarted => "SEARCH_COMPLETION_EXIT_CODE_NEW_SEARCH_STARTED",
|
||||
}
|
||||
}
|
||||
/// Creates an enum from field names used in the ProtoBuf definition.
|
||||
@@ -1436,9 +1309,7 @@ pub mod ripgrep_search_result_internal {
|
||||
match value {
|
||||
"SEARCH_COMPLETION_EXIT_CODE_UNSPECIFIED" => Some(Self::Unspecified),
|
||||
"SEARCH_COMPLETION_EXIT_CODE_NORMAL" => Some(Self::Normal),
|
||||
"SEARCH_COMPLETION_EXIT_CODE_NEW_SEARCH_STARTED" => {
|
||||
Some(Self::NewSearchStarted)
|
||||
}
|
||||
"SEARCH_COMPLETION_EXIT_CODE_NEW_SEARCH_STARTED" => Some(Self::NewSearchStarted),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -1464,17 +1335,7 @@ pub struct MissingFile {
|
||||
/// Nested message and enum types in `MissingFile`.
|
||||
pub mod missing_file {
|
||||
/// aiserver.v1.MissingFile.MissingReason
|
||||
#[derive(
|
||||
Clone,
|
||||
Copy,
|
||||
Debug,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Hash,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
::prost::Enumeration
|
||||
)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
|
||||
#[repr(i32)]
|
||||
pub enum MissingReason {
|
||||
Unspecified = 0,
|
||||
@@ -1705,17 +1566,13 @@ pub mod diff_history_result {
|
||||
#[prost(int32, tag = "2")]
|
||||
pub end_line_number_exclusive: i32,
|
||||
#[prost(string, repeated, tag = "3")]
|
||||
pub before_context_lines: ::prost::alloc::vec::Vec<
|
||||
::prost::alloc::string::String,
|
||||
>,
|
||||
pub before_context_lines: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
|
||||
#[prost(string, repeated, tag = "4")]
|
||||
pub removed_lines: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
|
||||
#[prost(string, repeated, tag = "5")]
|
||||
pub added_lines: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
|
||||
#[prost(string, repeated, tag = "6")]
|
||||
pub after_context_lines: ::prost::alloc::vec::Vec<
|
||||
::prost::alloc::string::String,
|
||||
>,
|
||||
pub after_context_lines: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
|
||||
}
|
||||
/// aiserver.v1.DiffHistoryResult.HumanChange
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
@@ -1772,15 +1629,7 @@ pub mod implementer_result {
|
||||
}
|
||||
/// aiserver.v1.ImplementerResult.FileDiff.Editor
|
||||
#[derive(
|
||||
Clone,
|
||||
Copy,
|
||||
Debug,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Hash,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
::prost::Enumeration
|
||||
Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration,
|
||||
)]
|
||||
#[repr(i32)]
|
||||
pub enum Editor {
|
||||
@@ -1950,10 +1799,7 @@ pub mod composer_capability_request {
|
||||
#[prost(string, tag = "2")]
|
||||
pub name: ::prost::alloc::string::String,
|
||||
#[prost(map = "string, message", tag = "3")]
|
||||
pub properties: ::std::collections::HashMap<
|
||||
::prost::alloc::string::String,
|
||||
SchemaProperty,
|
||||
>,
|
||||
pub properties: ::std::collections::HashMap<::prost::alloc::string::String, SchemaProperty>,
|
||||
#[prost(string, repeated, tag = "4")]
|
||||
pub required: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
|
||||
}
|
||||
@@ -2011,9 +1857,7 @@ pub mod composer_capability_request {
|
||||
#[prost(string, repeated, tag = "4")]
|
||||
pub files_in_context: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
|
||||
#[prost(string, repeated, tag = "5")]
|
||||
pub semantic_search_files: ::prost::alloc::vec::Vec<
|
||||
::prost::alloc::string::String,
|
||||
>,
|
||||
pub semantic_search_files: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
|
||||
}
|
||||
/// aiserver.v1.ComposerCapabilityRequest.DiffReviewCapability
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
@@ -2061,9 +1905,7 @@ pub mod composer_capability_request {
|
||||
#[prost(string, optional, tag = "1")]
|
||||
pub custom_instructions: ::core::option::Option<::prost::alloc::string::String>,
|
||||
#[prost(string, repeated, tag = "2")]
|
||||
pub potential_context_files: ::prost::alloc::vec::Vec<
|
||||
::prost::alloc::string::String,
|
||||
>,
|
||||
pub potential_context_files: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
|
||||
#[prost(message, repeated, tag = "3")]
|
||||
pub potential_context_code_chunks: ::prost::alloc::vec::Vec<super::CodeChunk>,
|
||||
#[prost(string, repeated, tag = "4")]
|
||||
@@ -2106,17 +1948,7 @@ pub mod composer_capability_request {
|
||||
pub custom_instructions: ::core::option::Option<::prost::alloc::string::String>,
|
||||
}
|
||||
/// aiserver.v1.ComposerCapabilityRequest.ComposerCapabilityType
|
||||
#[derive(
|
||||
Clone,
|
||||
Copy,
|
||||
Debug,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Hash,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
::prost::Enumeration
|
||||
)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
|
||||
#[repr(i32)]
|
||||
pub enum ComposerCapabilityType {
|
||||
Unspecified = 0,
|
||||
@@ -2171,9 +2003,7 @@ pub mod composer_capability_request {
|
||||
Self::UsageData => "COMPOSER_CAPABILITY_TYPE_USAGE_DATA",
|
||||
Self::Chimes => "COMPOSER_CAPABILITY_TYPE_CHIMES",
|
||||
Self::CodeDecayTracker => "COMPOSER_CAPABILITY_TYPE_CODE_DECAY_TRACKER",
|
||||
Self::BackgroundComposer => {
|
||||
"COMPOSER_CAPABILITY_TYPE_BACKGROUND_COMPOSER"
|
||||
}
|
||||
Self::BackgroundComposer => "COMPOSER_CAPABILITY_TYPE_BACKGROUND_COMPOSER",
|
||||
Self::Summarization => "COMPOSER_CAPABILITY_TYPE_SUMMARIZATION",
|
||||
}
|
||||
}
|
||||
@@ -2200,29 +2030,15 @@ pub mod composer_capability_request {
|
||||
"COMPOSER_CAPABILITY_TYPE_TOKEN_COUNTER" => Some(Self::TokenCounter),
|
||||
"COMPOSER_CAPABILITY_TYPE_USAGE_DATA" => Some(Self::UsageData),
|
||||
"COMPOSER_CAPABILITY_TYPE_CHIMES" => Some(Self::Chimes),
|
||||
"COMPOSER_CAPABILITY_TYPE_CODE_DECAY_TRACKER" => {
|
||||
Some(Self::CodeDecayTracker)
|
||||
}
|
||||
"COMPOSER_CAPABILITY_TYPE_BACKGROUND_COMPOSER" => {
|
||||
Some(Self::BackgroundComposer)
|
||||
}
|
||||
"COMPOSER_CAPABILITY_TYPE_CODE_DECAY_TRACKER" => Some(Self::CodeDecayTracker),
|
||||
"COMPOSER_CAPABILITY_TYPE_BACKGROUND_COMPOSER" => Some(Self::BackgroundComposer),
|
||||
"COMPOSER_CAPABILITY_TYPE_SUMMARIZATION" => Some(Self::Summarization),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
/// aiserver.v1.ComposerCapabilityRequest.ToolType
|
||||
#[derive(
|
||||
Clone,
|
||||
Copy,
|
||||
Debug,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Hash,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
::prost::Enumeration
|
||||
)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
|
||||
#[repr(i32)]
|
||||
pub enum ToolType {
|
||||
Unspecified = 0,
|
||||
@@ -2255,9 +2071,7 @@ pub mod composer_capability_request {
|
||||
"TOOL_TYPE_RUN_TERMINAL_COMMAND" => Some(Self::RunTerminalCommand),
|
||||
"TOOL_TYPE_ITERATE" => Some(Self::Iterate),
|
||||
"TOOL_TYPE_REMOVE_FILE_FROM_CONTEXT" => Some(Self::RemoveFileFromContext),
|
||||
"TOOL_TYPE_SEMANTIC_SEARCH_CODEBASE" => {
|
||||
Some(Self::SemanticSearchCodebase)
|
||||
}
|
||||
"TOOL_TYPE_SEMANTIC_SEARCH_CODEBASE" => Some(Self::SemanticSearchCodebase),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -2370,17 +2184,7 @@ pub struct StreamUnifiedChatRequest {}
|
||||
/// Nested message and enum types in `StreamUnifiedChatRequest`.
|
||||
pub mod stream_unified_chat_request {
|
||||
/// aiserver.v1.StreamUnifiedChatRequest.UnifiedMode
|
||||
#[derive(
|
||||
Clone,
|
||||
Copy,
|
||||
Debug,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Hash,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
::prost::Enumeration
|
||||
)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
|
||||
#[repr(i32)]
|
||||
pub enum UnifiedMode {
|
||||
Unspecified = 0,
|
||||
@@ -2434,13 +2238,10 @@ pub struct ServiceStatusUpdate {
|
||||
#[prost(string, tag = "2")]
|
||||
pub codicon: ::prost::alloc::string::String,
|
||||
#[prost(bool, optional, tag = "3")]
|
||||
pub allow_command_links_potentially_unsafe_please_only_use_for_handwritten_trusted_markdown: ::core::option::Option<
|
||||
bool,
|
||||
>,
|
||||
pub allow_command_links_potentially_unsafe_please_only_use_for_handwritten_trusted_markdown:
|
||||
::core::option::Option<bool>,
|
||||
#[prost(string, optional, tag = "4")]
|
||||
pub action_to_run_on_status_update: ::core::option::Option<
|
||||
::prost::alloc::string::String,
|
||||
>,
|
||||
pub action_to_run_on_status_update: ::core::option::Option<::prost::alloc::string::String>,
|
||||
}
|
||||
/// aiserver.v1.SymbolLink
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
@@ -2548,9 +2349,8 @@ pub struct ConversationMessage {
|
||||
#[prost(string, repeated, tag = "11")]
|
||||
pub attached_folders: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
|
||||
#[prost(message, repeated, tag = "12")]
|
||||
pub approximate_lint_errors: ::prost::alloc::vec::Vec<
|
||||
conversation_message::ApproximateLintError,
|
||||
>,
|
||||
pub approximate_lint_errors:
|
||||
::prost::alloc::vec::Vec<conversation_message::ApproximateLintError>,
|
||||
#[prost(string, tag = "13")]
|
||||
pub bubble_id: ::prost::alloc::string::String,
|
||||
#[prost(string, optional, tag = "32")]
|
||||
@@ -2560,9 +2360,8 @@ pub struct ConversationMessage {
|
||||
#[prost(message, repeated, tag = "15")]
|
||||
pub lints: ::prost::alloc::vec::Vec<conversation_message::Lints>,
|
||||
#[prost(message, repeated, tag = "16")]
|
||||
pub user_responses_to_suggested_code_blocks: ::prost::alloc::vec::Vec<
|
||||
UserResponseToSuggestedCodeBlock,
|
||||
>,
|
||||
pub user_responses_to_suggested_code_blocks:
|
||||
::prost::alloc::vec::Vec<UserResponseToSuggestedCodeBlock>,
|
||||
#[prost(string, repeated, tag = "17")]
|
||||
pub relevant_files: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
|
||||
#[prost(message, repeated, tag = "18")]
|
||||
@@ -2574,25 +2373,19 @@ pub struct ConversationMessage {
|
||||
#[prost(message, repeated, tag = "21")]
|
||||
pub capabilities: ::prost::alloc::vec::Vec<ComposerCapabilityRequest>,
|
||||
#[prost(message, repeated, tag = "22")]
|
||||
pub edit_trail_contexts: ::prost::alloc::vec::Vec<
|
||||
conversation_message::EditTrailContext,
|
||||
>,
|
||||
pub edit_trail_contexts: ::prost::alloc::vec::Vec<conversation_message::EditTrailContext>,
|
||||
#[prost(message, repeated, tag = "23")]
|
||||
pub suggested_code_blocks: ::prost::alloc::vec::Vec<SuggestedCodeBlock>,
|
||||
#[prost(message, repeated, tag = "24")]
|
||||
pub diffs_for_compressing_files: ::prost::alloc::vec::Vec<RedDiff>,
|
||||
#[prost(message, repeated, tag = "25")]
|
||||
pub multi_file_linter_errors: ::prost::alloc::vec::Vec<
|
||||
LinterErrorsWithoutFileContents,
|
||||
>,
|
||||
pub multi_file_linter_errors: ::prost::alloc::vec::Vec<LinterErrorsWithoutFileContents>,
|
||||
#[prost(message, repeated, tag = "26")]
|
||||
pub diff_histories: ::prost::alloc::vec::Vec<DiffHistoryData>,
|
||||
#[prost(message, repeated, tag = "27")]
|
||||
pub recently_viewed_files: ::prost::alloc::vec::Vec<conversation_message::CodeChunk>,
|
||||
#[prost(message, repeated, tag = "28")]
|
||||
pub recent_locations_history: ::prost::alloc::vec::Vec<
|
||||
conversation_message::RecentLocation,
|
||||
>,
|
||||
pub recent_locations_history: ::prost::alloc::vec::Vec<conversation_message::RecentLocation>,
|
||||
#[prost(bool, tag = "29")]
|
||||
pub is_agentic: bool,
|
||||
#[prost(message, repeated, tag = "30")]
|
||||
@@ -2618,9 +2411,7 @@ pub struct ConversationMessage {
|
||||
#[prost(bool, tag = "41")]
|
||||
pub attached_human_changes: bool,
|
||||
#[prost(message, repeated, tag = "42")]
|
||||
pub summarized_composers: ::prost::alloc::vec::Vec<
|
||||
conversation_message::ComposerContext,
|
||||
>,
|
||||
pub summarized_composers: ::prost::alloc::vec::Vec<conversation_message::ComposerContext>,
|
||||
#[prost(message, repeated, tag = "43")]
|
||||
pub cursor_rules: ::prost::alloc::vec::Vec<CursorRule>,
|
||||
#[prost(message, repeated, tag = "44")]
|
||||
@@ -2636,9 +2427,7 @@ pub struct ConversationMessage {
|
||||
)]
|
||||
pub unified_mode: ::core::option::Option<i32>,
|
||||
#[prost(message, repeated, tag = "48")]
|
||||
pub diffs_since_last_apply: ::prost::alloc::vec::Vec<
|
||||
conversation_message::DiffSinceLastApply,
|
||||
>,
|
||||
pub diffs_since_last_apply: ::prost::alloc::vec::Vec<conversation_message::DiffSinceLastApply>,
|
||||
#[prost(message, repeated, tag = "49")]
|
||||
pub deleted_files: ::prost::alloc::vec::Vec<conversation_message::DeletedFile>,
|
||||
#[prost(string, optional, tag = "50")]
|
||||
@@ -2676,15 +2465,7 @@ pub mod conversation_message {
|
||||
pub mod code_chunk {
|
||||
/// aiserver.v1.ConversationMessage.CodeChunk.Intent
|
||||
#[derive(
|
||||
Clone,
|
||||
Copy,
|
||||
Debug,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Hash,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
::prost::Enumeration
|
||||
Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration,
|
||||
)]
|
||||
#[repr(i32)]
|
||||
pub enum Intent {
|
||||
@@ -2717,9 +2498,7 @@ pub mod conversation_message {
|
||||
match value {
|
||||
"INTENT_UNSPECIFIED" => Some(Self::Unspecified),
|
||||
"INTENT_COMPOSER_FILE" => Some(Self::ComposerFile),
|
||||
"INTENT_COMPRESSED_COMPOSER_FILE" => {
|
||||
Some(Self::CompressedComposerFile)
|
||||
}
|
||||
"INTENT_COMPRESSED_COMPOSER_FILE" => Some(Self::CompressedComposerFile),
|
||||
"INTENT_RECENTLY_VIEWED_FILE" => Some(Self::RecentlyViewedFile),
|
||||
"INTENT_OUTLINE" => Some(Self::Outline),
|
||||
"INTENT_MENTIONED_FILE" => Some(Self::MentionedFile),
|
||||
@@ -2730,15 +2509,7 @@ pub mod conversation_message {
|
||||
}
|
||||
/// aiserver.v1.ConversationMessage.CodeChunk.SummarizationStrategy
|
||||
#[derive(
|
||||
Clone,
|
||||
Copy,
|
||||
Debug,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Hash,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
::prost::Enumeration
|
||||
Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration,
|
||||
)]
|
||||
#[repr(i32)]
|
||||
pub enum SummarizationStrategy {
|
||||
@@ -2761,9 +2532,7 @@ pub mod conversation_message {
|
||||
/// Creates an enum from field names used in the ProtoBuf definition.
|
||||
pub fn from_str_name(value: &str) -> ::core::option::Option<Self> {
|
||||
match value {
|
||||
"SUMMARIZATION_STRATEGY_NONE_UNSPECIFIED" => {
|
||||
Some(Self::NoneUnspecified)
|
||||
}
|
||||
"SUMMARIZATION_STRATEGY_NONE_UNSPECIFIED" => Some(Self::NoneUnspecified),
|
||||
"SUMMARIZATION_STRATEGY_SUMMARIZED" => Some(Self::Summarized),
|
||||
"SUMMARIZATION_STRATEGY_EMBEDDED" => Some(Self::Embedded),
|
||||
_ => None,
|
||||
@@ -2908,17 +2677,13 @@ pub mod conversation_message {
|
||||
#[prost(int32, tag = "2")]
|
||||
pub end_line_number_exclusive: i32,
|
||||
#[prost(string, repeated, tag = "3")]
|
||||
pub before_context_lines: ::prost::alloc::vec::Vec<
|
||||
::prost::alloc::string::String,
|
||||
>,
|
||||
pub before_context_lines: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
|
||||
#[prost(string, repeated, tag = "4")]
|
||||
pub removed_lines: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
|
||||
#[prost(string, repeated, tag = "5")]
|
||||
pub added_lines: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
|
||||
#[prost(string, repeated, tag = "6")]
|
||||
pub after_context_lines: ::prost::alloc::vec::Vec<
|
||||
::prost::alloc::string::String,
|
||||
>,
|
||||
pub after_context_lines: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
|
||||
}
|
||||
/// aiserver.v1.ConversationMessage.HumanChange
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
@@ -2959,17 +2724,7 @@ pub mod conversation_message {
|
||||
pub relative_workspace_path: ::prost::alloc::string::String,
|
||||
}
|
||||
/// aiserver.v1.ConversationMessage.MessageType
|
||||
#[derive(
|
||||
Clone,
|
||||
Copy,
|
||||
Debug,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Hash,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
::prost::Enumeration
|
||||
)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
|
||||
#[repr(i32)]
|
||||
pub enum MessageType {
|
||||
Unspecified = 0,
|
||||
@@ -3108,17 +2863,7 @@ pub struct UserResponseToSuggestedCodeBlock {
|
||||
/// Nested message and enum types in `UserResponseToSuggestedCodeBlock`.
|
||||
pub mod user_response_to_suggested_code_block {
|
||||
/// aiserver.v1.UserResponseToSuggestedCodeBlock.UserResponseType
|
||||
#[derive(
|
||||
Clone,
|
||||
Copy,
|
||||
Debug,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Hash,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
::prost::Enumeration
|
||||
)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
|
||||
#[repr(i32)]
|
||||
pub enum UserResponseType {
|
||||
Unspecified = 0,
|
||||
@@ -3182,17 +2927,7 @@ pub mod composer_file_diff {
|
||||
pub lines_added: i32,
|
||||
}
|
||||
/// aiserver.v1.ComposerFileDiff.Editor
|
||||
#[derive(
|
||||
Clone,
|
||||
Copy,
|
||||
Debug,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Hash,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
::prost::Enumeration
|
||||
)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
|
||||
#[repr(i32)]
|
||||
pub enum Editor {
|
||||
Unspecified = 0,
|
||||
@@ -3359,19 +3094,13 @@ pub mod available_models_response {
|
||||
pub price: ::core::option::Option<f64>,
|
||||
#[prost(message, optional, tag = "8")]
|
||||
pub tooltip_data: ::core::option::Option<TooltipData>,
|
||||
#[prost(bool, optional, tag = "9")]
|
||||
pub supports_thinking: ::core::option::Option<bool>,
|
||||
#[prost(bool, optional, tag = "10")]
|
||||
pub supports_images: ::core::option::Option<bool>,
|
||||
}
|
||||
/// aiserver.v1.AvailableModelsResponse.DegradationStatus
|
||||
#[derive(
|
||||
Clone,
|
||||
Copy,
|
||||
Debug,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Hash,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
::prost::Enumeration
|
||||
)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
|
||||
#[repr(i32)]
|
||||
pub enum DegradationStatus {
|
||||
Unspecified = 0,
|
||||
@@ -3451,13 +3180,9 @@ pub mod debug_info {
|
||||
#[prost(int32, tag = "2")]
|
||||
pub line_number: i32,
|
||||
#[prost(string, repeated, tag = "3")]
|
||||
pub lines_before_breakpoint: ::prost::alloc::vec::Vec<
|
||||
::prost::alloc::string::String,
|
||||
>,
|
||||
pub lines_before_breakpoint: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
|
||||
#[prost(string, repeated, tag = "4")]
|
||||
pub lines_after_breakpoint: ::prost::alloc::vec::Vec<
|
||||
::prost::alloc::string::String,
|
||||
>,
|
||||
pub lines_after_breakpoint: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
|
||||
#[prost(string, optional, tag = "5")]
|
||||
pub exception_info: ::core::option::Option<::prost::alloc::string::String>,
|
||||
}
|
||||
@@ -3480,9 +3205,7 @@ pub struct GetChatRequest {
|
||||
#[prost(message, optional, tag = "7")]
|
||||
pub model_details: ::core::option::Option<ModelDetails>,
|
||||
#[prost(string, repeated, tag = "8")]
|
||||
pub documentation_identifiers: ::prost::alloc::vec::Vec<
|
||||
::prost::alloc::string::String,
|
||||
>,
|
||||
pub documentation_identifiers: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
|
||||
#[prost(string, tag = "9")]
|
||||
pub request_id: ::prost::alloc::string::String,
|
||||
#[prost(message, optional, tag = "10")]
|
||||
@@ -3550,9 +3273,7 @@ pub struct StreamChatResponse {
|
||||
#[prost(string, optional, tag = "22")]
|
||||
pub server_bubble_id: ::core::option::Option<::prost::alloc::string::String>,
|
||||
#[prost(string, optional, tag = "2")]
|
||||
pub debugging_only_chat_prompt: ::core::option::Option<
|
||||
::prost::alloc::string::String,
|
||||
>,
|
||||
pub debugging_only_chat_prompt: ::core::option::Option<::prost::alloc::string::String>,
|
||||
#[prost(int32, optional, tag = "3")]
|
||||
pub debugging_only_token_count: ::core::option::Option<i32>,
|
||||
#[prost(message, optional, tag = "4")]
|
||||
@@ -3721,9 +3442,7 @@ impl ClientSideToolV2 {
|
||||
Self::DiffHistory => "CLIENT_SIDE_TOOL_V2_DIFF_HISTORY",
|
||||
Self::Implementer => "CLIENT_SIDE_TOOL_V2_IMPLEMENTER",
|
||||
Self::SearchSymbols => "CLIENT_SIDE_TOOL_V2_SEARCH_SYMBOLS",
|
||||
Self::BackgroundComposerFollowup => {
|
||||
"CLIENT_SIDE_TOOL_V2_BACKGROUND_COMPOSER_FOLLOWUP"
|
||||
}
|
||||
Self::BackgroundComposerFollowup => "CLIENT_SIDE_TOOL_V2_BACKGROUND_COMPOSER_FOLLOWUP",
|
||||
}
|
||||
}
|
||||
/// Creates an enum from field names used in the ProtoBuf definition.
|
||||
@@ -3744,9 +3463,7 @@ impl ClientSideToolV2 {
|
||||
"CLIENT_SIDE_TOOL_V2_REAPPLY" => Some(Self::Reapply),
|
||||
"CLIENT_SIDE_TOOL_V2_GET_RELATED_FILES" => Some(Self::GetRelatedFiles),
|
||||
"CLIENT_SIDE_TOOL_V2_PARALLEL_APPLY" => Some(Self::ParallelApply),
|
||||
"CLIENT_SIDE_TOOL_V2_RUN_TERMINAL_COMMAND_V2" => {
|
||||
Some(Self::RunTerminalCommandV2)
|
||||
}
|
||||
"CLIENT_SIDE_TOOL_V2_RUN_TERMINAL_COMMAND_V2" => Some(Self::RunTerminalCommandV2),
|
||||
"CLIENT_SIDE_TOOL_V2_FETCH_RULES" => Some(Self::FetchRules),
|
||||
"CLIENT_SIDE_TOOL_V2_PLANNER" => Some(Self::Planner),
|
||||
"CLIENT_SIDE_TOOL_V2_WEB_SEARCH" => Some(Self::WebSearch),
|
||||
@@ -3780,12 +3497,8 @@ impl RunTerminalCommandEndedReason {
|
||||
pub fn as_str_name(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Unspecified => "RUN_TERMINAL_COMMAND_ENDED_REASON_UNSPECIFIED",
|
||||
Self::ExecutionCompleted => {
|
||||
"RUN_TERMINAL_COMMAND_ENDED_REASON_EXECUTION_COMPLETED"
|
||||
}
|
||||
Self::ExecutionAborted => {
|
||||
"RUN_TERMINAL_COMMAND_ENDED_REASON_EXECUTION_ABORTED"
|
||||
}
|
||||
Self::ExecutionCompleted => "RUN_TERMINAL_COMMAND_ENDED_REASON_EXECUTION_COMPLETED",
|
||||
Self::ExecutionAborted => "RUN_TERMINAL_COMMAND_ENDED_REASON_EXECUTION_ABORTED",
|
||||
Self::ExecutionFailed => "RUN_TERMINAL_COMMAND_ENDED_REASON_EXECUTION_FAILED",
|
||||
Self::ErrorOccurredCheckingReason => {
|
||||
"RUN_TERMINAL_COMMAND_ENDED_REASON_ERROR_OCCURRED_CHECKING_REASON"
|
||||
@@ -3799,12 +3512,8 @@ impl RunTerminalCommandEndedReason {
|
||||
"RUN_TERMINAL_COMMAND_ENDED_REASON_EXECUTION_COMPLETED" => {
|
||||
Some(Self::ExecutionCompleted)
|
||||
}
|
||||
"RUN_TERMINAL_COMMAND_ENDED_REASON_EXECUTION_ABORTED" => {
|
||||
Some(Self::ExecutionAborted)
|
||||
}
|
||||
"RUN_TERMINAL_COMMAND_ENDED_REASON_EXECUTION_FAILED" => {
|
||||
Some(Self::ExecutionFailed)
|
||||
}
|
||||
"RUN_TERMINAL_COMMAND_ENDED_REASON_EXECUTION_ABORTED" => Some(Self::ExecutionAborted),
|
||||
"RUN_TERMINAL_COMMAND_ENDED_REASON_EXECUTION_FAILED" => Some(Self::ExecutionFailed),
|
||||
"RUN_TERMINAL_COMMAND_ENDED_REASON_ERROR_OCCURRED_CHECKING_REASON" => {
|
||||
Some(Self::ErrorOccurredCheckingReason)
|
||||
}
|
||||
|
||||
@@ -1268,6 +1268,8 @@ message AvailableModelsResponse { // aiserver.v1.AvailableModelsResponse
|
||||
optional DegradationStatus degradation_status = 6;
|
||||
optional double price = 7;
|
||||
optional TooltipData tooltip_data = 8;
|
||||
optional bool supports_thinking = 9;
|
||||
optional bool supports_images = 10;
|
||||
}
|
||||
repeated AvailableModel models = 2;
|
||||
repeated string model_names = 1;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::AppConfig;
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/key.rs"));
|
||||
// include!(concat!(env!("OUT_DIR"), "/key.rs"));
|
||||
include!("config/key.rs");
|
||||
|
||||
impl KeyConfig {
|
||||
pub fn new_with_global() -> Self {
|
||||
|
||||
@@ -7,12 +7,12 @@ message KeyConfig {
|
||||
// 认证令牌信息
|
||||
message TokenInfo {
|
||||
string sub = 1; // 用户标识符
|
||||
int64 exp = 2; // 过期时间(Unix 时间戳)
|
||||
string randomness = 3; // 随机字符串
|
||||
string signature = 4; // 签名
|
||||
bytes machine_id = 5; // 机器ID的SHA256哈希值
|
||||
bytes mac_id = 6; // MAC地址的SHA256哈希值
|
||||
// string timezone = 7; // 时区
|
||||
int64 start = 2; // 生成时间(Unix 时间戳)
|
||||
int64 end = 3; // 过期时间(Unix 时间戳)
|
||||
string randomness = 4; // 随机字符串
|
||||
string signature = 5; // 签名
|
||||
bytes machine_id = 6; // 机器ID的SHA256哈希值
|
||||
bytes mac_id = 7; // MAC地址的SHA256哈希值
|
||||
optional string proxy_name = 8; // 代理名称
|
||||
}
|
||||
|
||||
|
||||
102
src/core/config/key.rs
Normal file
102
src/core/config/key.rs
Normal file
@@ -0,0 +1,102 @@
|
||||
/// 动态配置的 API KEY
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct KeyConfig {
|
||||
/// 认证令牌(必需)
|
||||
#[prost(message, optional, tag = "1")]
|
||||
pub auth_token: ::core::option::Option<key_config::TokenInfo>,
|
||||
/// 是否禁用图片处理能力
|
||||
#[prost(bool, optional, tag = "4")]
|
||||
pub disable_vision: ::core::option::Option<bool>,
|
||||
/// 是否启用慢速池
|
||||
#[prost(bool, optional, tag = "5")]
|
||||
pub enable_slow_pool: ::core::option::Option<bool>,
|
||||
/// 使用量检查模型规则
|
||||
#[prost(message, optional, tag = "6")]
|
||||
pub usage_check_models: ::core::option::Option<key_config::UsageCheckModel>,
|
||||
/// 包含网络引用
|
||||
#[prost(bool, optional, tag = "7")]
|
||||
pub include_web_references: ::core::option::Option<bool>,
|
||||
}
|
||||
/// Nested message and enum types in `KeyConfig`.
|
||||
pub mod key_config {
|
||||
/// 认证令牌信息
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct TokenInfo {
|
||||
/// 用户标识符
|
||||
#[prost(string, tag = "1")]
|
||||
pub sub: ::prost::alloc::string::String,
|
||||
/// 生成时间(Unix 时间戳)
|
||||
#[prost(int64, tag = "2")]
|
||||
pub start: i64,
|
||||
/// 过期时间(Unix 时间戳)
|
||||
#[prost(int64, tag = "3")]
|
||||
pub end: i64,
|
||||
/// 随机字符串
|
||||
#[prost(string, tag = "4")]
|
||||
pub randomness: ::prost::alloc::string::String,
|
||||
/// 签名
|
||||
#[prost(string, tag = "5")]
|
||||
pub signature: ::prost::alloc::string::String,
|
||||
/// 机器ID的SHA256哈希值
|
||||
#[prost(bytes = "vec", tag = "6")]
|
||||
pub machine_id: ::prost::alloc::vec::Vec<u8>,
|
||||
/// MAC地址的SHA256哈希值
|
||||
#[prost(bytes = "vec", tag = "7")]
|
||||
pub mac_id: ::prost::alloc::vec::Vec<u8>,
|
||||
/// 代理名称
|
||||
#[prost(string, optional, tag = "8")]
|
||||
pub proxy_name: ::core::option::Option<::prost::alloc::string::String>,
|
||||
}
|
||||
/// 使用量检查模型规则
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct UsageCheckModel {
|
||||
/// 检查类型
|
||||
#[prost(enumeration = "usage_check_model::Type", tag = "1")]
|
||||
pub r#type: i32,
|
||||
/// 模型 ID 列表,当 type 为 TYPE_CUSTOM 时生效
|
||||
#[prost(string, repeated, tag = "2")]
|
||||
pub model_ids: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
|
||||
}
|
||||
/// Nested message and enum types in `UsageCheckModel`.
|
||||
pub mod usage_check_model {
|
||||
/// 检查类型
|
||||
#[derive(
|
||||
Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration,
|
||||
)]
|
||||
#[repr(i32)]
|
||||
pub enum Type {
|
||||
/// 未指定
|
||||
Default = 0,
|
||||
/// 禁用
|
||||
Disabled = 1,
|
||||
/// 全部
|
||||
All = 2,
|
||||
/// 自定义列表
|
||||
Custom = 3,
|
||||
}
|
||||
impl Type {
|
||||
/// String value of the enum field names used in the ProtoBuf definition.
|
||||
///
|
||||
/// The values are not transformed in any way and thus are considered stable
|
||||
/// (if the ProtoBuf definition does not change) and safe for programmatic use.
|
||||
pub fn as_str_name(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Default => "TYPE_DEFAULT",
|
||||
Self::Disabled => "TYPE_DISABLED",
|
||||
Self::All => "TYPE_ALL",
|
||||
Self::Custom => "TYPE_CUSTOM",
|
||||
}
|
||||
}
|
||||
/// Creates an enum from field names used in the ProtoBuf definition.
|
||||
pub fn from_str_name(value: &str) -> ::core::option::Option<Self> {
|
||||
match value {
|
||||
"TYPE_DEFAULT" => Some(Self::Default),
|
||||
"TYPE_DISABLED" => Some(Self::Disabled),
|
||||
"TYPE_ALL" => Some(Self::All),
|
||||
"TYPE_CUSTOM" => Some(Self::Custom),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,14 @@
|
||||
mod display_name;
|
||||
pub use display_name::calculate_display_name_v3;
|
||||
|
||||
use parking_lot::RwLock;
|
||||
use std::{
|
||||
sync::Arc,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use crate::app::lazy::get_start_time;
|
||||
|
||||
use super::model::Model;
|
||||
|
||||
macro_rules! def_pub_const {
|
||||
@@ -71,6 +76,7 @@ def_pub_const!(
|
||||
GEMINI_EXP_1206 => "gemini-exp-1206",
|
||||
GEMINI_2_0_PRO_EXP => "gemini-2.0-pro-exp",
|
||||
GEMINI_2_5_PRO_EXP_03_25 => "gemini-2.5-pro-exp-03-25",
|
||||
GEMINI_2_5_PRO_MAX => "gemini-2.5-pro-max",
|
||||
GEMINI_2_0_FLASH_THINKING_EXP => "gemini-2.0-flash-thinking-exp",
|
||||
GEMINI_2_0_FLASH => "gemini-2.0-flash",
|
||||
|
||||
@@ -93,13 +99,14 @@ macro_rules! create_models {
|
||||
$(
|
||||
Model {
|
||||
id: $model,
|
||||
display_name: $crate::leak::intern_string(calculate_display_name_v3($model)),
|
||||
created: CREATED,
|
||||
object: MODEL_OBJECT,
|
||||
owned_by: $owner,
|
||||
},
|
||||
)*
|
||||
]),
|
||||
last_update: Instant::now() - Duration::from_secs(30 * 60),
|
||||
last_update: Instant::now(),
|
||||
})
|
||||
});
|
||||
};
|
||||
@@ -154,7 +161,19 @@ impl Models {
|
||||
let mut data = INSTANCE.write();
|
||||
|
||||
// 检查时间间隔(30分钟)
|
||||
if data.last_update.elapsed() < Duration::from_secs(30 * 60) {
|
||||
if data.last_update.elapsed() < Duration::from_secs(30 * 60) && {
|
||||
static ONCE: std::sync::OnceLock<()> = std::sync::OnceLock::new();
|
||||
if ONCE.get().is_some() {
|
||||
true
|
||||
} else {
|
||||
let result =
|
||||
chrono::Local::now() - get_start_time() >= chrono::TimeDelta::minutes(30);
|
||||
if result {
|
||||
let _ = ONCE.set(());
|
||||
}
|
||||
result
|
||||
}
|
||||
} {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -202,6 +221,7 @@ create_models!(
|
||||
CLAUDE_3_5_HAIKU => ANTHROPIC,
|
||||
GEMINI_2_0_PRO_EXP => GOOGLE,
|
||||
GEMINI_2_5_PRO_EXP_03_25 => GOOGLE,
|
||||
GEMINI_2_5_PRO_MAX => GOOGLE,
|
||||
GEMINI_2_0_FLASH_THINKING_EXP => GOOGLE,
|
||||
GEMINI_2_0_FLASH => GOOGLE,
|
||||
DEEPSEEK_V3 => DEEPSEEK,
|
||||
|
||||
171
src/core/constant/display_name.rs
Normal file
171
src/core/constant/display_name.rs
Normal file
@@ -0,0 +1,171 @@
|
||||
/// 计算 AI 模型标识符的显示名称。
|
||||
///
|
||||
/// 规则:
|
||||
/// 1. 单个数字通过 '-' 连接时 (前后都不是数字),'-' 变为 '.' (例如 "3-5" -> "3.5")。
|
||||
/// 2. 日期格式中的 '-' (如 YYYY-MM-DD, MM-DD) 保持不变。
|
||||
/// 3. 其他所有的 '-' 都被替换为空格 ' '。
|
||||
/// 4. 由原始 '-' 分隔的各部分(处理后)首字母大写 (Title Case)。
|
||||
/// 5. 特殊规则:如果原始标识符以 "gpt" 开头,则输出的对应部分为 "GPT"。
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `identifier` - AI 模型的原始标识符字符串。
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `String` - 计算得到的显示名称。
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// assert_eq!(calculate_display_name_v3("claude-3-5-sonnet"), "Claude 3.5 Sonnet");
|
||||
/// assert_eq!(calculate_display_name_v3("gpt-4-turbo-2024-04-09"), "GPT 4 Turbo 2024-04-09"); // 日期 '-' 不变
|
||||
/// assert_eq!(calculate_display_name_v3("gemini-1.5-flash-500k"), "Gemini 1.5 Flash 500k");
|
||||
/// assert_eq!(calculate_display_name_v3("deepseek-v3"), "Deepseek V3");
|
||||
/// assert_eq!(calculate_display_name_v3("gpt-4o"), "GPT 4o");
|
||||
/// assert_eq!(calculate_display_name_v3("gpt-3.5-turbo"), "GPT 3.5 Turbo"); // 输入有 .
|
||||
/// assert_eq!(calculate_display_name_v3("gemini-2.5-pro-exp-03-25"), "Gemini 2.5 Pro Exp 03-25"); // 日期 '-' 不变
|
||||
/// assert_eq!(calculate_display_name_v3("version-10-beta"), "Version 10 Beta"); // 10 不是单数字
|
||||
/// assert_eq!(calculate_display_name_v3("model-1-test-9-case"), "Model 1 Test 9 Case"); // d-d 转义被空格隔开
|
||||
/// ```
|
||||
pub fn calculate_display_name_v3(identifier: &str) -> String {
|
||||
if identifier.is_empty() {
|
||||
return String::new();
|
||||
}
|
||||
|
||||
let mut result = String::new();
|
||||
let mut capitalize_next = true;
|
||||
let mut prev_char: Option<char> = None;
|
||||
let mut prev_prev_char: Option<char> = None;
|
||||
|
||||
let mut char_iter = identifier.chars().peekable();
|
||||
if identifier.starts_with("gpt-") {
|
||||
result.push_str("GPT");
|
||||
for _ in 0..4 {
|
||||
char_iter.next();
|
||||
}
|
||||
result.push(' ');
|
||||
capitalize_next = true;
|
||||
prev_char = Some('-');
|
||||
prev_prev_char = Some('t');
|
||||
} else if identifier == "gpt" {
|
||||
return "GPT".to_string();
|
||||
}
|
||||
|
||||
while let Some(current_char) = char_iter.next() {
|
||||
match current_char {
|
||||
'-' => {
|
||||
let prev_is_digit = prev_char.map_or(false, |p| p.is_ascii_digit());
|
||||
let next_is_digit = char_iter.peek().map_or(false, |&n| n.is_ascii_digit());
|
||||
|
||||
if prev_is_digit && next_is_digit {
|
||||
let prev_prev_is_digit = prev_prev_char.map_or(false, |pp| pp.is_ascii_digit());
|
||||
|
||||
let mut next_next_is_digit = false;
|
||||
let mut lookahead = char_iter.clone();
|
||||
lookahead.next();
|
||||
if let Some(next_next_char) = lookahead.peek() {
|
||||
if next_next_char.is_ascii_digit() {
|
||||
next_next_is_digit = true;
|
||||
}
|
||||
}
|
||||
|
||||
if prev_prev_is_digit || next_next_is_digit {
|
||||
result.push('-');
|
||||
capitalize_next = true;
|
||||
} else {
|
||||
result.push('.');
|
||||
capitalize_next = false;
|
||||
}
|
||||
} else {
|
||||
result.push(' ');
|
||||
capitalize_next = true;
|
||||
}
|
||||
prev_prev_char = prev_char;
|
||||
prev_char = Some('-');
|
||||
}
|
||||
c => {
|
||||
if capitalize_next {
|
||||
result.extend(c.to_uppercase());
|
||||
capitalize_next = false;
|
||||
} else {
|
||||
result.push(c);
|
||||
}
|
||||
prev_prev_char = prev_char;
|
||||
prev_char = Some(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_display_name_v3_final() {
|
||||
// Anthropic
|
||||
assert_eq!(calculate_display_name_v3("claude-3-opus"), "Claude 3 Opus");
|
||||
assert_eq!(
|
||||
calculate_display_name_v3("claude-3.5-sonnet"),
|
||||
"Claude 3.5 Sonnet"
|
||||
); // Input has dot
|
||||
assert_eq!(
|
||||
calculate_display_name_v3("claude-3-5-sonnet"),
|
||||
"Claude 3.5 Sonnet"
|
||||
); // d-d conversion
|
||||
assert_eq!(
|
||||
calculate_display_name_v3("claude-3-haiku-200k"),
|
||||
"Claude 3 Haiku 200k"
|
||||
);
|
||||
|
||||
// OpenAI (GPT & Date Preservation)
|
||||
assert_eq!(calculate_display_name_v3("gpt-4"), "GPT 4");
|
||||
assert_eq!(calculate_display_name_v3("gpt-4o"), "GPT 4o");
|
||||
assert_eq!(calculate_display_name_v3("gpt-3.5-turbo"), "GPT 3.5 Turbo"); // Input has dot
|
||||
assert_eq!(
|
||||
calculate_display_name_v3("gpt-4-turbo-2024-04-09"),
|
||||
"GPT 4 Turbo 2024-04-09"
|
||||
); // Date preserved!
|
||||
assert_eq!(calculate_display_name_v3("gpt-4o-mini"), "GPT 4o Mini");
|
||||
assert_eq!(calculate_display_name_v3("gpt"), "GPT");
|
||||
assert_eq!(calculate_display_name_v3("gpt-"), "GPT "); // Trailing hyphen becomes space
|
||||
|
||||
// Google (Date Preservation)
|
||||
assert_eq!(
|
||||
calculate_display_name_v3("gemini-1.5-flash-500k"),
|
||||
"Gemini 1.5 Flash 500k"
|
||||
); // Input has dot
|
||||
assert_eq!(
|
||||
calculate_display_name_v3("gemini-2.5-pro-exp-03-25"),
|
||||
"Gemini 2.5 Pro Exp 03-25"
|
||||
); // Date preserved!
|
||||
|
||||
// Deepseek
|
||||
assert_eq!(calculate_display_name_v3("deepseek-v3"), "Deepseek V3");
|
||||
|
||||
// Other & Edge Cases
|
||||
assert_eq!(calculate_display_name_v3("o1-mini"), "O1 Mini");
|
||||
assert_eq!(
|
||||
calculate_display_name_v3("model-1-test-9-case"),
|
||||
"Model 1 Test 9 Case"
|
||||
); // d-d handled
|
||||
assert_eq!(
|
||||
calculate_display_name_v3("version-10-beta"),
|
||||
"Version 10 Beta"
|
||||
); // 10 is not single digit
|
||||
assert_eq!(
|
||||
calculate_display_name_v3("alpha-1-5-omega"),
|
||||
"Alpha 1.5 Omega"
|
||||
); // d-d handled
|
||||
assert_eq!(calculate_display_name_v3("my-gpt-model"), "My Gpt Model"); // gpt not at start
|
||||
assert_eq!(calculate_display_name_v3(""), ""); // Empty
|
||||
assert_eq!(calculate_display_name_v3("-start-"), " Start "); // Leading/trailing hyphens
|
||||
assert_eq!(calculate_display_name_v3("a-b-c"), "A B C");
|
||||
assert_eq!(calculate_display_name_v3("3-5"), "3.5"); // Only d-d
|
||||
assert_eq!(calculate_display_name_v3("2024-release"), "2024 Release"); // Number-text
|
||||
assert_eq!(calculate_display_name_v3("data-01-01"), "Data 01-01"); // Date like MM-DD
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use super::{aiserver::v1::ErrorDetails, constant::UNKNOWN};
|
||||
use crate::common::model::{ApiStatus, ErrorResponse as CommonErrorResponse};
|
||||
use base64::{Engine as _, engine::general_purpose::STANDARD_NO_PAD};
|
||||
@@ -120,11 +122,13 @@ impl ErrorResponse {
|
||||
.error
|
||||
.as_mut()
|
||||
.map(|error| std::mem::take(&mut error.message))
|
||||
.or(Some(self.code)),
|
||||
.or(Some(self.code.clone()))
|
||||
.map(Cow::from),
|
||||
message: self
|
||||
.error
|
||||
.as_mut()
|
||||
.map(|error| std::mem::take(&mut error.details)),
|
||||
.map(|error| std::mem::take(&mut error.details))
|
||||
.map(Cow::from),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,19 +27,3 @@ pub async fn admin_auth_middleware(request: Request<Body>, next: Next) -> Respon
|
||||
.into_response(),
|
||||
}
|
||||
}
|
||||
|
||||
// 旧的认证中间件函数,保留向后兼容性
|
||||
// pub async fn auth_middleware(request: Request<Body>, next: Next) -> Result<Response, StatusCode> {
|
||||
// let auth_header = request
|
||||
// .headers()
|
||||
// .get(AUTHORIZATION)
|
||||
// .and_then(|h| h.to_str().ok())
|
||||
// .and_then(|h| h.strip_prefix(AUTHORIZATION_BEARER_PREFIX))
|
||||
// .ok_or(StatusCode::UNAUTHORIZED)?;
|
||||
|
||||
// if auth_header != AUTH_TOKEN.as_str() {
|
||||
// return Err(StatusCode::UNAUTHORIZED);
|
||||
// }
|
||||
|
||||
// Ok(next.run(request).await)
|
||||
// }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde::{ser::SerializeStruct as _, Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
@@ -117,14 +117,33 @@ pub struct StreamOptions {
|
||||
}
|
||||
|
||||
// 模型定义
|
||||
#[derive(Serialize)]
|
||||
pub struct Model {
|
||||
pub id: &'static str,
|
||||
pub display_name: &'static str,
|
||||
pub created: &'static i64,
|
||||
pub object: &'static str,
|
||||
pub owned_by: &'static str,
|
||||
}
|
||||
|
||||
impl Serialize for Model {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
let mut state = serializer.serialize_struct("Model", 7)?;
|
||||
|
||||
state.serialize_field("id", &self.id)?;
|
||||
state.serialize_field("display_name", &self.display_name)?;
|
||||
state.serialize_field("created", self.created)?;
|
||||
state.serialize_field("created_at", self.created)?;
|
||||
state.serialize_field("object", &self.object)?;
|
||||
state.serialize_field("type", &self.object)?;
|
||||
state.serialize_field("owned_by", &self.owned_by)?;
|
||||
|
||||
state.end()
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Model {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.id == other.id
|
||||
|
||||
@@ -7,17 +7,17 @@ pub use token::{handle_basic_calibration, handle_build_key};
|
||||
mod tokens;
|
||||
pub use tokens::{
|
||||
handle_add_tokens, handle_delete_tokens, handle_get_token_tags, handle_get_tokens,
|
||||
handle_get_tokens_by_tag, handle_update_token_tags, handle_update_tokens,
|
||||
handle_update_tokens_profile,
|
||||
handle_get_tokens_by_tag, handle_set_token_tags, handle_set_tokens, handle_set_tokens_status,
|
||||
handle_update_tokens_profile, handle_upgrade_tokens,
|
||||
};
|
||||
mod checksum;
|
||||
pub use checksum::{handle_get_checksum, handle_get_hash, handle_get_timestamp_header};
|
||||
mod profile;
|
||||
pub use profile::handle_user_info;
|
||||
pub use profile::{handle_token_upgrade, handle_user_info};
|
||||
mod proxies;
|
||||
pub use proxies::{
|
||||
handle_add_proxy, handle_delete_proxies, handle_get_proxies, handle_set_general_proxy,
|
||||
handle_update_proxies,
|
||||
handle_set_proxies,
|
||||
};
|
||||
mod page;
|
||||
pub use page::{
|
||||
|
||||
@@ -7,13 +7,16 @@ use crate::{
|
||||
ROUTE_ENV_EXAMPLE_PATH, ROUTE_GET_CHECKSUM, ROUTE_GET_HASH, ROUTE_GET_TIMESTAMP_HEADER,
|
||||
ROUTE_HEALTH_PATH, ROUTE_LOGS_PATH, ROUTE_PROXIES_ADD_PATH, ROUTE_PROXIES_DELETE_PATH,
|
||||
ROUTE_PROXIES_GET_PATH, ROUTE_PROXIES_PATH, ROUTE_PROXIES_SET_GENERAL_PATH,
|
||||
ROUTE_PROXIES_UPDATE_PATH, ROUTE_README_PATH, ROUTE_ROOT_PATH, ROUTE_STATIC_PATH,
|
||||
ROUTE_TOKENS_ADD_PATH, ROUTE_TOKENS_BY_TAG_GET_PATH, ROUTE_TOKENS_DELETE_PATH,
|
||||
ROUTE_TOKENS_GET_PATH, ROUTE_TOKENS_PATH, ROUTE_TOKENS_PROFILE_UPDATE_PATH,
|
||||
ROUTE_TOKENS_TAGS_GET_PATH, ROUTE_TOKENS_TAGS_UPDATE_PATH, ROUTE_TOKENS_UPDATE_PATH,
|
||||
ROUTE_PROXIES_SET_PATH, ROUTE_README_PATH, ROUTE_ROOT_PATH, ROUTE_STATIC_PATH,
|
||||
ROUTE_TOKEN_UPGRADE_PATH, ROUTE_TOKENS_ADD_PATH, ROUTE_TOKENS_BY_TAG_GET_PATH,
|
||||
ROUTE_TOKENS_DELETE_PATH, ROUTE_TOKENS_GET_PATH, ROUTE_TOKENS_PATH,
|
||||
ROUTE_TOKENS_PROFILE_UPDATE_PATH, ROUTE_TOKENS_SET_PATH, ROUTE_TOKENS_STATUS_SET_PATH,
|
||||
ROUTE_TOKENS_TAGS_GET_PATH, ROUTE_TOKENS_TAGS_SET_PATH, ROUTE_TOKENS_UPGRADE_PATH,
|
||||
ROUTE_USER_INFO_PATH,
|
||||
},
|
||||
lazy::{AUTH_TOKEN, ROUTE_CHAT_PATH, ROUTE_MODELS_PATH, get_start_time},
|
||||
lazy::{
|
||||
AUTH_TOKEN, ROUTE_CHAT_PATH, ROUTE_MESSAGES_PATH, ROUTE_MODELS_PATH, get_start_time,
|
||||
},
|
||||
model::{AppConfig, AppState, PageContent},
|
||||
},
|
||||
common::model::{
|
||||
@@ -55,6 +58,45 @@ pub async fn handle_root() -> impl IntoResponse {
|
||||
}
|
||||
}
|
||||
|
||||
static ENDPOINTS: std::sync::LazyLock<[&'static str; 34]> = std::sync::LazyLock::new(|| {
|
||||
[
|
||||
&*ROUTE_CHAT_PATH,
|
||||
&*ROUTE_MESSAGES_PATH,
|
||||
&*ROUTE_MODELS_PATH,
|
||||
ROUTE_TOKENS_PATH,
|
||||
ROUTE_TOKENS_GET_PATH,
|
||||
ROUTE_TOKENS_SET_PATH,
|
||||
ROUTE_TOKENS_ADD_PATH,
|
||||
ROUTE_TOKENS_DELETE_PATH,
|
||||
ROUTE_TOKENS_TAGS_GET_PATH,
|
||||
ROUTE_TOKENS_TAGS_SET_PATH,
|
||||
ROUTE_TOKENS_BY_TAG_GET_PATH,
|
||||
ROUTE_TOKENS_PROFILE_UPDATE_PATH,
|
||||
ROUTE_TOKENS_UPGRADE_PATH,
|
||||
ROUTE_TOKENS_STATUS_SET_PATH,
|
||||
ROUTE_PROXIES_PATH,
|
||||
ROUTE_PROXIES_GET_PATH,
|
||||
ROUTE_PROXIES_SET_PATH,
|
||||
ROUTE_PROXIES_ADD_PATH,
|
||||
ROUTE_PROXIES_DELETE_PATH,
|
||||
ROUTE_PROXIES_SET_GENERAL_PATH,
|
||||
ROUTE_LOGS_PATH,
|
||||
ROUTE_ENV_EXAMPLE_PATH,
|
||||
ROUTE_CONFIG_PATH,
|
||||
ROUTE_STATIC_PATH,
|
||||
ROUTE_ABOUT_PATH,
|
||||
ROUTE_README_PATH,
|
||||
ROUTE_API_PATH,
|
||||
ROUTE_GET_HASH,
|
||||
ROUTE_GET_CHECKSUM,
|
||||
ROUTE_GET_TIMESTAMP_HEADER,
|
||||
ROUTE_BASIC_CALIBRATION_PATH,
|
||||
ROUTE_USER_INFO_PATH,
|
||||
ROUTE_BUILD_KEY_PATH,
|
||||
ROUTE_TOKEN_UPGRADE_PATH,
|
||||
]
|
||||
});
|
||||
|
||||
pub async fn handle_health(
|
||||
State(state): State<Arc<Mutex<AppState>>>,
|
||||
headers: HeaderMap,
|
||||
@@ -116,37 +158,6 @@ pub async fn handle_health(
|
||||
uptime,
|
||||
stats,
|
||||
models: Models::ids(),
|
||||
endpoints: vec![
|
||||
ROUTE_CHAT_PATH.as_str(),
|
||||
ROUTE_MODELS_PATH.as_str(),
|
||||
ROUTE_TOKENS_PATH,
|
||||
ROUTE_TOKENS_GET_PATH,
|
||||
ROUTE_TOKENS_UPDATE_PATH,
|
||||
ROUTE_TOKENS_ADD_PATH,
|
||||
ROUTE_TOKENS_DELETE_PATH,
|
||||
ROUTE_TOKENS_TAGS_GET_PATH,
|
||||
ROUTE_TOKENS_TAGS_UPDATE_PATH,
|
||||
ROUTE_TOKENS_BY_TAG_GET_PATH,
|
||||
ROUTE_TOKENS_PROFILE_UPDATE_PATH,
|
||||
ROUTE_PROXIES_PATH,
|
||||
ROUTE_PROXIES_GET_PATH,
|
||||
ROUTE_PROXIES_UPDATE_PATH,
|
||||
ROUTE_PROXIES_ADD_PATH,
|
||||
ROUTE_PROXIES_DELETE_PATH,
|
||||
ROUTE_PROXIES_SET_GENERAL_PATH,
|
||||
ROUTE_LOGS_PATH,
|
||||
ROUTE_ENV_EXAMPLE_PATH,
|
||||
ROUTE_CONFIG_PATH,
|
||||
ROUTE_STATIC_PATH,
|
||||
ROUTE_ABOUT_PATH,
|
||||
ROUTE_README_PATH,
|
||||
ROUTE_API_PATH,
|
||||
ROUTE_GET_HASH,
|
||||
ROUTE_GET_CHECKSUM,
|
||||
ROUTE_GET_TIMESTAMP_HEADER,
|
||||
ROUTE_BASIC_CALIBRATION_PATH,
|
||||
ROUTE_USER_INFO_PATH,
|
||||
ROUTE_BUILD_KEY_PATH,
|
||||
],
|
||||
endpoints: &*ENDPOINTS,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use crate::{
|
||||
app::model::proxy_pool::ProxyPool,
|
||||
common::{
|
||||
model::userinfo::GetUserInfo,
|
||||
utils::{extract_token, get_token_profile},
|
||||
model::{ApiStatus, userinfo::GetUserInfo},
|
||||
utils::{extract_token, get_new_token, get_token_profile},
|
||||
},
|
||||
core::constant::ERR_NODATA,
|
||||
};
|
||||
@@ -36,3 +36,49 @@ pub async fn handle_user_info(Json(request): Json<TokenRequest>) -> Json<GetUser
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize)]
|
||||
pub struct TokenUpgradeResponse {
|
||||
status: ApiStatus,
|
||||
message: &'static str,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
result: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn handle_token_upgrade(Json(request): Json<TokenRequest>) -> Json<TokenUpgradeResponse> {
|
||||
// 从请求头中获取并验证 auth token
|
||||
let auth_token = match request.token {
|
||||
Some(token) => token,
|
||||
None => {
|
||||
return Json(TokenUpgradeResponse {
|
||||
status: ApiStatus::Error,
|
||||
message: "未提供授权令牌",
|
||||
result: None,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
let token = match extract_token(&auth_token) {
|
||||
Some(token) => token,
|
||||
None => {
|
||||
return Json(TokenUpgradeResponse {
|
||||
status: ApiStatus::Error,
|
||||
message: "无法解析授权令牌",
|
||||
result: None,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
match get_new_token(ProxyPool::get_general_client(), &token, false).await {
|
||||
Some(token) => Json(TokenUpgradeResponse {
|
||||
status: ApiStatus::Success,
|
||||
message: "升级成功",
|
||||
result: Some(token),
|
||||
}),
|
||||
None => Json(TokenUpgradeResponse {
|
||||
status: ApiStatus::Failure,
|
||||
message: "升级失败",
|
||||
result: None,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ use crate::{
|
||||
common::model::{ApiStatus, ErrorResponse},
|
||||
};
|
||||
use axum::{Json, extract::State, http::StatusCode};
|
||||
use std::sync::Arc;
|
||||
use std::{borrow::Cow, sync::Arc};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
// 获取所有代理配置
|
||||
@@ -32,7 +32,7 @@ pub async fn handle_get_proxies(
|
||||
}
|
||||
|
||||
// 更新代理配置
|
||||
pub async fn handle_update_proxies(
|
||||
pub async fn handle_set_proxies(
|
||||
State(state): State<Arc<Mutex<AppState>>>,
|
||||
Json(request): Json<ProxyUpdateRequest>,
|
||||
) -> Result<Json<ProxyInfoResponse>, (StatusCode, Json<ErrorResponse>)> {
|
||||
@@ -46,8 +46,10 @@ pub async fn handle_update_proxies(
|
||||
Json(ErrorResponse {
|
||||
status: ApiStatus::Error,
|
||||
code: None,
|
||||
error: Some(format!("Failed to save proxy configuration: {e}")),
|
||||
message: Some("无法保存代理配置".to_string()),
|
||||
error: Some(Cow::Owned(format!(
|
||||
"Failed to save proxy configuration: {e}"
|
||||
))),
|
||||
message: Some(Cow::Borrowed("无法保存代理配置")),
|
||||
}),
|
||||
));
|
||||
}
|
||||
@@ -109,8 +111,10 @@ pub async fn handle_add_proxy(
|
||||
Json(ErrorResponse {
|
||||
status: ApiStatus::Error,
|
||||
code: None,
|
||||
error: Some(format!("Failed to save proxy configuration: {e}")),
|
||||
message: Some("无法保存代理配置".to_string()),
|
||||
error: Some(Cow::Owned(format!(
|
||||
"Failed to save proxy configuration: {e}"
|
||||
))),
|
||||
message: Some(Cow::Borrowed("无法保存代理配置")),
|
||||
}),
|
||||
));
|
||||
}
|
||||
@@ -177,8 +181,10 @@ pub async fn handle_delete_proxies(
|
||||
Json(ErrorResponse {
|
||||
status: ApiStatus::Error,
|
||||
code: None,
|
||||
error: Some(format!("Failed to save proxy configuration: {e}")),
|
||||
message: Some("无法保存代理配置".to_string()),
|
||||
error: Some(Cow::Owned(format!(
|
||||
"Failed to save proxy configuration: {e}"
|
||||
))),
|
||||
message: Some(Cow::Borrowed("无法保存代理配置")),
|
||||
}),
|
||||
));
|
||||
}
|
||||
@@ -225,8 +231,8 @@ pub async fn handle_set_general_proxy(
|
||||
Json(ErrorResponse {
|
||||
status: ApiStatus::Error,
|
||||
code: None,
|
||||
error: Some("Proxy name not found".to_string()),
|
||||
message: Some("代理名称不存在".to_string()),
|
||||
error: Some(Cow::Borrowed("Proxy name not found")),
|
||||
message: Some(Cow::Borrowed("代理名称不存在")),
|
||||
}),
|
||||
));
|
||||
}
|
||||
@@ -241,8 +247,10 @@ pub async fn handle_set_general_proxy(
|
||||
Json(ErrorResponse {
|
||||
status: ApiStatus::Error,
|
||||
code: None,
|
||||
error: Some(format!("Failed to save proxy configuration: {e}")),
|
||||
message: Some("无法保存代理配置".to_string()),
|
||||
error: Some(Cow::Owned(format!(
|
||||
"Failed to save proxy configuration: {e}"
|
||||
))),
|
||||
message: Some(Cow::Borrowed("无法保存代理配置")),
|
||||
}),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ use crate::{
|
||||
common::{
|
||||
model::ApiStatus,
|
||||
utils::{
|
||||
extract_time, extract_time_ks, extract_user_id, to_base64, token_to_tokeninfo,
|
||||
JwtTime, extract_time, extract_time_ks, extract_user_id, to_base64, token_to_tokeninfo,
|
||||
validate_token_and_checksum,
|
||||
},
|
||||
},
|
||||
@@ -28,11 +28,11 @@ pub struct TokenRequest {
|
||||
#[derive(Serialize)]
|
||||
pub struct BasicCalibrationResponse {
|
||||
pub status: ApiStatus,
|
||||
pub message: Option<String>,
|
||||
pub message: &'static str,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub user_id: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub create_at: Option<String>,
|
||||
pub time: Option<JwtTime>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub checksum_time: Option<u64>,
|
||||
}
|
||||
@@ -46,9 +46,9 @@ pub async fn handle_basic_calibration(
|
||||
None => {
|
||||
return Json(BasicCalibrationResponse {
|
||||
status: ApiStatus::Error,
|
||||
message: Some("未提供授权令牌".to_string()),
|
||||
message: "未提供授权令牌",
|
||||
user_id: None,
|
||||
create_at: None,
|
||||
time: None,
|
||||
checksum_time: None,
|
||||
});
|
||||
}
|
||||
@@ -60,9 +60,9 @@ pub async fn handle_basic_calibration(
|
||||
None => {
|
||||
return Json(BasicCalibrationResponse {
|
||||
status: ApiStatus::Error,
|
||||
message: Some("无效令牌或无效校验和".to_string()),
|
||||
message: "无效令牌或无效校验和",
|
||||
user_id: None,
|
||||
create_at: None,
|
||||
time: None,
|
||||
checksum_time: None,
|
||||
});
|
||||
}
|
||||
@@ -70,15 +70,15 @@ pub async fn handle_basic_calibration(
|
||||
|
||||
// 提取用户ID和创建时间
|
||||
let user_id = extract_user_id(&token);
|
||||
let create_at = extract_time(&token).map(|dt| dt.to_string());
|
||||
let time = extract_time(&token);
|
||||
let checksum_time = extract_time_ks(&checksum[..8]);
|
||||
|
||||
// 返回校验结果
|
||||
Json(BasicCalibrationResponse {
|
||||
status: ApiStatus::Success,
|
||||
message: Some("校验成功".to_string()),
|
||||
message: "校验成功",
|
||||
user_id,
|
||||
create_at,
|
||||
time,
|
||||
checksum_time,
|
||||
})
|
||||
}
|
||||
@@ -94,12 +94,10 @@ pub async fn handle_build_key(
|
||||
.and_then(|h| h.to_str().ok())
|
||||
.and_then(|h| h.strip_prefix(AUTHORIZATION_BEARER_PREFIX));
|
||||
|
||||
if auth_header
|
||||
.is_none_or(|h| h != AppConfig::get_share_token().as_str() && h != AUTH_TOKEN.as_str())
|
||||
{
|
||||
if auth_header.is_none_or(|h| !AppConfig::share_token_eq(h) && h != AUTH_TOKEN.as_str()) {
|
||||
return (
|
||||
StatusCode::UNAUTHORIZED,
|
||||
Json(BuildKeyResponse::Error("Unauthorized".to_owned())),
|
||||
Json(BuildKeyResponse::Error("Unauthorized")),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -110,7 +108,7 @@ pub async fn handle_build_key(
|
||||
None => {
|
||||
return (
|
||||
StatusCode::BAD_REQUEST,
|
||||
Json(BuildKeyResponse::Error("Invalid auth token".to_owned())),
|
||||
Json(BuildKeyResponse::Error("Invalid auth token")),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use crate::{
|
||||
app::model::{
|
||||
AppState, CommonResponse, TokenAddRequest, TokenInfo, TokenInfoResponse, TokenManager,
|
||||
TokenTagsUpdateRequest, TokenUpdateRequest, TokensDeleteRequest, TokensDeleteResponse,
|
||||
TokenStatusSetRequest, TokenTagsUpdateRequest, TokenUpdateRequest, TokensDeleteRequest,
|
||||
TokensDeleteResponse,
|
||||
},
|
||||
common::{
|
||||
model::{ApiStatus, ErrorResponse, NormalResponse},
|
||||
@@ -12,7 +13,7 @@ use crate::{
|
||||
},
|
||||
};
|
||||
use axum::{Json, extract::State, http::StatusCode};
|
||||
use std::sync::Arc;
|
||||
use std::{borrow::Cow, collections::HashSet, sync::Arc};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
pub async fn handle_get_tokens(
|
||||
@@ -30,7 +31,7 @@ pub async fn handle_get_tokens(
|
||||
}))
|
||||
}
|
||||
|
||||
pub async fn handle_update_tokens(
|
||||
pub async fn handle_set_tokens(
|
||||
State(state): State<Arc<Mutex<AppState>>>,
|
||||
Json(tokens): Json<TokenUpdateRequest>,
|
||||
) -> Result<Json<TokenInfoResponse>, StatusCode> {
|
||||
@@ -44,7 +45,7 @@ pub async fn handle_update_tokens(
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
// 更新应用状态
|
||||
// 设置应用状态
|
||||
{
|
||||
let mut state = state.lock().await;
|
||||
state.token_manager = token_manager;
|
||||
@@ -87,6 +88,7 @@ pub async fn handle_add_tokens(
|
||||
.as_deref()
|
||||
.map(generate_checksum_with_repair)
|
||||
.unwrap_or_else(generate_checksum_with_default),
|
||||
status: request.status,
|
||||
client_key: Some(generate_hash()),
|
||||
profile: None,
|
||||
tags: request.tags.clone(),
|
||||
@@ -100,7 +102,7 @@ pub async fn handle_add_tokens(
|
||||
token_manager.tokens.extend(new_tokens);
|
||||
let tokens_count = token_manager.tokens.len();
|
||||
|
||||
// 更新全局标签
|
||||
// 设置全局标签
|
||||
if let Some(ref tags) = request.tags {
|
||||
token_manager.update_global_tags(tags);
|
||||
}
|
||||
@@ -112,13 +114,13 @@ pub async fn handle_add_tokens(
|
||||
Json(ErrorResponse {
|
||||
status: ApiStatus::Error,
|
||||
code: None,
|
||||
error: Some("Failed to save token list".to_string()),
|
||||
message: Some("无法保存token list".to_string()),
|
||||
error: Some(Cow::Borrowed("Failed to save token list")),
|
||||
message: Some(Cow::Borrowed("无法保存token list")),
|
||||
}),
|
||||
)
|
||||
})?;
|
||||
|
||||
// 更新应用状态
|
||||
// 设置应用状态
|
||||
{
|
||||
let mut state = state.lock().await;
|
||||
state.token_manager = token_manager;
|
||||
@@ -184,7 +186,7 @@ pub async fn handle_delete_tokens(
|
||||
|
||||
let new_count: usize = token_manager.tokens.len();
|
||||
|
||||
// 如果有tokens被删除才进行更新操作
|
||||
// 如果有tokens被删除才进行设置操作
|
||||
if new_count < original_count {
|
||||
// 保存到文件
|
||||
token_manager.save_tokens().await.map_err(|_| {
|
||||
@@ -193,8 +195,8 @@ pub async fn handle_delete_tokens(
|
||||
Json(ErrorResponse {
|
||||
status: ApiStatus::Error,
|
||||
code: None,
|
||||
error: Some("Failed to save token list".to_string()),
|
||||
message: Some("无法保存token list".to_string()),
|
||||
error: Some(Cow::Borrowed("Failed to save token list")),
|
||||
message: Some(Cow::Borrowed("无法保存token list")),
|
||||
}),
|
||||
)
|
||||
})?;
|
||||
@@ -212,7 +214,7 @@ pub async fn handle_delete_tokens(
|
||||
None
|
||||
};
|
||||
|
||||
// 更新状态
|
||||
// 设置状态
|
||||
{
|
||||
let mut state = state.lock().await;
|
||||
state.token_manager = token_manager;
|
||||
@@ -243,24 +245,24 @@ pub async fn handle_delete_tokens(
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn handle_update_token_tags(
|
||||
pub async fn handle_set_token_tags(
|
||||
State(state): State<Arc<Mutex<AppState>>>,
|
||||
Json(request): Json<TokenTagsUpdateRequest>,
|
||||
) -> Result<Json<CommonResponse>, (StatusCode, Json<ErrorResponse>)> {
|
||||
// 获取并更新 token_manager
|
||||
// 获取并设置 token_manager
|
||||
{
|
||||
let mut state = state.lock().await;
|
||||
if let Err(e) = state
|
||||
.token_manager
|
||||
.update_tokens_tags(request.tokens, request.tags)
|
||||
.update_tokens_tags(&request.tokens, request.tags)
|
||||
{
|
||||
return Err((
|
||||
StatusCode::BAD_REQUEST,
|
||||
Json(ErrorResponse {
|
||||
status: ApiStatus::Error,
|
||||
code: None,
|
||||
error: Some(e.to_string()),
|
||||
message: Some("更新标签失败".to_string()),
|
||||
error: Some(Cow::Owned(e.to_string())),
|
||||
message: Some(Cow::Borrowed("设置标签失败")),
|
||||
}),
|
||||
));
|
||||
}
|
||||
@@ -272,8 +274,8 @@ pub async fn handle_update_token_tags(
|
||||
Json(ErrorResponse {
|
||||
status: ApiStatus::Error,
|
||||
code: None,
|
||||
error: Some("Failed to save token tags".to_string()),
|
||||
message: Some("无法保存标签信息".to_string()),
|
||||
error: Some(Cow::Borrowed("Failed to save token tags")),
|
||||
message: Some(Cow::Borrowed("无法保存标签信息")),
|
||||
}),
|
||||
));
|
||||
}
|
||||
@@ -281,13 +283,13 @@ pub async fn handle_update_token_tags(
|
||||
|
||||
Ok(Json(CommonResponse {
|
||||
status: ApiStatus::Success,
|
||||
message: Some("标签更新成功".to_string()),
|
||||
message: Some("标签设置成功".to_string()),
|
||||
}))
|
||||
}
|
||||
|
||||
pub async fn handle_update_tokens_profile(
|
||||
State(state): State<Arc<Mutex<AppState>>>,
|
||||
Json(tokens): Json<Vec<String>>,
|
||||
Json(tokens): Json<HashSet<String>>,
|
||||
) -> Result<Json<CommonResponse>, (StatusCode, Json<ErrorResponse>)> {
|
||||
// 验证请求
|
||||
if tokens.is_empty() {
|
||||
@@ -296,8 +298,8 @@ pub async fn handle_update_tokens_profile(
|
||||
Json(ErrorResponse {
|
||||
status: ApiStatus::Error,
|
||||
code: None,
|
||||
error: Some("No tokens provided".to_string()),
|
||||
message: Some("未提供任何令牌".to_string()),
|
||||
error: Some(Cow::Borrowed("No tokens provided")),
|
||||
message: Some(Cow::Borrowed("未提供任何令牌")),
|
||||
}),
|
||||
));
|
||||
}
|
||||
@@ -306,7 +308,7 @@ pub async fn handle_update_tokens_profile(
|
||||
let mut state_guard = state.lock().await;
|
||||
let token_manager = &mut state_guard.token_manager;
|
||||
|
||||
// 批量更新tokens的profile
|
||||
// 批量设置tokens的profile
|
||||
let mut updated_count: u32 = 0;
|
||||
let mut failed_count: u32 = 0;
|
||||
|
||||
@@ -325,7 +327,7 @@ pub async fn handle_update_tokens_profile(
|
||||
)
|
||||
.await
|
||||
{
|
||||
// 更新profile
|
||||
// 设置profile
|
||||
token_manager.tokens[token_idx].profile = Some(profile);
|
||||
updated_count += 1;
|
||||
} else {
|
||||
@@ -343,8 +345,8 @@ pub async fn handle_update_tokens_profile(
|
||||
Json(ErrorResponse {
|
||||
status: ApiStatus::Error,
|
||||
code: None,
|
||||
error: Some("Failed to save token profiles".to_string()),
|
||||
message: Some("无法保存令牌配置数据".to_string()),
|
||||
error: Some(Cow::Borrowed("Failed to save token profiles")),
|
||||
message: Some(Cow::Borrowed("无法保存令牌配置数据")),
|
||||
}),
|
||||
));
|
||||
}
|
||||
@@ -357,6 +359,135 @@ pub async fn handle_update_tokens_profile(
|
||||
}))
|
||||
}
|
||||
|
||||
pub async fn handle_upgrade_tokens(
|
||||
State(state): State<Arc<Mutex<AppState>>>,
|
||||
Json(tokens): Json<HashSet<String>>,
|
||||
) -> Result<Json<CommonResponse>, (StatusCode, Json<ErrorResponse>)> {
|
||||
// 验证请求
|
||||
if tokens.is_empty() {
|
||||
return Err((
|
||||
StatusCode::BAD_REQUEST,
|
||||
Json(ErrorResponse {
|
||||
status: ApiStatus::Error,
|
||||
code: None,
|
||||
error: Some(Cow::Borrowed("No tokens provided")),
|
||||
message: Some(Cow::Borrowed("未提供任何令牌")),
|
||||
}),
|
||||
));
|
||||
}
|
||||
|
||||
// 获取当前的 token_manager
|
||||
let mut state_guard = state.lock().await;
|
||||
let token_manager = &mut state_guard.token_manager;
|
||||
|
||||
// 批量设置tokens的profile
|
||||
let mut updated_count: u32 = 0;
|
||||
let mut failed_count: u32 = 0;
|
||||
|
||||
for token in &tokens {
|
||||
if let Some(token_idx) = token_manager
|
||||
.tokens
|
||||
.iter()
|
||||
.position(|info| info.token == *token)
|
||||
{
|
||||
if let Some(new_token) = crate::common::utils::get_new_token(
|
||||
token_manager.tokens[token_idx].get_client(),
|
||||
token,
|
||||
true,
|
||||
)
|
||||
.await
|
||||
{
|
||||
token_manager.tokens[token_idx].token = new_token;
|
||||
updated_count += 1;
|
||||
} else {
|
||||
failed_count += 1;
|
||||
}
|
||||
} else {
|
||||
failed_count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// 保存更改
|
||||
if updated_count > 0 && token_manager.save_tokens().await.is_err() {
|
||||
return Err((
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(ErrorResponse {
|
||||
status: ApiStatus::Error,
|
||||
code: None,
|
||||
error: Some(Cow::Borrowed("Failed to save tokens")),
|
||||
message: Some(Cow::Borrowed("无法保存令牌数据")),
|
||||
}),
|
||||
));
|
||||
}
|
||||
|
||||
let message = format!("已升级{updated_count}个令牌, {failed_count}个令牌升级失败");
|
||||
|
||||
Ok(Json(CommonResponse {
|
||||
status: ApiStatus::Success,
|
||||
message: Some(message),
|
||||
}))
|
||||
}
|
||||
|
||||
pub async fn handle_set_tokens_status(
|
||||
State(state): State<Arc<Mutex<AppState>>>,
|
||||
Json(request): Json<TokenStatusSetRequest>,
|
||||
) -> Result<Json<CommonResponse>, (StatusCode, Json<ErrorResponse>)> {
|
||||
// 验证请求
|
||||
if request.tokens.is_empty() {
|
||||
return Err((
|
||||
StatusCode::BAD_REQUEST,
|
||||
Json(ErrorResponse {
|
||||
status: ApiStatus::Error,
|
||||
code: None,
|
||||
error: Some(Cow::Borrowed("No tokens provided")),
|
||||
message: Some(Cow::Borrowed("未提供任何令牌")),
|
||||
}),
|
||||
));
|
||||
}
|
||||
|
||||
// 获取当前的 token_manager
|
||||
let mut state_guard = state.lock().await;
|
||||
let token_manager = &mut state_guard.token_manager;
|
||||
|
||||
// 批量设置tokens的profile
|
||||
let mut updated_count: u32 = 0;
|
||||
let mut failed_count: u32 = 0;
|
||||
|
||||
for token in &request.tokens {
|
||||
// 验证token是否在token_manager中存在
|
||||
if let Some(token_idx) = token_manager
|
||||
.tokens
|
||||
.iter()
|
||||
.position(|info| info.token == *token)
|
||||
{
|
||||
token_manager.tokens[token_idx].status = request.status;
|
||||
updated_count += 1;
|
||||
} else {
|
||||
failed_count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// 保存更改
|
||||
if updated_count > 0 && token_manager.save_tokens().await.is_err() {
|
||||
return Err((
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Json(ErrorResponse {
|
||||
status: ApiStatus::Error,
|
||||
code: None,
|
||||
error: Some(Cow::Borrowed("Failed to save token statuses")),
|
||||
message: Some(Cow::Borrowed("无法保存令牌状态数据")),
|
||||
}),
|
||||
));
|
||||
}
|
||||
|
||||
let message = format!("已设置{updated_count}个令牌状态, {failed_count}个令牌设置失败");
|
||||
|
||||
Ok(Json(CommonResponse {
|
||||
status: ApiStatus::Success,
|
||||
message: Some(message),
|
||||
}))
|
||||
}
|
||||
|
||||
pub async fn handle_get_token_tags(
|
||||
State(state): State<Arc<Mutex<AppState>>>,
|
||||
) -> Result<Json<NormalResponse<Vec<String>>>, StatusCode> {
|
||||
@@ -367,7 +498,7 @@ pub async fn handle_get_token_tags(
|
||||
Ok(Json(NormalResponse {
|
||||
status: ApiStatus::Success,
|
||||
data: Some(tags),
|
||||
message: Some(format!("获取到{len}个标签")),
|
||||
message: Some(Cow::Owned(format!("获取到{len}个标签"))),
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -397,8 +528,8 @@ pub async fn handle_get_tokens_by_tag(
|
||||
Json(ErrorResponse {
|
||||
status: ApiStatus::Error,
|
||||
code: None,
|
||||
error: Some(e.to_string()),
|
||||
message: Some(format!("标签\"{tag}\"不存在")),
|
||||
error: Some(Cow::Owned(e.to_string())),
|
||||
message: Some(Cow::Owned(format!("标签\"{tag}\"不存在"))),
|
||||
}),
|
||||
)),
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ use axum::{
|
||||
use bytes::Bytes;
|
||||
use futures::StreamExt;
|
||||
use prost::Message as _;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::{borrow::Cow, sync::atomic::{AtomicUsize, Ordering}};
|
||||
use std::{
|
||||
convert::Infallible,
|
||||
sync::{Arc, atomic::AtomicBool},
|
||||
@@ -63,6 +63,8 @@ use super::model::{ChatRequest, Model};
|
||||
const NO_CACHE: &str = "no-cache, must-revalidate";
|
||||
const KEEP_ALIVE: &str = "keep-alive";
|
||||
|
||||
static CURRENT_KEY_INDEX: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
pub async fn handle_models(
|
||||
State(state): State<Arc<Mutex<AppState>>>,
|
||||
headers: HeaderMap,
|
||||
@@ -87,10 +89,15 @@ pub async fn handle_models(
|
||||
// 管理员Token
|
||||
token
|
||||
if token == AUTH_TOKEN.as_str()
|
||||
|| (AppConfig::is_share() && token == AppConfig::get_share_token().as_str()) =>
|
||||
|| (AppConfig::is_share() && AppConfig::share_token_eq(token)) =>
|
||||
{
|
||||
let state_guard = state.lock().await;
|
||||
let token_infos = &state_guard.token_manager.tokens;
|
||||
let token_infos: Vec<_> = state_guard
|
||||
.token_manager
|
||||
.tokens
|
||||
.iter()
|
||||
.filter(|t| t.is_enabled())
|
||||
.collect();
|
||||
|
||||
if token_infos.is_empty() {
|
||||
return Err((
|
||||
@@ -99,7 +106,6 @@ pub async fn handle_models(
|
||||
));
|
||||
}
|
||||
|
||||
static CURRENT_KEY_INDEX: AtomicUsize = AtomicUsize::new(0);
|
||||
let index = CURRENT_KEY_INDEX.fetch_add(1, Ordering::SeqCst) % token_infos.len();
|
||||
let token_info = &token_infos[index];
|
||||
is_pri = true;
|
||||
@@ -152,8 +158,8 @@ pub async fn handle_models(
|
||||
Json(ErrorResponse {
|
||||
status: ApiStatus::Failure,
|
||||
code: Some(StatusCode::INTERNAL_SERVER_ERROR.as_u16()),
|
||||
error: Some("Failed to fetch available models".to_string()),
|
||||
message: Some("Unable to get available models".to_string()),
|
||||
error: Some(Cow::Borrowed("Failed to fetch available models")),
|
||||
message: Some(Cow::Borrowed("Unable to get available models")),
|
||||
}),
|
||||
))?;
|
||||
|
||||
@@ -164,8 +170,8 @@ pub async fn handle_models(
|
||||
Json(ErrorResponse {
|
||||
status: ApiStatus::Failure,
|
||||
code: Some(StatusCode::INTERNAL_SERVER_ERROR.as_u16()),
|
||||
error: Some("Failed to update models".to_string()),
|
||||
message: Some(e.to_string()),
|
||||
error: Some(Cow::Borrowed("Failed to update models")),
|
||||
message: Some(Cow::Borrowed(e)),
|
||||
}),
|
||||
)
|
||||
})?;
|
||||
@@ -223,15 +229,20 @@ pub async fn handle_chat(
|
||||
let mut is_pri = false;
|
||||
|
||||
// 验证认证token并获取token信息
|
||||
let mut now_with_tz = None;
|
||||
let (auth_token, checksum, client_key, client, timezone) = match auth_header {
|
||||
// 管理员Token验证逻辑
|
||||
token
|
||||
if token == AUTH_TOKEN.as_str()
|
||||
|| (AppConfig::is_share() && token == AppConfig::get_share_token().as_str()) =>
|
||||
|| (AppConfig::is_share() && AppConfig::share_token_eq(token)) =>
|
||||
{
|
||||
static CURRENT_KEY_INDEX: AtomicUsize = AtomicUsize::new(0);
|
||||
let state_guard = state.lock().await;
|
||||
let token_infos = &state_guard.token_manager.tokens;
|
||||
let token_infos: Vec<_> = state_guard
|
||||
.token_manager
|
||||
.tokens
|
||||
.iter()
|
||||
.filter(|t| t.is_enabled())
|
||||
.collect();
|
||||
|
||||
// 检查是否存在可用的token
|
||||
if token_infos.is_empty() {
|
||||
@@ -245,6 +256,7 @@ pub async fn handle_chat(
|
||||
let index = CURRENT_KEY_INDEX.fetch_add(1, Ordering::SeqCst) % token_infos.len();
|
||||
let token_info = &token_infos[index];
|
||||
is_pri = true;
|
||||
now_with_tz = Some(token_info.now());
|
||||
(
|
||||
token_info.token.clone(),
|
||||
token_info.checksum.clone(),
|
||||
@@ -393,6 +405,7 @@ pub async fn handle_chat(
|
||||
token_info: TokenInfo {
|
||||
token: auth_token.clone(),
|
||||
checksum: checksum.clone(),
|
||||
status: Default::default(),
|
||||
client_key: None,
|
||||
profile: None,
|
||||
tags: None,
|
||||
@@ -416,6 +429,7 @@ pub async fn handle_chat(
|
||||
// 将消息转换为hex格式
|
||||
let hex_data = match super::adapter::encode_chat_message(
|
||||
request.messages,
|
||||
now_with_tz,
|
||||
model,
|
||||
current_config.disable_vision(),
|
||||
current_config.enable_slow_pool(),
|
||||
@@ -1048,7 +1062,7 @@ pub async fn handle_chat(
|
||||
let error_response = ErrorResponse {
|
||||
status: ApiStatus::Error,
|
||||
code: Some(500),
|
||||
error: Some(e.to_string()),
|
||||
error: Some(Cow::Owned(e.to_string())),
|
||||
message: None,
|
||||
};
|
||||
return Err((StatusCode::INTERNAL_SERVER_ERROR, Json(error_response)));
|
||||
|
||||
@@ -11,6 +11,14 @@ use std::time::Instant;
|
||||
// 解压gzip数据
|
||||
#[inline]
|
||||
fn decompress_gzip(data: &[u8]) -> Option<Vec<u8>> {
|
||||
if data.len() < 3
|
||||
|| unsafe { *data.get_unchecked(0) } != 0x1f
|
||||
|| unsafe { *data.get_unchecked(1) } != 0x8b
|
||||
|| unsafe { *data.get_unchecked(2) } != 0x08
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut decoder = GzDecoder::new(data);
|
||||
let mut decompressed = Vec::new();
|
||||
|
||||
|
||||
36
src/main.rs
36
src/main.rs
@@ -10,11 +10,12 @@ use app::{
|
||||
ROUTE_BUILD_KEY_PATH, ROUTE_CONFIG_PATH, ROUTE_ENV_EXAMPLE_PATH, ROUTE_GET_CHECKSUM,
|
||||
ROUTE_GET_HASH, ROUTE_GET_TIMESTAMP_HEADER, ROUTE_HEALTH_PATH, ROUTE_LOGS_PATH,
|
||||
ROUTE_PROXIES_ADD_PATH, ROUTE_PROXIES_DELETE_PATH, ROUTE_PROXIES_GET_PATH,
|
||||
ROUTE_PROXIES_PATH, ROUTE_PROXIES_SET_GENERAL_PATH, ROUTE_PROXIES_UPDATE_PATH,
|
||||
ROUTE_README_PATH, ROUTE_ROOT_PATH, ROUTE_STATIC_PATH, ROUTE_TOKENS_ADD_PATH,
|
||||
ROUTE_TOKENS_BY_TAG_GET_PATH, ROUTE_TOKENS_DELETE_PATH, ROUTE_TOKENS_GET_PATH,
|
||||
ROUTE_TOKENS_PATH, ROUTE_TOKENS_PROFILE_UPDATE_PATH, ROUTE_TOKENS_TAGS_GET_PATH,
|
||||
ROUTE_TOKENS_TAGS_UPDATE_PATH, ROUTE_TOKENS_UPDATE_PATH, ROUTE_USER_INFO_PATH,
|
||||
ROUTE_PROXIES_PATH, ROUTE_PROXIES_SET_GENERAL_PATH, ROUTE_PROXIES_SET_PATH,
|
||||
ROUTE_README_PATH, ROUTE_ROOT_PATH, ROUTE_STATIC_PATH, ROUTE_TOKEN_UPGRADE_PATH,
|
||||
ROUTE_TOKENS_ADD_PATH, ROUTE_TOKENS_BY_TAG_GET_PATH, ROUTE_TOKENS_DELETE_PATH,
|
||||
ROUTE_TOKENS_GET_PATH, ROUTE_TOKENS_PATH, ROUTE_TOKENS_PROFILE_UPDATE_PATH,
|
||||
ROUTE_TOKENS_SET_PATH, ROUTE_TOKENS_STATUS_SET_PATH, ROUTE_TOKENS_TAGS_GET_PATH,
|
||||
ROUTE_TOKENS_TAGS_SET_PATH, ROUTE_TOKENS_UPGRADE_PATH, ROUTE_USER_INFO_PATH,
|
||||
},
|
||||
lazy::{AUTH_TOKEN, ROUTE_CHAT_PATH, ROUTE_MODELS_PATH},
|
||||
model::*,
|
||||
@@ -32,9 +33,10 @@ use core::{
|
||||
handle_delete_proxies, handle_delete_tokens, handle_env_example, handle_get_checksum,
|
||||
handle_get_hash, handle_get_proxies, handle_get_timestamp_header, handle_get_token_tags,
|
||||
handle_get_tokens, handle_get_tokens_by_tag, handle_health, handle_logs, handle_logs_post,
|
||||
handle_proxies_page, handle_readme, handle_root, handle_set_general_proxy, handle_static,
|
||||
handle_tokens_page, handle_update_proxies, handle_update_token_tags, handle_update_tokens,
|
||||
handle_update_tokens_profile, handle_user_info,
|
||||
handle_proxies_page, handle_readme, handle_root, handle_set_general_proxy,
|
||||
handle_set_proxies, handle_set_token_tags, handle_set_tokens, handle_set_tokens_status,
|
||||
handle_static, handle_token_upgrade, handle_tokens_page, handle_update_tokens_profile,
|
||||
handle_upgrade_tokens, handle_user_info,
|
||||
},
|
||||
service::{handle_chat, handle_models},
|
||||
};
|
||||
@@ -80,10 +82,7 @@ async fn main() {
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
// 获取当前时间戳
|
||||
let now = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs();
|
||||
let now = common::utils::now_secs();
|
||||
|
||||
// 计算距离下一个整1000秒的等待时间
|
||||
let next_reload = (now / 1000 + 1) * 1000;
|
||||
@@ -152,21 +151,20 @@ async fn main() {
|
||||
.merge(
|
||||
Router::new()
|
||||
.route(ROUTE_TOKENS_GET_PATH, post(handle_get_tokens))
|
||||
.route(ROUTE_TOKENS_UPDATE_PATH, post(handle_update_tokens))
|
||||
.route(ROUTE_TOKENS_SET_PATH, post(handle_set_tokens))
|
||||
.route(ROUTE_TOKENS_ADD_PATH, post(handle_add_tokens))
|
||||
.route(ROUTE_TOKENS_DELETE_PATH, post(handle_delete_tokens))
|
||||
.route(ROUTE_TOKENS_TAGS_GET_PATH, post(handle_get_token_tags))
|
||||
.route(
|
||||
ROUTE_TOKENS_TAGS_UPDATE_PATH,
|
||||
post(handle_update_token_tags),
|
||||
)
|
||||
.route(ROUTE_TOKENS_TAGS_SET_PATH, post(handle_set_token_tags))
|
||||
.route(ROUTE_TOKENS_BY_TAG_GET_PATH, post(handle_get_tokens_by_tag))
|
||||
.route(
|
||||
ROUTE_TOKENS_PROFILE_UPDATE_PATH,
|
||||
post(handle_update_tokens_profile),
|
||||
)
|
||||
.route(ROUTE_TOKENS_UPGRADE_PATH, post(handle_upgrade_tokens))
|
||||
.route(ROUTE_TOKENS_STATUS_SET_PATH, post(handle_set_tokens_status))
|
||||
.route(ROUTE_PROXIES_GET_PATH, post(handle_get_proxies))
|
||||
.route(ROUTE_PROXIES_UPDATE_PATH, post(handle_update_proxies))
|
||||
.route(ROUTE_PROXIES_SET_PATH, post(handle_set_proxies))
|
||||
.route(ROUTE_PROXIES_ADD_PATH, post(handle_add_proxy))
|
||||
.route(ROUTE_PROXIES_DELETE_PATH, post(handle_delete_proxies))
|
||||
.route(
|
||||
@@ -177,6 +175,7 @@ async fn main() {
|
||||
)
|
||||
.route(ROUTE_MODELS_PATH.as_str(), get(handle_models))
|
||||
.route(ROUTE_CHAT_PATH.as_str(), post(handle_chat))
|
||||
// .route(ROUTE_MESSAGES_PATH.as_str(), post(handle_chat))
|
||||
.route(ROUTE_LOGS_PATH, get(handle_logs))
|
||||
.route(ROUTE_LOGS_PATH, post(handle_logs_post))
|
||||
.route(ROUTE_ENV_EXAMPLE_PATH, get(handle_env_example))
|
||||
@@ -193,6 +192,7 @@ async fn main() {
|
||||
.route(ROUTE_USER_INFO_PATH, post(handle_user_info))
|
||||
.route(ROUTE_BUILD_KEY_PATH, get(handle_build_key_page))
|
||||
.route(ROUTE_BUILD_KEY_PATH, post(handle_build_key))
|
||||
.route(ROUTE_TOKEN_UPGRADE_PATH, post(handle_token_upgrade))
|
||||
.layer(RequestBodyLimitLayer::new(
|
||||
1024 * 1024 * parse_usize_from_env("REQUEST_BODY_LIMIT_MB", 2),
|
||||
))
|
||||
|
||||
@@ -267,7 +267,7 @@
|
||||
// 缓存校准结果
|
||||
calibrationCache.set(token, {
|
||||
user_id: result.user_id,
|
||||
create_at: result.create_at,
|
||||
time: result.time,
|
||||
checksum_time: result.checksum_time
|
||||
});
|
||||
|
||||
@@ -317,7 +317,7 @@
|
||||
// 添加用户基本信息
|
||||
if (tokenInfo.user || calibInfo) {
|
||||
const user = tokenInfo.user || {};
|
||||
userDetails.innerHTML += `<p>用户ID: ${calibInfo ? calibInfo.user_id : user.id}</p><p>邮箱: ${user.email || ''}</p><p>用户名: ${user.name || ''}</p>${user.updated_at ? `<p>更新时间: ${new Date(user.updated_at).toLocaleString()}</p>` : ''}${calibInfo ? `<p>令牌创建时间: ${new Date(calibInfo.create_at).toLocaleString()}</p>` : ''}${calibInfo && calibInfo.checksum_time ? `<p>校验和时间区间: ${new Date(calibInfo.checksum_time * 1e6).toLocaleString()} - ${new Date((calibInfo.checksum_time + 1) * 1e6 - 1).toLocaleString()}</p>` : ''}`;
|
||||
userDetails.innerHTML += `<p>用户ID: ${calibInfo ? calibInfo.user_id : user.id}</p><p>邮箱: ${user.email || ''}</p><p>用户名: ${user.name || ''}</p>${user.updated_at ? `<p>更新时间: ${new Date(user.updated_at).toLocaleString()}</p>` : ''}${calibInfo ? `<p>令牌创建时间: ${new Date(calibInfo.time.iat).toLocaleString()}</p>` : ''}${calibInfo ? `<p>令牌过期时间: ${new Date(calibInfo.time.exp).toLocaleString()}</p>` : ''}${calibInfo && calibInfo.checksum_time ? `<p>校验和时间区间: ${new Date(calibInfo.checksum_time * 1e6).toLocaleString()} - ${new Date((calibInfo.checksum_time + 1) * 1e6 - 1).toLocaleString()}</p>` : ''}`;
|
||||
}
|
||||
|
||||
// 添加 Stripe 会员信息
|
||||
|
||||
@@ -846,7 +846,7 @@
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await makeAuthenticatedRequest('/tokens/delete', {
|
||||
const data = await makeAuthenticatedRequest('/tokens/del', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
tokens: [currentToken],
|
||||
@@ -861,8 +861,6 @@
|
||||
message = 'Token删除失败:未找到该Token';
|
||||
}
|
||||
showGlobalMessage(message);
|
||||
// 刷新日志列表
|
||||
fetchLogs();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1070,7 +1068,7 @@
|
||||
<td>${formatTiming(log.timing.total)}</td>
|
||||
<td>${log.stream ? '是' : '否'}</td>
|
||||
<td>${log.status}</td>
|
||||
<td>${log.error || '-'}</td>
|
||||
<td>${log.error.details || log.error || '-'}</td>
|
||||
</tr>`;
|
||||
}).join('');
|
||||
|
||||
|
||||
@@ -1020,7 +1020,7 @@
|
||||
closeModal('confirmModal');
|
||||
showToast('正在删除代理...', 'info');
|
||||
|
||||
const data = await makeAuthenticatedRequest('/proxies/delete', {
|
||||
const data = await makeAuthenticatedRequest('/proxies/del', {
|
||||
body: JSON.stringify({
|
||||
names: proxiesToDelete,
|
||||
expectation: 'detailed'
|
||||
@@ -1185,7 +1185,7 @@
|
||||
closeModal('importModal');
|
||||
showToast('正在导入代理配置...', 'info');
|
||||
|
||||
const data = await makeAuthenticatedRequest('/proxies/update', {
|
||||
const data = await makeAuthenticatedRequest('/proxies/set', {
|
||||
body: JSON.stringify({
|
||||
proxies: config
|
||||
})
|
||||
|
||||
@@ -649,6 +649,74 @@
|
||||
.proxy-name:hover::after {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* 状态指示标样式 */
|
||||
.status-indicator {
|
||||
display: inline-block;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.status-enabled {
|
||||
background-color: #4CAF50;
|
||||
/* 绿色 */
|
||||
}
|
||||
|
||||
.status-disabled {
|
||||
background-color: #F44336;
|
||||
/* 红色 */
|
||||
}
|
||||
|
||||
.status-other {
|
||||
background-color: #FFC107;
|
||||
/* 黄色 */
|
||||
}
|
||||
|
||||
/* 状态子菜单样式 */
|
||||
.status-menu {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.status-submenu {
|
||||
position: absolute;
|
||||
left: 100%;
|
||||
top: 0;
|
||||
background: var(--card-background);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--border-radius);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||
padding: 4px 0;
|
||||
min-width: 150px;
|
||||
z-index: 1001;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: opacity 0.2s, visibility 0.2s;
|
||||
}
|
||||
|
||||
.status-menu:hover .status-submenu {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
/* 添加向上和向左打开的样式 */
|
||||
.status-menu.open-upward .status-submenu {
|
||||
top: auto;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.status-menu.open-leftward .status-submenu {
|
||||
left: auto;
|
||||
right: 100%;
|
||||
}
|
||||
|
||||
/* 添加状态计数样式 */
|
||||
.status-count {
|
||||
color: var(--text-secondary);
|
||||
font-size: 12px;
|
||||
margin-left: 8px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
@@ -774,6 +842,21 @@
|
||||
|
||||
<!-- 右键菜单 -->
|
||||
<div id="contextMenu" class="context-menu">
|
||||
<div class="context-menu-item status-menu">
|
||||
<span>切换状态</span>
|
||||
<div class="status-submenu" id="statusSubmenu">
|
||||
<div class="context-menu-item" onclick="setTokenStatus('enabled')">
|
||||
<span>启用</span>
|
||||
<span class="status-count">0</span>
|
||||
<span class="check-mark"></span>
|
||||
</div>
|
||||
<div class="context-menu-item" onclick="setTokenStatus('disabled')">
|
||||
<span>禁用</span>
|
||||
<span class="status-count">0</span>
|
||||
<span class="check-mark"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="context-menu-item" onclick="viewDetails()">
|
||||
<span>查看详情</span>
|
||||
<span class="context-menu-shortcut">Enter</span>
|
||||
@@ -782,6 +865,10 @@
|
||||
<span>刷新Profile</span>
|
||||
<span class="context-menu-shortcut">F5</span>
|
||||
</div>
|
||||
<div class="context-menu-item" onclick="upgradeSelectedTokens()">
|
||||
<span>升级Token</span>
|
||||
<span class="context-menu-shortcut">Ctrl+U</span>
|
||||
</div>
|
||||
<div class="context-menu-item" onclick="generateKey()">
|
||||
<span>生成Key</span>
|
||||
<span class="context-menu-shortcut">Ctrl+G</span>
|
||||
@@ -925,6 +1012,13 @@
|
||||
<label>添加Token:</label>
|
||||
<textarea id="addTokensInput" placeholder="每行输入一个token,格式为token或token,checksum" rows="8"></textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Token状态:</label>
|
||||
<select id="addTokensStatus">
|
||||
<option value="enabled">启用</option>
|
||||
<option value="disabled">禁用</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button onclick="closeModal('addTokensModal')" class="secondary">取消</button>
|
||||
@@ -1146,6 +1240,12 @@
|
||||
getTokenInfo();
|
||||
}
|
||||
break;
|
||||
case 'u':
|
||||
if (modifierKey) {
|
||||
e.preventDefault();
|
||||
upgradeSelectedTokens();
|
||||
}
|
||||
break;
|
||||
case 'g':
|
||||
if (modifierKey) {
|
||||
e.preventDefault();
|
||||
@@ -1290,14 +1390,21 @@
|
||||
// 更新代理子菜单
|
||||
updateProxySubmenu();
|
||||
|
||||
// 更新状态子菜单
|
||||
updateStatusSubmenu();
|
||||
|
||||
// 更新多选时的菜单项文本
|
||||
if (selectedTokens.size > 1) {
|
||||
document.querySelector('.context-menu-item[onclick="viewDetails()"] span:first-child').textContent = "查看详情(单个)";
|
||||
document.querySelector('.context-menu-item[onclick="refreshSelectedProfiles()"] span:first-child').textContent = `刷新Profile(已选${selectedTokens.size}个)`;
|
||||
document.querySelector('.context-menu-item[onclick="upgradeSelectedTokens()"] span:first-child').textContent = `升级Token(已选${selectedTokens.size}个)`;
|
||||
document.querySelector('.context-menu-item[onclick="openTimezoneSelector()"] span:first-child').textContent = `设置时区(已选${selectedTokens.size}个)`;
|
||||
document.querySelector('.context-menu-item.proxy-menu span:first-child').textContent = `设置代理(已选${selectedTokens.size}个)`;
|
||||
document.querySelector('.context-menu-item[onclick="deleteSelectedTokens()"] span:first-child').textContent = `删除(已选${selectedTokens.size}个)`;
|
||||
} else {
|
||||
document.querySelector('.context-menu-item[onclick="viewDetails()"] span:first-child').textContent = "查看详情";
|
||||
document.querySelector('.context-menu-item[onclick="refreshSelectedProfiles()"] span:first-child').textContent = "刷新Profile";
|
||||
document.querySelector('.context-menu-item[onclick="upgradeSelectedTokens()"] span:first-child').textContent = "升级Token";
|
||||
document.querySelector('.context-menu-item[onclick="openTimezoneSelector()"] span:first-child').textContent = "设置时区";
|
||||
document.querySelector('.context-menu-item.proxy-menu span:first-child').textContent = "设置代理";
|
||||
document.querySelector('.context-menu-item[onclick="deleteSelectedTokens()"] span:first-child').textContent = "删除";
|
||||
@@ -1400,43 +1507,44 @@
|
||||
|
||||
// 更新代理子菜单
|
||||
function updateProxySubmenu() {
|
||||
|
||||
const submenu = document.getElementById('proxySubmenu');
|
||||
if (!submenu) return;
|
||||
|
||||
if (!submenu) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取选中token的代理信息
|
||||
const proxyUsage = {};
|
||||
// 获取选中的token对象
|
||||
const selectedTokenObjs = allTokens.filter(t => selectedTokens.has(t.token));
|
||||
const selectionSize = selectedTokenObjs.length;
|
||||
|
||||
allTokens.forEach(token => {
|
||||
const tags = token.tags || [];
|
||||
const proxy = tags[1] || '';
|
||||
proxyUsage[proxy] = (proxyUsage[proxy] || 0) + 1;
|
||||
// 计算选中项中的代理使用情况
|
||||
const selectedProxyUsage = { '': 0 }; // 初始化 "未指定" 计数
|
||||
proxyList.forEach(proxy => { selectedProxyUsage[proxy] = 0; }); // 初始化已知代理计数
|
||||
|
||||
selectedTokenObjs.forEach(token => {
|
||||
const proxy = token.tags?.proxy || ''; // 获取选中 token 的代理
|
||||
// 确保计数对象里有这个key
|
||||
if (!(proxy in selectedProxyUsage)) {
|
||||
selectedProxyUsage[proxy] = 0;
|
||||
}
|
||||
selectedProxyUsage[proxy]++; // 增加对应代理的计数
|
||||
});
|
||||
|
||||
// 确保"未指定"选项始终存在
|
||||
// 构建 "未指定" 选项
|
||||
const isUnspecifiedActive = selectionSize > 0 && selectedProxyUsage[''] === selectionSize;
|
||||
let html = `
|
||||
<div class="context-menu-item ${selectedTokenObjs.length > 0 && proxyUsage[''] === selectedTokenObjs.length ? 'active' : ''}" onclick="setProxy('')">
|
||||
<div class="context-menu-item ${isUnspecifiedActive ? 'active' : ''}" onclick="setProxy('')">
|
||||
<span>未指定</span>
|
||||
<span class="proxy-count">${proxyUsage[''] || 0}</span>
|
||||
<span class="proxy-count">${selectedProxyUsage['']}</span>
|
||||
<span class="check-mark"></span>
|
||||
</div>
|
||||
<div class="context-menu-divider"></div>
|
||||
`;
|
||||
|
||||
// 添加所有可用代理
|
||||
// 添加所有可用代理选项
|
||||
if (proxyList.length > 0) {
|
||||
proxyList.forEach(proxy => {
|
||||
const count = proxyUsage[proxy] || 0;
|
||||
|
||||
// 检查所有选中的token是否都使用了这个相同的代理
|
||||
const isActive = selectedTokenObjs.length > 0 &&
|
||||
selectedTokenObjs.every(token =>
|
||||
(token.tags || [])[1] === proxy
|
||||
);
|
||||
if (!proxy) return; // 跳过空代理名
|
||||
const count = selectedProxyUsage[proxy] || 0; // 获取选中项中使用这个代理的数量
|
||||
// 检查是否所有选中的 Token 都使用了此代理
|
||||
const isActive = selectionSize > 0 && count === selectionSize && proxy !== '';
|
||||
|
||||
html += `
|
||||
<div class="context-menu-item ${isActive ? 'active' : ''}" onclick="setProxy('${proxy}')">
|
||||
@@ -1447,23 +1555,16 @@
|
||||
`;
|
||||
});
|
||||
} else {
|
||||
html += `<div class="context-menu-item disabled">
|
||||
<span>无可用代理</span>
|
||||
</div>
|
||||
`;
|
||||
html += `<div class="context-menu-item disabled"><span>无可用代理</span></div>`;
|
||||
}
|
||||
|
||||
submenu.innerHTML = html;
|
||||
|
||||
// 修改代理菜单项添加has-submenu类
|
||||
// 添加 has-submenu 类以便显示箭头
|
||||
const proxyMenuEl = document.querySelector('.proxy-menu');
|
||||
if (proxyMenuEl) {
|
||||
proxyMenuEl.classList.add('has-submenu');
|
||||
}
|
||||
|
||||
// 关闭右键菜单
|
||||
const contextMenu = document.getElementById('contextMenu');
|
||||
contextMenu.style.display = 'none';
|
||||
}
|
||||
|
||||
// 获取Token信息
|
||||
@@ -1491,6 +1592,7 @@
|
||||
updateStatusBar();
|
||||
updateProxyFilter(); // 更新代理筛选下拉框
|
||||
updateTimezoneFilter(); // 更新时区筛选下拉框
|
||||
updateStatusSubmenu(); // 更新状态子菜单
|
||||
|
||||
// 恢复筛选条件
|
||||
document.getElementById('searchInput').value = searchTerm;
|
||||
@@ -1546,14 +1648,20 @@
|
||||
const tokensData = tokensToUpdate.map(token => {
|
||||
// 获取当前token的时区设置
|
||||
const tokenObj = allTokens.find(t => t.token === token);
|
||||
const timezone = tokenObj?.tags?.[0] || '';
|
||||
// 保留时区设置,更新代理设置
|
||||
return { token, tags: [timezone, proxyName] };
|
||||
const timezone = tokenObj.tags?.timezone || '';
|
||||
// 构建新的tags对象
|
||||
return {
|
||||
token,
|
||||
tags: {
|
||||
timezone: timezone || undefined,
|
||||
proxy: proxyName
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
showToast('正在更新代理设置...', 'info');
|
||||
const promises = tokensData.map(data =>
|
||||
makeAuthenticatedRequest('/tokens/tags/update', {
|
||||
makeAuthenticatedRequest('/tokens/tags/set', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
tokens: [data.token],
|
||||
@@ -1612,11 +1720,12 @@
|
||||
|
||||
const email = user.email || '';
|
||||
const displayName = email || token.token.substring(0, 15) + '...';
|
||||
const timezone = token.tags?.[0] || ''; // 获取时区
|
||||
const timezone = token.tags?.timezone || ''; // 获取时区
|
||||
|
||||
return `
|
||||
<tr data-token="${token.token}" data-checksum="${token.checksum}" data-index="${index}" class="${selectedTokens.has(token.token) ? 'selected' : ''}">
|
||||
<td>
|
||||
<span class="status-indicator ${token.status === 'disabled' ? 'status-disabled' : token.status === undefined ? 'status-enabled' : 'status-' + token.status}"></span>
|
||||
<span class="file-icon">🔑</span>
|
||||
${displayName}
|
||||
</td>
|
||||
@@ -1624,7 +1733,7 @@
|
||||
<td>${premium.requests || 0}/${premium.max_requests || '∞'}</td>
|
||||
<td>${stripe.days_remaining_on_trial > 0 ? `${stripe.days_remaining_on_trial}天` : '-'}</td>
|
||||
<td><span class="timezone-name" onclick="openTimezoneSelectorForToken('${token.token}', event)">${timezone || '未指定'}</span></td>
|
||||
<td><span class="proxy-name" onclick="openProxySelector('${displayName}','${token.token}', event)">${token.tags ? token.tags[1] || '未指定' : '未指定'}</span></td>
|
||||
<td><span class="proxy-name" onclick="openProxySelector('${displayName}','${token.token}', event)">${token.tags?.proxy || '未指定'}</span></td>
|
||||
</tr>
|
||||
`;
|
||||
}).join('');
|
||||
@@ -1768,7 +1877,7 @@
|
||||
// 代理筛选
|
||||
let proxyMatch = true;
|
||||
if (proxyFilter !== 'all') {
|
||||
const tokenProxy = token.tags?.[1] || '';
|
||||
const tokenProxy = token.tags?.proxy || '';
|
||||
if (proxyFilter === 'none') {
|
||||
proxyMatch = tokenProxy === '';
|
||||
} else {
|
||||
@@ -1779,7 +1888,7 @@
|
||||
// 时区筛选
|
||||
let timezoneMatch = true;
|
||||
if (timezoneFilter !== 'all') {
|
||||
const tokenTimezone = token.tags?.[0] || '';
|
||||
const tokenTimezone = token.tags?.timezone || '';
|
||||
if (timezoneFilter === 'none') {
|
||||
timezoneMatch = tokenTimezone === '';
|
||||
} else {
|
||||
@@ -1910,8 +2019,8 @@
|
||||
const stripe = profile.stripe || {};
|
||||
const usage = profile.usage || {};
|
||||
const premium = usage.premium || {};
|
||||
const timezone = tokenObj.tags?.[0] || '-';
|
||||
const proxy = tokenObj.tags?.[1] || '-';
|
||||
const timezone = tokenObj.tags?.timezone || '-';
|
||||
const proxy = tokenObj.tags?.proxy || '-';
|
||||
|
||||
document.getElementById('detailsTitle').textContent = user.email || '未知账户';
|
||||
|
||||
@@ -2079,7 +2188,7 @@
|
||||
closeModal('confirmModal');
|
||||
showToast('正在删除Token...', 'info');
|
||||
|
||||
const data = await makeAuthenticatedRequest('/tokens/delete', {
|
||||
const data = await makeAuthenticatedRequest('/tokens/del', {
|
||||
body: JSON.stringify({
|
||||
tokens: tokensToDelete,
|
||||
expectation: 'failed_tokens'
|
||||
@@ -2134,6 +2243,7 @@
|
||||
// 确认添加Token
|
||||
async function confirmAddTokens() {
|
||||
const tokensInput = document.getElementById('addTokensInput').value;
|
||||
const status = document.getElementById('addTokensStatus').value;
|
||||
|
||||
if (!tokensInput) {
|
||||
showToast('请输入要添加的Token', 'warning');
|
||||
@@ -2151,14 +2261,16 @@
|
||||
const parts = line.includes(',') ? line.split(',') : [line];
|
||||
return {
|
||||
token: parts[0].trim(),
|
||||
checksum: parts[1]?.trim() || null
|
||||
checksum: parts[1]?.trim() || null,
|
||||
status: status
|
||||
};
|
||||
});
|
||||
|
||||
const data = await makeAuthenticatedRequest('/tokens/add', {
|
||||
body: JSON.stringify({
|
||||
tokens: tokenList,
|
||||
tags: []
|
||||
tags: {},
|
||||
status: status
|
||||
})
|
||||
});
|
||||
|
||||
@@ -2230,7 +2342,7 @@
|
||||
closeModal('importModal');
|
||||
showToast('正在导入Token...', 'info');
|
||||
|
||||
const data = await makeAuthenticatedRequest('/tokens/update', {
|
||||
const data = await makeAuthenticatedRequest('/tokens/set', {
|
||||
body: JSON.stringify(tokensJson)
|
||||
});
|
||||
|
||||
@@ -2316,8 +2428,8 @@
|
||||
requestBody.proxy_name = proxyName;
|
||||
} else {
|
||||
// 否则使用Token的tags中设置的代理
|
||||
const tags = tokenObj.tags || [];
|
||||
const tokenProxyName = tags[1] || '';
|
||||
const tags = tokenObj.tags || {};
|
||||
const tokenProxyName = tags.proxy || '';
|
||||
if (tokenProxyName) {
|
||||
requestBody.proxy_name = tokenProxyName;
|
||||
}
|
||||
@@ -2557,7 +2669,7 @@
|
||||
});
|
||||
|
||||
// 设置当前选中的代理
|
||||
const currentProxy = tokenObj.tags?.[1] || '';
|
||||
const currentProxy = tokenObj.tags?.proxy || '';
|
||||
proxySelect.value = currentProxy;
|
||||
|
||||
// 显示模态框
|
||||
@@ -2571,10 +2683,10 @@
|
||||
closeModal('proxySelectModal')
|
||||
|
||||
const selectedProxy = document.getElementById('proxySelectDropdown').value;
|
||||
const tags = selectedProxy ? ['', selectedProxy] : [];
|
||||
const tags = { proxy: selectedProxy || undefined };
|
||||
|
||||
showToast('正在更新代理设置...', 'info');
|
||||
const data = await makeAuthenticatedRequest('/tokens/tags/update', {
|
||||
const data = await makeAuthenticatedRequest('/tokens/tags/set', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
tokens: [currentEditingToken],
|
||||
@@ -2595,7 +2707,7 @@
|
||||
function getProxyUsageStats() {
|
||||
const proxyUsage = {};
|
||||
allTokens.forEach(token => {
|
||||
const proxy = token.tags?.[1] || '';
|
||||
const proxy = token.tags?.proxy?.value || '';
|
||||
proxyUsage[proxy] = (proxyUsage[proxy] || 0) + 1;
|
||||
});
|
||||
return proxyUsage;
|
||||
@@ -2631,7 +2743,7 @@
|
||||
initializeTimezoneList();
|
||||
|
||||
// 设置当前选中的时区
|
||||
const currentTimezone = tokenObj.tags?.[0] || '';
|
||||
const currentTimezone = tokenObj.tags?.timezone || '';
|
||||
highlightSelectedTimezone(currentTimezone);
|
||||
|
||||
// 显示模态框
|
||||
@@ -2775,9 +2887,15 @@
|
||||
const tokenObj = allTokens.find(t => t.token === token);
|
||||
if (!tokenObj) return null;
|
||||
|
||||
// 保持原来的代理设置(在tags[1]中)
|
||||
const proxyName = tokenObj.tags?.[1] || '';
|
||||
return { token, tags: [selectedTimezone, proxyName] };
|
||||
// 保持原来的代理设置
|
||||
const proxyName = tokenObj.tags?.proxy || '';
|
||||
return {
|
||||
token,
|
||||
tags: {
|
||||
timezone: selectedTimezone || undefined,
|
||||
proxy: proxyName || undefined
|
||||
}
|
||||
};
|
||||
}).filter(Boolean); // 过滤掉null值
|
||||
|
||||
if (tokensData.length === 0) {
|
||||
@@ -2789,7 +2907,7 @@
|
||||
|
||||
// 使用Promise.all并行处理所有更新请求
|
||||
const promises = tokensData.map(data =>
|
||||
makeAuthenticatedRequest('/tokens/tags/update', {
|
||||
makeAuthenticatedRequest('/tokens/tags/set', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
tokens: [data.token],
|
||||
@@ -2820,7 +2938,7 @@
|
||||
// 获取所有使用的时区并统计数量
|
||||
const timezoneUsage = {};
|
||||
allTokens.forEach(token => {
|
||||
const timezone = token.tags?.[0] || '';
|
||||
const timezone = token.tags?.timezone || '';
|
||||
if (timezone) { // 只统计非空时区
|
||||
timezoneUsage[timezone] = (timezoneUsage[timezone] || 0) + 1;
|
||||
}
|
||||
@@ -2839,6 +2957,129 @@
|
||||
timezoneFilterSelect.appendChild(option);
|
||||
});
|
||||
}
|
||||
|
||||
// 设置Token状态
|
||||
async function setTokenStatus(status) {
|
||||
if (selectedTokens.size === 0) {
|
||||
showToast('请先选择Token', 'info');
|
||||
return;
|
||||
}
|
||||
|
||||
const tokensArray = [...selectedTokens];
|
||||
|
||||
showToast('正在更新Token状态...', 'info');
|
||||
|
||||
try {
|
||||
const result = await makeAuthenticatedRequest('/tokens/status/set', {
|
||||
body: JSON.stringify({
|
||||
tokens: tokensArray,
|
||||
status: status
|
||||
})
|
||||
});
|
||||
|
||||
if (result && result.status === 'success') {
|
||||
showToast(result.message || '状态更新成功', 'success');
|
||||
getTokenInfo(); // 刷新Token列表
|
||||
} else {
|
||||
// 显示详细的错误信息
|
||||
const errorMessage = result?.error || result?.message || '状态更新失败';
|
||||
showToast(errorMessage, 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
// 显示详细的错误信息
|
||||
const errorMessage = error.response?.data?.message || error.response?.data?.error || error.message;
|
||||
showToast(`状态更新失败: ${errorMessage}`, 'error');
|
||||
}
|
||||
|
||||
// 关闭上下文菜单
|
||||
document.getElementById('contextMenu').style.display = 'none';
|
||||
// 更新状态子菜单
|
||||
updateStatusSubmenu();
|
||||
}
|
||||
|
||||
// 更新状态子菜单
|
||||
function updateStatusSubmenu() {
|
||||
const submenu = document.getElementById('statusSubmenu');
|
||||
if (!submenu) return;
|
||||
|
||||
// 获取选中的token对象
|
||||
const selectedTokenObjs = allTokens.filter(t => selectedTokens.has(t.token));
|
||||
const selectionSize = selectedTokenObjs.length;
|
||||
|
||||
// 计算选中项的状态统计
|
||||
const selectedStatusStats = {
|
||||
'enabled': 0,
|
||||
'disabled': 0
|
||||
};
|
||||
|
||||
selectedTokenObjs.forEach(token => {
|
||||
// 将 undefined 或 'enabled' 状态视为 'enabled'
|
||||
const status = token.status === 'disabled' ? 'disabled' : 'enabled';
|
||||
selectedStatusStats[status]++; // 增加对应状态的计数
|
||||
});
|
||||
|
||||
// 更新 "启用" 选项
|
||||
const enabledItem = submenu.querySelector('.context-menu-item[onclick="setTokenStatus(\\"enabled\\")"]');
|
||||
if (enabledItem) {
|
||||
const enabledCountSpan = enabledItem.querySelector('.status-count');
|
||||
if (enabledCountSpan) {
|
||||
// 显示选中项中启用的数量
|
||||
enabledCountSpan.textContent = selectedStatusStats['enabled'];
|
||||
}
|
||||
// 检查是否所有选中的 Token 都处于启用状态
|
||||
const isEnabledActive = selectionSize > 0 && selectedStatusStats['enabled'] === selectionSize;
|
||||
enabledItem.classList.toggle('active', isEnabledActive); // 设置选中标记
|
||||
}
|
||||
|
||||
// 更新 "禁用" 选项
|
||||
const disabledItem = submenu.querySelector('.context-menu-item[onclick="setTokenStatus(\\"disabled\\")"]');
|
||||
if (disabledItem) {
|
||||
const disabledCountSpan = disabledItem.querySelector('.status-count');
|
||||
if (disabledCountSpan) {
|
||||
// 显示选中项中禁用的数量
|
||||
disabledCountSpan.textContent = selectedStatusStats['disabled'];
|
||||
}
|
||||
// 检查是否所有选中的 Token 都处于禁用状态
|
||||
const isDisabledActive = selectionSize > 0 && selectedStatusStats['disabled'] === selectionSize;
|
||||
disabledItem.classList.toggle('active', isDisabledActive); // 设置选中标记
|
||||
}
|
||||
|
||||
// 添加 has-submenu 类以便显示箭头
|
||||
const statusMenuEl = document.querySelector('.status-menu');
|
||||
if (statusMenuEl) {
|
||||
statusMenuEl.classList.add('has-submenu');
|
||||
}
|
||||
}
|
||||
|
||||
// 升级选中的Token
|
||||
async function upgradeSelectedTokens() {
|
||||
if (selectedTokens.size === 0) {
|
||||
showToast('请先选择要升级的Token', 'info');
|
||||
return;
|
||||
}
|
||||
|
||||
const tokensToUpgrade = [...selectedTokens];
|
||||
showToast(`正在升级 ${tokensToUpgrade.length} 个Token...`, 'info');
|
||||
|
||||
// 关闭右键菜单
|
||||
document.getElementById('contextMenu').style.display = 'none';
|
||||
|
||||
try {
|
||||
const data = await makeAuthenticatedRequest('/tokens/upgrade', {
|
||||
body: JSON.stringify(tokensToUpgrade)
|
||||
});
|
||||
|
||||
if (data && data.status === 'success') {
|
||||
showToast(data.message || '升级成功', 'success');
|
||||
getTokenInfo();
|
||||
} else {
|
||||
showToast('升级失败: ' + (data.message || data.error || '未知错误'), 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
const errorMessage = error.response?.data?.message || error.response?.data?.error || error.message;
|
||||
showToast(`升级失败: ${errorMessage}`, 'error');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ fn obfuscate_bytes(bytes: &mut [u8]) {
|
||||
fn generate_timestamp_header() -> String {
|
||||
let timestamp = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.expect("system time before Unix epoch")
|
||||
.as_secs()
|
||||
/ 1_000;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user