v0.1.3-rc.3.2

This commit is contained in:
wisdgod
2025-01-17 04:28:06 +08:00
parent 061156fb79
commit 42c8feb982
20 changed files with 253 additions and 5155 deletions

View File

@@ -6,9 +6,12 @@ PORT=3000
# 路由前缀,必须以 / 开头(如果不为空)
ROUTE_PREFIX=
# 认证令牌,必填
# 最高权限的认证令牌,必填
AUTH_TOKEN=
# 共享的认证令牌仅Chat端点权限(轮询与AUTH_TOKEN同步),无其余权限
SHARED_AUTH_TOKEN=
# 启用流式响应检查关闭则无法响应错误代价是会对第一个块解析2次
ENABLE_STREAM_CHECK=true

2
Cargo.lock generated
View File

@@ -327,7 +327,7 @@ dependencies = [
[[package]]
name = "cursor-api"
version = "0.1.3-rc.3"
version = "0.1.3-rc.3.2"
dependencies = [
"axum",
"base64",

View File

@@ -1,6 +1,6 @@
[package]
name = "cursor-api"
version = "0.1.3-rc.3"
version = "0.1.3-rc.3.2"
edition = "2021"
authors = ["wisdgod <nav@wisdgod.com>"]
description = "OpenAI format compatibility layer for the Cursor API"

View File

@@ -14,8 +14,10 @@ def_pub_const!(EMPTY_STRING, "");
def_pub_const!(ROUTE_ROOT_PATH, "/");
def_pub_const!(ROUTE_HEALTH_PATH, "/health");
def_pub_const!(ROUTE_GET_HASH, "/get-hash");
def_pub_const!(ROUTE_GET_CHECKSUM, "/get-checksum");
def_pub_const!(ROUTE_GET_USER_INFO_PATH, "/get-userinfo");
def_pub_const!(ROUTE_GET_TIMESTAMP_HEADER, "/get-tsheader");
def_pub_const!(ROUTE_USER_INFO_PATH, "/userinfo");
def_pub_const!(ROUTE_API_PATH, "/api");
def_pub_const!(ROUTE_LOGS_PATH, "/logs");
def_pub_const!(ROUTE_CONFIG_PATH, "/config");

View File

@@ -51,6 +51,10 @@ def_pub_static!(DEFAULT_INSTRUCTIONS, env: "DEFAULT_INSTRUCTIONS", default: "Res
def_pub_static!(REVERSE_PROXY_HOST, env: "REVERSE_PROXY_HOST", default: "");
def_pub_static!(SHARED_AUTH_TOKEN, env: "SHARED_AUTH_TOKEN", default: EMPTY_STRING);
pub static USE_SHARE: LazyLock<bool> = LazyLock::new(|| !SHARED_AUTH_TOKEN.is_empty());
pub static USE_PROXY: LazyLock<bool> = LazyLock::new(|| !REVERSE_PROXY_HOST.is_empty());
pub static CURSOR_API2_CHAT_URL: LazyLock<String> = LazyLock::new(|| {

File diff suppressed because it is too large Load Diff

View File

@@ -4,11 +4,12 @@ mod health;
pub use health::{handle_health, handle_root};
mod token;
pub use token::{
handle_basic_calibration, handle_get_checksum, handle_get_tokeninfo, handle_tokeninfo_page,
handle_update_tokeninfo, handle_update_tokeninfo_post,
handle_basic_calibration, handle_get_checksum, handle_get_hash, handle_get_timestamp_header,
handle_get_tokeninfo, handle_tokeninfo_page, handle_update_tokeninfo,
handle_update_tokeninfo_post,
};
mod profile;
pub use profile::get_user_info;
pub use profile::handle_user_info;
mod config;
pub use config::{
handle_about, handle_config_page, handle_env_example, handle_readme, handle_static,

View File

@@ -74,8 +74,8 @@ pub async fn handle_static(Path(path): Path<String>) -> impl IntoResponse {
}
}
pub async fn handle_about() -> impl IntoResponse {
match AppConfig::get_page_content(ROUTE_ABOUT_PATH).unwrap_or_default() {
pub async fn handle_readme() -> impl IntoResponse {
match AppConfig::get_page_content(ROUTE_README_PATH).unwrap_or_default() {
PageContent::Default => Response::builder()
.header(CONTENT_TYPE, CONTENT_TYPE_TEXT_HTML_WITH_UTF8)
.body(include_str!("../../../static/readme.min.html").to_string())
@@ -91,11 +91,11 @@ pub async fn handle_about() -> impl IntoResponse {
}
}
pub async fn handle_readme() -> impl IntoResponse {
match AppConfig::get_page_content(ROUTE_README_PATH).unwrap_or_default() {
pub async fn handle_about() -> impl IntoResponse {
match AppConfig::get_page_content(ROUTE_ABOUT_PATH).unwrap_or_default() {
PageContent::Default => Response::builder()
.status(StatusCode::TEMPORARY_REDIRECT)
.header(LOCATION, ROUTE_ABOUT_PATH)
.header(LOCATION, ROUTE_README_PATH)
.body(Body::empty())
.unwrap(),
PageContent::Text(content) => Response::builder()

View File

@@ -4,9 +4,10 @@ use crate::{
AUTHORIZATION_BEARER_PREFIX, CONTENT_TYPE_TEXT_HTML_WITH_UTF8,
CONTENT_TYPE_TEXT_PLAIN_WITH_UTF8, PKG_VERSION, ROUTE_ABOUT_PATH, ROUTE_API_PATH,
ROUTE_BASIC_CALIBRATION_PATH, ROUTE_CONFIG_PATH, ROUTE_ENV_EXAMPLE_PATH,
ROUTE_GET_CHECKSUM, ROUTE_GET_TOKENINFO_PATH, ROUTE_GET_USER_INFO_PATH,
ROUTE_HEALTH_PATH, ROUTE_LOGS_PATH, ROUTE_README_PATH, ROUTE_ROOT_PATH,
ROUTE_STATIC_PATH, ROUTE_TOKENINFO_PATH, ROUTE_UPDATE_TOKENINFO_PATH,
ROUTE_GET_CHECKSUM, ROUTE_GET_HASH, ROUTE_GET_TIMESTAMP_HEADER,
ROUTE_GET_TOKENINFO_PATH, ROUTE_HEALTH_PATH, ROUTE_LOGS_PATH, ROUTE_README_PATH,
ROUTE_ROOT_PATH, ROUTE_STATIC_PATH, ROUTE_TOKENINFO_PATH, ROUTE_UPDATE_TOKENINFO_PATH,
ROUTE_USER_INFO_PATH,
},
lazy::{get_start_time, AUTH_TOKEN, ROUTE_CHAT_PATH, ROUTE_MODELS_PATH},
model::{AppConfig, AppState, PageContent},
@@ -115,7 +116,6 @@ pub async fn handle_health(
endpoints: vec![
ROUTE_CHAT_PATH.as_str(),
ROUTE_MODELS_PATH.as_str(),
ROUTE_GET_CHECKSUM,
ROUTE_TOKENINFO_PATH,
ROUTE_UPDATE_TOKENINFO_PATH,
ROUTE_GET_TOKENINFO_PATH,
@@ -125,9 +125,12 @@ pub async fn handle_health(
ROUTE_STATIC_PATH,
ROUTE_ABOUT_PATH,
ROUTE_README_PATH,
ROUTE_BASIC_CALIBRATION_PATH,
ROUTE_GET_USER_INFO_PATH,
ROUTE_API_PATH,
ROUTE_GET_HASH,
ROUTE_GET_CHECKSUM,
ROUTE_GET_TIMESTAMP_HEADER,
ROUTE_BASIC_CALIBRATION_PATH,
ROUTE_USER_INFO_PATH,
],
})
}

View File

@@ -6,7 +6,7 @@ use axum::Json;
use super::token::TokenRequest;
pub async fn get_user_info(Json(request): Json<TokenRequest>) -> Json<GetUserInfo> {
pub async fn handle_user_info(Json(request): Json<TokenRequest>) -> Json<GetUserInfo> {
let auth_token = match request.token {
Some(token) => token,
None => {

View File

@@ -10,7 +10,7 @@ use crate::{
common::{
models::{ApiStatus, NormalResponseNoData},
utils::{
extract_time, extract_time_ks, extract_user_id, generate_checksum_with_default, generate_checksum_with_repair, load_tokens, validate_token_and_checksum
extract_time, extract_time_ks, extract_user_id, generate_checksum_with_default, generate_checksum_with_repair, generate_hash, generate_timestamp_header, load_tokens, validate_token_and_checksum
},
},
};
@@ -28,24 +28,49 @@ use serde::{Deserialize, Serialize};
use std::sync::Arc;
use tokio::sync::Mutex;
pub async fn handle_get_hash() -> Response {
let hash = generate_hash();
let mut headers = HeaderMap::new();
headers.insert(
CONTENT_TYPE,
CONTENT_TYPE_TEXT_PLAIN_WITH_UTF8.parse().unwrap(),
);
(headers, hash).into_response()
}
#[derive(Deserialize)]
pub struct ChecksumQuery {
#[serde(default, alias = "checksum")]
pub bad_checksum: Option<String>,
#[serde(default)]
pub checksum: Option<String>,
}
#[derive(Serialize)]
pub struct ChecksumResponse {
pub checksum: String,
pub async fn handle_get_checksum(Query(query): Query<ChecksumQuery>) -> Response {
let checksum = match query.checksum {
None => generate_checksum_with_default(),
Some(checksum) => generate_checksum_with_repair(&checksum),
};
let mut headers = HeaderMap::new();
headers.insert(
CONTENT_TYPE,
CONTENT_TYPE_TEXT_PLAIN_WITH_UTF8.parse().unwrap(),
);
(headers, checksum).into_response()
}
pub async fn handle_get_checksum(
Query(query): Query<ChecksumQuery>
) -> Json<ChecksumResponse> {
match query.bad_checksum {
None => Json(ChecksumResponse { checksum: generate_checksum_with_default() }),
Some(bad_checksum) => Json(ChecksumResponse { checksum: generate_checksum_with_repair(&bad_checksum) })
}
pub async fn handle_get_timestamp_header() -> Response {
let timestamp_header = generate_timestamp_header();
let mut headers = HeaderMap::new();
headers.insert(
CONTENT_TYPE,
CONTENT_TYPE_TEXT_PLAIN_WITH_UTF8.parse().unwrap(),
);
(headers, timestamp_header).into_response()
}
// 更新 TokenInfo 处理

View File

@@ -1,11 +1,10 @@
use crate::{
app::{
constant::{
AUTHORIZATION_BEARER_PREFIX, FINISH_REASON_STOP,
OBJECT_CHAT_COMPLETION, OBJECT_CHAT_COMPLETION_CHUNK, STATUS_FAILED, STATUS_PENDING,
STATUS_SUCCESS,
AUTHORIZATION_BEARER_PREFIX, FINISH_REASON_STOP, OBJECT_CHAT_COMPLETION,
OBJECT_CHAT_COMPLETION_CHUNK, STATUS_FAILED, STATUS_PENDING, STATUS_SUCCESS,
},
lazy::AUTH_TOKEN,
lazy::{AUTH_TOKEN, SHARED_AUTH_TOKEN, USE_SHARE},
model::{AppConfig, AppState, ChatRequest, RequestLog, TimingInfo, TokenInfo},
},
chat::{
@@ -19,9 +18,7 @@ use crate::{
common::{
client::build_client,
models::{error::ChatError, userinfo::MembershipType, ErrorResponse},
utils::{
format_time_ms, get_token_profile, validate_token_and_checksum,
},
utils::{format_time_ms, get_token_profile, validate_token_and_checksum},
},
};
use axum::{
@@ -95,29 +92,33 @@ pub async fn handle_chat(
Json(ChatError::Unauthorized.to_json()),
))?;
// 验证 AuthToken获取 token 信息
let (auth_token, checksum) = if auth_header == AUTH_TOKEN.as_str() {
// 如果是管理员Token,使用原有逻辑
static CURRENT_KEY_INDEX: AtomicUsize = AtomicUsize::new(0);
let state_guard = state.lock().await;
let token_infos = &state_guard.token_infos;
// 验证认证token获取token信息
let (auth_token, checksum) = match auth_header {
// 管理员Token验证逻辑
token if token == AUTH_TOKEN.as_str() || (*USE_SHARE && token == SHARED_AUTH_TOKEN.as_str()) => {
static CURRENT_KEY_INDEX: AtomicUsize = AtomicUsize::new(0);
let state_guard = state.lock().await;
let token_infos = &state_guard.token_infos;
if token_infos.is_empty() {
return Err((
StatusCode::SERVICE_UNAVAILABLE,
Json(ChatError::NoTokens.to_json()),
));
}
// 检查是否存在可用的token
if token_infos.is_empty() {
return Err((
StatusCode::SERVICE_UNAVAILABLE,
Json(ChatError::NoTokens.to_json()),
));
}
let index = CURRENT_KEY_INDEX.fetch_add(1, Ordering::SeqCst) % token_infos.len();
let token_info = &token_infos[index];
(token_info.token.clone(), token_info.checksum.clone())
} else {
// 否则尝试解析token
validate_token_and_checksum(auth_header).ok_or((
// 轮询选择token
let index = CURRENT_KEY_INDEX.fetch_add(1, Ordering::SeqCst) % token_infos.len();
let token_info = &token_infos[index];
(token_info.token.clone(), token_info.checksum.clone())
},
// 普通用户Token验证逻辑
token => validate_token_and_checksum(token).ok_or((
StatusCode::UNAUTHORIZED,
Json(ChatError::Unauthorized.to_json()),
))?
))?,
};
let current_id: u64;
@@ -352,7 +353,8 @@ pub async fn handle_chat(
{
log.status = STATUS_FAILED;
log.error = Some(error_respone.native_code());
log.timing.total = format_time_ms(start_time.elapsed().as_secs_f64());
log.timing.total =
format_time_ms(start_time.elapsed().as_secs_f64());
state.error_requests += 1;
}
}
@@ -432,7 +434,8 @@ pub async fn handle_chat(
// 记录首字时间(如果还未记录)
if let Ok(mut first_time) = first_chunk_time.try_lock() {
if first_time.is_none() {
*first_time = Some(format_time_ms(start_time.elapsed().as_secs_f64()));
*first_time =
Some(format_time_ms(start_time.elapsed().as_secs_f64()));
}
}

View File

@@ -131,3 +131,79 @@ pub fn parse_stream_data(data: &[u8]) -> Result<StreamMessage, StreamError> {
Ok(StreamMessage::Content(messages))
}
}
#[test]
fn test_parse_stream_data() {
// 使用include_str!加载测试数据文件
let stream_data = include_str!("../../tests/data/stream_data.txt");
// 将整个字符串按每两个字符分割成字节
let bytes: Vec<u8> = stream_data
.as_bytes()
.chunks(2)
.map(|chunk| {
let hex_str = std::str::from_utf8(chunk).unwrap();
u8::from_str_radix(hex_str, 16).unwrap()
})
.collect();
// 辅助函数:找到下一个消息边界
fn find_next_message_boundary(bytes: &[u8]) -> usize {
if bytes.len() < 5 {
return bytes.len();
}
let msg_len = u32::from_be_bytes([bytes[1], bytes[2], bytes[3], bytes[4]]) as usize;
5 + msg_len
}
// 辅助函数将字节转换为hex字符串
fn bytes_to_hex(bytes: &[u8]) -> String {
bytes.iter()
.map(|b| format!("{:02X}", b))
.collect::<Vec<String>>()
.join("")
}
// 多次解析数据
let mut offset = 0;
while offset < bytes.len() {
let remaining_bytes = &bytes[offset..];
let msg_boundary = find_next_message_boundary(remaining_bytes);
let current_msg_bytes = &remaining_bytes[..msg_boundary];
let hex_str = bytes_to_hex(current_msg_bytes);
match parse_stream_data(current_msg_bytes) {
Ok(message) => {
match message {
StreamMessage::Content(messages) => {
print!("消息内容 [hex: {}]:", hex_str);
for msg in messages {
println!(" {}", msg);
}
offset += msg_boundary;
}
StreamMessage::Debug(_) => {
// println!("调试信息 [hex: {}]: {}", hex_str, prompt);
offset += msg_boundary;
}
StreamMessage::StreamEnd => {
println!("流结束 [hex: {}]", hex_str);
break;
}
StreamMessage::StreamStart => {
println!("流开始 [hex: {}]", hex_str);
offset += msg_boundary;
}
StreamMessage::Incomplete => {
println!("数据不完整 [hex: {}]", hex_str);
break;
}
}
}
Err(e) => {
println!("解析错误 [hex: {}]: {}", hex_str, e);
break;
}
}
}
}

View File

@@ -15,6 +15,8 @@ use reqwest::header::{
use reqwest::{Client, RequestBuilder};
use uuid::Uuid;
use super::utils::generate_hash;
macro_rules! def_const {
($name:ident, $value:expr) => {
const $name: &'static str = $value;
@@ -74,7 +76,7 @@ pub fn build_client(auth_token: &str, checksum: &str) -> RequestBuilder {
.header("connect-protocol-version", ONE)
.header(USER_AGENT, "connect-es/1.6.1")
.header("x-amzn-trace-id", format!("Root={}", trace_id))
// .header("x-client-key", client_key)
.header("x-client-key", generate_hash())
.header("x-cursor-checksum", checksum)
.header("x-cursor-client-version", "0.42.5")
.header("x-cursor-timezone", "Asia/Shanghai")

View File

@@ -2,7 +2,7 @@ use base64::{engine::general_purpose::STANDARD as BASE64, Engine as _};
use rand::Rng;
use sha2::{Digest, Sha256};
fn generate_hash() -> String {
pub fn generate_hash() -> String {
let random_bytes = rand::thread_rng().gen::<[u8; 32]>();
let mut hasher = Sha256::new();
hasher.update(random_bytes);
@@ -27,7 +27,7 @@ fn deobfuscate_bytes(bytes: &mut [u8]) {
}
}
fn generate_checksum(device_id: &str, mac_addr: Option<&str>) -> String {
pub fn generate_timestamp_header() -> String {
let timestamp = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
@@ -44,8 +44,11 @@ fn generate_checksum(device_id: &str, mac_addr: Option<&str>) -> String {
];
obfuscate_bytes(&mut timestamp_bytes);
let encoded = BASE64.encode(&timestamp_bytes);
BASE64.encode(&timestamp_bytes)
}
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),
@@ -56,10 +59,10 @@ pub fn generate_checksum_with_default() -> String {
generate_checksum(&generate_hash(), Some(&generate_hash()))
}
pub fn generate_checksum_with_repair(bad_checksum: &str) -> String {
pub fn generate_checksum_with_repair(checksum: &str) -> String {
// 预校验检查字符串是否为空或只包含合法的Base64字符和'/'
if bad_checksum.is_empty()
|| !bad_checksum
if checksum.is_empty()
|| !checksum
.chars()
.all(|c| (c.is_ascii_alphanumeric() || c == '/' || c == '+' || c == '='))
{
@@ -101,14 +104,14 @@ pub fn generate_checksum_with_repair(bad_checksum: &str) -> String {
None
}
if bad_checksum.len() == 8 {
if checksum.len() == 8 {
// 尝试修复时间戳头
if let Some(fixed_timestamp) = try_fix_timestamp(bad_checksum) {
if let Some(fixed_timestamp) = try_fix_timestamp(checksum) {
return format!("{}{}/{}", fixed_timestamp, generate_hash(), generate_hash());
}
// 验证原始时间戳
if let Some(timestamp) = extract_time_ks(bad_checksum) {
if let Some(timestamp) = extract_time_ks(checksum) {
let current_timestamp = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
@@ -116,16 +119,16 @@ pub fn generate_checksum_with_repair(bad_checksum: &str) -> String {
/ 1_000;
if timestamp <= current_timestamp {
return format!("{}{}/{}", bad_checksum, generate_hash(), generate_hash());
return format!("{}{}/{}", checksum, generate_hash(), generate_hash());
}
}
} else if bad_checksum.len() > 8 {
} else if checksum.len() > 8 {
// 处理可能包含hash的情况
let parts: Vec<&str> = bad_checksum.split('/').collect();
let parts: Vec<&str> = checksum.split('/').collect();
match parts.len() {
1 => {
let timestamp_base64 = &bad_checksum[..8];
let device_id = &bad_checksum[8..];
let timestamp_base64 = &checksum[..8];
let device_id = &checksum[8..];
if is_valid_hash(device_id) {
// 先尝试修复时间戳
@@ -144,7 +147,7 @@ pub fn generate_checksum_with_repair(bad_checksum: &str) -> String {
if timestamp <= current_timestamp {
return format!(
"{}{}/{}",
timestamp_base64,
generate_timestamp_header(),
device_id,
generate_hash()
);
@@ -175,7 +178,12 @@ pub fn generate_checksum_with_repair(bad_checksum: &str) -> String {
/ 1_000;
if timestamp <= current_timestamp {
return bad_checksum.to_string();
return format!(
"{}{}/{}",
generate_timestamp_header(),
device_id,
mac_hash
);
}
}
}

View File

@@ -59,10 +59,14 @@ pub fn load_tokens() -> Vec<TokenInfo> {
.lines()
.filter_map(|line| {
let line = line.trim();
if line.is_empty() || line.starts_with('#') || !validate_token(line) {
if line.is_empty() || line.starts_with('#') {
return None;
}
parse_token(line)
let parsed = parse_token(line);
if parsed.is_none() || !validate_token(&parsed.as_ref().unwrap()) {
return None;
}
parsed
})
.collect::<Vec<_>>()
}

View File

@@ -5,10 +5,7 @@ mod common;
use app::{
config::handle_config_update,
constant::{
EMPTY_STRING, PKG_VERSION, ROUTE_ABOUT_PATH, ROUTE_API_PATH, ROUTE_BASIC_CALIBRATION_PATH,
ROUTE_CONFIG_PATH, ROUTE_ENV_EXAMPLE_PATH, ROUTE_GET_CHECKSUM, ROUTE_GET_TOKENINFO_PATH,
ROUTE_GET_USER_INFO_PATH, ROUTE_HEALTH_PATH, ROUTE_LOGS_PATH, ROUTE_README_PATH,
ROUTE_ROOT_PATH, ROUTE_STATIC_PATH, ROUTE_TOKENINFO_PATH, ROUTE_UPDATE_TOKENINFO_PATH,
EMPTY_STRING, PKG_VERSION, ROUTE_ABOUT_PATH, ROUTE_API_PATH, ROUTE_BASIC_CALIBRATION_PATH, ROUTE_CONFIG_PATH, ROUTE_ENV_EXAMPLE_PATH, ROUTE_GET_CHECKSUM, ROUTE_GET_HASH, ROUTE_GET_TIMESTAMP_HEADER, ROUTE_GET_TOKENINFO_PATH, ROUTE_HEALTH_PATH, ROUTE_LOGS_PATH, ROUTE_README_PATH, ROUTE_ROOT_PATH, ROUTE_STATIC_PATH, ROUTE_TOKENINFO_PATH, ROUTE_UPDATE_TOKENINFO_PATH, ROUTE_USER_INFO_PATH
},
lazy::{AUTH_TOKEN, ROUTE_CHAT_PATH, ROUTE_MODELS_PATH},
model::*,
@@ -19,10 +16,7 @@ use axum::{
};
use chat::{
route::{
get_user_info, handle_about, handle_api_page, handle_basic_calibration, handle_config_page,
handle_env_example, handle_get_checksum, handle_get_tokeninfo, handle_health, handle_logs,
handle_logs_post, handle_readme, handle_root, handle_static, handle_tokeninfo_page,
handle_update_tokeninfo, handle_update_tokeninfo_post,
handle_about, handle_api_page, handle_basic_calibration, handle_config_page, handle_env_example, handle_get_checksum, handle_get_hash, handle_get_timestamp_header, handle_get_tokeninfo, handle_health, handle_logs, handle_logs_post, handle_readme, handle_root, handle_static, handle_tokeninfo_page, handle_update_tokeninfo, handle_update_tokeninfo_post, handle_user_info
},
service::{handle_chat, handle_models},
};
@@ -73,7 +67,6 @@ async fn main() {
.route(ROUTE_HEALTH_PATH, get(handle_health))
.route(ROUTE_TOKENINFO_PATH, get(handle_tokeninfo_page))
.route(ROUTE_MODELS_PATH.as_str(), get(handle_models))
.route(ROUTE_GET_CHECKSUM, get(handle_get_checksum))
.route(ROUTE_UPDATE_TOKENINFO_PATH, get(handle_update_tokeninfo))
.route(ROUTE_GET_TOKENINFO_PATH, post(handle_get_tokeninfo))
.route(
@@ -89,9 +82,12 @@ async fn main() {
.route(ROUTE_STATIC_PATH, get(handle_static))
.route(ROUTE_ABOUT_PATH, get(handle_about))
.route(ROUTE_README_PATH, get(handle_readme))
.route(ROUTE_BASIC_CALIBRATION_PATH, post(handle_basic_calibration))
.route(ROUTE_GET_USER_INFO_PATH, post(get_user_info))
.route(ROUTE_API_PATH, get(handle_api_page))
.route(ROUTE_GET_HASH, get(handle_get_hash))
.route(ROUTE_GET_CHECKSUM, get(handle_get_checksum))
.route(ROUTE_GET_TIMESTAMP_HEADER, get(handle_get_timestamp_header))
.route(ROUTE_BASIC_CALIBRATION_PATH, post(handle_basic_calibration))
.route(ROUTE_USER_INFO_PATH, post(handle_user_info))
.layer(RequestBodyLimitLayer::new(
1024 * 1024 * parse_usize_from_env("REQUEST_BODY_LIMIT_MB", 2),
))

View File

@@ -287,7 +287,7 @@
}
}
const result = await makeTokenRequest('/get-userinfo', token);
const result = await makeTokenRequest('/userinfo', token);
if (result) {
const container = document.getElementById('userInfoContainer');
container.style.display = 'block';

View File

@@ -153,13 +153,15 @@ gemini-2.0-flash-exp
}
],
"usage": {
"prompt_tokens": number, // 0
"completion_tokens": number, // 0
"total_tokens": number // 0
"prompt_tokens": 0,
"completion_tokens": 0,
"total_tokens": 0
}
}
</code></pre>
<p>不进行 tokens 计算主要是担心性能问题。</p>
<p>如果 <code>stream</code><code>true</code>:</p>
<pre><code>data: {"id":"string","object":"chat.completion.chunk","created":number,"model":"string","choices":[{"index":number,"delta":{"role":"assistant","content":"string"},"finish_reason":null}]}
@@ -372,6 +374,16 @@ data: [DONE]
}
</code></pre>
<h3>获取一个随机hash</h3>
<ul>
<li>接口地址: <code>/get-hash</code></li>
<li>请求方法: GET</li>
<li>响应格式:</li>
</ul>
<pre><code class="language-plaintext">string</code></pre>
<h3>获取或修复checksum</h3>
<ul>
@@ -379,23 +391,30 @@ data: [DONE]
<li>请求方法: GET</li>
<li>请求参数:
<ul>
<li><code>bad_checksum</code>: 可选用于修复的旧版本生成的checksum也可只传入前8个字符可用别名checksum</li>
<li><code>checksum</code>: 可选用于修复的旧版本生成的checksum也可只传入前8个字符可用来自动刷新时间戳头</li>
</ul>
</li>
<li>响应格式:</li>
</ul>
<pre><code class="language-json">{
"checksum": "string"
}
</code></pre>
<pre><code class="language-plaintext">string</code></pre>
<p>说明:</p>
<ul>
<li>如果不提供<code>bad_checksum</code>参数将生成一个新的随机checksum</li>
<li>如果提供<code>bad_checksum</code>参数将尝试修复旧版本的checksum以适配当前版本(v0.1.3-rc.3)使用修复失败会返回新的checksum</li>
<li>如果不提供<code>checksum</code>参数将生成一个新的随机checksum</li>
<li>如果提供<code>checksum</code>参数将尝试修复旧版本的checksum以适配v0.1.3-rc.3之后的版本使用修复失败会返回新的checksum若输入的checksum本来就有效则返回更新tsheader后的checksum</li>
</ul>
<h3>获取当前的tsheader</h3>
<ul>
<li>接口地址: <code>/get-tsheader</code></li>
<li>请求方法: GET</li>
<li>响应格式:</li>
</ul>
<pre><code class="language-plaintext">string</code></pre>
<h3>健康检查接口</h3>
<ul>
@@ -508,7 +527,7 @@ data: [DONE]
<h3>获取用户信息</h3>
<ul>
<li>接口地址: <code>/get-userinfo</code></li>
<li>接口地址: <code>/userinfo</code></li>
<li>请求方法: POST</li>
<li>认证方式: 请求体中包含token</li>
<li>请求格式:</li>
@@ -612,7 +631,7 @@ data: [DONE]
<p>求赞助还是有点不要脸了,接下来是吐槽:</p>
<p>辛辛苦苦做这个也不知道是为了谁好累。其实还有很多功能可以做比如直接传token支持配置其实这个要专门做一个页面</p>
<p>辛辛苦苦做这个也不知道是为了谁好累。其实还有很多功能可以做比如直接传token支持配置其实这个要专门做一个页面这个作为rc.4的计划之一吧</p>
<p>主要没想做用户管理所以不存在是否接入LinuxDo的问题。虽然那个半成品公益版做好了就是了。</p>
@@ -620,4 +639,6 @@ data: [DONE]
<p>为什么一直说要跑路呢主要是有时Cursor的Claude太假了堪比gpt-4o-mini我对比发现真没啥差别比以前差远了无力了所以不太想做了。我也感觉很奇怪。</p>
<p>查询额度会在一开始检测导致和完成时的额度有些差别,但是懒得改了,反正差别不大,对话也没响应内容,恰好完成了统一。</p>
<p>查询额度会在一开始检测导致和完成时的额度有些差别,但是懒得改了,反正差别不大,对话也没响应内容,恰好完成了统一。</p>
<p>有人说少个二维码来着,还是算了。如果觉得好用,给点支持。其实没啥大不了的,没兴趣就不做了。不想那么多了。</p>

View File

@@ -0,0 +1 @@
01000005231F8B080000000000000385553D8F1C451025EE981F509060A3F5AC6C0C81395932F6D95C6063F9CEB21CF96A676A669AEDA91EF7C7EE0DBE937046CE0F3004C4C40810093FE50422E22FA0EA9EB9DD8593894E37DBF5EAEBBD57EFBFF3E1EFEFEE9D7EB6FFE0E0D18BC3E78747FB0F4F6FABE736023A0264D01CC818DD1007E89D6D1C761DB919F4764D8E2A580CF0E0F1D1B59B163ACDBA8029B4C5BE1F205868C9F480ECD7E400798097917CD0963D8416038496207A72D0A2872BD147346690AF03ACB531B020C0858D014A5B696EAE164A5D2FE0594BBC09D51ED02F3537505B0754E9E0257368493B89A319F486D013D818FA1800C1EBAE37BAD654C18A9CD796C1D60951DEC3C2D872990B6C75D31ADDB482293FB7C80D79602AC97B740320578055E5A1B45D479C736BAE74898160DD9223889CE3AA0C2FBD2E8818FC52F73D5505DC97C24FB0EB0DDD52C7C7C706B989D8D0AD1E433B0F765E6B436A3E87A228804EB40FD26E022B8A42BD7A95DA7E711DCECEFEFFD58DB7BE3A3E3E5647D3684B64F044A975E2A01D815432036FF3927A47353969D9B219C0115679317D8581A63D24F402BEA843DA9BF679B91D216F68E003BA3027AEA65548A60B168CA39AC1228614F38107BBC4E13D78426BA7C37F6ACC05E91A7C4FA5AE759998E5281150667EC7AC71F0C2EA95AE0810164E530D74D21B640C5B9C189B99416443DE6F987719B4CF892FBA56EA4601F72CB00D60348175D0E15240A1C632F842A98F0A38A801336627BC128A0D368266406135E9866122C5059D1DF9DE7225AFD214A70785523713A73A0C02E3C6979EE469876E59D935174A7D3C4A4906282410A131ADB744E02F92E55E7363532238B807580721402BD83A6834B0C07219748A357A49E0ED2D105AF543682D0B0D13FED959E29AFAE49222B60A48A246DE30351370AC0A8DB73BA5A5D58B6ADE565812AD231F70E44D47A1B515CCA134E87D9E99D4908D6041C6722364DE74B4A3526F3B9ADBD092CB42AD23978940770EEEB6183ED73E58375CB90AAF14002499C9DFAD494C9FF34CF64EF71FDDDBF8B19A1CFAE9E1FE93D3DBEAF156EFB535C6AEA589B45D1F5C2CB3BF0A778C110EEF50C08B261C195A2107D167974CD90D053CB2C044957CC472C9766DA86AE832EC4A3B2A83E88B77D10BB557461F6CF762FBFD6DF56443D5BBAD66015C0C50518DD104A5F61E3E3D3CBA682C7ACAA81D9EE82E7662AD7D0C499368F2ED492B0C76490C46773A649A80D7DC18DA940347ADE625B4E82AF26196A0D3C2AD0F1074470927FD979350B62C09AA887AF2A150EA482C4BFB4D64EF6C6DA3383F5729D0D0C9C569836B133B276F49CDC825211FE06544A3C3301E45A9901C89C66749F2D306881B5198E654472E49733393DB423D71252779F3B514F59468B63EA5DA1C61D02BBAF85C28F58CA08FCE47023A098E3A828AFAD0824361B05809838F3DB95A974936E2EAA1FD14D624C76009E4BD182D1A2145BE8DBBB1AEC692AE195A9101E2D8914BBB4B08636ACD6C57BBA5ED80B4B8D021A20147A5CEAB2F60E4871424E7D9D9D8B42047174D66417AE767D0D98536FA2B4A0A18D5BC4D2147DE46578AA7CB942AEAACD055EC20BD0E2E8A7D37621C2BCAD8E2D4F7AD6B68F4D4DE9A3C723340699D08426E18973403479D5D4D58537B65CEA1398C6917E8A902CBD0C60E1918437499904234DBA7B17103BDD35CEADE909F2ED2DA3A53CDB66E97233429D22EBEA432153DF24BEDCDB3B8F6E6972A73349BD15AFE6D354A9DFFFADD9F6F7EF8EBF5B7E73F7F7DFED337E7BF7DFFC7EB1FFFFEE5CD6EDC3FD3E84DBB490A0000000000000000000000050A03E6889100000000080A06E68BA5E69C8900000000080A06E585B3E4BA8E00000000050A03E7BC9600000000050A03E7A88B00000000050A03E3808100000000080A06E8BDAFE4BBB600000000080A06E5BC80E58F9100000000050A03E3808100000000080A06E7AE97E6B39500000000050A03E3808100000000080A06E695B0E68DAE00000000080A06E7BB93E69E8400000000050A03E3808100000000080A06E69CBAE599A800000000080A06E5ADA6E4B9A000000000050A03E3808100000000080A06E4BABAE5B7A500000000080A06E699BAE883BD00000000050A03E7AD8900000000080A06E5A49AE4B8AA00000000080A06E9A286E59F9F000000000B0A09E79A84E4BFA1E681AF00000000050A03E3808200000000080A06E68891E79A8400000000080A06E79FA5E8AF8600000000050A03E6B6B500000000050A03E79B9600000000050A03E4BA8600000000050A03E5A49A00000000050A03E7A78D00000000050A03E7BC9600000000050A03E7A88B00000000080A06E8AFADE8A88000000000050A03EFBC8800000000050A03E5A68200000000080A06507974686F6E00000000050A03E3808100000000060A044A61766100000000050A03E3808100000000060A044A61766100000000080A0653637269707400000000050A03E3808100000000030A014300000000040A022B2B00000000050A03E7AD8900000000080A06EFBC89E3808100000000080A06E5BC80E58F9100000000050A03E6A18600000000050A03E69EB600000000050A03E3808100000000080A06E5B7A5E585B700000000050A03E5928C00000000080A06E69C80E4BDB300000000080A06E5AE9EE8B7B5000000000B0A09E38082E6ADA4E5A49600000000080A06EFBC8CE6889100000000050A03E8BF9800000000080A06E58FAFE4BBA500000000080A06E68F90E4BE9B00000000080A06E585B3E4BA8E00000000080A06E8AEA1E7AE9700000000050A03E69CBA00000000080A06E7A791E5ADA600000000050A03E79A8400000000080A06E79086E8AEBA00000000080A06E79FA5E8AF8600000000050A03E3808100000000080A06E68A80E69CAF00000000080A06E8B68BE58ABF00000000050A03E5928C00000000080A06E8A18CE4B89A00000000080A06E58AA8E68081000000000B0A09E79A84E4BFA1E681AF00000000070A05E380820A0A00000000080A06E5A682E69E9C00000000050A03E4BDA000000000050A03E69C8900000000080A06E585B7E4BD93000000000B0A09E79A84E997AEE9A29800000000050A03E6889600000000080A06E99C80E8A68100000000080A06E5B8AEE58AA900000000050A03E79A8400000000080A06E59CB0E696B900000000080A06EFBC8CE8AFB700000000080A06E5918AE8AF8900000000050A03E6889100000000080A06EFBC8CE6889100000000050A03E4BC9A00000000050A03E5B0BD00000000050A03E58A9B00000000080A06E68F90E4BE9B00000000080A06E8AFA6E7BB8600000000050A03E5928C00000000080A06E6B7B1E585A500000000050A03E79A8400000000050A03E8A7A300000000050A03E7AD9400000000050A03E3808202000000027B7D