Merge pull request #201 from VIISEVEN27/feat/tokenplan-harness

feat(token-plan): 新增 Token Plan harness 权益额度用量命令
This commit is contained in:
ls
2026-09-14 11:22:10 +08:00
committed by GitHub
10 changed files with 581 additions and 8 deletions
+2
View File
@@ -130,6 +130,7 @@ import {
tokenPlanCreateKey,
tokenPlanAssignSeats,
tokenPlanAddMember,
tokenPlanHarnessQuota,
workspaceInit,
pluginInstall,
pluginLink,
@@ -366,6 +367,7 @@ export const commands: Record<string, AnyCommand> = {
"token-plan create-key": tokenPlanCreateKey,
"token-plan assign-seats": tokenPlanAssignSeats,
"token-plan add-member": tokenPlanAddMember,
"token-plan harness-quota": tokenPlanHarnessQuota,
"workspace init": workspaceInit,
"plugin install": pluginInstall,
"plugin link": pluginLink,
@@ -0,0 +1,169 @@
import { randomUUID } from "node:crypto";
import { defineCommand, detectOutputFormat, unwrapResponse } from "bailian-cli-core";
import { emitResult } from "bailian-cli-runtime";
import { printQuotaBox, readNumber, type QuotaSection } from "../usage/quota-box.ts";
import { formatNumber } from "../shared/format.ts";
import type { HarnessBenefitItem, TokenPlanEquityInfo } from "./types.ts";
const HARNESS_LIST_API = "zeldaEasy.broadscope-bailian.token-plan.detail";
const EQUITY_INFO_API = "zeldaEasy.bailian-commerce.tokenPlan.queryTokenPlanEquityInfo";
/** One harness that carries an entitlement quota, issued or still pending. */
interface HarnessQuotaRow {
planCode: string;
title: string;
type: string;
/** `issuing` means the harness carries a quota but no entitlement is issued yet. */
status: "issued" | "issuing";
quotaUnit: string;
instanceId?: string;
totalQuota?: number;
availableQuota?: number;
usedQuota?: number;
/** Used ratio in percent with 0.1 precision, matching the console display. */
usedPercent?: number;
instanceStartTime?: number;
instanceEndTime?: number;
}
function readArray<T>(result: unknown, field: string): T[] {
const response = unwrapResponse(result as Record<string, unknown>);
const value = response[field];
return Array.isArray(value) ? (value as T[]) : [];
}
/**
* Join the harness list with the issued entitlements, keeping the server order.
* A harness carrying a resource pack but no matching entitlement is still
* listed as `issuing` — issuance lags the purchase by a few minutes.
*/
function buildRows(
items: HarnessBenefitItem[],
equityInfos: TokenPlanEquityInfo[],
): HarnessQuotaRow[] {
const rows: HarnessQuotaRow[] = [];
for (const item of items) {
// Only harnesses that carry a resource pack have an entitlement quota at all.
if (item.hasResourcePack !== true) continue;
const planCodes = item.planCodes ?? [];
const equity = equityInfos.find(
(equityInfo) => equityInfo.equityType && planCodes.includes(equityInfo.equityType),
);
const planCode = equity?.equityType ?? planCodes[0] ?? "";
const row: HarnessQuotaRow = {
planCode,
title: item.title ?? planCode,
type: item.type ?? "",
status: equity ? "issued" : "issuing",
quotaUnit: item.quotaUnit ?? item.priceInfo?.unit ?? "",
};
if (!equity) {
rows.push(row);
continue;
}
if (equity.instanceId) row.instanceId = equity.instanceId;
const totalQuota = readNumber(equity.totalQuota);
if (totalQuota !== undefined) row.totalQuota = totalQuota;
const availableQuota = readNumber(equity.availableQuota);
if (availableQuota !== undefined) row.availableQuota = availableQuota;
if (totalQuota !== undefined && availableQuota !== undefined) {
row.usedQuota = Math.max(totalQuota - availableQuota, 0);
if (totalQuota > 0) {
row.usedPercent = Math.round((row.usedQuota / totalQuota) * 1000) / 10;
}
}
const instanceStartTime = readNumber(equity.instanceStartTime);
if (instanceStartTime !== undefined) row.instanceStartTime = instanceStartTime;
const instanceEndTime = readNumber(equity.instanceEndTime);
if (instanceEndTime !== undefined) row.instanceEndTime = instanceEndTime;
rows.push(row);
}
return rows;
}
function toSection(row: HarnessQuotaRow): QuotaSection {
const section: QuotaSection = {
label: `${row.title} (${row.planCode})`,
emptyMessage:
row.status === "issuing"
? "Quota is being issued; issuance can take up to 5 minutes."
: "No positive quota total reported; check the Bailian Token Plan console.",
};
if (row.status === "issuing") return section;
if (row.totalQuota !== undefined && row.usedQuota !== undefined) {
const unitSuffix = row.quotaUnit ? ` ${row.quotaUnit}` : "";
section.detail = `Used: ${formatNumber(row.usedQuota)} / ${formatNumber(row.totalQuota)}${unitSuffix}`;
if (row.totalQuota > 0) section.percentage = row.usedQuota / row.totalQuota;
}
if (row.instanceEndTime !== undefined) section.resetTime = row.instanceEndTime;
return section;
}
export default defineCommand({
description: {
"en-US": "Show Token Plan harness entitlement quota usage",
"zh-CN": "查看 Token Plan harness 权益额度用量",
},
auth: "console",
usageArgs: "[flags]",
flags: {
type: {
type: "string",
valueHint: "<type>",
choices: ["official_tool", "infrastructure"] as const,
description: {
"en-US": "Filter harness list by type: official_tool, infrastructure",
"zh-CN": "按类型筛选 harness 列表official_tool、infrastructure",
},
},
},
exampleArgs: ["", "--type official_tool", "--output json"],
async run(ctx) {
const { settings, flags } = ctx;
const format = detectOutputFormat(settings.output);
const benefitsData = flags.type ? { type: flags.type } : {};
const equityData = { queryTokenPlanEquityInfoRequest: { requestId: randomUUID() } };
if (settings.dryRun) {
emitResult(
{
requests: [
{ api: HARNESS_LIST_API, data: benefitsData },
{ api: EQUITY_INFO_API, data: equityData },
],
},
format,
);
return;
}
const [benefitsResult, equityResult] = await Promise.all([
ctx.client.console(HARNESS_LIST_API, benefitsData),
ctx.client.console(EQUITY_INFO_API, equityData),
]);
const rows = buildRows(
readArray<HarnessBenefitItem>(benefitsResult, "items"),
readArray<TokenPlanEquityInfo>(equityResult, "tokenPlanEquityInfos"),
);
if (format === "json") {
emitResult({ generatedAt: Date.now(), items: rows }, format);
return;
}
if (rows.length === 0) {
process.stdout.write("No harness with entitlement quota found.\n");
return;
}
printQuotaBox("Token Plan Harness Quota", rows.map(toSection), Date.now());
},
});
@@ -65,3 +65,25 @@ export interface AddOrganizationMemberResponse {
SeatAssigned?: boolean;
};
}
// Console gateway payloads keep the server's camelCase keys, unlike the
// PascalCase OpenAPI shapes above.
export interface HarnessBenefitItem {
type?: string;
hasResourcePack?: boolean;
planCodes?: string[];
title?: string;
quotaUnit?: string;
priceInfo?: { unit?: string } | null;
}
export interface TokenPlanEquityInfo {
instanceId?: string;
templateCode?: string;
equityType?: string;
totalQuota?: number;
availableQuota?: number;
instanceStartTime?: number;
instanceEndTime?: number;
}
+1
View File
@@ -137,6 +137,7 @@ export { default as tokenPlanListSeats } from "./commands/token-plan/list-seats.
export { default as tokenPlanCreateKey } from "./commands/token-plan/create-key.ts";
export { default as tokenPlanAssignSeats } from "./commands/token-plan/assign-seats.ts";
export { default as tokenPlanAddMember } from "./commands/token-plan/add-member.ts";
export { default as tokenPlanHarnessQuota } from "./commands/token-plan/harness-quota.ts";
export { default as managedAgentInit } from "./commands/managed-agent/init.ts";
export { default as managedAgentValidate } from "./commands/managed-agent/validate.ts";
export { default as managedAgentPlan } from "./commands/managed-agent/plan.ts";
@@ -1,5 +1,12 @@
import { describe, expect, test } from "vite-plus/test";
import { makeE2eOutputDir, parseStdoutJson, runCommandHelp, runCommandE2e } from "./helpers.ts";
import {
isConsoleAuthFailure,
isConsoleE2EReady,
makeE2eOutputDir,
parseStdoutJson,
runCommandHelp,
runCommandE2e,
} from "./helpers.ts";
import { TOKEN_PLAN_ROUTES } from "./topic-routes.ts";
describe("e2e: token-plan", () => {
@@ -60,4 +67,92 @@ describe("e2e: token-plan", () => {
expect(stderr).toMatch(/ALIBABA_CLOUD_ACCESS_KEY_ID/);
expect(stderr).not.toMatch(/auth login --api-key/);
});
test("token-plan harness-quota help 展示 --type 与 console 鉴权域 flags", async () => {
const { stderr, exitCode } = await runCommandHelp(TOKEN_PLAN_ROUTES, [
"token-plan",
"harness-quota",
"--help",
]);
expect(exitCode, stderr).toBe(0);
expect(stderr).toMatch(/--type <official_tool\|infrastructure>/);
expect(stderr).toMatch(/--console-region/);
expect(stderr).not.toMatch(/--access-key-id/);
});
});
describe.skipIf(!isConsoleE2EReady())("e2e: token-plan harness-quotaConsole", () => {
test("harness-quota --dry-run 输出两个网关请求计划", async () => {
const { stdout, stderr, exitCode } = await runCommandE2e(TOKEN_PLAN_ROUTES, [
"token-plan",
"harness-quota",
"--dry-run",
"--output",
"json",
]);
expect(exitCode, stderr).toBe(0);
const data = parseStdoutJson<{
requests?: Array<{ api?: string; data?: Record<string, unknown> }>;
}>(stdout);
expect(data.requests?.[0]?.api).toBe("zeldaEasy.broadscope-bailian.token-plan.detail");
expect(data.requests?.[1]?.api).toBe(
"zeldaEasy.bailian-commerce.tokenPlan.queryTokenPlanEquityInfo",
);
expect(data.requests?.[0]?.data).toEqual({});
});
test("harness-quota --type 透传给 harness 列表接口", async () => {
const { stdout, stderr, exitCode } = await runCommandE2e(TOKEN_PLAN_ROUTES, [
"token-plan",
"harness-quota",
"--type",
"official_tool",
"--dry-run",
"--output",
"json",
]);
expect(exitCode, stderr).toBe(0);
const data = parseStdoutJson<{ requests?: Array<{ data?: Record<string, unknown> }> }>(stdout);
expect(data.requests?.[0]?.data).toEqual({ type: "official_tool" });
});
test("harness-quota --output json 返回权益额度条目", async () => {
const result = await runCommandE2e(TOKEN_PLAN_ROUTES, [
"token-plan",
"harness-quota",
"--output",
"json",
]);
if (isConsoleAuthFailure(result)) return;
expect(result.exitCode, result.stderr).toBe(0);
const data = parseStdoutJson<{
generatedAt?: number;
items?: Array<{
planCode?: string;
status?: string;
totalQuota?: number;
availableQuota?: number;
usedQuota?: number;
usedPercent?: number;
}>;
}>(result.stdout);
expect(Array.isArray(data.items)).toBe(true);
for (const item of data.items ?? []) {
expect(item.planCode).toBeTypeOf("string");
expect(["issued", "issuing"]).toContain(item.status);
const numbers = [item.totalQuota, item.availableQuota, item.usedQuota, item.usedPercent];
for (const value of numbers) {
if (value !== undefined) expect(value).toBeTypeOf("number");
}
}
});
test("harness-quota 默认渲染额度框或空态文案", async () => {
const result = await runCommandE2e(TOKEN_PLAN_ROUTES, ["token-plan", "harness-quota"]);
if (isConsoleAuthFailure(result)) return;
expect(result.exitCode, result.stderr).toBe(0);
expect(result.stdout).toMatch(
/Token Plan Harness Quota|No harness with entitlement quota found/,
);
});
});
@@ -177,6 +177,7 @@ export const TOKEN_PLAN_ROUTES: E2eRouteExports = {
"token-plan create-key": "tokenPlanCreateKey",
"token-plan assign-seats": "tokenPlanAssignSeats",
"token-plan add-member": "tokenPlanAddMember",
"token-plan harness-quota": "tokenPlanHarnessQuota",
};
export const SKILL_ROUTES: E2eRouteExports = {
@@ -0,0 +1,247 @@
import { afterEach, describe, expect, test, vi } from "vite-plus/test";
import harnessQuota from "../src/commands/token-plan/harness-quota.ts";
afterEach(() => {
vi.restoreAllMocks();
});
function captureStdout(): string[] {
const output: string[] = [];
vi.spyOn(process.stdout, "write").mockImplementation((chunk) => {
output.push(String(chunk));
return true;
});
return output;
}
/** Gateway envelope produced by `callConsoleGateway` (DataV2 wrapper included). */
function wrapResponse(payload: Record<string, unknown>): Record<string, unknown> {
return { data: { DataV2: { data: { data: payload } } } };
}
const IMAGE_HARNESS = {
type: "official_tool",
hasResourcePack: true,
title: "图像生成 MCP",
quotaUnit: "张",
planCodes: ["tokenplan_harnesstool_image_generation", "tokenplan_harnesstool_image_monthly"],
};
const SEARCH_HARNESS = {
type: "official_tool",
hasResourcePack: true,
title: "联网搜索 MCP",
priceInfo: { unit: "次" },
planCodes: ["tokenplan_harnesstool_web_search"],
};
/** Harness without an entitlement quota — never shown, same as the console card. */
const NO_QUOTA_HARNESS = {
type: "infrastructure",
hasResourcePack: false,
title: "无额度 Harness",
planCodes: ["tokenplan_harnesstool_no_quota"],
};
async function runHarnessQuota(
items: Record<string, unknown>[],
equityInfos: Record<string, unknown>[],
options: { output?: string; type?: string } = {},
): Promise<Record<string, unknown>[]> {
const calls: Record<string, unknown>[] = [];
await harnessQuota.run({
client: {
console: vi.fn().mockImplementation((api: string, data: Record<string, unknown>) => {
calls.push({ api, data });
return Promise.resolve(
api.endsWith("token-plan.detail")
? wrapResponse({ items })
: wrapResponse({ userId: "1256099523640572", tokenPlanEquityInfos: equityInfos }),
);
}),
},
flags: options.type ? { type: options.type } : {},
settings: { dryRun: false, output: options.output },
} as never);
return calls;
}
describe("token-plan harness-quota view", () => {
test("renders one gauge per harness with issued quota", async () => {
const output = captureStdout();
await runHarnessQuota(
[IMAGE_HARNESS, NO_QUOTA_HARNESS],
[
{
instanceId: "instance-1",
equityType: "tokenplan_harnesstool_image_generation",
totalQuota: 100,
availableQuota: 60,
instanceStartTime: 1_786_000_000_000,
instanceEndTime: 1_788_000_000_000,
},
],
);
const rendered = output.join("");
expect(rendered).toContain("Token Plan Harness Quota");
expect(rendered).toContain("图像生成 MCP (tokenplan_harnesstool_image_generation)");
expect(rendered).toContain("40% used");
expect(rendered).toContain("Used: 40 / 100 张");
expect(rendered).toMatch(/Resets: \d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/);
// Harness without a resource pack has no entitlement quota at all.
expect(rendered).not.toContain("无额度 Harness");
});
test("marks a harness whose entitlement is not issued yet as issuing", async () => {
const output = captureStdout();
await runHarnessQuota(
[IMAGE_HARNESS, SEARCH_HARNESS],
[
{
equityType: "tokenplan_harnesstool_image_generation",
totalQuota: 100,
availableQuota: 60,
instanceEndTime: 1_788_000_000_000,
},
],
);
const rendered = output.join("");
// Pending harness keeps its first plan code and shows no gauge or reset line.
expect(rendered).toContain("联网搜索 MCP (tokenplan_harnesstool_web_search)");
expect(rendered).toContain("Quota is being issued; issuance can take up to 5 minutes.");
expect(rendered).not.toContain("Resets: not applicable");
});
test("falls back to the price unit when quotaUnit is absent", async () => {
const output = captureStdout();
await runHarnessQuota(
[SEARCH_HARNESS],
[
{
equityType: "tokenplan_harnesstool_web_search",
totalQuota: 2000,
availableQuota: 500,
instanceEndTime: 1_788_000_000_000,
},
],
);
expect(output.join("")).toContain("Used: 1,500 / 2,000 次");
});
test("reports a non-positive quota total instead of a gauge", async () => {
const output = captureStdout();
await runHarnessQuota(
[IMAGE_HARNESS],
[
{
equityType: "tokenplan_harnesstool_image_monthly",
totalQuota: 0,
availableQuota: 0,
instanceEndTime: 1_788_000_000_000,
},
],
);
expect(output.join("")).toContain(
"No positive quota total reported; check the Bailian Token Plan console.",
);
});
test("prints the empty state when no harness carries an entitlement quota", async () => {
const output = captureStdout();
await runHarnessQuota([NO_QUOTA_HARNESS], []);
expect(output.join("")).toBe("No harness with entitlement quota found.\n");
});
test("passes --type through to the harness list API only", async () => {
captureStdout();
const calls = await runHarnessQuota([], [], { type: "infrastructure" });
expect(calls[0]).toEqual({
api: "zeldaEasy.broadscope-bailian.token-plan.detail",
data: { type: "infrastructure" },
});
expect(calls[1]?.api).toBe("zeldaEasy.bailian-commerce.tokenPlan.queryTokenPlanEquityInfo");
});
});
describe("token-plan harness-quota json", () => {
test("emits the joined quota fields with --output json", async () => {
const output = captureStdout();
await runHarnessQuota(
[IMAGE_HARNESS, SEARCH_HARNESS],
[
{
instanceId: "instance-1",
equityType: "tokenplan_harnesstool_image_generation",
totalQuota: 100,
availableQuota: 60,
instanceStartTime: 1_786_000_000_000,
instanceEndTime: 1_788_000_000_000,
},
],
{ output: "json" },
);
const parsed = JSON.parse(output.join("")) as { items: Record<string, unknown>[] };
expect(parsed.items).toEqual([
{
planCode: "tokenplan_harnesstool_image_generation",
title: "图像生成 MCP",
type: "official_tool",
status: "issued",
quotaUnit: "张",
instanceId: "instance-1",
totalQuota: 100,
availableQuota: 60,
usedQuota: 40,
usedPercent: 40,
instanceStartTime: 1_786_000_000_000,
instanceEndTime: 1_788_000_000_000,
},
{
planCode: "tokenplan_harnesstool_web_search",
title: "联网搜索 MCP",
type: "official_tool",
status: "issuing",
quotaUnit: "次",
},
]);
});
test("treats non-numeric quota fields as absent instead of failing", async () => {
const output = captureStdout();
await runHarnessQuota(
[IMAGE_HARNESS],
[
{
equityType: "tokenplan_harnesstool_image_generation",
totalQuota: "not-a-number",
availableQuota: Number.NaN,
},
],
{ output: "json" },
);
const parsed = JSON.parse(output.join("")) as { items: Record<string, unknown>[] };
expect(parsed.items[0]).toEqual({
planCode: "tokenplan_harnesstool_image_generation",
title: "图像生成 MCP",
type: "official_tool",
status: "issued",
quotaUnit: "张",
});
});
});
+1
View File
@@ -74,6 +74,7 @@ Use this table only after the decision table in [`bailian-protocol`](../bailian-
| Bailian rate limits / quota | `bl quota list` / `check` / `request` | Console auth; class 2 — ask which product first if unnamed |
| Bailian free tier / usage stats | `bl usage free` / `stats` / `freetier` | Console auth; class 2 — ask which product first if unnamed |
| Bailian Token Plan quota usage | `bl usage token-plan` | Console auth; class 2 — ask which product first if unnamed |
| Token Plan harness entitlement quota | `bl token-plan harness-quota` | Console auth; issued quota usage + still-issuing harnesses |
| Bailian Coding Plan quota usage | `bl usage coding-plan` | Console auth; class 2 — ask which product first if unnamed |
| Console API (advanced) | `bl console call` | Console auth |
| Bailian workspace listing | `bl workspace list` | Console auth |
+2 -1
View File
@@ -95,6 +95,7 @@ Use this index for the skill-scoped quick index and global flags.
| `bl token-plan add-member` | AK/SK | Add a member to a Token Plan organization | [token-plan.md](token-plan.md) |
| `bl token-plan assign-seats` | AK/SK | Batch assign Token Plan seats to members | [token-plan.md](token-plan.md) |
| `bl token-plan create-key` | AK/SK | Create a Token Plan API key for a seat | [token-plan.md](token-plan.md) |
| `bl token-plan harness-quota` | Console | Show Token Plan harness entitlement quota usage | [token-plan.md](token-plan.md) |
| `bl token-plan list-seats` | AK/SK | List Token Plan subscription seat details | [token-plan.md](token-plan.md) |
| `bl update` | No Auth | Update the CLI to the latest or a specified version | [update.md](update.md) |
| `bl usage coding-plan` | Console | Show Coding Plan quota usage | [usage.md](usage.md) |
@@ -127,7 +128,7 @@ Use this index for the skill-scoped quick index and global flags.
| `search` | `web` | [search.md](search.md) |
| `skill` | `add`, `init`, `list`, `remove`, `update` | [skill.md](skill.md) |
| `text` | `chat` | [text.md](text.md) |
| `token-plan` | `add-member`, `assign-seats`, `create-key`, `list-seats` | [token-plan.md](token-plan.md) |
| `token-plan` | `add-member`, `assign-seats`, `create-key`, `harness-quota`, `list-seats` | [token-plan.md](token-plan.md) |
| `update` | `(root)` | [update.md](update.md) |
| `usage` | `coding-plan`, `free`, `freetier`, `stats`, `summary`, `token-plan` | [usage.md](usage.md) |
| `workspace` | `init`, `list` | [workspace.md](workspace.md) |
+40 -6
View File
@@ -7,12 +7,13 @@ Index: [index.md](index.md)
## Commands in this group
| Command | Authentication | Description |
| ---------------------------- | -------------- | ----------------------------------------- |
| `bl token-plan add-member` | AK/SK | Add a member to a Token Plan organization |
| `bl token-plan assign-seats` | AK/SK | Batch assign Token Plan seats to members |
| `bl token-plan create-key` | AK/SK | Create a Token Plan API key for a seat |
| `bl token-plan list-seats` | AK/SK | List Token Plan subscription seat details |
| Command | Authentication | Description |
| ----------------------------- | -------------- | ----------------------------------------------- |
| `bl token-plan add-member` | AK/SK | Add a member to a Token Plan organization |
| `bl token-plan assign-seats` | AK/SK | Batch assign Token Plan seats to members |
| `bl token-plan create-key` | AK/SK | Create a Token Plan API key for a seat |
| `bl token-plan harness-quota` | Console | Show Token Plan harness entitlement quota usage |
| `bl token-plan list-seats` | AK/SK | List Token Plan subscription seat details |
## Command details
@@ -118,6 +119,39 @@ bl token-plan create-key --account-id acc_123 --workspace-id ws_456
bl token-plan create-key --account-id acc_123 --workspace-id ws_456 --description 'Dev key'
```
### `bl token-plan harness-quota`
| Field | Value |
| ------------------ | ----------------------------------------------- |
| **Name** | `token-plan harness-quota` |
| **Description** | Show Token Plan harness entitlement quota usage |
| **Authentication** | Console |
| **Usage** | `bl token-plan harness-quota [flags]` |
#### Flags
| Flag | Type | Required | Description |
| ---------------------------------------- | ------ | -------- | ---------------------------------------------------------- |
| `--type <official_tool\|infrastructure>` | string | no | Filter harness list by type: official_tool, infrastructure |
| `--console-region <region>` | string | no | Console gateway region (e.g. cn-beijing, ap-southeast-1) |
| `--console-site <site>` | string | no | Console site: domestic, international |
| `--console-switch-agent <uid>` | number | no | Switch agent UID for delegated access |
| `--workspace-id <id>` | string | no | Workspace ID (env: BAILIAN_WORKSPACE_ID) |
#### Examples
```bash
bl token-plan harness-quota
```
```bash
bl token-plan harness-quota --type official_tool
```
```bash
bl token-plan harness-quota --output json
```
### `bl token-plan list-seats`
| Field | Value |