diff --git a/packages/cli/src/commands/quota/check.ts b/packages/cli/src/commands/quota/check.ts index 5c52fa8..7a17509 100644 --- a/packages/cli/src/commands/quota/check.ts +++ b/packages/cli/src/commands/quota/check.ts @@ -64,9 +64,9 @@ function formatRatio(usage: number, limit: number): string { function getStatus(usage: number, limit: number): string { if (limit <= 0) return "-"; const pct = (usage / limit) * 100; - if (pct >= 100) return "已限流"; - if (pct >= 80) return "接近限流"; - return "正常"; + if (pct >= 100) return "Throttled"; + if (pct >= 80) return "Near Limit"; + return "Normal"; } function getNestedRecord( @@ -193,7 +193,6 @@ function printTable(rows: CheckRow[], noColor: boolean): void { const yellow = noColor ? (t: string) => t : (t: string) => `\x1b[33m${t}\x1b[0m`; const red = noColor ? (t: string) => t : (t: string) => `\x1b[31m${t}\x1b[0m`; - const headersCn = ["模型", "RPM 用量/限额", "TPM 用量/限额", "状态"]; const headersEn = ["Model", "RPM Usage/Limit", "TPM Usage/Limit", "Status"]; const tableRows = rows.map((r) => { @@ -215,36 +214,30 @@ function printTable(rows: CheckRow[], noColor: boolean): void { return; } - const widths = headersCn.map((label, col) => - Math.max( - displayWidth(label), - displayWidth(headersEn[col]), - ...tableRows.map((r) => displayWidth(r.cells[col])), - ), + const widths = headersEn.map((label, col) => + Math.max(displayWidth(label), ...tableRows.map((r) => displayWidth(r.cells[col]))), ); - const cnLine = headersCn.map((label, col) => bold(padEnd(label, widths[col]))).join(" "); - const enLine = headersEn.map((label, col) => dim(padEnd(label, widths[col]))).join(" "); + const headerLine = headersEn.map((label, col) => bold(padEnd(label, widths[col]))).join(" "); const separator = widths.map((w) => dim("─".repeat(w))).join("──"); - process.stdout.write(cnLine + "\n"); - process.stdout.write(enLine + "\n"); + process.stdout.write(headerLine + "\n"); process.stdout.write(separator + "\n"); const statusCol = 3; for (const r of tableRows) { const cells = r.cells.map((cell, col) => { if (col === statusCol) { - if (cell === "已限流") return red(padEnd(cell, widths[col])); - if (cell === "接近限流") return yellow(padEnd(cell, widths[col])); - if (cell === "正常") return green(padEnd(cell, widths[col])); + if (cell === "Throttled") return red(padEnd(cell, widths[col])); + if (cell === "Near Limit") return yellow(padEnd(cell, widths[col])); + if (cell === "Normal") return green(padEnd(cell, widths[col])); } return padEnd(cell, widths[col]); }); process.stdout.write(cells.join(" ") + "\n"); } - process.stdout.write(dim(`\n共 ${rows.length} 个模型 (Total: ${rows.length})`) + "\n"); + process.stdout.write(dim(`\nTotal: ${rows.length} models`) + "\n"); } export default defineCommand({ diff --git a/packages/cli/src/commands/quota/history.ts b/packages/cli/src/commands/quota/history.ts index 99dd510..a5f2081 100644 --- a/packages/cli/src/commands/quota/history.ts +++ b/packages/cli/src/commands/quota/history.ts @@ -65,7 +65,6 @@ function printTable(records: LimitApplicationItem[], noColor: boolean, total: nu const bold = noColor ? (t: string) => t : (t: string) => `\x1b[1m${t}\x1b[0m`; const dim = noColor ? (t: string) => t : (t: string) => `\x1b[2m${t}\x1b[0m`; - const headersCn = ["模型", "Token 账号限流", "申请时间"]; const headersEn = ["Model", "Token Limit", "Applied At"]; const rows = records.map((r) => [ @@ -74,27 +73,21 @@ function printTable(records: LimitApplicationItem[], noColor: boolean, total: nu formatDateTime(r.gmtCreate), ]); - const widths = headersCn.map((label, col) => - Math.max( - displayWidth(label), - displayWidth(headersEn[col]), - ...rows.map((row) => displayWidth(row[col])), - ), + const widths = headersEn.map((label, col) => + Math.max(displayWidth(label), ...rows.map((row) => displayWidth(row[col]))), ); - const cnLine = headersCn.map((label, col) => bold(padEnd(label, widths[col]))).join(" "); - const enLine = headersEn.map((label, col) => dim(padEnd(label, widths[col]))).join(" "); + const headerLine = headersEn.map((label, col) => bold(padEnd(label, widths[col]))).join(" "); const separator = widths.map((w) => dim("─".repeat(w))).join("──"); - process.stdout.write(cnLine + "\n"); - process.stdout.write(enLine + "\n"); + process.stdout.write(headerLine + "\n"); process.stdout.write(separator + "\n"); for (const row of rows) { process.stdout.write(row.map((cell, col) => padEnd(cell, widths[col])).join(" ") + "\n"); } - process.stdout.write(dim(`\n共 ${total} 条记录 (Total: ${total})`) + "\n"); + process.stdout.write(dim(`\nTotal: ${total} records`) + "\n"); } export default defineCommand({ @@ -161,11 +154,6 @@ export default defineCommand({ throw err; } - if (format === "json") { - emitResult(result, format); - return; - } - const resp = extractResponseData(result as Record); let records = (resp.records as LimitApplicationItem[]) ?? []; const total = (resp.items as number) ?? records.length; @@ -174,6 +162,16 @@ export default defineCommand({ records = records.filter((r) => r.deployedModel === modelFilter); } + if (format === "json") { + const items = records.map((r) => ({ + model: r.deployedModel, + tokenLimit: r.usageLimit, + appliedAt: formatDateTime(r.gmtCreate), + })); + emitResult({ records: items, total: modelFilter ? records.length : total }, format); + return; + } + if (records.length === 0) { process.stdout.write("No quota change history found.\n"); return; diff --git a/packages/cli/src/commands/quota/list.ts b/packages/cli/src/commands/quota/list.ts index 3002757..9b2054d 100644 --- a/packages/cli/src/commands/quota/list.ts +++ b/packages/cli/src/commands/quota/list.ts @@ -108,7 +108,6 @@ function printTable(models: ModelWithQpm[], noColor: boolean): void { const bold = noColor ? (t: string) => t : (t: string) => `\x1b[1m${t}\x1b[0m`; const dim = noColor ? (t: string) => t : (t: string) => `\x1b[2m${t}\x1b[0m`; - const headersCn = ["模型", "RPM", "TPM", "可设上限 TPM"]; const headersEn = ["Model", "Req/min", "Token/min", "Max TPM"]; const rows = models.map((m) => { @@ -135,27 +134,21 @@ function printTable(models: ModelWithQpm[], noColor: boolean): void { return; } - const widths = headersCn.map((label, col) => - Math.max( - displayWidth(label), - displayWidth(headersEn[col]), - ...rows.map((row) => displayWidth(row[col])), - ), + const widths = headersEn.map((label, col) => + Math.max(displayWidth(label), ...rows.map((row) => displayWidth(row[col]))), ); - const cnLine = headersCn.map((label, col) => bold(padEnd(label, widths[col]))).join(" "); - const enLine = headersEn.map((label, col) => dim(padEnd(label, widths[col]))).join(" "); + const headerLine = headersEn.map((label, col) => bold(padEnd(label, widths[col]))).join(" "); const separator = widths.map((w) => dim("─".repeat(w))).join("──"); - process.stdout.write(cnLine + "\n"); - process.stdout.write(enLine + "\n"); + process.stdout.write(headerLine + "\n"); process.stdout.write(separator + "\n"); for (const row of rows) { process.stdout.write(row.map((cell, col) => padEnd(cell, widths[col])).join(" ") + "\n"); } - process.stdout.write(dim(`\n共 ${models.length} 个模型 (Total: ${models.length})`) + "\n"); + process.stdout.write(dim(`\nTotal: ${models.length} models`) + "\n"); } export default defineCommand({ @@ -221,7 +214,25 @@ export default defineCommand({ } if (format === "json") { - emitResult(models, format); + const items = models.map((m) => { + const qpm = m.qpmInfo; + const modelDefault = qpm?.["model-default"]; + const userSpec = qpm?.["user-spec"]; + + const defaultRPM = calculateRPM(modelDefault); + const defaultTPM = calculateTPM(modelDefault); + const currentRPM = calculateRPM(userSpec, modelDefault?.count_limit_period) || defaultRPM; + const currentTPM = calculateTPM(userSpec, modelDefault?.usage_limit_period) || defaultTPM; + const maxTPM = defaultTPM * 2; + + return { + model: m.model, + rpm: currentRPM > 0 ? currentRPM : null, + tpm: currentTPM > 0 ? currentTPM : null, + maxTPM: maxTPM > 0 ? maxTPM : null, + }; + }); + emitResult(items, format); return; } diff --git a/packages/cli/src/commands/usage/free.ts b/packages/cli/src/commands/usage/free.ts index 3dd11a0..986e234 100644 --- a/packages/cli/src/commands/usage/free.ts +++ b/packages/cli/src/commands/usage/free.ts @@ -78,7 +78,6 @@ function printTable( typeMap: Map, noColor: boolean, ): void { - const headersCn = ["模型", "类型", "剩余/总量", "使用率", "过期时间", "用完即停"]; const headersEn = ["Model", "Type", "Remaining/Total", "Usage", "Expires", "Auto-Stop"]; const rows = quotas.map((quota) => { @@ -102,12 +101,8 @@ function printTable( ]; }); - const widths = headersCn.map((label, col) => - Math.max( - displayWidth(label), - displayWidth(headersEn[col]), - ...rows.map((row) => displayWidth(row[col])), - ), + const widths = headersEn.map((label, col) => + Math.max(displayWidth(label), ...rows.map((row) => displayWidth(row[col]))), ); const dim = noColor ? (text: string) => text : (text: string) => `\x1b[2m${text}\x1b[0m`; @@ -115,12 +110,10 @@ function printTable( const green = noColor ? (text: string) => text : (text: string) => `\x1b[32m${text}\x1b[0m`; const yellow = noColor ? (text: string) => text : (text: string) => `\x1b[33m${text}\x1b[0m`; - const autoStopCol = headersCn.length - 1; - const cnLine = headersCn.map((label, col) => bold(padEnd(label, widths[col]))).join(" "); - const enLine = headersEn.map((label, col) => dim(padEnd(label, widths[col]))).join(" "); + const autoStopCol = headersEn.length - 1; + const enLine = headersEn.map((label, col) => bold(padEnd(label, widths[col]))).join(" "); const separator = widths.map((width) => dim("─".repeat(width))).join("──"); - process.stdout.write(cnLine + "\n"); process.stdout.write(enLine + "\n"); process.stdout.write(separator + "\n"); @@ -296,11 +289,6 @@ export default defineCommand({ }), ]); - if (format === "json") { - emitResult(quotaResult, format); - return; - } - const allQuotas = extractQuotas(quotaResult); let quotas = modelFlag ? allQuotas @@ -321,14 +309,44 @@ export default defineCommand({ quotas.sort((a, b) => (a.quotaValidityPeriod ?? 0) - (b.quotaValidityPeriod ?? 0)); } + const stopStatuses = extractFreeTierOnlyStatuses(stopResult); + const stopMap = new Map(stopStatuses.map((status) => [status.model, status.freeTierOnly])); + + if (format === "json") { + const items = quotas.map((quota) => { + const hasQuota = quota.quotaInitTotal != null && quota.quotaTotal != null; + const used = hasQuota ? quota.quotaInitTotal - quota.quotaTotal : 0; + const stopStatus = stopMap.get(quota.model); + const autoStop = + quota.quotaStatus === "UNKNOWN" + ? "unsupported" + : stopStatus === true + ? true + : stopStatus === false + ? false + : null; + return { + model: quota.model, + type: typeMap.get(quota.model) || null, + remaining: hasQuota ? quota.quotaTotal : null, + total: hasQuota ? quota.quotaInitTotal : null, + usagePercent: + hasQuota && quota.quotaInitTotal > 0 + ? Math.round((used / quota.quotaInitTotal) * 1000) / 10 + : null, + expires: quota.quotaValidityPeriod ? formatDate(quota.quotaValidityPeriod) : null, + autoStop, + }; + }); + emitResult(items, format); + return; + } + if (quotas.length === 0) { process.stdout.write("No free-tier quota found.\n"); return; } - const stopStatuses = extractFreeTierOnlyStatuses(stopResult); - const stopMap = new Map(stopStatuses.map((status) => [status.model, status.freeTierOnly])); - printTable(quotas, stopMap, typeMap, config.noColor); }, }); diff --git a/packages/cli/src/commands/usage/stats.ts b/packages/cli/src/commands/usage/stats.ts index fb5074b..7efbcd5 100644 --- a/packages/cli/src/commands/usage/stats.ts +++ b/packages/cli/src/commands/usage/stats.ts @@ -155,33 +155,32 @@ function resolveUsageMap(item: ModelStatisticItem): Record { } interface UsageLabel { - cn: string; en: string; unit?: string; } const USAGE_KEY_LABELS: Record = { - total_token: { cn: "总 Token", en: "Total Tokens", unit: "tokens" }, - input_token: { cn: "输入 Token", en: "Input Tokens", unit: "tokens" }, - output_token: { cn: "输出 Token", en: "Output Tokens", unit: "tokens" }, - input_token_cache: { cn: "缓存 Token", en: "Cached Tokens", unit: "tokens" }, - input_token_cache_read: { cn: "缓存读取", en: "Cache Read", unit: "tokens" }, - input_token_cache_creation: { cn: "缓存创建", en: "Cache Creation", unit: "tokens" }, - thinking_input_token: { cn: "思考输入", en: "Thinking Input", unit: "tokens" }, - thinking_output_token: { cn: "思考输出", en: "Thinking Output", unit: "tokens" }, - text_input_token: { cn: "文本输入", en: "Text Input", unit: "tokens" }, - purein_text_output_token: { cn: "文本输出", en: "Text Output", unit: "tokens" }, - embedding_token: { cn: "向量", en: "Embedding", unit: "tokens" }, - image_number: { cn: "图片数", en: "Images", unit: "张" }, - video_duration: { cn: "视频时长", en: "Video Duration", unit: "秒" }, - content_duration: { cn: "音频时长", en: "Audio Duration", unit: "秒" }, - tts_text_number: { cn: "语音合成", en: "TTS Chars", unit: "字符" }, - total_token_avg: { cn: "平均 Token/次", en: "Avg Tokens/Req" }, + total_token: { en: "Total Tokens", unit: "tokens" }, + input_token: { en: "Input Tokens", unit: "tokens" }, + output_token: { en: "Output Tokens", unit: "tokens" }, + input_token_cache: { en: "Cached Tokens", unit: "tokens" }, + input_token_cache_read: { en: "Cache Read", unit: "tokens" }, + input_token_cache_creation: { en: "Cache Creation", unit: "tokens" }, + thinking_input_token: { en: "Thinking Input", unit: "tokens" }, + thinking_output_token: { en: "Thinking Output", unit: "tokens" }, + text_input_token: { en: "Text Input", unit: "tokens" }, + purein_text_output_token: { en: "Text Output", unit: "tokens" }, + embedding_token: { en: "Embedding", unit: "tokens" }, + image_number: { en: "Images", unit: "images" }, + video_duration: { en: "Video Duration", unit: "seconds" }, + content_duration: { en: "Audio Duration", unit: "seconds" }, + tts_text_number: { en: "TTS Chars", unit: "chars" }, + total_token_avg: { en: "Avg Tokens/Req" }, }; function formatLabel(label: UsageLabel): string { const unitSuffix = label.unit ? ` [${label.unit}]` : ""; - return `${label.cn} (${label.en})${unitSuffix}`; + return `${label.en}${unitSuffix}`; } function printOverview( @@ -195,12 +194,12 @@ function printOverview( const dim = noColor ? (text: string) => text : (text: string) => `\x1b[2m${text}\x1b[0m`; process.stdout.write( - `${dim("时间范围 Period:")} ${formatDate(startTime)} ~ ${formatDate(endTime)} ${dim(`(${days} 天)`)}\n\n`, + `${dim("Period:")} ${formatDate(startTime)} ~ ${formatDate(endTime)} ${dim(`(${days} days)`)}\n\n`, ); const rows: [string, string][] = [ - ["调用模型数 (Models Called)", formatNumber(stat.modelCount ?? 0)], - ["调用成功次数 (Successful Calls)", formatNumber(stat.callSuccessCount ?? 0)], + ["Models Called", formatNumber(stat.modelCount ?? 0)], + ["Successful Calls", formatNumber(stat.callSuccessCount ?? 0)], ]; for (const usage of stat.usages ?? []) { @@ -226,7 +225,7 @@ function printModelTable( const dim = noColor ? (text: string) => text : (text: string) => `\x1b[2m${text}\x1b[0m`; process.stdout.write( - `${dim("时间范围 Period:")} ${formatDate(startTime)} ~ ${formatDate(endTime)} ${dim(`(${days} 天)`)}\n\n`, + `${dim("Period:")} ${formatDate(startTime)} ~ ${formatDate(endTime)} ${dim(`(${days} days)`)}\n\n`, ); if (items.length === 0) { @@ -257,15 +256,6 @@ function printModelTable( return (idxA === -1 ? 999 : idxA) - (idxB === -1 ? 999 : idxB); }); - const headersCn = [ - "模型", - "调用次数", - ...orderedKeys.map((key) => { - const label = USAGE_KEY_LABELS[key]; - if (!label) return key; - return label.unit ? `${label.cn} [${label.unit}]` : label.cn; - }), - ]; const headersEn = [ "Model", "Calls", @@ -280,20 +270,14 @@ function printModelTable( }), ]); - const widths = headersCn.map((label, col) => - Math.max( - displayWidth(label), - displayWidth(headersEn[col]), - ...rows.map((row) => displayWidth(row[col])), - ), + const widths = headersEn.map((label, col) => + Math.max(displayWidth(label), ...rows.map((row) => displayWidth(row[col]))), ); - const cnLine = headersCn.map((label, col) => bold(padEnd(label, widths[col]))).join(" "); - const enLine = headersEn.map((label, col) => dim(padEnd(label, widths[col]))).join(" "); + const headerLine = headersEn.map((label, col) => bold(padEnd(label, widths[col]))).join(" "); const separator = widths.map((width) => dim("─".repeat(width))).join("──"); - process.stdout.write(cnLine + "\n"); - process.stdout.write(enLine + "\n"); + process.stdout.write(headerLine + "\n"); process.stdout.write(separator + "\n"); for (const row of rows) { @@ -301,7 +285,7 @@ function printModelTable( process.stdout.write(cells.join(" ") + "\n"); } - process.stdout.write(dim(`\n共 ${items.length} 个模型 (Total: ${items.length})`) + "\n"); + process.stdout.write(dim(`\nTotal: ${items.length} models`) + "\n"); } export default defineCommand({ @@ -391,16 +375,31 @@ export default defineCommand({ ); const allItems: ModelStatisticItem[] = []; - const jsonResults: unknown[] = []; for (const result of results) { if (!result) continue; - jsonResults.push(result); const listData = extractListData(result); allItems.push(...listData.list); } if (format === "json") { - emitResult(jsonResults.length === 1 ? jsonResults[0] : jsonResults, format); + const items = allItems.map((item) => { + const usage = resolveUsageMap(item); + const clean: Record = { + model: item.model, + successfulCalls: item.callSuccessCount ?? 0, + }; + for (const [key, val] of Object.entries(usage)) { + clean[key] = val; + } + return clean; + }); + emitResult( + { + period: { start: formatDate(startTime), end: formatDate(endTime), days: daysFlag }, + items, + }, + format, + ); return; } @@ -425,12 +424,37 @@ export default defineCommand({ process.exit(1); } + const stat = extractOverviewData(result); + if (format === "json") { - emitResult(result, format); + if (!stat) { + emitResult( + { + period: { start: formatDate(startTime), end: formatDate(endTime), days: daysFlag }, + modelsCalled: 0, + successfulCalls: 0, + }, + format, + ); + return; + } + emitResult( + { + period: { start: formatDate(startTime), end: formatDate(endTime), days: daysFlag }, + modelsCalled: stat.modelCount ?? 0, + successfulCalls: stat.callSuccessCount ?? 0, + usages: (stat.usages ?? []).map((u) => ({ + key: u.key, + value: u.value, + unit: u.unit, + label: USAGE_KEY_LABELS[u.key]?.en ?? u.key, + })), + }, + format, + ); return; } - const stat = extractOverviewData(result); if (!stat) { process.stdout.write("No usage data found.\n"); return; diff --git a/packages/cli/src/commands/workspace/list.ts b/packages/cli/src/commands/workspace/list.ts index b9f6edb..3f9a1e9 100644 --- a/packages/cli/src/commands/workspace/list.ts +++ b/packages/cli/src/commands/workspace/list.ts @@ -46,8 +46,7 @@ function printTable(workspaces: WorkspaceInfo[], noColor: boolean): void { const dim = noColor ? (text: string) => text : (text: string) => `\x1b[2m${text}\x1b[0m`; const green = noColor ? (text: string) => text : (text: string) => `\x1b[32m${text}\x1b[0m`; - const headersCn = ["空间名称", "Workspace ID", "默认空间"]; - const headersEn = ["Name", "", "Default"]; + const headersEn = ["Name", "Workspace ID", "Default"]; const rows = workspaces.map((ws) => [ ws.agentName, @@ -55,20 +54,14 @@ function printTable(workspaces: WorkspaceInfo[], noColor: boolean): void { ws.defaultAgent ? "Yes" : "-", ]); - const widths = headersCn.map((label, col) => - Math.max( - displayWidth(label), - displayWidth(headersEn[col]), - ...rows.map((row) => displayWidth(row[col])), - ), + const widths = headersEn.map((label, col) => + Math.max(displayWidth(label), ...rows.map((row) => displayWidth(row[col]))), ); - const cnLine = headersCn.map((label, col) => bold(padEnd(label, widths[col]))).join(" "); - const enLine = headersEn.map((label, col) => dim(padEnd(label, widths[col]))).join(" "); + const headerLine = headersEn.map((label, col) => bold(padEnd(label, widths[col]))).join(" "); const separator = widths.map((width) => dim("─".repeat(width))).join("──"); - process.stdout.write(cnLine + "\n"); - process.stdout.write(enLine + "\n"); + process.stdout.write(headerLine + "\n"); process.stdout.write(separator + "\n"); for (const row of rows) { @@ -79,9 +72,7 @@ function printTable(workspaces: WorkspaceInfo[], noColor: boolean): void { process.stdout.write(cells.join(" ") + "\n"); } - process.stdout.write( - dim(`\n共 ${workspaces.length} 个空间 (Total: ${workspaces.length})`) + "\n", - ); + process.stdout.write(dim(`\nTotal: ${workspaces.length} workspaces`) + "\n"); } export default defineCommand({ @@ -117,21 +108,30 @@ export default defineCommand({ region, }); - if (format === "json") { - emitResult(result, format); - return; - } - const resp = extractResponseData(result as Record); const dataArr = resp.data as Record[] | undefined; if (!Array.isArray(dataArr) || dataArr.length === 0) { - process.stdout.write("No workspace found.\n"); + if (format === "json") { + emitResult([], format); + } else { + process.stdout.write("No workspace found.\n"); + } return; } let workspaces = dataArr as unknown as WorkspaceInfo[]; if (limit > 0) workspaces = workspaces.slice(0, limit); + if (format === "json") { + const items = workspaces.map((ws) => ({ + workspaceId: ws.workspaceId, + name: ws.agentName, + default: ws.defaultAgent, + })); + emitResult(items, format); + return; + } + printTable(workspaces, config.noColor); }, }); diff --git a/packages/cli/tests/e2e/quota.e2e.test.ts b/packages/cli/tests/e2e/quota.e2e.test.ts index 19e537d..997f961 100644 --- a/packages/cli/tests/e2e/quota.e2e.test.ts +++ b/packages/cli/tests/e2e/quota.e2e.test.ts @@ -96,7 +96,7 @@ describe.skipIf(!isConsoleE2EReady())("e2e: quota(Console)", () => { expect(data.data?.input?.supports).toBeUndefined(); }); - test("quota list 文本输出包含双行表头", async () => { + test("quota list 文本输出包含单行英⽂表头", async () => { const { stdout, stderr, exitCode } = await runCli([ "quota", "list", @@ -105,11 +105,9 @@ describe.skipIf(!isConsoleE2EReady())("e2e: quota(Console)", () => { "--no-color", ]); expect(exitCode, stderr).toBe(0); - expect(stdout).toContain("模型"); expect(stdout).toContain("Model"); - expect(stdout).toContain("RPM"); - expect(stdout).toContain("TPM"); - expect(stdout).toContain("可设上限 TPM"); + expect(stdout).toContain("Req/min"); + expect(stdout).toContain("Token/min"); expect(stdout).toContain("Max TPM"); }); @@ -125,7 +123,7 @@ describe.skipIf(!isConsoleE2EReady())("e2e: quota(Console)", () => { ]); expect(exitCode, stderr).toBe(0); expect(stdout).toContain("qwen3.6-plus"); - expect(stdout).toMatch(/共 1 个模型/); + expect(stdout).toMatch(/Total: 1 models/); }); test("quota list --model 不存在的模型报错", async () => { @@ -141,14 +139,19 @@ describe.skipIf(!isConsoleE2EReady())("e2e: quota(Console)", () => { expect(stderr).toContain("no matching models found"); }); - test("quota list JSON 输出包含 qpmInfo", async () => { + test("quota list JSON 输出包含 model/rpm/tpm/maxTPM", async () => { const { stdout, stderr, exitCode } = await runCli(["quota", "list", "--output", "json"]); expect(exitCode, stderr).toBe(0); - const data = parseStdoutJson>(stdout); + const data = + parseStdoutJson< + Array<{ model?: string; rpm?: number | null; tpm?: number | null; maxTPM?: number | null }> + >(stdout); expect(Array.isArray(data)).toBe(true); expect(data.length).toBeGreaterThan(0); expect(data[0].model).toBeTypeOf("string"); - expect(data[0].qpmInfo).toBeDefined(); + expect(data[0].rpm).toBeTypeOf("number"); + expect(data[0].tpm).toBeTypeOf("number"); + expect(data[0].maxTPM).toBeTypeOf("number"); }); test("quota request --dry-run 输出请求参数", async () => { @@ -235,7 +238,7 @@ describe.skipIf(!isConsoleE2EReady())("e2e: quota(Console)", () => { expect(data.apis).toContain("zeldaEasy.bailian-telemetry.monitor.getMonitorData"); }); - test("quota check 文本输出包含双行表头", async () => { + test("quota check 文本输出包含单行英⽂表头", async () => { const { stdout, stderr, exitCode } = await runCli([ "quota", "check", @@ -244,12 +247,10 @@ describe.skipIf(!isConsoleE2EReady())("e2e: quota(Console)", () => { "--no-color", ]); expect(exitCode, stderr).toBe(0); - expect(stdout).toContain("模型"); expect(stdout).toContain("Model"); - expect(stdout).toContain("RPM 用量/限额"); expect(stdout).toContain("RPM Usage/Limit"); - expect(stdout).toContain("TPM 用量/限额"); - expect(stdout).toContain("状态"); + expect(stdout).toContain("TPM Usage/Limit"); + expect(stdout).toContain("Status"); }); test("quota check --model 指定单模型", async () => { @@ -264,7 +265,7 @@ describe.skipIf(!isConsoleE2EReady())("e2e: quota(Console)", () => { ]); expect(exitCode, stderr).toBe(0); expect(stdout).toContain("qwen3.6-plus"); - expect(stdout).toMatch(/共 1 个模型/); + expect(stdout).toMatch(/Total: 1 models/); }); test("quota check --model 逗号分隔多模型", async () => { @@ -280,7 +281,7 @@ describe.skipIf(!isConsoleE2EReady())("e2e: quota(Console)", () => { expect(exitCode, stderr).toBe(0); expect(stdout).toContain("qwen3.6-plus"); expect(stdout).toContain("qwen-plus"); - expect(stdout).toMatch(/共 2 个模型/); + expect(stdout).toMatch(/Total: 2 models/); }); test("quota check JSON 输出包含用量和限额字段", async () => { @@ -311,7 +312,7 @@ describe.skipIf(!isConsoleE2EReady())("e2e: quota(Console)", () => { expect(data[0].tpmLimit).toBeTypeOf("number"); }); - test("quota check 状态列显示正常/接近限流/已限流之一", async () => { + test("quota check 状态列显示 Normal/Near Limit/Throttled 之一", async () => { const { stdout, stderr, exitCode } = await runCli([ "quota", "check", @@ -323,7 +324,7 @@ describe.skipIf(!isConsoleE2EReady())("e2e: quota(Console)", () => { ]); expect(exitCode, stderr).toBe(0); const hasStatus = - stdout.includes("正常") || stdout.includes("接近限流") || stdout.includes("已限流"); + stdout.includes("Normal") || stdout.includes("Near Limit") || stdout.includes("Throttled"); expect(hasStatus).toBe(true); }); diff --git a/packages/cli/tests/e2e/usage-free.e2e.test.ts b/packages/cli/tests/e2e/usage-free.e2e.test.ts index 064f027..ff4087a 100644 --- a/packages/cli/tests/e2e/usage-free.e2e.test.ts +++ b/packages/cli/tests/e2e/usage-free.e2e.test.ts @@ -133,12 +133,21 @@ describe.skipIf(!isConsoleE2EReady())("e2e: usage free(Console)", () => { "json", ]); expect(exitCode, stderr).toBe(0); - const data = parseStdoutJson<{ - code?: string; - successResponse?: boolean; - }>(stdout); - expect(data.code).toBe("200"); - expect(data.successResponse).toBe(true); + const data = parseStdoutJson< + Array<{ + model?: string; + type?: string | null; + remaining?: number | null; + total?: number | null; + usagePercent?: number | null; + expires?: string | null; + autoStop?: boolean | string | null; + }> + >(stdout); + expect(Array.isArray(data)).toBe(true); + expect(data.length).toBeGreaterThan(0); + expect(data[0].model).toBe("qwen3-max"); + expect(data[0].type).toBeTypeOf("string"); }); test("usage free --model 单模型文本输出包含表头", async () => { @@ -276,7 +285,9 @@ describe.skipIf(!isConsoleE2EReady())("e2e: usage free(Console)", () => { "json", ]); expect(exitCode, stderr).toBe(0); - const data = parseStdoutJson<{ code?: string }>(stdout); - expect(data.code).toBe("200"); + const data = parseStdoutJson>(stdout); + expect(Array.isArray(data)).toBe(true); + expect(data.length).toBeGreaterThan(0); + expect(data[0].model).toBe("qwen3-max"); }); }); diff --git a/packages/cli/tests/e2e/usage-stats.e2e.test.ts b/packages/cli/tests/e2e/usage-stats.e2e.test.ts index 29c9e0d..9aab48b 100644 --- a/packages/cli/tests/e2e/usage-stats.e2e.test.ts +++ b/packages/cli/tests/e2e/usage-stats.e2e.test.ts @@ -169,11 +169,17 @@ describe.skipIf(!isConsoleE2EReady())("e2e: usage stats(Console)", () => { ]); expect(exitCode, stderr).toBe(0); const data = parseStdoutJson<{ - code?: string; - successResponse?: boolean; + period?: { start?: string; end?: string; days?: number }; + modelsCalled?: number; + successfulCalls?: number; + usages?: Array<{ key?: string; value?: number }>; }>(stdout); - expect(data.code).toBe("200"); - expect(data.successResponse).toBe(true); + expect(data.period).toBeDefined(); + expect(data.period?.start).toBeTypeOf("string"); + expect(data.period?.end).toBeTypeOf("string"); + expect(data.period?.days).toBeTypeOf("number"); + expect(data.modelsCalled).toBeTypeOf("number"); + expect(data.successfulCalls).toBeTypeOf("number"); }); test("usage stats 概览文本输出包含中英文表头", async () => {