diff --git a/packages/cli/src/commands.ts b/packages/cli/src/commands.ts index 27422935b..cfd82b1da 100644 --- a/packages/cli/src/commands.ts +++ b/packages/cli/src/commands.ts @@ -85,6 +85,7 @@ import { workspaceList, quotaList, quotaUpdate, + quotaDelete, quotaHistory, quotaCheck, permissionList, @@ -246,6 +247,7 @@ export const commands: Record = { "workspace list": workspaceList, "quota list": quotaList, "quota update": quotaUpdate, + "quota delete": quotaDelete, "quota history": quotaHistory, "quota check": quotaCheck, "permission list": permissionList, diff --git a/packages/commands/src/commands/quota/delete.ts b/packages/commands/src/commands/quota/delete.ts new file mode 100644 index 000000000..1ae925510 --- /dev/null +++ b/packages/commands/src/commands/quota/delete.ts @@ -0,0 +1,71 @@ +import { defineCommand, detectOutputFormat, modelsLimitsPath } from "bailian-cli-core"; +import { emitResult, confirmDangerousAction } from "bailian-cli-runtime"; + +export default defineCommand({ + description: { + "en-US": "Clear all custom rate limits (QPM/TPM) for a model", + "zh-CN": "清除模型的所有自定义限流配置(QPM/TPM)", + }, + auth: "apiKey", + usageArgs: "--model [--yes]", + flags: { + model: { + type: "string", + valueHint: "", + description: { "en-US": "Model name (required)", "zh-CN": "模型名称(必填)" }, + required: true, + }, + yes: { + type: "switch", + description: { + "en-US": "Skip the confirmation prompt", + "zh-CN": "跳过确认提示", + }, + }, + }, + exampleArgs: ["--model qwen-plus", "--model qwen-plus --yes", "--model qwen-plus --output json"], + notes: [ + { + "en-US": + "Irreversible — the server-side OVERLAY is reset to defaults, so your custom QPM/TPM configuration is permanently removed.", + "zh-CN": + "该操作不可撤销——服务端 OVERLAY 会重置为默认值,你的自定义 QPM/TPM 配置将被永久删除。", + }, + { + "en-US": "Requires confirmation; pass --yes to skip the prompt in scripts.", + "zh-CN": "需要确认;脚本中可加 --yes 跳过交互提示。", + }, + ], + async run(ctx) { + const { settings, flags } = ctx; + const modelName = flags.model; + const format = detectOutputFormat(settings.output); + + const body = { models: [{ model: modelName, operation_type: "DELETE" }] }; + + if (settings.dryRun) { + emitResult( + { endpoint: ctx.client.url(modelsLimitsPath()), method: "POST", request: body }, + format, + ); + return; + } + + await confirmDangerousAction( + `Clear all custom rate limits for model ${modelName}.\nYour custom QPM/TPM configuration will be removed.`, + flags.yes ?? false, + ); + + const result = await ctx.client.requestJson<{ request_id?: string }>({ + path: modelsLimitsPath(), + method: "POST", + body, + }); + + if (format === "json") { + emitResult({ model: modelName, ...result }, format); + return; + } + process.stdout.write(`Rate limits cleared for "${modelName}".\n`); + }, +}); diff --git a/packages/commands/src/commands/quota/update.ts b/packages/commands/src/commands/quota/update.ts index 8b69688bc..5d7766e39 100644 --- a/packages/commands/src/commands/quota/update.ts +++ b/packages/commands/src/commands/quota/update.ts @@ -1,16 +1,16 @@ import { defineCommand, detectOutputFormat, modelsLimitsPath } from "bailian-cli-core"; -import { emitResult, confirmDangerousAction } from "bailian-cli-runtime"; +import { emitResult } from "bailian-cli-runtime"; import { formatNumber } from "../shared/format.ts"; const MINUTE_SECONDS = 60; export default defineCommand({ description: { - "en-US": "Update model rate limits (QPM/TPM), or clear them with --delete", - "zh-CN": "更新模型限流配置(QPM/TPM),或使用 --delete 清除配置", + "en-US": "Update model rate limits (QPM/TPM)", + "zh-CN": "更新模型限流配置(QPM/TPM)", }, auth: "apiKey", - usageArgs: "--model [--rpm ] [--tpm ] [--delete] [--yes]", + usageArgs: "--model [--rpm ] [--tpm ]", flags: { model: { type: "string", @@ -34,38 +34,18 @@ export default defineCommand({ "zh-CN": "每分钟最大 Token 数(TPM)", }, }, - delete: { - type: "switch", - description: { - "en-US": "Clear all custom rate limits for the model", - "zh-CN": "清除该模型的所有自定义限流配置", - }, - }, - yes: { - type: "switch", - description: { - "en-US": "Skip the confirmation prompt for --delete", - "zh-CN": "使用 --delete 时跳过确认提示", - }, - }, }, exampleArgs: [ "--model qwen-plus --rpm 60 --tpm 100000", "--model qwen3-max --tpm 500000", - "--model qwen-plus --delete", - "--model qwen-plus --delete --yes", "--model qwen-plus --rpm 60 --output json", ], notes: [ { "en-US": - "Fields you omit keep their current values (server-side OVERLAY merge); --delete clears all custom limits.", + 'Fields you omit keep their current values (server-side OVERLAY merge). Clear all custom limits with the "quota delete" command instead.', "zh-CN": - "未指定的字段将保留当前值(服务端 OVERLAY 合并);--delete 会清除所有自定义限流配置。", - }, - { - "en-US": "--delete requires confirmation; pass --yes to skip the prompt in scripts.", - "zh-CN": "--delete 需要确认;脚本中可加 --yes 跳过交互提示。", + "未指定的字段将保留当前值(服务端 OVERLAY 合并)。清除全部自定义限流配置请改用 “quota delete” 命令。", }, { "en-US": @@ -74,10 +54,8 @@ export default defineCommand({ }, ], validate: (flags) => { - if (flags.delete && (flags.rpm !== undefined || flags.tpm !== undefined)) - return "--delete cannot be combined with --rpm/--tpm."; - if (!flags.delete && flags.rpm === undefined && flags.tpm === undefined) - return "one of --rpm / --tpm / --delete is required."; + if (flags.rpm === undefined && flags.tpm === undefined) + return "one of --rpm / --tpm is required."; if (flags.rpm !== undefined && flags.rpm < 0) return "--rpm must be a non-negative number."; if (flags.tpm !== undefined && flags.tpm < 0) return "--tpm must be a non-negative number."; return undefined; @@ -88,17 +66,13 @@ export default defineCommand({ const format = detectOutputFormat(settings.output); const entry: Record = { model: modelName }; - if (flags.delete) { - entry.operation_type = "DELETE"; - } else { - if (flags.rpm !== undefined) { - entry.request_limit = flags.rpm; - entry.request_limit_period = MINUTE_SECONDS; - } - if (flags.tpm !== undefined) { - entry.usage_limit = flags.tpm; - entry.usage_limit_period = MINUTE_SECONDS; - } + if (flags.rpm !== undefined) { + entry.request_limit = flags.rpm; + entry.request_limit_period = MINUTE_SECONDS; + } + if (flags.tpm !== undefined) { + entry.usage_limit = flags.tpm; + entry.usage_limit_period = MINUTE_SECONDS; } const body = { models: [entry] }; @@ -110,13 +84,6 @@ export default defineCommand({ return; } - if (flags.delete) { - await confirmDangerousAction( - `Clear all custom rate limits for model ${modelName}.\nYour custom QPM/TPM configuration will be removed.`, - flags.yes ?? false, - ); - } - const result = await ctx.client.requestJson<{ request_id?: string }>({ path: modelsLimitsPath(), method: "POST", @@ -127,11 +94,6 @@ export default defineCommand({ emitResult({ model: modelName, ...result }, format); return; } - - if (flags.delete) { - process.stdout.write(`Rate limits cleared for "${modelName}".\n`); - return; - } const parts: string[] = []; if (flags.rpm !== undefined) parts.push(`QPM ${formatNumber(flags.rpm)}`); if (flags.tpm !== undefined) parts.push(`TPM ${formatNumber(flags.tpm)}`); diff --git a/packages/commands/src/index.ts b/packages/commands/src/index.ts index 79e311ebf..ba8dfb22a 100644 --- a/packages/commands/src/index.ts +++ b/packages/commands/src/index.ts @@ -88,6 +88,7 @@ export { default as modelList } from "./commands/model/list.ts"; export { default as workspaceList } from "./commands/workspace/list.ts"; export { default as quotaList } from "./commands/quota/list.ts"; export { default as quotaUpdate } from "./commands/quota/update.ts"; +export { default as quotaDelete } from "./commands/quota/delete.ts"; export { default as quotaHistory } from "./commands/quota/history.ts"; export { default as quotaCheck } from "./commands/quota/check.ts"; export { default as permissionList } from "./commands/permission/list.ts"; diff --git a/packages/commands/tests/e2e/quota.e2e.test.ts b/packages/commands/tests/e2e/quota.e2e.test.ts index c6e2d3859..fefc11f03 100644 --- a/packages/commands/tests/e2e/quota.e2e.test.ts +++ b/packages/commands/tests/e2e/quota.e2e.test.ts @@ -31,15 +31,20 @@ describe("e2e: quota", () => { expect(stderr).toContain("--model"); expect(stderr).toContain("--rpm"); expect(stderr).toContain("--tpm"); - expect(stderr).toContain("--delete"); + }); + + test("quota delete --help 正常退出", async () => { + const { stderr, exitCode } = await runCommandHelp(QUOTA_ROUTES, ["quota", "delete", "--help"]); + expect(exitCode, stderr).toBe(0); + expect(stderr).toContain("--model"); expect(stderr).toContain("--yes"); + expect(stderr).toContain("bl quota delete --model qwen-plus"); }); test("quota request 作为 quota update 的兼容别名可用", async () => { const { stderr, exitCode } = await runCommandHelp(QUOTA_ROUTES, ["quota", "request", "--help"]); expect(exitCode, stderr).toBe(0); expect(stderr).toContain("--rpm"); - expect(stderr).toContain("--delete"); }); test("quota history --help 正常退出", async () => { @@ -68,7 +73,7 @@ describe("e2e: quota", () => { expect(stderr).toContain("at least 1 minute"); }); - test("quota update 缺少 --rpm/--tpm/--delete 报用法错误", async () => { + test("quota update 缺少 --rpm/--tpm 报用法错误", async () => { const { stderr, exitCode } = await runCommandE2e(QUOTA_ROUTES, [ "quota", "update", @@ -76,28 +81,33 @@ describe("e2e: quota", () => { "qwen-plus", ]); expect(exitCode).toBe(2); - expect(stderr).toContain("one of --rpm / --tpm / --delete"); + expect(stderr).toContain("one of --rpm / --tpm"); }); - test("quota update --delete 与 --rpm 互斥", async () => { + test("quota update 不再接受 --delete", async () => { const { stderr, exitCode } = await runCommandE2e(QUOTA_ROUTES, [ "quota", "update", "--model", "qwen-plus", "--delete", - "--rpm", - "60", ]); expect(exitCode).toBe(2); - expect(stderr).toContain("cannot be combined"); + expect(stderr).toContain("Unknown flag"); }); - test("quota update --delete 非 TTY 无 --yes 报 USAGE (2)", async () => { + test("quota delete 缺少 --model 报用法错误", async () => { + // 裸 `quota delete`(无任何 flag)会渲染 help 并正常退出,需带 flag 触发必填校验 + const { stderr, exitCode } = await runCommandE2e(QUOTA_ROUTES, ["quota", "delete", "--yes"]); + expect(exitCode).toBe(2); + expect(stderr).toContain("Missing required flag: --model"); + }); + + test("quota delete 非 TTY 无 --yes 报 USAGE (2)", async () => { // 注入假 key 让 apiKey 鉴权通过;确认门在发任何网络请求前触发 const { stderr, exitCode } = await runCommandE2e( QUOTA_ROUTES, - ["quota", "update", "--model", "qwen-plus", "--delete"], + ["quota", "delete", "--model", "qwen-plus"], { DASHSCOPE_API_KEY: "sk-e2e-quota-delete" }, ); expect(exitCode).toBe(2); @@ -196,13 +206,12 @@ describe("e2e: quota", () => { expect(entry?.usage_limit_period).toBe(60); }); - test("quota update --delete --dry-run 输出 DELETE 操作", async () => { + test("quota delete --dry-run 输出 DELETE 操作", async () => { const { stdout, stderr, exitCode } = await runCommandE2e(QUOTA_ROUTES, [ "quota", - "update", + "delete", "--model", "qwen-plus", - "--delete", "--dry-run", "--output", "json", @@ -211,6 +220,7 @@ describe("e2e: quota", () => { const data = parseStdoutJson<{ request?: { models?: { model?: string; operation_type?: string }[] }; }>(stdout); + expect(data.request?.models?.[0]?.model).toBe("qwen-plus"); expect(data.request?.models?.[0]?.operation_type).toBe("DELETE"); }); @@ -285,8 +295,8 @@ describe("e2e: quota", () => { }); }); -// 真实调用 GET /api/v1/models/limits。quota update 只测 --dry-run——live POST -// 会真实改写账号限流,不做 e2e。 +// 真实调用 GET /api/v1/models/limits。quota update / quota delete 只测 +// --dry-run——live POST 会真实改写账号限流,不做 e2e。 describe.skipIf(!isDashScopeE2EReady())("e2e: quota(DashScope)", () => { test("quota list 文本输出正常退出", async () => { const { stderr, exitCode } = await runCommandE2e(QUOTA_ROUTES, [ diff --git a/packages/commands/tests/e2e/topic-routes.ts b/packages/commands/tests/e2e/topic-routes.ts index 2c3167746..093a55597 100644 --- a/packages/commands/tests/e2e/topic-routes.ts +++ b/packages/commands/tests/e2e/topic-routes.ts @@ -103,6 +103,7 @@ export const ADVISOR_ROUTES: E2eRouteExports = { export const QUOTA_ROUTES: E2eRouteExports = { "quota list": "quotaList", "quota update": "quotaUpdate", + "quota delete": "quotaDelete", // Backward-compatible alias of "quota update". "quota request": "quotaUpdate", "quota history": "quotaHistory", diff --git a/skills/bailian-cli/reference/index.md b/skills/bailian-cli/reference/index.md index 9b79c3f59..1a0c94b41 100644 --- a/skills/bailian-cli/reference/index.md +++ b/skills/bailian-cli/reference/index.md @@ -81,9 +81,10 @@ Use this index for the skill-scoped quick index and global flags. | `bl plugin list` | No Auth | List installed Command Packs and their load status | [plugin.md](plugin.md) | | `bl plugin remove` | No Auth | Remove an installed Command Pack | [plugin.md](plugin.md) | | `bl quota check` | Console | Check current usage against rate limits | [quota.md](quota.md) | +| `bl quota delete` | API Key | Clear all custom rate limits (QPM/TPM) for a model | [quota.md](quota.md) | | `bl quota history` | Console | View quota change history | [quota.md](quota.md) | | `bl quota list` | API Key | View model rate limits (QPM/TPM, account and workspace level) | [quota.md](quota.md) | -| `bl quota update` | API Key | Update model rate limits (QPM/TPM), or clear them with --delete | [quota.md](quota.md) | +| `bl quota update` | API Key | Update model rate limits (QPM/TPM) | [quota.md](quota.md) | | `bl search web` | API Key | Search the web using DashScope MCP WebSearch service | [search.md](search.md) | | `bl skill add` | No Auth | Install skills from the Bailian skill registry into local agents | [skill.md](skill.md) | | `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. | `permission` | `grant`, `list`, `revoke` | [permission.md](permission.md) | | `pipeline` | `run`, `validate` | [pipeline.md](pipeline.md) | | `plugin` | `install`, `link`, `list`, `remove` | [plugin.md](plugin.md) | -| `quota` | `check`, `history`, `list`, `update` | [quota.md](quota.md) | +| `quota` | `check`, `delete`, `history`, `list`, `update` | [quota.md](quota.md) | | `search` | `web` | [search.md](search.md) | | `skill` | `add`, `init`, `list`, `remove`, `update` | [skill.md](skill.md) | | `text` | `chat` | [text.md](text.md) | diff --git a/skills/bailian-cli/reference/quota.md b/skills/bailian-cli/reference/quota.md index 97b51231c..b66cb584f 100644 --- a/skills/bailian-cli/reference/quota.md +++ b/skills/bailian-cli/reference/quota.md @@ -7,12 +7,13 @@ Index: [index.md](index.md) ## Commands in this group -| Command | Authentication | Description | -| ------------------ | -------------- | --------------------------------------------------------------- | -| `bl quota check` | Console | Check current usage against rate limits | -| `bl quota history` | Console | View quota change history | -| `bl quota list` | API Key | View model rate limits (QPM/TPM, account and workspace level) | -| `bl quota update` | API Key | Update model rate limits (QPM/TPM), or clear them with --delete | +| Command | Authentication | Description | +| ------------------ | -------------- | ------------------------------------------------------------- | +| `bl quota check` | Console | Check current usage against rate limits | +| `bl quota delete` | API Key | Clear all custom rate limits (QPM/TPM) for a model | +| `bl quota history` | Console | View quota change history | +| `bl quota list` | API Key | View model rate limits (QPM/TPM, account and workspace level) | +| `bl quota update` | API Key | Update model rate limits (QPM/TPM) | ## Command details @@ -58,6 +59,43 @@ bl quota check --model qwen3.6-plus,qwen-turbo bl quota check --output json ``` +### `bl quota delete` + +| Field | Value | +| ------------------ | -------------------------------------------------- | +| **Name** | `quota delete` | +| **Description** | Clear all custom rate limits (QPM/TPM) for a model | +| **Authentication** | API Key | +| **Usage** | `bl quota delete --model [--yes]` | + +#### Flags + +| Flag | Type | Required | Description | +| ------------------ | ------ | -------- | ---------------------------- | +| `--model ` | string | yes | Model name (required) | +| `--yes` | switch | no | Skip the confirmation prompt | +| `--api-key ` | string | no | API key | +| `--base-url ` | string | no | API base URL | + +#### Notes + +- Irreversible — the server-side OVERLAY is reset to defaults, so your custom QPM/TPM configuration is permanently removed. +- Requires confirmation; pass --yes to skip the prompt in scripts. + +#### Examples + +```bash +bl quota delete --model qwen-plus +``` + +```bash +bl quota delete --model qwen-plus --yes +``` + +```bash +bl quota delete --model qwen-plus --output json +``` + ### `bl quota history` | Field | Value | @@ -149,29 +187,26 @@ bl quota list --output json ### `bl quota update` -| Field | Value | -| ------------------ | ---------------------------------------------------------------------------- | -| **Name** | `quota update` | -| **Description** | Update model rate limits (QPM/TPM), or clear them with --delete | -| **Authentication** | API Key | -| **Usage** | `bl quota update --model [--rpm ] [--tpm ] [--delete] [--yes]` | +| Field | Value | +| ------------------ | --------------------------------------------------------- | +| **Name** | `quota update` | +| **Description** | Update model rate limits (QPM/TPM) | +| **Authentication** | API Key | +| **Usage** | `bl quota update --model [--rpm ] [--tpm ]` | #### Flags -| Flag | Type | Required | Description | -| ------------------ | ------ | -------- | ------------------------------------------ | -| `--model ` | string | yes | Model name (required) | -| `--rpm ` | number | no | Max requests per minute (QPM) | -| `--tpm ` | number | no | Max tokens per minute (TPM) | -| `--delete` | switch | no | Clear all custom rate limits for the model | -| `--yes` | switch | no | Skip the confirmation prompt for --delete | -| `--api-key ` | string | no | API key | -| `--base-url ` | string | no | API base URL | +| Flag | Type | Required | Description | +| ------------------ | ------ | -------- | ----------------------------- | +| `--model ` | string | yes | Model name (required) | +| `--rpm ` | number | no | Max requests per minute (QPM) | +| `--tpm ` | number | no | Max tokens per minute (TPM) | +| `--api-key ` | string | no | API key | +| `--base-url ` | string | no | API base URL | #### Notes -- Fields you omit keep their current values (server-side OVERLAY merge); --delete clears all custom limits. -- --delete requires confirmation; pass --yes to skip the prompt in scripts. +- Fields you omit keep their current values (server-side OVERLAY merge). Clear all custom limits with the "quota delete" command instead. - Setting TPM without an existing QPM limit is rejected server-side — pass --rpm first or together. #### Examples @@ -184,14 +219,6 @@ bl quota update --model qwen-plus --rpm 60 --tpm 100000 bl quota update --model qwen3-max --tpm 500000 ``` -```bash -bl quota update --model qwen-plus --delete -``` - -```bash -bl quota update --model qwen-plus --delete --yes -``` - ```bash bl quota update --model qwen-plus --rpm 60 --output json ```