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
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:
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user