Skip to content

Commit 241345d

Browse files
authored
Merge pull request #203 from modelstudioai/feat/tokenplan-harness
feat(token-plan): add harness-quota command and prepare 1.25.0
2 parents 64b1e72 + 46b55b2 commit 241345d

23 files changed

Lines changed: 607 additions & 20 deletions

File tree

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,13 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
66

77
[中文版](CHANGELOG.zh.md) · [README](README.md) · [Contributing](CONTRIBUTING.md)
88

9+
## [1.25.0] - 2026-09-14
10+
11+
### Added
12+
13+
- **Token Plan harness quota**`bl token-plan harness-quota` shows Token Plan harness entitlement quota usage (Console auth), joining the harness list with issued entitlements to display used/total quota, usage ratio, and reset time; harnesses with a pending entitlement are listed as issuing.
14+
- Filter the harness list with `--type official_tool|infrastructure`; render as a quota box or `--output json`.
15+
916
## [1.24.0] - 2026-09-11
1017

1118
### Added

CHANGELOG.zh.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,13 @@
66

77
[English](CHANGELOG.md) · [README](README.zh.md) · [参与贡献](CONTRIBUTING.zh.md)
88

9+
## [1.25.0] - 2026-09-14
10+
11+
### 新增
12+
13+
- **Token Plan harness 权益额度** —— `bl token-plan harness-quota` 查看 Token Plan harness 权益额度用量(Console 认证),联合 harness 列表与已发放权益,展示已用/总额度、使用比例和重置时间;待发放权益的 harness 以「发放中」状态列出。
14+
- 通过 `--type official_tool|infrastructure` 筛选 harness 列表;支持额度框输出或 `--output json`
15+
916
## [1.24.0] - 2026-09-11
1017

1118
### 新增

