From 69eb7594903e05bfcab53f9aa9efe06bf69cb938 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=95=85=E7=92=83?= Date: Wed, 12 Aug 2026 16:30:08 +0800 Subject: [PATCH 1/2] refactor(usage): consolidate shared poll logic; migrate freeTrial APIs to bailian-commerce Dedup: - shared.ts: extract generic pollConsoleUntilDone (request-builder callback absorbs each wrapper convention); pollTelemetryApi becomes a thin wrapper; add pollFreeTierBatch - freetier.ts / stats.ts: drop inline duplicates of extractResponseData, polling, model-list paging, free-tier extractors and usage label maps; import from shared.ts (behaviour unchanged: freetier keeps its 20-poll budget, telemetry keeps 30) Endpoint migration (broadscope-bailian.freeTrial -> bailian-commerce.freeTrial): - queryFreeTierQuota, queryFreeTierOnlyStatus, batchActivateFreeTierOnly, batchDeactivateFreeTierOnly - update the console call example and the gateway doc comment to match Note: verified statically and via dry-run; live calls pending a fresh console login (session expired). --- .../commands/src/commands/console/call.ts | 2 +- .../commands/src/commands/usage/freetier.ts | 111 ++--------- .../commands/src/commands/usage/shared.ts | 55 ++++-- packages/commands/src/commands/usage/stats.ts | 180 ++---------------- packages/core/src/console/gateway.ts | 2 +- skills/bailian-cli/reference/console.md | 2 +- 6 files changed, 79 insertions(+), 273 deletions(-) diff --git a/packages/commands/src/commands/console/call.ts b/packages/commands/src/commands/console/call.ts index 28fb8473..0a6a8791 100644 --- a/packages/commands/src/commands/console/call.ts +++ b/packages/commands/src/commands/console/call.ts @@ -25,7 +25,7 @@ export default defineCommand({ }, }, exampleArgs: [ - `--api zeldaEasy.broadscope-bailian.freeTrial.queryFreeTierQuota --data '{"queryFreeTierQuotaRequest":{"models":["qwen3-max"]}}'`, + `--api zeldaEasy.bailian-commerce.freeTrial.queryFreeTierQuota --data '{"queryFreeTierQuotaRequest":{"models":["qwen3-max"]}}'`, `--api some.api.name --data '{"key":"value"}' --console-region cn-beijing`, ], async run(ctx) { diff --git a/packages/commands/src/commands/usage/freetier.ts b/packages/commands/src/commands/usage/freetier.ts index 0166fb8a..e503d6d7 100644 --- a/packages/commands/src/commands/usage/freetier.ts +++ b/packages/commands/src/commands/usage/freetier.ts @@ -1,95 +1,22 @@ -import { defineCommand, detectOutputFormat, fetchModelList, type Client } from "bailian-cli-core"; +import { defineCommand, detectOutputFormat, unwrapResponse } from "bailian-cli-core"; import { emitResult } from "bailian-cli-runtime"; - -const ACTIVATE_API = "zeldaEasy.broadscope-bailian.freeTrial.batchActivateFreeTierOnly"; -const DEACTIVATE_API = "zeldaEasy.broadscope-bailian.freeTrial.batchDeactivateFreeTierOnly"; -const FREE_TIER_API = "zeldaEasy.broadscope-bailian.freeTrial.queryFreeTierQuota"; -const FREE_TIER_ONLY_STATUS_API = "zeldaEasy.broadscope-bailian.freeTrial.queryFreeTierOnlyStatus"; - -interface FreeTierQuota { - model: string; - quotaTotal: number; - quotaInitTotal: number; -} - -interface FreeTierOnlyStatus { - model: string; - freeTierOnly: boolean; -} +import { + FREE_TIER_API, + FREE_TIER_ONLY_STATUS_API, + extractFreeTierOnlyStatuses, + extractQuotas, + fetchAllModels, + pollFreeTierBatch, +} from "./shared.ts"; + +const ACTIVATE_API = "zeldaEasy.bailian-commerce.freeTrial.batchActivateFreeTierOnly"; +const DEACTIVATE_API = "zeldaEasy.bailian-commerce.freeTrial.batchDeactivateFreeTierOnly"; interface BatchResultFailure { failureModelId: string; errorCode: string; } -function getNestedRecord( - obj: Record, - key: string, -): Record | undefined { - const val = obj[key]; - if (val && typeof val === "object" && !Array.isArray(val)) return val as Record; - return undefined; -} - -function extractResponseData(result: Record): Record { - const data = getNestedRecord(result, "data"); - if (!data) return result; - - const dataV2 = getNestedRecord(data, "DataV2"); - if (dataV2) { - const inner = getNestedRecord(dataV2, "data"); - const innerData = inner ? getNestedRecord(inner, "data") : undefined; - return innerData ?? inner ?? dataV2; - } - - const direct = getNestedRecord(data, "data"); - return direct ?? data; -} - -const POLL_INTERVAL_MS = 500; -const MAX_POLLS = 20; - -async function pollUntilDone( - client: Client, - api: string, - requestKey: string, - models: string[], -): Promise { - let nextTaskId: string | undefined; - - for (let attempt = 0; attempt < MAX_POLLS; attempt++) { - const requestData = { - [requestKey]: nextTaskId ? { taskId: nextTaskId } : { models }, - }; - - const raw = await client.console(api, requestData); - - const resp = extractResponseData(raw as Record); - if (resp.taskId && Object.keys(resp).length === 1) { - nextTaskId = resp.taskId as string; - await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS)); - continue; - } - return raw; - } - return null; -} - -async function fetchAllModelNames(client: Client): Promise { - const allModels: Record[] = []; - let page = 1; - while (true) { - const result = await fetchModelList((api, data) => client.console(api, data), { - pageNo: page, - pageSize: 50, - }); - allModels.push(...result.models); - if (allModels.length >= result.total) break; - page++; - } - return allModels.map((item) => item.model as string).filter(Boolean); -} - export default defineCommand({ description: "Enable or disable auto-stop for free-tier models. Enables by default; use --off to disable", @@ -161,7 +88,7 @@ export default defineCommand({ } if (!modelFlag) { - models = await fetchAllModelNames(ctx.client); + models = (await fetchAllModels(ctx.client)).map((model) => model.name); } if (off) { @@ -172,12 +99,10 @@ export default defineCommand({ }), ]); - const quotaData = extractResponseData(quotaResult as Record); - const quotas = (quotaData.freeTierQuotas ?? []) as FreeTierQuota[]; + const quotas = extractQuotas(quotaResult); const quotaMap = new Map(quotas.map((quota) => [quota.model, quota])); - const stopData = extractResponseData(stopResult as Record); - const stopStatuses = (stopData.freeTierOnlyStatuses ?? []) as FreeTierOnlyStatus[]; + const stopStatuses = extractFreeTierOnlyStatuses(stopResult); const stopMap = new Map(stopStatuses.map((status) => [status.model, status.freeTierOnly])); for (const name of models) { @@ -192,7 +117,7 @@ export default defineCommand({ ); continue; } - await pollUntilDone(ctx.client, api, requestKey, [name]); + await pollFreeTierBatch(ctx.client, api, requestKey, [name]); process.stdout.write(`Disabled auto-stop for "${name}".\n`); } return; @@ -200,13 +125,13 @@ export default defineCommand({ const jsonResults: unknown[] = []; for (const name of models) { - const result = await pollUntilDone(ctx.client, api, requestKey, [name]); + const result = await pollFreeTierBatch(ctx.client, api, requestKey, [name]); if (format === "json") { jsonResults.push(result); continue; } if (result) { - const resultData = extractResponseData(result as Record); + const resultData = unwrapResponse(result as Record); const failureModels = (resultData.failureModels as BatchResultFailure[]) ?? []; if (failureModels.length > 0) { process.stderr.write( diff --git a/packages/commands/src/commands/usage/shared.ts b/packages/commands/src/commands/usage/shared.ts index ec75b0cc..93e32c2a 100644 --- a/packages/commands/src/commands/usage/shared.ts +++ b/packages/commands/src/commands/usage/shared.ts @@ -102,9 +102,9 @@ export async function fetchAllModels(client: Client): Promise { // Free-tier quota // --------------------------------------------------------------------------- -export const FREE_TIER_API = "zeldaEasy.broadscope-bailian.freeTrial.queryFreeTierQuota"; +export const FREE_TIER_API = "zeldaEasy.bailian-commerce.freeTrial.queryFreeTierQuota"; export const FREE_TIER_ONLY_STATUS_API = - "zeldaEasy.broadscope-bailian.freeTrial.queryFreeTierOnlyStatus"; + "zeldaEasy.bailian-commerce.freeTrial.queryFreeTierOnlyStatus"; export interface FreeTierQuota { model: string; @@ -257,22 +257,27 @@ export interface ListStatisticResponse { } const POLL_INTERVAL_MS = 500; -const MAX_POLLS = 30; +const DEFAULT_MAX_POLLS = 30; -export async function pollTelemetryApi( +/** + * Poll a console API until it returns a terminal (non task-id) response. + * The gateway answers an async request with a bare `{taskId}` envelope; the + * caller re-issues with that id until real data arrives or the budget runs out. + * `buildRequest` shapes each attempt (initial call vs. taskId follow-up) so the + * same loop serves every request-wrapper convention (telemetry `reqDTO`, + * free-tier batch `…Request`). + */ +export async function pollConsoleUntilDone( client: Client, api: string, - reqDTO: Record, + buildRequest: (taskId: string | undefined) => Record, + maxPolls = DEFAULT_MAX_POLLS, ): Promise { let nextTaskId: string | undefined; - for (let attempt = 0; attempt < MAX_POLLS; attempt++) { - const requestData = nextTaskId - ? { reqDTO: { ...reqDTO, asyncTaskId: nextTaskId } } - : { reqDTO }; - - const raw = await client.console(api, requestData); - const resp = extractResponseData(raw as Record); + for (let attempt = 0; attempt < maxPolls; attempt++) { + const raw = await client.console(api, buildRequest(nextTaskId)); + const resp = unwrapResponse(raw as Record); if (resp.taskId && Object.keys(resp).length === 1) { nextTaskId = resp.taskId as string; @@ -284,6 +289,32 @@ export async function pollTelemetryApi( return null; } +/** Telemetry APIs wrap the payload in `reqDTO` and echo the task id as `asyncTaskId`. */ +export async function pollTelemetryApi( + client: Client, + api: string, + reqDTO: Record, +): Promise { + return pollConsoleUntilDone(client, api, (taskId) => + taskId ? { reqDTO: { ...reqDTO, asyncTaskId: taskId } } : { reqDTO }, + ); +} + +/** Free-tier batch activate/deactivate wrap the payload in `requestKey` and echo `taskId`. */ +export async function pollFreeTierBatch( + client: Client, + api: string, + requestKey: string, + models: string[], +): Promise { + return pollConsoleUntilDone( + client, + api, + (taskId) => ({ [requestKey]: taskId ? { taskId } : { models } }), + 20, + ); +} + export function extractOverviewData(result: unknown): OverviewStatistic | undefined { const resp = extractResponseData(result as Record); if (resp.callSuccessCount !== undefined || resp.usages !== undefined) { diff --git a/packages/commands/src/commands/usage/stats.ts b/packages/commands/src/commands/usage/stats.ts index 0d45ac16..13cd0423 100644 --- a/packages/commands/src/commands/usage/stats.ts +++ b/packages/commands/src/commands/usage/stats.ts @@ -1,176 +1,26 @@ -import { - defineCommand, - BailianError, - ExitCode, - detectOutputFormat, - type Settings, - type Client, -} from "bailian-cli-core"; +import { defineCommand, BailianError, ExitCode, detectOutputFormat } from "bailian-cli-core"; import { ansi, emitResult } from "bailian-cli-runtime"; import { displayWidth, padEnd } from "bailian-cli-runtime"; - -const OVERVIEW_API = "zeldaEasy.bailian-telemetry.model.getModelUsageStatistic"; -const LIST_API = "zeldaEasy.bailian-telemetry.model.listModelUsageStatisticData"; - -interface UsageItem { - key: string; - value: number; - unit: string; -} - -interface OverviewStatistic { - callCount: number; - modelCount: number; - callSuccessCount: number; - usages: UsageItem[]; -} - -interface ModelStatisticItem { - model: string; - callSuccessCount: number; - usages?: UsageItem[]; - usage?: Record; -} - -interface ListStatisticResponse { - list: ModelStatisticItem[]; - totalCount: number; - maxResults: number; -} - -function getNestedRecord( - obj: Record, - key: string, -): Record | undefined { - const val = obj[key]; - if (val && typeof val === "object" && !Array.isArray(val)) return val as Record; - return undefined; -} - -function extractResponseData(result: Record): Record { - const data = getNestedRecord(result, "data"); - if (!data) return result; - - const dataV2 = getNestedRecord(data, "DataV2"); - if (dataV2) { - const inner = getNestedRecord(dataV2, "data"); - const innerData = inner ? getNestedRecord(inner, "data") : undefined; - return innerData ?? inner ?? dataV2; - } - - const direct = getNestedRecord(data, "data"); - return direct ?? data; -} - -const POLL_INTERVAL_MS = 500; -const MAX_POLLS = 30; - -async function pollTelemetryApi( - client: Client, - api: string, - reqDTO: Record, -): Promise { - let nextTaskId: string | undefined; - - for (let attempt = 0; attempt < MAX_POLLS; attempt++) { - const requestData = nextTaskId - ? { reqDTO: { ...reqDTO, asyncTaskId: nextTaskId } } - : { reqDTO }; - - const raw = await client.console(api, requestData); - - const resp = extractResponseData(raw as Record); - - if (resp.taskId && Object.keys(resp).length === 1) { - nextTaskId = resp.taskId as string; - await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS)); - continue; - } - - return raw; - } - return null; -} - -function requireWorkspaceId(settings: Settings, binName: string): string { - if (settings.workspaceId) return settings.workspaceId; - - throw new BailianError( - `workspace-id is required. Set via --workspace-id, BAILIAN_WORKSPACE_ID, or \`${binName} config set workspace_id \`.`, - ExitCode.GENERAL, - `Run \`${binName} workspace list\` to view available workspaces.`, - ); -} - -function formatNumber(num: number): string { - return num.toLocaleString("en-US"); -} - -function formatDate(ts: number): string { - const date = new Date(ts); - const year = date.getFullYear(); - const month = String(date.getMonth() + 1).padStart(2, "0"); - const day = String(date.getDate()).padStart(2, "0"); - return `${year}-${month}-${day}`; -} - -function extractOverviewData(result: unknown): OverviewStatistic | undefined { - const resp = extractResponseData(result as Record); - if (resp.callSuccessCount !== undefined || resp.usages !== undefined) { - return resp as unknown as OverviewStatistic; - } - return undefined; -} - -function extractListData(result: unknown): ListStatisticResponse { - const resp = extractResponseData(result as Record); - const list = (resp.list as ModelStatisticItem[]) ?? []; - const totalCount = (resp.totalCount as number) ?? 0; - const maxResults = (resp.maxResults as number) ?? 0; - return { list, totalCount, maxResults }; -} - -function resolveUsageMap(item: ModelStatisticItem): Record { - const out: Record = {}; - if (item.usages && Array.isArray(item.usages)) { - for (const entry of item.usages) { - if (entry.key && entry.value != null) { - out[entry.key] = entry.value; - } - } - } - if (item.usage && typeof item.usage === "object") { - for (const [key, val] of Object.entries(item.usage)) { - if (val != null) out[key] = val; - } - } - return out; -} +import { + LIST_API, + OVERVIEW_API, + USAGE_KEY_LABELS, + extractListData, + extractOverviewData, + formatDate, + formatNumber, + pollTelemetryApi, + requireWorkspaceId, + resolveUsageMap, + type ModelStatisticItem, + type OverviewStatistic, +} from "./shared.ts"; interface UsageLabel { en: string; unit?: string; } -const USAGE_KEY_LABELS: Record = { - 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: "sec" }, - content_duration: { en: "Audio Duration", unit: "sec" }, - 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.en}${unitSuffix}`; diff --git a/packages/core/src/console/gateway.ts b/packages/core/src/console/gateway.ts index 38c7d9b7..7881a7b7 100644 --- a/packages/core/src/console/gateway.ts +++ b/packages/core/src/console/gateway.ts @@ -58,7 +58,7 @@ export function effectiveConsoleGatewayConfig( } export interface ConsoleGatewayRequest { - /** Console API name, e.g. zeldaEasy.broadscope-bailian.freeTrial.queryFreeTierQuota */ + /** Console API name, e.g. zeldaEasy.bailian-commerce.freeTrial.queryFreeTierQuota */ api: string; data: Record; } diff --git a/skills/bailian-cli/reference/console.md b/skills/bailian-cli/reference/console.md index 5e073abd..5030a444 100644 --- a/skills/bailian-cli/reference/console.md +++ b/skills/bailian-cli/reference/console.md @@ -35,7 +35,7 @@ Index: [index.md](index.md) #### Examples ```bash -bl console call --api zeldaEasy.broadscope-bailian.freeTrial.queryFreeTierQuota --data '{"queryFreeTierQuotaRequest":{"models":["qwen3-max"]}}' +bl console call --api zeldaEasy.bailian-commerce.freeTrial.queryFreeTierQuota --data '{"queryFreeTierQuotaRequest":{"models":["qwen3-max"]}}' ``` ```bash From 61d9a741667e818be6ba05fe822d6f1bebccbebe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=95=85=E7=92=83?= Date: Wed, 12 Aug 2026 17:05:18 +0800 Subject: [PATCH 2/2] fix: 1.14.3 --- packages/cli/package.json | 2 +- packages/commands/package.json | 2 +- packages/core/package.json | 2 +- packages/kscli/package.json | 2 +- packages/runtime/package.json | 2 +- pnpm-lock.yaml | 6 +++--- skills/bailian-cli/SKILL.md | 2 +- skills/bailian-finetune/SKILL.md | 2 +- skills/bailian-gen/SKILL.md | 2 +- skills/bailian-managed-agent/SKILL.md | 2 +- skills/bailian-protocol/SKILL.md | 2 +- 11 files changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index d57360f3..744bd614 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "bailian-cli", - "version": "1.14.2", + "version": "1.14.3", "description": "CLI for Aliyun Model Studio (DashScope) AI Platform.", "keywords": [ "agent", diff --git a/packages/commands/package.json b/packages/commands/package.json index 76d50640..68280322 100644 --- a/packages/commands/package.json +++ b/packages/commands/package.json @@ -1,6 +1,6 @@ { "name": "bailian-cli-commands", - "version": "1.14.2", + "version": "1.14.3", "description": "Command library for bailian-cli products (knowledge, memory, media, …). See https://www.npmjs.com/package/bailian-cli for usage.", "homepage": "https://bailian.console.aliyun.com/cli", "bugs": { diff --git a/packages/core/package.json b/packages/core/package.json index 3634b292..6446b4df 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "bailian-cli-core", - "version": "1.14.2", + "version": "1.14.3", "description": "Core SDK for bailian-cli. See https://www.npmjs.com/package/bailian-cli for usage.", "homepage": "https://bailian.console.aliyun.com/cli", "bugs": { diff --git a/packages/kscli/package.json b/packages/kscli/package.json index 02ed56d5..59382f27 100644 --- a/packages/kscli/package.json +++ b/packages/kscli/package.json @@ -1,6 +1,6 @@ { "name": "knowledge-studio-cli", - "version": "1.14.2", + "version": "1.14.3", "description": "Lightweight RAG CLI for Aliyun Model Studio — focused on knowledge-base retrieval.", "keywords": [ "alibaba-cloud", diff --git a/packages/runtime/package.json b/packages/runtime/package.json index 79cc59d0..3f5feeef 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -1,6 +1,6 @@ { "name": "bailian-cli-runtime", - "version": "1.14.2", + "version": "1.14.3", "description": "Runtime framework for bailian-cli (createCli, registry, args, output, pipeline). See https://www.npmjs.com/package/bailian-cli for usage.", "homepage": "https://bailian.console.aliyun.com/cli", "bugs": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index eb698a35..d3dd398e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -24,12 +24,12 @@ catalogs: chalk: specifier: ^5.6.2 version: 5.6.2 - tar-stream: - specifier: ^3.2.0 - version: 3.2.0 smol-toml: specifier: ^1.4.2 version: 1.7.0 + tar-stream: + specifier: ^3.2.0 + version: 3.2.0 tsx: specifier: ^4.23.0 version: 4.23.0 diff --git a/skills/bailian-cli/SKILL.md b/skills/bailian-cli/SKILL.md index 0d2ae3da..946d02a4 100644 --- a/skills/bailian-cli/SKILL.md +++ b/skills/bailian-cli/SKILL.md @@ -1,7 +1,7 @@ --- name: bailian-cli metadata: - version: "1.14.2" + version: "1.14.3" requires: bins: ["bl"] description: >- diff --git a/skills/bailian-finetune/SKILL.md b/skills/bailian-finetune/SKILL.md index d6cc6c07..1248ab92 100644 --- a/skills/bailian-finetune/SKILL.md +++ b/skills/bailian-finetune/SKILL.md @@ -1,7 +1,7 @@ --- name: bailian-finetune metadata: - version: "1.14.2" + version: "1.14.3" requires: bins: ["bl"] description: >- diff --git a/skills/bailian-gen/SKILL.md b/skills/bailian-gen/SKILL.md index 233e99b8..14297c2f 100644 --- a/skills/bailian-gen/SKILL.md +++ b/skills/bailian-gen/SKILL.md @@ -1,7 +1,7 @@ --- name: bailian-gen metadata: - version: "1.14.2" + version: "1.14.3" requires: bins: ["bl"] description: >- diff --git a/skills/bailian-managed-agent/SKILL.md b/skills/bailian-managed-agent/SKILL.md index 648d9918..5595a0fc 100644 --- a/skills/bailian-managed-agent/SKILL.md +++ b/skills/bailian-managed-agent/SKILL.md @@ -1,7 +1,7 @@ --- name: bailian-managed-agent metadata: - version: "1.14.2" + version: "1.14.3" requires: bins: ["bl"] description: >- diff --git a/skills/bailian-protocol/SKILL.md b/skills/bailian-protocol/SKILL.md index c924eed5..2232a4f7 100644 --- a/skills/bailian-protocol/SKILL.md +++ b/skills/bailian-protocol/SKILL.md @@ -1,7 +1,7 @@ --- name: bailian-protocol metadata: - version: "1.14.2" + version: "1.14.3" requires: bins: ["bl"] description: >-