Skip to content

Commit 2d2ec1c

Browse files
committed
Merge remote-tracking branch 'origin/feat/quota-delete-split' into feat/runtime-risk-confirmation
2 parents 719c0b6 + fd96fd6 commit 2d2ec1c

8 files changed

Lines changed: 176 additions & 101 deletions

File tree

packages/cli/src/commands.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ import {
8585
workspaceList,
8686
quotaList,
8787
quotaUpdate,
88+
quotaDelete,
8889
quotaHistory,
8990
quotaCheck,
9091
permissionList,
@@ -246,6 +247,7 @@ export const commands: Record<string, AnyCommand> = {
246247
"workspace list": workspaceList,
247248
"quota list": quotaList,
248249
"quota update": quotaUpdate,
250+
"quota delete": quotaDelete,
249251
"quota history": quotaHistory,
250252
"quota check": quotaCheck,
251253
"permission list": permissionList,
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { defineCommand, detectOutputFormat, modelsLimitsPath } from "bailian-cli-core";
2+
import { emitResult, confirmDangerousAction } from "bailian-cli-runtime";
3+
4+
export default defineCommand({
5+
description: {
6+
"en-US": "Clear all custom rate limits (QPM/TPM) for a model",
7+
"zh-CN": "清除模型的所有自定义限流配置(QPM/TPM)",
8+
},
9+
auth: "apiKey",
10+
usageArgs: "--model <model> [--yes]",
11+
flags: {
12+
model: {
13+
type: "string",
14+
valueHint: "<model>",
15+
description: { "en-US": "Model name (required)", "zh-CN": "模型名称(必填)" },
16+
required: true,
17+
},
18+
yes: {
19+
type: "switch",
20+
description: {
21+
"en-US": "Skip the confirmation prompt",
22+
"zh-CN": "跳过确认提示",
23+
},
24+
},
25+
},
26+
exampleArgs: ["--model qwen-plus", "--model qwen-plus --yes", "--model qwen-plus --output json"],
27+
notes: [
28+
{
29+
"en-US":
30+
"Irreversible — the server-side OVERLAY is reset to defaults, so your custom QPM/TPM configuration is permanently removed.",
31+
"zh-CN":
32+
"该操作不可撤销——服务端 OVERLAY 会重置为默认值,你的自定义 QPM/TPM 配置将被永久删除。",
33+
},
34+
{
35+
"en-US": "Requires confirmation; pass --yes to skip the prompt in scripts.",
36+
"zh-CN": "需要确认;脚本中可加 --yes 跳过交互提示。",
37+
},
38+
],
39+
async run(ctx) {
40+
const { settings, flags } = ctx;
41+
const modelName = flags.model;
42+
const format = detectOutputFormat(settings.output);
43+
44+
const body = { models: [{ model: modelName, operation_type: "DELETE" }] };
45+
46+
if (settings.dryRun) {
47+
emitResult(
48+
{ endpoint: ctx.client.url(modelsLimitsPath()), method: "POST", request: body },
49+
format,
50+
);
51+
return;
52+
}
53+
54+
await confirmDangerousAction(
55+
`Clear all custom rate limits for model ${modelName}.\nYour custom QPM/TPM configuration will be removed.`,
56+
flags.yes ?? false,
57+
);
58+
59+
const result = await ctx.client.requestJson<{ request_id?: string }>({
60+
path: modelsLimitsPath(),
61+
method: "POST",
62+
body,
63+
});
64+
65+
if (format === "json") {
66+
emitResult({ model: modelName, ...result }, format);
67+
return;
68+
}
69+
process.stdout.write(`Rate limits cleared for "${modelName}".\n`);
70+
},
71+
});

packages/commands/src/commands/quota/update.ts

Lines changed: 15 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
import { defineCommand, detectOutputFormat, modelsLimitsPath } from "bailian-cli-core";
2-
import { emitResult, confirmDangerousAction } from "bailian-cli-runtime";
2+
import { emitResult } from "bailian-cli-runtime";
33
import { formatNumber } from "../shared/format.ts";
44

55
const MINUTE_SECONDS = 60;
66

77
export default defineCommand({
88
description: {
9-
"en-US": "Update model rate limits (QPM/TPM), or clear them with --delete",
10-
"zh-CN": "更新模型限流配置(QPM/TPM),或使用 --delete 清除配置",
9+
"en-US": "Update model rate limits (QPM/TPM)",
10+
"zh-CN": "更新模型限流配置(QPM/TPM)",
1111
},
1212
auth: "apiKey",
13-
usageArgs: "--model <model> [--rpm <n>] [--tpm <n>] [--delete] [--yes]",
13+
usageArgs: "--model <model> [--rpm <n>] [--tpm <n>]",
1414
flags: {
1515
model: {
1616
type: "string",
@@ -34,38 +34,18 @@ export default defineCommand({
3434
"zh-CN": "每分钟最大 Token 数(TPM)",
3535
},
3636
},
37-
delete: {
38-
type: "switch",
39-
description: {
40-
"en-US": "Clear all custom rate limits for the model",
41-
"zh-CN": "清除该模型的所有自定义限流配置",
42-
},
43-
},
44-
yes: {
45-
type: "switch",
46-
description: {
47-
"en-US": "Skip the confirmation prompt for --delete",
48-
"zh-CN": "使用 --delete 时跳过确认提示",
49-
},
50-
},
5137
},
5238
exampleArgs: [
5339
"--model qwen-plus --rpm 60 --tpm 100000",
5440
"--model qwen3-max --tpm 500000",
55-
"--model qwen-plus --delete",
56-
"--model qwen-plus --delete --yes",
5741
"--model qwen-plus --rpm 60 --output json",
5842
],
5943
notes: [
6044
{
6145
"en-US":
62-
"Fields you omit keep their current values (server-side OVERLAY merge); --delete clears all custom limits.",
46+
'Fields you omit keep their current values (server-side OVERLAY merge). Clear all custom limits with the "quota delete" command instead.',
6347
"zh-CN":
64-
"未指定的字段将保留当前值(服务端 OVERLAY 合并);--delete 会清除所有自定义限流配置。",
65-
},
66-
{
67-
"en-US": "--delete requires confirmation; pass --yes to skip the prompt in scripts.",
68-
"zh-CN": "--delete 需要确认;脚本中可加 --yes 跳过交互提示。",
48+
"未指定的字段将保留当前值(服务端 OVERLAY 合并)。清除全部自定义限流配置请改用 “quota delete” 命令。",
6949
},
7050
{
7151
"en-US":
@@ -74,10 +54,8 @@ export default defineCommand({
7454
},
7555
],
7656
validate: (flags) => {
77-
if (flags.delete && (flags.rpm !== undefined || flags.tpm !== undefined))
78-
return "--delete cannot be combined with --rpm/--tpm.";
79-
if (!flags.delete && flags.rpm === undefined && flags.tpm === undefined)
80-
return "one of --rpm / --tpm / --delete is required.";
57+
if (flags.rpm === undefined && flags.tpm === undefined)
58+
return "one of --rpm / --tpm is required.";
8159
if (flags.rpm !== undefined && flags.rpm < 0) return "--rpm must be a non-negative number.";
8260
if (flags.tpm !== undefined && flags.tpm < 0) return "--tpm must be a non-negative number.";
8361
return undefined;
@@ -88,17 +66,13 @@ export default defineCommand({
8866
const format = detectOutputFormat(settings.output);
8967

9068
const entry: Record<string, unknown> = { model: modelName };
91-
if (flags.delete) {
92-
entry.operation_type = "DELETE";
93-
} else {
94-
if (flags.rpm !== undefined) {
95-
entry.request_limit = flags.rpm;
96-
entry.request_limit_period = MINUTE_SECONDS;
97-
}
98-
if (flags.tpm !== undefined) {
99-
entry.usage_limit = flags.tpm;
100-
entry.usage_limit_period = MINUTE_SECONDS;
101-
}
69+
if (flags.rpm !== undefined) {
70+
entry.request_limit = flags.rpm;
71+
entry.request_limit_period = MINUTE_SECONDS;
72+
}
73+
if (flags.tpm !== undefined) {
74+
entry.usage_limit = flags.tpm;
75+
entry.usage_limit_period = MINUTE_SECONDS;
10276
}
10377
const body = { models: [entry] };
10478

@@ -110,13 +84,6 @@ export default defineCommand({
11084
return;
11185
}
11286

113-
if (flags.delete) {
114-
await confirmDangerousAction(
115-
`Clear all custom rate limits for model ${modelName}.\nYour custom QPM/TPM configuration will be removed.`,
116-
flags.yes ?? false,
117-
);
118-
}
119-
12087
const result = await ctx.client.requestJson<{ request_id?: string }>({
12188
path: modelsLimitsPath(),
12289
method: "POST",
@@ -127,11 +94,6 @@ export default defineCommand({
12794
emitResult({ model: modelName, ...result }, format);
12895
return;
12996
}
130-
131-
if (flags.delete) {
132-
process.stdout.write(`Rate limits cleared for "${modelName}".\n`);
133-
return;
134-
}
13597
const parts: string[] = [];
13698
if (flags.rpm !== undefined) parts.push(`QPM ${formatNumber(flags.rpm)}`);
13799
if (flags.tpm !== undefined) parts.push(`TPM ${formatNumber(flags.tpm)}`);

packages/commands/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ export { default as modelList } from "./commands/model/list.ts";
8888
export { default as workspaceList } from "./commands/workspace/list.ts";
8989
export { default as quotaList } from "./commands/quota/list.ts";
9090
export { default as quotaUpdate } from "./commands/quota/update.ts";
91+
export { default as quotaDelete } from "./commands/quota/delete.ts";
9192
export { default as quotaHistory } from "./commands/quota/history.ts";
9293
export { default as quotaCheck } from "./commands/quota/check.ts";
9394
export { default as permissionList } from "./commands/permission/list.ts";

packages/commands/tests/e2e/quota.e2e.test.ts

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,20 @@ describe("e2e: quota", () => {
3131
expect(stderr).toContain("--model");
3232
expect(stderr).toContain("--rpm");
3333
expect(stderr).toContain("--tpm");
34-
expect(stderr).toContain("--delete");
34+
});
35+
36+
test("quota delete --help 正常退出", async () => {
37+
const { stderr, exitCode } = await runCommandHelp(QUOTA_ROUTES, ["quota", "delete", "--help"]);
38+
expect(exitCode, stderr).toBe(0);
39+
expect(stderr).toContain("--model");
3540
expect(stderr).toContain("--yes");
41+
expect(stderr).toContain("bl quota delete --model qwen-plus");
3642
});
3743

3844
test("quota request 作为 quota update 的兼容别名可用", async () => {
3945
const { stderr, exitCode } = await runCommandHelp(QUOTA_ROUTES, ["quota", "request", "--help"]);
4046
expect(exitCode, stderr).toBe(0);
4147
expect(stderr).toContain("--rpm");
42-
expect(stderr).toContain("--delete");
4348
});
4449

4550
test("quota history --help 正常退出", async () => {
@@ -68,36 +73,41 @@ describe("e2e: quota", () => {
6873
expect(stderr).toContain("at least 1 minute");
6974
});
7075

71-
test("quota update 缺少 --rpm/--tpm/--delete 报用法错误", async () => {
76+
test("quota update 缺少 --rpm/--tpm 报用法错误", async () => {
7277
const { stderr, exitCode } = await runCommandE2e(QUOTA_ROUTES, [
7378
"quota",
7479
"update",
7580
"--model",
7681
"qwen-plus",
7782
]);
7883
expect(exitCode).toBe(2);
79-
expect(stderr).toContain("one of --rpm / --tpm / --delete");
84+
expect(stderr).toContain("one of --rpm / --tpm");
8085
});
8186

82-
test("quota update --delete 与 --rpm 互斥", async () => {
87+
test("quota update 不再接受 --delete", async () => {
8388
const { stderr, exitCode } = await runCommandE2e(QUOTA_ROUTES, [
8489
"quota",
8590
"update",
8691
"--model",
8792
"qwen-plus",
8893
"--delete",
89-
"--rpm",
90-
"60",
9194
]);
9295
expect(exitCode).toBe(2);
93-
expect(stderr).toContain("cannot be combined");
96+
expect(stderr).toContain("Unknown flag");
9497
});
9598

96-
test("quota update --delete 非 TTY 无 --yes 报 USAGE (2)", async () => {
99+
test("quota delete 缺少 --model 报用法错误", async () => {
100+
// 裸 `quota delete`(无任何 flag)会渲染 help 并正常退出,需带 flag 触发必填校验
101+
const { stderr, exitCode } = await runCommandE2e(QUOTA_ROUTES, ["quota", "delete", "--yes"]);
102+
expect(exitCode).toBe(2);
103+
expect(stderr).toContain("Missing required flag: --model");
104+
});
105+
106+
test("quota delete 非 TTY 无 --yes 报 USAGE (2)", async () => {
97107
// 注入假 key 让 apiKey 鉴权通过;确认门在发任何网络请求前触发
98108
const { stderr, exitCode } = await runCommandE2e(
99109
QUOTA_ROUTES,
100-
["quota", "update", "--model", "qwen-plus", "--delete"],
110+
["quota", "delete", "--model", "qwen-plus"],
101111
{ DASHSCOPE_API_KEY: "sk-e2e-quota-delete" },
102112
);
103113
expect(exitCode).toBe(2);
@@ -196,13 +206,12 @@ describe("e2e: quota", () => {
196206
expect(entry?.usage_limit_period).toBe(60);
197207
});
198208

199-
test("quota update --delete --dry-run 输出 DELETE 操作", async () => {
209+
test("quota delete --dry-run 输出 DELETE 操作", async () => {
200210
const { stdout, stderr, exitCode } = await runCommandE2e(QUOTA_ROUTES, [
201211
"quota",
202-
"update",
212+
"delete",
203213
"--model",
204214
"qwen-plus",
205-
"--delete",
206215
"--dry-run",
207216
"--output",
208217
"json",
@@ -211,6 +220,7 @@ describe("e2e: quota", () => {
211220
const data = parseStdoutJson<{
212221
request?: { models?: { model?: string; operation_type?: string }[] };
213222
}>(stdout);
223+
expect(data.request?.models?.[0]?.model).toBe("qwen-plus");
214224
expect(data.request?.models?.[0]?.operation_type).toBe("DELETE");
215225
});
216226

@@ -285,8 +295,8 @@ describe("e2e: quota", () => {
285295
});
286296
});
287297

288-
// 真实调用 GET /api/v1/models/limits。quota update 只测 --dry-run——live POST
289-
// 会真实改写账号限流,不做 e2e。
298+
// 真实调用 GET /api/v1/models/limits。quota update / quota delete 只测
299+
// --dry-run——live POST 会真实改写账号限流,不做 e2e。
290300
describe.skipIf(!isDashScopeE2EReady())("e2e: quota(DashScope)", () => {
291301
test("quota list 文本输出正常退出", async () => {
292302
const { stderr, exitCode } = await runCommandE2e(QUOTA_ROUTES, [

packages/commands/tests/e2e/topic-routes.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ export const ADVISOR_ROUTES: E2eRouteExports = {
103103
export const QUOTA_ROUTES: E2eRouteExports = {
104104
"quota list": "quotaList",
105105
"quota update": "quotaUpdate",
106+
"quota delete": "quotaDelete",
106107
// Backward-compatible alias of "quota update".
107108
"quota request": "quotaUpdate",
108109
"quota history": "quotaHistory",

skills/bailian-cli/reference/index.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,10 @@ Use this index for the skill-scoped quick index and global flags.
8181
| `bl plugin list` | No Auth | List installed Command Packs and their load status | [plugin.md](plugin.md) |
8282
| `bl plugin remove` | No Auth | Remove an installed Command Pack | [plugin.md](plugin.md) |
8383
| `bl quota check` | Console | Check current usage against rate limits | [quota.md](quota.md) |
84+
| `bl quota delete` | API Key | Clear all custom rate limits (QPM/TPM) for a model | [quota.md](quota.md) |
8485
| `bl quota history` | Console | View quota change history | [quota.md](quota.md) |
8586
| `bl quota list` | API Key | View model rate limits (QPM/TPM, account and workspace level) | [quota.md](quota.md) |
86-
| `bl quota update` | API Key | Update model rate limits (QPM/TPM), or clear them with --delete | [quota.md](quota.md) |
87+
| `bl quota update` | API Key | Update model rate limits (QPM/TPM) | [quota.md](quota.md) |
8788
| `bl search web` | API Key | Search the web using DashScope MCP WebSearch service | [search.md](search.md) |
8889
| `bl skill add` | No Auth | Install skills from the Bailian skill registry into local agents | [skill.md](skill.md) |
8990
| `bl skill init` | No Auth | Install all bailian-\* skills (one-shot bootstrap for new environments) | [skill.md](skill.md) |
@@ -122,7 +123,7 @@ Use this index for the skill-scoped quick index and global flags.
122123
| `permission` | `grant`, `list`, `revoke` | [permission.md](permission.md) |
123124
| `pipeline` | `run`, `validate` | [pipeline.md](pipeline.md) |
124125
| `plugin` | `install`, `link`, `list`, `remove` | [plugin.md](plugin.md) |
125-
| `quota` | `check`, `history`, `list`, `update` | [quota.md](quota.md) |
126+
| `quota` | `check`, `delete`, `history`, `list`, `update` | [quota.md](quota.md) |
126127
| `search` | `web` | [search.md](search.md) |
127128
| `skill` | `add`, `init`, `list`, `remove`, `update` | [skill.md](skill.md) |
128129
| `text` | `chat` | [text.md](text.md) |

0 commit comments

Comments
 (0)