packages/cli/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "bailian-cli",
3-
"version": "1.24.0",
3+
"version": "1.25.0",
44
"description": "CLI for Aliyun Model Studio (DashScope) AI Platform.",
55
"keywords": [
66
"agent",

packages/cli/src/commands.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ import {
130130
tokenPlanCreateKey,
131131
tokenPlanAssignSeats,
132132
tokenPlanAddMember,
133+
tokenPlanHarnessQuota,
133134
workspaceInit,
134135
pluginInstall,
135136
pluginLink,
@@ -366,6 +367,7 @@ export const commands: Record<string, AnyCommand> = {
366367
"token-plan create-key": tokenPlanCreateKey,
367368
"token-plan assign-seats": tokenPlanAssignSeats,
368369
"token-plan add-member": tokenPlanAddMember,
370+
"token-plan harness-quota": tokenPlanHarnessQuota,
369371
"workspace init": workspaceInit,
370372
"plugin install": pluginInstall,
371373
"plugin link": pluginLink,

packages/commands/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "bailian-cli-commands",
3-
"version": "1.24.0",
3+
"version": "1.25.0",
44
"description": "Command library for bailian-cli products (knowledge, memory, media, …). See https://www.npmjs.com/package/bailian-cli for usage.",
55
"homepage": "https://bailian.console.aliyun.com/cli",
66
"bugs": {
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
import { randomUUID } from "node:crypto";
2+
import { defineCommand, detectOutputFormat, unwrapResponse } from "bailian-cli-core";
3+
import { emitResult } from "bailian-cli-runtime";
4+
import { printQuotaBox, readNumber, type QuotaSection } from "../usage/quota-box.ts";
5+
import { formatNumber } from "../shared/format.ts";
6+
import type { HarnessBenefitItem, TokenPlanEquityInfo } from "./types.ts";
7+
8+
const HARNESS_LIST_API = "zeldaEasy.broadscope-bailian.token-plan.detail";
9+
const EQUITY_INFO_API = "zeldaEasy.bailian-commerce.tokenPlan.queryTokenPlanEquityInfo";
10+
11+
/** One harness that carries an entitlement quota, issued or still pending. */
12+
interface HarnessQuotaRow {
13+
planCode: string;
14+
title: string;
15+
type: string;
16+
/** `issuing` means the harness carries a quota but no entitlement is issued yet. */
17+
status: "issued" | "issuing";
18+
quotaUnit: string;
19+
instanceId?: string;
20+
totalQuota?: number;
21+
availableQuota?: number;
22+
usedQuota?: number;
23+
/** Used ratio in percent with 0.1 precision, matching the console display. */
24+
usedPercent?: number;
25+
instanceStartTime?: number;
26+
instanceEndTime?: number;
27+
}
28+
29+
function readArray<T>(result: unknown, field: string): T[] {
30+
const response = unwrapResponse(result as Record<string, unknown>);
31+
const value = response[field];
32+
return Array.isArray(value) ? (value as T[]) : [];
33+
}
34+
35+
/**
36+
* Join the harness list with the issued entitlements, keeping the server order.
37+
* A harness carrying a resource pack but no matching entitlement is still
38+
* listed as `issuing` — issuance lags the purchase by a few minutes.
39+
*/
40+
function buildRows(
41+
items: HarnessBenefitItem[],
42+
equityInfos: TokenPlanEquityInfo[],
43+
): HarnessQuotaRow[] {
44+
const rows: HarnessQuotaRow[] = [];
45+
46+
for (const item of items) {
47+
// Only harnesses that carry a resource pack have an entitlement quota at all.
48+
if (item.hasResourcePack !== true) continue;
49+
50+
const planCodes = item.planCodes ?? [];
51+
const equity = equityInfos.find(
52+
(equityInfo) => equityInfo.equityType && planCodes.includes(equityInfo.equityType),
53+
);
54+
const planCode = equity?.equityType ?? planCodes[0] ?? "";
55+
const row: HarnessQuotaRow = {
56+
planCode,
57+
title: item.title ?? planCode,
58+
type: item.type ?? "",
59+
status: equity ? "issued" : "issuing",
60+
quotaUnit: item.quotaUnit ?? item.priceInfo?.unit ?? "",
61+
};
62+
63+
if (!equity) {
64+
rows.push(row);
65+
continue;
66+
}
67+
68+
if (equity.instanceId) row.instanceId = equity.instanceId;
69+
const totalQuota = readNumber(equity.totalQuota);
70+
if (totalQuota !== undefined) row.totalQuota = totalQuota;
71+
const availableQuota = readNumber(equity.availableQuota);
72+
if (availableQuota !== undefined) row.availableQuota = availableQuota;
73+
if (totalQuota !== undefined && availableQuota !== undefined) {
74+
row.usedQuota = Math.max(totalQuota - availableQuota, 0);
75+
if (totalQuota > 0) {
76+
row.usedPercent = Math.round((row.usedQuota / totalQuota) * 1000) / 10;
77+
}
78+
}
79+
const instanceStartTime = readNumber(equity.instanceStartTime);
80+
if (instanceStartTime !== undefined) row.instanceStartTime = instanceStartTime;
81+
const instanceEndTime = readNumber(equity.instanceEndTime);
82+
if (instanceEndTime !== undefined) row.instanceEndTime = instanceEndTime;
83+
84+
rows.push(row);
85+
}
86+
87+
return rows;
88+
}
89+
90+
function toSection(row: HarnessQuotaRow): QuotaSection {
91+
const section: QuotaSection = {
92+
label: `${row.title} (${row.planCode})`,
93+
emptyMessage:
94+
row.status === "issuing"
95+
? "Quota is being issued; issuance can take up to 5 minutes."
96+
: "No positive quota total reported; check the Bailian Token Plan console.",
97+
};
98+
if (row.status === "issuing") return section;
99+
100+
if (row.totalQuota !== undefined && row.usedQuota !== undefined) {
101+
const unitSuffix = row.quotaUnit ? ` ${row.quotaUnit}` : "";
102+
section.detail = `Used: ${formatNumber(row.usedQuota)} / ${formatNumber(row.totalQuota)}${unitSuffix}`;
103+
if (row.totalQuota > 0) section.percentage = row.usedQuota / row.totalQuota;
104+
}
105+
if (row.instanceEndTime !== undefined) section.resetTime = row.instanceEndTime;
106+
107+
return section;
108+
}
109+
110+
export default defineCommand({
111+
description: {
112+
"en-US": "Show Token Plan harness entitlement quota usage",
113+
"zh-CN": "查看 Token Plan harness 权益额度用量",
114+
},
115+
auth: "console",
116+
usageArgs: "[flags]",
117+
flags: {
118+
type: {
119+
type: "string",
120+
valueHint: "<type>",
121+
choices: ["official_tool", "infrastructure"] as const,
122+
description: {
123+
"en-US": "Filter harness list by type: official_tool, infrastructure",
124+
"zh-CN": "按类型筛选 harness 列表:official_tool、infrastructure",
125+
},
126+
},
127+
},
128+
exampleArgs: ["", "--type official_tool", "--output json"],
129+
async run(ctx) {
130+
const { settings, flags } = ctx;
131+
const format = detectOutputFormat(settings.output);
132+
const benefitsData = flags.type ? { type: flags.type } : {};
133+
const equityData = { queryTokenPlanEquityInfoRequest: { requestId: randomUUID() } };
134+
135+
if (settings.dryRun) {
136+
emitResult(
137+
{
138+
requests: [
139+
{ api: HARNESS_LIST_API, data: benefitsData },
140+
{ api: EQUITY_INFO_API, data: equityData },
141+
],
142+
},
143+
format,
144+
);
145+
return;
146+
}
147+
148+
const [benefitsResult, equityResult] = await Promise.all([
149+
ctx.client.console(HARNESS_LIST_API, benefitsData),
150+
ctx.client.console(EQUITY_INFO_API, equityData),
151+
]);
152+
const rows = buildRows(
153+
readArray<HarnessBenefitItem>(benefitsResult, "items"),
154+
readArray<TokenPlanEquityInfo>(equityResult, "tokenPlanEquityInfos"),
155+
);
156+
157+
if (format === "json") {
158+
emitResult({ generatedAt: Date.now(), items: rows }, format);
159+
return;
160+
}
161+
162+
if (rows.length === 0) {
163+
process.stdout.write("No harness with entitlement quota found.\n");
164+
return;
165+
}
166+
167+
printQuotaBox("Token Plan Harness Quota", rows.map(toSection), Date.now());
168+
},
169+
});

packages/commands/src/commands/token-plan/types.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,25 @@ export interface AddOrganizationMemberResponse {
6565
SeatAssigned?: boolean;
6666
};
6767
}
68+
69+
// Console gateway payloads keep the server's camelCase keys, unlike the
70+
// PascalCase OpenAPI shapes above.
71+
72+
export interface HarnessBenefitItem {
73+
type?: string;
74+
hasResourcePack?: boolean;
75+
planCodes?: string[];
76+
title?: string;
77+
quotaUnit?: string;
78+
priceInfo?: { unit?: string } | null;
79+
}
80+
81+
export interface TokenPlanEquityInfo {
82+
instanceId?: string;
83+
templateCode?: string;
84+
equityType?: string;
85+
totalQuota?: number;
86+
availableQuota?: number;
87+
instanceStartTime?: number;
88+
instanceEndTime?: number;
89+
}

packages/commands/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ export { default as tokenPlanListSeats } from "./commands/token-plan/list-seats.
137137
export { default as tokenPlanCreateKey } from "./commands/token-plan/create-key.ts";
138138
export { default as tokenPlanAssignSeats } from "./commands/token-plan/assign-seats.ts";
139139
export { default as tokenPlanAddMember } from "./commands/token-plan/add-member.ts";
140+
export { default as tokenPlanHarnessQuota } from "./commands/token-plan/harness-quota.ts";
140141
export { default as managedAgentInit } from "./commands/managed-agent/init.ts";
141142
export { default as managedAgentValidate } from "./commands/managed-agent/validate.ts";
142143
export { default as managedAgentPlan } from "./commands/managed-agent/plan.ts";

packages/commands/tests/e2e/token-plan.e2e.test.ts

Lines changed: 96 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
import { describe, expect, test } from "vite-plus/test";
2-
import { makeE2eOutputDir, parseStdoutJson, runCommandHelp, runCommandE2e } from "./helpers.ts";
2+
import {
3+
isConsoleAuthFailure,
4+
isConsoleE2EReady,
5+
makeE2eOutputDir,
6+
parseStdoutJson,
7+
runCommandHelp,
8+
runCommandE2e,
9+
} from "./helpers.ts";
310
import { TOKEN_PLAN_ROUTES } from "./topic-routes.ts";
411

512
describe("e2e: token-plan", () => {
@@ -60,4 +67,92 @@ describe("e2e: token-plan", () => {
6067
expect(stderr).toMatch(/ALIBABA_CLOUD_ACCESS_KEY_ID/);
6168
expect(stderr).not.toMatch(/auth login --api-key/);
6269
});
70+
71+
test("token-plan harness-quota help 展示 --type 与 console 鉴权域 flags", async () => {
72+
const { stderr, exitCode } = await runCommandHelp(TOKEN_PLAN_ROUTES, [
73+
"token-plan",
74+
"harness-quota",
75+
"--help",
76+
]);
77+
expect(exitCode, stderr).toBe(0);
78+
expect(stderr).toMatch(/--type <official_tool\|infrastructure>/);
79+
expect(stderr).toMatch(/--console-region/);
80+
expect(stderr).not.toMatch(/--access-key-id/);
81+
});
82+
});
83+
84+
describe.skipIf(!isConsoleE2EReady())("e2e: token-plan harness-quota(Console)", () => {
85+
test("harness-quota --dry-run 输出两个网关请求计划", async () => {
86+
const { stdout, stderr, exitCode } = await runCommandE2e(TOKEN_PLAN_ROUTES, [
87+
"token-plan",
88+
"harness-quota",
89+
"--dry-run",
90+
"--output",
91+
"json",
92+
]);
93+
expect(exitCode, stderr).toBe(0);
94+
const data = parseStdoutJson<{
95+
requests?: Array<{ api?: string; data?: Record<string, unknown> }>;
96+
}>(stdout);
97+
expect(data.requests?.[0]?.api).toBe("zeldaEasy.broadscope-bailian.token-plan.detail");
98+
expect(data.requests?.[1]?.api).toBe(
99+
"zeldaEasy.bailian-commerce.tokenPlan.queryTokenPlanEquityInfo",
100+
);
101+
expect(data.requests?.[0]?.data).toEqual({});
102+
});
103+
104+
test("harness-quota --type 透传给 harness 列表接口", async () => {
105+
const { stdout, stderr, exitCode } = await runCommandE2e(TOKEN_PLAN_ROUTES, [
106+
"token-plan",
107+
"harness-quota",
108+
"--type",
109+
"official_tool",
110+
"--dry-run",
111+
"--output",
112+
"json",
113+
]);
114+
expect(exitCode, stderr).toBe(0);
115+
const data = parseStdoutJson<{ requests?: Array<{ data?: Record<string, unknown> }> }>(stdout);
116+
expect(data.requests?.[0]?.data).toEqual({ type: "official_tool" });
117+
});
118+
119+
test("harness-quota --output json 返回权益额度条目", async () => {
120+
const result = await runCommandE2e(TOKEN_PLAN_ROUTES, [
121+
"token-plan",
122+
"harness-quota",
123+
"--output",
124+
"json",
125+
]);
126+
if (isConsoleAuthFailure(result)) return;
127+
expect(result.exitCode, result.stderr).toBe(0);
128+
const data = parseStdoutJson<{
129+
generatedAt?: number;
130+
items?: Array<{
131+
planCode?: string;
132+
status?: string;
133+
totalQuota?: number;
134+
availableQuota?: number;
135+
usedQuota?: number;
136+
usedPercent?: number;
137+
}>;
138+
}>(result.stdout);
139+
expect(Array.isArray(data.items)).toBe(true);
140+
for (const item of data.items ?? []) {
141+
expect(item.planCode).toBeTypeOf("string");
142+
expect(["issued", "issuing"]).toContain(item.status);
143+
const numbers = [item.totalQuota, item.availableQuota, item.usedQuota, item.usedPercent];
144+
for (const value of numbers) {
145+
if (value !== undefined) expect(value).toBeTypeOf("number");
146+
}
147+
}
148+
});
149+
150+
test("harness-quota 默认渲染额度框或空态文案", async () => {
151+
const result = await runCommandE2e(TOKEN_PLAN_ROUTES, ["token-plan", "harness-quota"]);
152+
if (isConsoleAuthFailure(result)) return;
153+
expect(result.exitCode, result.stderr).toBe(0);
154+
expect(result.stdout).toMatch(
155+
/Token Plan Harness Quota|No harness with entitlement quota found/,
156+
);
157+
});
63158
});

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ export const TOKEN_PLAN_ROUTES: E2eRouteExports = {
177177
"token-plan create-key": "tokenPlanCreateKey",
178178
"token-plan assign-seats": "tokenPlanAssignSeats",
179179
"token-plan add-member": "tokenPlanAddMember",
180+
"token-plan harness-quota": "tokenPlanHarnessQuota",
180181
};
181182

182183
export const SKILL_ROUTES: E2eRouteExports = {

0 commit comments

Comments
 (0)