Router API Key 分组改为与账号同组的 taonier
Project CI / AI game creator shell Rust crates (push) Successful in 1m23s
Project CI / AI game creator shell Rust smoke (push) Successful in 1m56s
Project CI / Backend tests (push) Successful in 5m40s
Project CI / AI game creator shell Rust lane 2/2 (push) Successful in 8m43s
Project CI / Native shell tests (push) Successful in 7m21s
Project CI / Frontend tests (push) Successful in 2m40s
Project CI / AI game creator shell Rust lane 1/2 (push) Successful in 9m45s
Project CI / Repository checks (push) Successful in 1m55s
Project CI / AI game creator shell web tests (push) Successful in 1m25s

- LLM_ROUTER_TOKEN_GROUP 由 default 改为 taonier,Router 用户与它名下固定 Token / API Key 现在同属 taonier 分组
- 保留固定契约归一:创建后无条件 PUT 完整契约(group / unlimited_quota / expired_time),复用与登录恢复路径同样 PUT,存量 default 分组 Token 会在下次 provisioning、登录恢复或显式准备 Key 时纠正
- 同步创建路径与复用路径的注释,保留“分组缺模型会失败为 model_not_found”的排查提示
- 用例同步:2 个用例重命名为 taonier 语义,6 处 group 断言改为 taonier
- 文档同步:外部 OpenAPI 与 APIKey 接入方案写明 Token 与用户同组 taonier 及其前提;决策记录追加 2026-09-23 调整说明
- 验证:cargo test -p api-server --manifest-path server-rs/Cargo.toml --bin api-server external_api_keys(22 passed)、同命令 router 过滤(27 passed)、cargo fmt --all --check、npm run check:encoding、npm run check:doc-index、git diff --check
This commit is contained in:
kdletters
2026-09-23 20:49:47 +08:00
parent 1e0aeb95b0
commit 985df53a4e
3 changed files with 26 additions and 22 deletions
@@ -47,8 +47,9 @@ const EXTERNAL_API_KEY_SCOPES: [&str; 4] = [
/// New API token 标识。它只用于在每个 Router 用户账号内定位同一个 Token,
/// 不承载产品展示语义;Router 用户本身通过完整 owner id 的稳定短哈希区分。
const LLM_ROUTER_TOKEN_IDENTIFIER: &str = "agc_auto_generate";
/// Router 用户(账号)与它名下固定 Token / API Key 都归属同一分组 `taonier`。
const LLM_ROUTER_USER_GROUP: &str = "taonier";
const LLM_ROUTER_TOKEN_GROUP: &str = "default";
const LLM_ROUTER_TOKEN_GROUP: &str = "taonier";
const LLM_ROUTER_API_KEY_SCOPES: [&str; 1] = ["llm:responses"];
const LLM_ROUTER_SUBSCRIPTION_PLAN_ID: i64 = 1;
const LLM_ROUTER_SUBSCRIPTION_RENEWAL_THRESHOLD_SECONDS: i64 = 24 * 60 * 60;
@@ -1194,13 +1195,14 @@ async fn provision_router_account_via_new_api(
{
// Keep the fixed token contract authoritative even when the Router
// returns an incomplete token summary (for example without `group`).
// The user remains in `taonier`; only this API token must be in the
// `default` group with unlimited quota and no expiry.
// The user and its fixed API token both belong to `taonier`, with
// unlimited quota and no expiry.
// PUT the complete fixed token contract, not just the group. This
// repairs old tokens that were created with `taonier`, and also
// restores unlimited quota/permanent expiry if an operator changed
// either field. The request is cheap because this path only runs when
// provisioning or recovering an account, not for every LLM call.
// repairs tokens that were created with another group (for example the
// Router's `default`), and also restores unlimited quota/permanent
// expiry if an operator changed either field. The request is cheap
// because this path only runs when provisioning or recovering an
// account, not for every LLM call.
ensure_router_token_contract(
&client,
origin.as_str(),
@@ -1258,10 +1260,11 @@ async fn provision_router_account_via_new_api(
)
})?
};
// New API may accept the create request while applying the user's
// default group. Normalize the freshly-created token before issuing a
// key; otherwise a successful POST can still produce a token routed
// through `taonier` and later fail with `model_not_found`.
// New API may accept the create request while applying another group
// (for example its own `default`) to the token. Normalize the
// freshly-created token before issuing a key so the Key always runs in
// the same `taonier` group as its Router account; a token stuck in a
// group without the required models fails with `model_not_found`.
ensure_router_token_contract(
&client,
origin.as_str(),
@@ -2291,13 +2294,13 @@ mod tests {
}
#[test]
fn new_api_token_request_is_unlimited_and_bound_to_default_group() {
fn new_api_token_request_is_unlimited_and_bound_to_taonier_group() {
let payload = router_token_request();
assert_eq!(payload["name"], LLM_ROUTER_TOKEN_IDENTIFIER);
assert_eq!(payload["expired_time"], -1);
assert_eq!(payload["unlimited_quota"], true);
assert_eq!(payload["group"], "default");
assert_eq!(payload["group"], "taonier");
assert!(payload.get("idempotencyKey").is_none());
}
@@ -2363,14 +2366,14 @@ mod tests {
}
#[test]
fn existing_router_token_group_is_normalized_to_default() {
fn existing_router_token_group_is_normalized_to_taonier() {
let payload = router_token_update_request(77);
assert_eq!(payload["id"], 77);
assert_eq!(payload["name"], LLM_ROUTER_TOKEN_IDENTIFIER);
assert_eq!(payload["expired_time"], -1);
assert_eq!(payload["unlimited_quota"], true);
assert_eq!(payload["group"], "default");
assert_eq!(payload["group"], "taonier");
}
#[test]
@@ -2480,14 +2483,14 @@ mod tests {
serde_json::from_str(request_body(&requests[8])).expect("token json");
assert_eq!(token_payload["unlimited_quota"], true);
assert_eq!(token_payload["expired_time"], -1);
assert_eq!(token_payload["group"], "default");
assert_eq!(token_payload["group"], "taonier");
let token_update_payload: Value =
serde_json::from_str(request_body(&requests[9])).expect("token update json");
assert_eq!(token_update_payload["id"], 77);
assert_eq!(token_update_payload["unlimited_quota"], true);
assert_eq!(token_update_payload["expired_time"], -1);
assert_eq!(token_update_payload["group"], "default");
assert_eq!(token_update_payload["group"], "taonier");
assert!(
requests[7]
@@ -2569,7 +2572,7 @@ mod tests {
let token_update_payload: Value =
serde_json::from_str(request_body(&requests[5])).expect("token update json");
assert_eq!(token_update_payload["id"], 77);
assert_eq!(token_update_payload["group"], "default");
assert_eq!(token_update_payload["group"], "taonier");
assert!(
!requests
.iter()
@@ -2583,7 +2586,7 @@ mod tests {
}
#[tokio::test]
async fn active_router_key_repair_keeps_user_taonier_and_token_default() {
async fn active_router_key_repair_keeps_user_and_token_in_taonier_group() {
let owner_user_id = "owner-active-repair";
let username = router_username_for_owner(owner_user_id);
let password =
@@ -2655,7 +2658,7 @@ mod tests {
let token_payload: Value =
serde_json::from_str(request_body(&requests[4])).expect("token update json");
assert_eq!(token_payload["group"], "default");
assert_eq!(token_payload["group"], "taonier");
assert_eq!(token_payload["unlimited_quota"], true);
assert_eq!(token_payload["expired_time"], -1);