diff --git a/packages/cli/tests/e2e/auth.e2e.test.ts b/packages/cli/tests/e2e/auth.e2e.test.ts index 7c46ab3..95a01bc 100644 --- a/packages/cli/tests/e2e/auth.e2e.test.ts +++ b/packages/cli/tests/e2e/auth.e2e.test.ts @@ -152,12 +152,10 @@ describe("e2e: auth", () => { expect(exitCode, stderr).toBe(0); const data = parseStdoutJson<{ authenticated?: boolean; - api_key?: { configured?: boolean }; - dashscope_commands?: { method?: string }; + api_key?: { source?: string; masked?: string; base_url?: string }; }>(stdout); expect(data.authenticated).toBe(true); - expect(data.api_key?.configured).toBe(true); - expect(data.dashscope_commands?.method).toBeDefined(); + expect(data.api_key?.source).toBeDefined(); }); test.skipIf(!isDashScopeE2EReady())( @@ -174,11 +172,9 @@ describe("e2e: auth", () => { "https://dashscope.aliyuncs.com", ]); expect(exitCode, stderr).toBe(0); - const data = parseStdoutJson<{ authenticated?: boolean; dashscope_commands?: unknown }>( - stdout, - ); + const data = parseStdoutJson<{ authenticated?: boolean; api_key?: unknown }>(stdout); expect(data.authenticated).toBe(true); - expect(data.dashscope_commands).toBeDefined(); + expect(data.api_key).toBeDefined(); }, ); }); diff --git a/packages/cli/tests/e2e/quota.e2e.test.ts b/packages/cli/tests/e2e/quota.e2e.test.ts index e2d3f6b..b2eae02 100644 --- a/packages/cli/tests/e2e/quota.e2e.test.ts +++ b/packages/cli/tests/e2e/quota.e2e.test.ts @@ -4,7 +4,6 @@ import { readConfigFile } from "bailian-cli-core"; function isConsoleE2EReady(): boolean { if (!isBailianE2EEnabled()) return false; - if (process.env.DASHSCOPE_ACCESS_TOKEN?.trim()) return true; try { const config = readConfigFile(); return typeof config.access_token === "string" && config.access_token.length > 0; diff --git a/packages/cli/tests/e2e/usage-free.e2e.test.ts b/packages/cli/tests/e2e/usage-free.e2e.test.ts index 7998758..d83ef1d 100644 --- a/packages/cli/tests/e2e/usage-free.e2e.test.ts +++ b/packages/cli/tests/e2e/usage-free.e2e.test.ts @@ -4,7 +4,6 @@ import { readConfigFile } from "bailian-cli-core"; function isConsoleE2EReady(): boolean { if (!isBailianE2EEnabled()) return false; - if (process.env.DASHSCOPE_ACCESS_TOKEN?.trim()) return true; try { const config = readConfigFile(); return typeof config.access_token === "string" && config.access_token.length > 0; diff --git a/packages/cli/tests/e2e/usage-stats.e2e.test.ts b/packages/cli/tests/e2e/usage-stats.e2e.test.ts index 2f1af38..22b9931 100644 --- a/packages/cli/tests/e2e/usage-stats.e2e.test.ts +++ b/packages/cli/tests/e2e/usage-stats.e2e.test.ts @@ -4,7 +4,6 @@ import { readConfigFile } from "bailian-cli-core"; function isConsoleE2EReady(): boolean { if (!isBailianE2EEnabled()) return false; - if (process.env.DASHSCOPE_ACCESS_TOKEN?.trim()) return true; try { const config = readConfigFile(); return typeof config.access_token === "string" && config.access_token.length > 0; diff --git a/packages/commands/src/commands/advisor/recommend.ts b/packages/commands/src/commands/advisor/recommend.ts index f07364d..29b6eb2 100644 --- a/packages/commands/src/commands/advisor/recommend.ts +++ b/packages/commands/src/commands/advisor/recommend.ts @@ -230,7 +230,8 @@ export default defineCommand({ '--message "Low-cost high-concurrency online customer service" --output json', '--message "Long document summarization" --dry-run', ], - async run(config, flags) { + async run(ctx) { + const { config, flags } = ctx; const userInput = flags.message; const top = 3; diff --git a/packages/commands/src/commands/app/call.ts b/packages/commands/src/commands/app/call.ts index 43d1933..d8bbddd 100644 --- a/packages/commands/src/commands/app/call.ts +++ b/packages/commands/src/commands/app/call.ts @@ -1,8 +1,6 @@ import { defineCommand, - request, - requestJson, - appCompletionEndpoint, + appCompletionPath, parseSSE, detectOutputFormat, type AppCompletionRequest, @@ -65,7 +63,8 @@ export default defineCommand({ '--app-id abc123 --prompt "Search for materials" --pipeline-ids pipe1,pipe2', '--app-id abc123 --prompt "Start" --biz-params \'{"key":"value"}\'', ], - async run(config, flags) { + async run(ctx) { + const { config, flags } = ctx; const appId = flags.appId; const prompt = flags.prompt; @@ -121,16 +120,14 @@ export default defineCommand({ } if (config.dryRun) { - emitResult({ endpoint: appCompletionEndpoint(config.baseUrl, appId), request: body }, format); + emitResult({ endpoint: ctx.client.url(appCompletionPath(appId)), request: body }, format); return; } - const url = appCompletionEndpoint(config.baseUrl, appId); - if (shouldStream) { const headers: Record = { "X-DashScope-SSE": "enable" }; - const res = await request(config, { - url, + const res = await ctx.client.request({ + path: appCompletionPath(appId), method: "POST", body, headers, @@ -188,8 +185,8 @@ export default defineCommand({ process.stdout.write("\n"); } } else { - const response = await requestJson(config, { - url, + const response = await ctx.client.requestJson({ + path: appCompletionPath(appId), method: "POST", body, }); diff --git a/packages/commands/src/commands/app/list.ts b/packages/commands/src/commands/app/list.ts index da1bced..66745a5 100644 --- a/packages/commands/src/commands/app/list.ts +++ b/packages/commands/src/commands/app/list.ts @@ -1,9 +1,4 @@ -import { - defineCommand, - callConsoleGateway, - resolveConsoleGatewayCredential, - detectOutputFormat, -} from "bailian-cli-core"; +import { defineCommand, detectOutputFormat } from "bailian-cli-core"; import { emitResult } from "bailian-cli-runtime"; const APP_LIST_API = "zeldaEasy.broadscope-bailian.app-control.list"; @@ -37,14 +32,13 @@ export default defineCommand({ consoleSwitchAgent: { type: "number", valueHint: "", description: "Switch agent UID" }, }, exampleArgs: ["", "--name customer service", "--page 2 --page-size 10", "--output json"], - async run(config, flags) { + async run(ctx) { + const { config, flags } = ctx; const name = flags.name || ""; const pageNo = flags.page || 1; const pageSize = flags.pageSize || 30; const format = detectOutputFormat(config.output); - const credential = await resolveConsoleGatewayCredential(config); - const data = { reqDTO: { name, @@ -57,14 +51,11 @@ export default defineCommand({ }; if (config.dryRun) { - emitResult({ api: APP_LIST_API, data, token: credential.token.slice(0, 8) + "..." }, format); + emitResult({ api: APP_LIST_API, data }, format); return; } - const result = (await callConsoleGateway(config, credential.token, { - api: APP_LIST_API, - data, - })) as any; + const result = await ctx.client.console(APP_LIST_API, data); const list: unknown[] = result?.data?.DataV2?.data?.data?.list ?? []; const total: number = result?.data?.DataV2?.data?.data?.total ?? 0; diff --git a/packages/commands/src/commands/auth/login-console.ts b/packages/commands/src/commands/auth/login-console.ts index bfe3193..eb121f8 100644 --- a/packages/commands/src/commands/auth/login-console.ts +++ b/packages/commands/src/commands/auth/login-console.ts @@ -5,7 +5,7 @@ import http from "node:http"; import { BailianError, ExitCode, - chatEndpoint, + chatPath, getConfigPath, readConfigFile, requestJson, @@ -406,7 +406,7 @@ export async function validateAndPersistApiKey( process.stderr.write("Testing key... "); const testConfig = { ...config, apiKey: key, baseUrl }; const requestOpts = { - url: chatEndpoint(testConfig.baseUrl), + url: testConfig.baseUrl + chatPath(), method: "POST", timeout: Math.min(config.timeout, 30), body: { diff --git a/packages/commands/src/commands/auth/login.ts b/packages/commands/src/commands/auth/login.ts index f5b2e49..a0d4381 100644 --- a/packages/commands/src/commands/auth/login.ts +++ b/packages/commands/src/commands/auth/login.ts @@ -26,7 +26,8 @@ export default defineCommand({ }, exampleArgs: ["--api-key sk-xxxxx", "--console"], validate: (f) => (!f.console && !f.apiKey ? "Provide --api-key or --console" : undefined), - async run(config, flags) { + async run(ctx) { + const { config, flags } = ctx; if (flags.console) { if (config.dryRun) { emitBare( diff --git a/packages/commands/src/commands/auth/logout.ts b/packages/commands/src/commands/auth/logout.ts index 6c8ab47..f2d4990 100644 --- a/packages/commands/src/commands/auth/logout.ts +++ b/packages/commands/src/commands/auth/logout.ts @@ -27,7 +27,8 @@ export default defineCommand({ yes: { type: "switch", description: "Skip confirmation prompt" }, }, exampleArgs: ["", "--console", "--dry-run", "--yes"], - async run(config, flags) { + async run(ctx) { + const { config, flags } = ctx; const file = readConfigFile(); if (flags.console) { diff --git a/packages/commands/src/commands/auth/status.ts b/packages/commands/src/commands/auth/status.ts index 8e52253..1fcd705 100644 --- a/packages/commands/src/commands/auth/status.ts +++ b/packages/commands/src/commands/auth/status.ts @@ -1,142 +1,7 @@ -import { - defineCommand, - resolveCredential, - resolveConsoleGatewayCredential, - detectOutputFormat, - maskToken, - type Config, - type ResolvedCredential, -} from "bailian-cli-core"; +import { defineCommand, describeAuth, detectOutputFormat, maskToken } from "bailian-cli-core"; import { emitResult, emitBare } from "bailian-cli-runtime"; import { API_KEY_PAGE } from "bailian-cli-runtime"; -interface StoredCredential { - configured: boolean; - source?: string; - masked?: string; -} - -interface AuthStatusPayload { - api_key: StoredCredential; - access_token: StoredCredential; - dashscope_commands?: { method: string; source: string; masked: string }; - console_gateway_commands?: { method: string; source: string; masked: string }; -} - -function storedApiKey(config: Config): StoredCredential { - if (config.apiKey) { - return { configured: true, source: "flag", masked: maskToken(config.apiKey) }; - } - if (config.fileApiKey) { - return { configured: true, source: "config.json", masked: maskToken(config.fileApiKey) }; - } - const env = process.env.DASHSCOPE_API_KEY?.trim(); - if (env) { - return { configured: true, source: "DASHSCOPE_API_KEY", masked: maskToken(env) }; - } - return { configured: false }; -} - -function storedAccessToken(config: Config): StoredCredential { - if (config.accessTokenEnv) { - return { - configured: true, - source: "DASHSCOPE_ACCESS_TOKEN", - masked: maskToken(config.accessTokenEnv), - }; - } - if (config.fileAccessToken) { - return { - configured: true, - source: "config.json", - masked: maskToken(config.fileAccessToken), - }; - } - return { configured: false }; -} - -async function tryResolveDashscope(config: Config): Promise { - try { - return await resolveCredential(config); - } catch { - return undefined; - } -} - -async function tryResolveConsole(config: Config): Promise { - try { - return await resolveConsoleGatewayCredential(config); - } catch { - return undefined; - } -} - -async function buildStatus(config: Config): Promise { - const status: AuthStatusPayload = { - api_key: storedApiKey(config), - access_token: storedAccessToken(config), - }; - - const dashscope = await tryResolveDashscope(config); - if (dashscope) { - status.dashscope_commands = { - method: dashscope.method, - source: dashscope.source, - masked: maskToken(dashscope.token), - }; - } - - const consoleGw = await tryResolveConsole(config); - if (consoleGw) { - status.console_gateway_commands = { - method: consoleGw.method, - source: consoleGw.source, - masked: maskToken(consoleGw.token), - }; - } - - return status; -} - -function hasAnyAuth(status: AuthStatusPayload): boolean { - return ( - status.api_key.configured || - status.access_token.configured || - !!status.dashscope_commands || - !!status.console_gateway_commands - ); -} - -function emitTextStatus(status: AuthStatusPayload, config: Config): void { - emitBare("Authentication Status:"); - emitBare(" Stored credentials (can coexist):"); - if (status.api_key.configured) { - emitBare(` API key: ${status.api_key.source} ${status.api_key.masked}`); - } else { - emitBare(" API key: not configured"); - } - if (status.access_token.configured) { - emitBare(` Console token: ${status.access_token.source} ${status.access_token.masked}`); - } else { - emitBare(" Console token: not configured"); - } - emitBare(" Effective credential per command family:"); - if (status.dashscope_commands) { - emitBare( - ` DashScope API: ${status.dashscope_commands.method} (${status.dashscope_commands.source}) ${status.dashscope_commands.masked}`, - ); - } else { - emitBare(" DashScope API: unavailable"); - } - if (status.console_gateway_commands) { - emitBare( - ` Console gateway: ${status.console_gateway_commands.method} (${status.console_gateway_commands.source}) ${status.console_gateway_commands.masked}`, - ); - } else { - emitBare(` Console gateway: unavailable (run ${config.binName} auth login --console)`); - } -} - export default defineCommand({ description: "Show current authentication state", auth: "none", @@ -150,30 +15,62 @@ export default defineCommand({ }, consoleSwitchAgent: { type: "number", valueHint: "", description: "Switch agent UID" }, }, - async run(config, _flags) { + async run(ctx) { + const { config } = ctx; const format = detectOutputFormat(config.output); - const status = await buildStatus(config); + const auth = await describeAuth(config); - if (!hasAnyAuth(status)) { - const result = { - authenticated: false, - message: "Not authenticated.", - hint: [ - `DashScope API: ${config.binName} auth login --api-key or DASHSCOPE_API_KEY`, - `Console gateway: ${config.binName} auth login --console or DASHSCOPE_ACCESS_TOKEN`, - `Get API Key: ${API_KEY_PAGE}`, - ].join("\n"), - ...status, - }; - emitResult(result, format); + const apiKey = auth.apiKey + ? { + source: auth.apiKey.source, + masked: maskToken(auth.apiKey.token), + base_url: auth.apiKey.baseUrl, + } + : undefined; + const consoleCred = auth.console + ? { + source: auth.console.source, + masked: maskToken(auth.console.token), + region: auth.console.region, + site: auth.console.site, + } + : undefined; + + const authenticated = !!(apiKey || consoleCred); + + if (!authenticated) { + emitResult( + { + authenticated: false, + message: "Not authenticated.", + hint: [ + `API key (model): ${config.binName} auth login --api-key or DASHSCOPE_API_KEY`, + `Console gateway: ${config.binName} auth login --console`, + `Get API Key: ${API_KEY_PAGE}`, + ].join("\n"), + }, + format, + ); return; } if (format !== "text") { - emitResult({ authenticated: true, ...status }, format); + emitResult({ authenticated: true, api_key: apiKey, console: consoleCred }, format); return; } - emitTextStatus(status, config); + emitBare("Authentication Status:"); + if (apiKey) { + emitBare(` API key (model): ${apiKey.source} ${apiKey.masked}`); + } else { + emitBare(" API key (model): not configured"); + } + if (consoleCred) { + emitBare( + ` Console gateway: ${consoleCred.source} ${consoleCred.masked} (${consoleCred.region}, ${consoleCred.site})`, + ); + } else { + emitBare(` Console gateway: not configured (run ${config.binName} auth login --console)`); + } }, }); diff --git a/packages/commands/src/commands/config/set.ts b/packages/commands/src/commands/config/set.ts index fe75df0..adf0e0f 100644 --- a/packages/commands/src/commands/config/set.ts +++ b/packages/commands/src/commands/config/set.ts @@ -66,7 +66,8 @@ export default defineCommand({ "--key timeout --value 600", "--key base_url --value https://dashscope.aliyuncs.com", ], - async run(config, flags) { + async run(ctx) { + const { config, flags } = ctx; const key = flags.key; const value = flags.value; diff --git a/packages/commands/src/commands/config/show.ts b/packages/commands/src/commands/config/show.ts index 8a39abd..d746cb7 100644 --- a/packages/commands/src/commands/config/show.ts +++ b/packages/commands/src/commands/config/show.ts @@ -11,7 +11,8 @@ export default defineCommand({ description: "Display current configuration", auth: "none", exampleArgs: ["", "--output json"], - async run(config, _flags) { + async run(ctx) { + const { config } = ctx; const file = loadConfigFile(); const format = detectOutputFormat(config.output); diff --git a/packages/commands/src/commands/console/call.ts b/packages/commands/src/commands/console/call.ts index fda4939..d7b6048 100644 --- a/packages/commands/src/commands/console/call.ts +++ b/packages/commands/src/commands/console/call.ts @@ -1,12 +1,4 @@ -import { - defineCommand, - callConsoleGateway, - effectiveConsoleGatewayConfig, - resolveConsoleGatewayCredential, - CONSOLE_GATEWAY_NO_TOKEN_MESSAGE, - BailianError, - detectOutputFormat, -} from "bailian-cli-core"; +import { defineCommand, effectiveConsoleGatewayConfig, detectOutputFormat } from "bailian-cli-core"; import { emitResult } from "bailian-cli-runtime"; export default defineCommand({ @@ -38,7 +30,8 @@ export default defineCommand({ `--api zeldaEasy.broadscope-bailian.freeTrial.queryFreeTierQuota --data '{"queryFreeTierQuotaRequest":{"models":["qwen3-max"]}}'`, `--api some.api.name --data '{"key":"value"}' --console-region cn-beijing`, ], - async run(config, flags) { + async run(ctx) { + const { config, flags } = ctx; const api = flags.api; const dataRaw = flags.data; @@ -52,21 +45,11 @@ export default defineCommand({ const format = detectOutputFormat(config.output); - let token: string | undefined; - try { - token = (await resolveConsoleGatewayCredential(config)).token; - } catch (err) { - if (!(err instanceof BailianError && err.message === CONSOLE_GATEWAY_NO_TOKEN_MESSAGE)) { - throw err; - } - } - if (config.dryRun) { emitResult( { api, data, - token: token ? token.slice(0, 8) + "..." : null, ...effectiveConsoleGatewayConfig(config), }, format, @@ -74,10 +57,7 @@ export default defineCommand({ return; } - const result = await callConsoleGateway(config, token, { - api, - data, - }); + const result = await ctx.client.console(api, data); emitResult(result, format); }, diff --git a/packages/commands/src/commands/file/upload.ts b/packages/commands/src/commands/file/upload.ts index 74bdf08..a26069b 100644 --- a/packages/commands/src/commands/file/upload.ts +++ b/packages/commands/src/commands/file/upload.ts @@ -1,4 +1,4 @@ -import { defineCommand, resolveCredential, detectOutputFormat, uploadFile } from "bailian-cli-core"; +import { defineCommand, detectOutputFormat } from "bailian-cli-core"; import { emitResult, emitBare } from "bailian-cli-runtime"; export default defineCommand({ @@ -25,7 +25,8 @@ export default defineCommand({ "--file audio.wav --model qwen3-asr-flash", "--file cat.png --model qwen-image-2.0", ], - async run(config, flags) { + async run(ctx) { + const { config, flags } = ctx; const filePath = flags.file; const model = flags.model; @@ -36,14 +37,7 @@ export default defineCommand({ return; } - // Resolve API key for upload - const credential = await resolveCredential(config); - - const ossUrl = await uploadFile({ - apiKey: credential.token, - model, - filePath, - }); + const ossUrl = await ctx.client.uploadFile(filePath, model); if (config.quiet) { emitBare(ossUrl); diff --git a/packages/commands/src/commands/image/edit.ts b/packages/commands/src/commands/image/edit.ts index 676bf3f..b52c472 100644 --- a/packages/commands/src/commands/image/edit.ts +++ b/packages/commands/src/commands/image/edit.ts @@ -1,10 +1,7 @@ import { defineCommand, - requestJson, - imageSyncEndpoint, + imageSyncPath, detectOutputFormat, - resolveCredential, - resolveFileUrl, resolveOutputDir, generateFilename, stripUndefined, @@ -84,7 +81,8 @@ export default defineCommand({ '--image https://example.com/photo.png --prompt "Remove the person" --model qwen-image-2.0-pro', '--image ./photo.png --prompt "Replace the background with a beach" --watermark false', ], - async run(config, flags) { + async run(ctx) { + const { config, flags } = ctx; // Normalize --image to string array (supports both single and repeated flags) let rawImages: string[] = []; if (Array.isArray(flags.image)) { @@ -97,9 +95,8 @@ export default defineCommand({ const model = flags.model || config.defaultImageModel || "qwen-image-2.0"; // Auto-upload local files (resolve all images in parallel) - const credential = await resolveCredential(config); const resolvedImages = await Promise.all( - rawImages.map((img) => resolveFileUrl(img, credential.token, model)), + rawImages.map((img) => ctx.client.uploadFile(img, model)), ); const n = flags.n ?? 1; @@ -147,12 +144,11 @@ export default defineCommand({ process.stderr.write(`[Model: ${model}] [Mode: sync] [Images: ${resolvedImages.length}]\n`); } - const url = imageSyncEndpoint(config.baseUrl); const concurrent = getConcurrency(flags); const results = await runConcurrent(concurrent, config, () => - requestJson(config, { - url, + ctx.client.requestJson({ + path: imageSyncPath(), method: "POST", body, }), diff --git a/packages/commands/src/commands/image/generate.ts b/packages/commands/src/commands/image/generate.ts index a5e0b7b..bbfe56b 100644 --- a/packages/commands/src/commands/image/generate.ts +++ b/packages/commands/src/commands/image/generate.ts @@ -1,10 +1,10 @@ import { defineCommand, - requestJson, - imageEndpoint, - imageSyncEndpoint, - taskEndpoint, + imagePath, + imageSyncPath, + taskPath, detectOutputFormat, + type Client, type Config, type FlagsDef, type Flags, @@ -107,7 +107,8 @@ export default defineCommand({ '--prompt "Pro quality" --model qwen-image-2.0-pro', '--prompt "Product shots" --n 2 --concurrent 3 # 6 images in parallel', ], - async run(config, flags) { + async run(ctx) { + const { config, flags } = ctx; const prompt = flags.prompt; const model = flags.model || config.defaultImageModel || "qwen-image-2.0"; @@ -153,9 +154,9 @@ export default defineCommand({ } if (useSync) { - await handleSyncMode(config, model, body, flags, format, concurrent); + await handleSyncMode(ctx.client, config, model, body, flags, format, concurrent); } else { - await handleAsyncMode(config, model, body, flags, format, concurrent); + await handleAsyncMode(ctx.client, config, model, body, flags, format, concurrent); } }, }); @@ -163,6 +164,7 @@ export default defineCommand({ // ---- Sync mode: qwen-image-2.0 series ---- async function handleSyncMode( + client: Client, config: Config, _model: string, body: DashScopeImageRequest, @@ -170,10 +172,8 @@ async function handleSyncMode( format: string, concurrent: number, ): Promise { - const url = imageSyncEndpoint(config.baseUrl); - const results = await runConcurrent(concurrent, config, () => - requestJson(config, { url, method: "POST", body }), + client.requestJson({ path: imageSyncPath(), method: "POST", body }), ); const imageUrls = results @@ -192,6 +192,7 @@ async function handleSyncMode( // ---- Async mode: wan2.x / qwen-image-plus ---- async function handleAsyncMode( + client: Client, config: Config, _model: string, body: DashScopeImageRequest, @@ -199,12 +200,16 @@ async function handleAsyncMode( format: string, concurrent: number, ): Promise { - const url = imageEndpoint(config.baseUrl); - const responses = await runConcurrent( concurrent, config, - () => requestJson(config, { url, method: "POST", body, async: true }), + () => + client.requestJson({ + path: imagePath(), + method: "POST", + body, + async: true, + }), "tasks", ); const taskIds = responses.map((r) => r.output.task_id); @@ -219,7 +224,7 @@ async function handleAsyncMode( const pollInterval = flags.pollInterval ?? 3; const pollPromises = taskIds.map((taskId) => { - const pollUrl = taskEndpoint(config.baseUrl, taskId); + const pollUrl = client.url(taskPath(taskId)); return poll(config, { url: pollUrl, intervalSec: pollInterval, diff --git a/packages/commands/src/commands/knowledge/retrieve.ts b/packages/commands/src/commands/knowledge/retrieve.ts index 94e2724..89a666b 100644 --- a/packages/commands/src/commands/knowledge/retrieve.ts +++ b/packages/commands/src/commands/knowledge/retrieve.ts @@ -1,12 +1,12 @@ import { defineCommand, - knowledgeRetrieveEndpoint, + knowledgeRetrievePath, signRequest, - requestJson, detectOutputFormat, maskToken, - resolveCredential, + resolveApiKeyCredential, trackingHeaders, + type Client, type Config, type Flags, type FlagsDef, @@ -98,7 +98,8 @@ export default defineCommand({ '--index-id idx_xxx --query "How to use Alibaba Cloud Bailian"', '--api-key $DASHSCOPE_API_KEY --index-id idx_xxx --query "RAG retrieval" --rerank --rerank-model qwen3-rerank-hybrid', ], - async run(config, flags) { + async run(ctx) { + const { config, flags } = ctx; const indexId = flags.indexId; const query = flags.query; @@ -108,20 +109,20 @@ export default defineCommand({ const hasExplicitAkSk = !!(flags.accessKeyId && flags.accessKeySecret); if (hasExplicitApiKey) { - await runWithApiKey(config, flags, indexId, query, format); + await runWithApiKey(ctx.client, config, flags, indexId, query, format); } else if (hasExplicitAkSk) { await runWithAkSk(config, flags, indexId, query, format); } else { let useApiKey = false; try { - await resolveCredential(config); + await resolveApiKeyCredential(config); useApiKey = true; } catch { // No API-KEY credential available } if (useApiKey) { - await runWithApiKey(config, flags, indexId, query, format); + await runWithApiKey(ctx.client, config, flags, indexId, query, format); } else { await runWithAkSk(config, flags, indexId, query, format); } @@ -132,6 +133,7 @@ export default defineCommand({ // ---- API-KEY path (DashScope gateway, snake_case) ---- async function runWithApiKey( + client: Client, config: Config, flags: RetrieveFlags, indexId: string, @@ -165,15 +167,13 @@ async function runWithApiKey( body.rerank = [rerankEntry]; } - const url = knowledgeRetrieveEndpoint(config.baseUrl); - if (config.dryRun) { - emitResult({ endpoint: url, request: body }, format); + emitResult({ endpoint: client.url(knowledgeRetrievePath()), request: body }, format); return; } - const response = await requestJson(config, { - url, + const response = await client.requestJson({ + path: knowledgeRetrievePath(), method: "POST", body, }); diff --git a/packages/commands/src/commands/mcp/call.ts b/packages/commands/src/commands/mcp/call.ts index 489f16a..dad77d0 100644 --- a/packages/commands/src/commands/mcp/call.ts +++ b/packages/commands/src/commands/mcp/call.ts @@ -1,6 +1,5 @@ -import { defineCommand, McpClient, bailianMcpUrl, detectOutputFormat } from "bailian-cli-core"; +import { defineCommand, bailianMcpPath, detectOutputFormat } from "bailian-cli-core"; import { emitResult } from "bailian-cli-runtime"; -import { ensureApiKey } from "bailian-cli-runtime"; function parseArgFlags(raw: string[]): Record { const out: Record = {}; @@ -59,7 +58,8 @@ export default defineCommand({ '--target market-cmapi00073529.FinQuery --json \'{"q":"Guizhou Maotai","limit":5}\'', "--target market-cmapi00073529.SmartFundSelection --arg riskLevel=R3 --arg minScale=10", ], - async run(config, flags) { + async run(ctx) { + const { config, flags } = ctx; const target = flags.target; const dot = target.indexOf("."); @@ -87,7 +87,7 @@ export default defineCommand({ Object.assign(toolArgs, parseArgFlags(flags.arg ?? [])); if (flags.query !== undefined) toolArgs.query = flags.query; - const url = flags.url || bailianMcpUrl(config.baseUrl, serverCode); + const url = flags.url || ctx.client.url(bailianMcpPath(serverCode)); const format = detectOutputFormat(config.output); if (config.dryRun) { @@ -103,8 +103,7 @@ export default defineCommand({ return; } - await ensureApiKey(config); - const client = new McpClient(config, url); + const client = ctx.client.mcp(url); await client.initialize(); const result = await client.callTool(toolName, toolArgs); diff --git a/packages/commands/src/commands/mcp/list.ts b/packages/commands/src/commands/mcp/list.ts index 971eda1..a0d8b72 100644 --- a/packages/commands/src/commands/mcp/list.ts +++ b/packages/commands/src/commands/mcp/list.ts @@ -1,8 +1,6 @@ import { defineCommand, - callConsoleGateway, effectiveConsoleGatewayConfig, - resolveConsoleGatewayCredential, detectOutputFormat, BailianError, ExitCode, @@ -48,7 +46,8 @@ export default defineCommand({ consoleSwitchAgent: { type: "number", valueHint: "", description: "Switch agent UID" }, }, exampleArgs: ["", "--name finance", "--output json"], - async run(config, flags) { + async run(ctx) { + const { config, flags } = ctx; const serverName = flags.name || ""; const type = flags.type || "OFFICIAL"; const pageNo = flags.page || 1; @@ -71,12 +70,7 @@ export default defineCommand({ return; } - const credential = await resolveConsoleGatewayCredential(config); - - const result = (await callConsoleGateway(config, credential.token, { - api: MCP_LIST_API, - data, - })) as Record; + const result = (await ctx.client.console(MCP_LIST_API, data)) as Record; const dataField = (result?.data as Record | undefined) ?? {}; if (dataField.success === false) { diff --git a/packages/commands/src/commands/mcp/tools.ts b/packages/commands/src/commands/mcp/tools.ts index 83904ba..152e053 100644 --- a/packages/commands/src/commands/mcp/tools.ts +++ b/packages/commands/src/commands/mcp/tools.ts @@ -1,6 +1,5 @@ -import { defineCommand, McpClient, bailianMcpUrl, detectOutputFormat } from "bailian-cli-core"; +import { defineCommand, bailianMcpPath, detectOutputFormat } from "bailian-cli-core"; import { emitResult } from "bailian-cli-runtime"; -import { ensureApiKey } from "bailian-cli-runtime"; export default defineCommand({ description: "List tools exposed by an MCP server (tools/list)", @@ -24,10 +23,11 @@ export default defineCommand({ "--server market-cmapi00073529 --output json", "--server my-server --url https://example.com/mcp", ], - async run(config, flags) { + async run(ctx) { + const { config, flags } = ctx; const code = flags.server; - const url = flags.url || bailianMcpUrl(config.baseUrl, code); + const url = flags.url || ctx.client.url(bailianMcpPath(code)); const format = detectOutputFormat(config.output); if (config.dryRun) { @@ -35,8 +35,7 @@ export default defineCommand({ return; } - await ensureApiKey(config); - const client = new McpClient(config, url); + const client = ctx.client.mcp(url); await client.initialize(); const tools = await client.listTools(); emitResult({ server: code, url, tools }, format); diff --git a/packages/commands/src/commands/memory/add.ts b/packages/commands/src/commands/memory/add.ts index 1c4c07f..88bbd56 100644 --- a/packages/commands/src/commands/memory/add.ts +++ b/packages/commands/src/commands/memory/add.ts @@ -1,7 +1,6 @@ import { defineCommand, - requestJson, - memoryAddEndpoint, + memoryAddPath, detectOutputFormat, type FlagsDef, type Flags, @@ -43,7 +42,8 @@ export default defineCommand({ ], validate: (f: AddFlags) => !f.messages && !f.content ? "Provide --messages or --content." : undefined, - async run(config, flags) { + async run(ctx) { + const { config, flags } = ctx; const userId = flags.userId; const body: MemoryAddRequest = { user_id: userId }; @@ -67,13 +67,12 @@ export default defineCommand({ const format = detectOutputFormat(config.output); if (config.dryRun) { - emitResult({ endpoint: memoryAddEndpoint(config.baseUrl), request: body }, format); + emitResult({ endpoint: ctx.client.url(memoryAddPath()), request: body }, format); return; } - const url = memoryAddEndpoint(config.baseUrl); - const response = await requestJson(config, { - url, + const response = await ctx.client.requestJson({ + path: memoryAddPath(), method: "POST", body, }); diff --git a/packages/commands/src/commands/memory/delete.ts b/packages/commands/src/commands/memory/delete.ts index 0375b7b..69b174d 100644 --- a/packages/commands/src/commands/memory/delete.ts +++ b/packages/commands/src/commands/memory/delete.ts @@ -1,9 +1,4 @@ -import { - defineCommand, - requestJson, - memoryNodeEndpoint, - detectOutputFormat, -} from "bailian-cli-core"; +import { defineCommand, memoryNodePath, detectOutputFormat } from "bailian-cli-core"; import { emitResult, emitBare } from "bailian-cli-runtime"; export default defineCommand({ @@ -30,22 +25,23 @@ export default defineCommand({ }, }, exampleArgs: ["--node-id node_xxx --user-id user1"], - async run(config, flags) { + async run(ctx) { + const { config, flags } = ctx; const nodeId = flags.nodeId; const userId = flags.userId; const format = detectOutputFormat(config.output); const params = new URLSearchParams({ user_id: userId }); if (flags.memoryLibraryId) params.set("memory_library_id", flags.memoryLibraryId); - const url = `${memoryNodeEndpoint(config.baseUrl, nodeId)}?${params.toString()}`; + const path = `${memoryNodePath(nodeId)}?${params.toString()}`; if (config.dryRun) { - emitResult({ endpoint: url, method: "DELETE" }, format); + emitResult({ endpoint: ctx.client.url(path), method: "DELETE" }, format); return; } - const response = await requestJson<{ request_id: string }>(config, { - url, + const response = await ctx.client.requestJson<{ request_id: string }>({ + path, method: "DELETE", }); diff --git a/packages/commands/src/commands/memory/list.ts b/packages/commands/src/commands/memory/list.ts index ccc2a96..136ef47 100644 --- a/packages/commands/src/commands/memory/list.ts +++ b/packages/commands/src/commands/memory/list.ts @@ -1,7 +1,6 @@ import { defineCommand, - requestJson, - memoryListEndpoint, + memoryListPath, detectOutputFormat, type MemoryNodeListResponse, } from "bailian-cli-core"; @@ -27,7 +26,8 @@ export default defineCommand({ memoryLibraryId: { type: "string", valueHint: "", description: "Memory library ID" }, }, exampleArgs: ["--user-id user1", "--user-id user1 --page-size 20 --page 2"], - async run(config, flags) { + async run(ctx) { + const { config, flags } = ctx; const userId = flags.userId; const format = detectOutputFormat(config.output); @@ -37,15 +37,15 @@ export default defineCommand({ if (flags.page !== undefined) params.set("page_num", String(flags.page)); if (flags.memoryLibraryId) params.set("memory_library_id", flags.memoryLibraryId); - const url = `${memoryListEndpoint(config.baseUrl)}?${params.toString()}`; + const path = `${memoryListPath()}?${params.toString()}`; if (config.dryRun) { - emitResult({ endpoint: url, method: "GET" }, format); + emitResult({ endpoint: ctx.client.url(path), method: "GET" }, format); return; } - const response = await requestJson(config, { - url, + const response = await ctx.client.requestJson({ + path, method: "GET", }); diff --git a/packages/commands/src/commands/memory/profile-create.ts b/packages/commands/src/commands/memory/profile-create.ts index ab7fd10..912b7de 100644 --- a/packages/commands/src/commands/memory/profile-create.ts +++ b/packages/commands/src/commands/memory/profile-create.ts @@ -1,7 +1,6 @@ import { defineCommand, - requestJson, - profileSchemaEndpoint, + profileSchemaPath, detectOutputFormat, type ProfileSchemaCreateRequest, type ProfileSchemaCreateResponse, @@ -30,7 +29,8 @@ export default defineCommand({ exampleArgs: [ '--name "user_basic" --attributes \'[{"name":"age","description":"age"},{"name":"hobby","description":"hobby"}]\'', ], - async run(config, flags) { + async run(ctx) { + const { config, flags } = ctx; const name = flags.name; const attrStr = flags.attributes; @@ -48,13 +48,12 @@ export default defineCommand({ const format = detectOutputFormat(config.output); if (config.dryRun) { - emitResult({ endpoint: profileSchemaEndpoint(config.baseUrl), request: body }, format); + emitResult({ endpoint: ctx.client.url(profileSchemaPath()), request: body }, format); return; } - const url = profileSchemaEndpoint(config.baseUrl); - const response = await requestJson(config, { - url, + const response = await ctx.client.requestJson({ + path: profileSchemaPath(), method: "POST", body, }); diff --git a/packages/commands/src/commands/memory/profile-get.ts b/packages/commands/src/commands/memory/profile-get.ts index 805a0e5..66d1ca3 100644 --- a/packages/commands/src/commands/memory/profile-get.ts +++ b/packages/commands/src/commands/memory/profile-get.ts @@ -1,7 +1,6 @@ import { defineCommand, - requestJson, - userProfileEndpoint, + userProfilePath, detectOutputFormat, type UserProfileResponse, } from "bailian-cli-core"; @@ -26,21 +25,22 @@ export default defineCommand({ }, }, exampleArgs: ["--schema-id schema_xxx --user-id user1"], - async run(config, flags) { + async run(ctx) { + const { config, flags } = ctx; const schemaId = flags.schemaId; const userId = flags.userId; const format = detectOutputFormat(config.output); const params = new URLSearchParams({ user_id: userId }); - const url = `${userProfileEndpoint(config.baseUrl, schemaId)}?${params.toString()}`; + const path = `${userProfilePath(schemaId)}?${params.toString()}`; if (config.dryRun) { - emitResult({ endpoint: url, method: "GET" }, format); + emitResult({ endpoint: ctx.client.url(path), method: "GET" }, format); return; } - const response = await requestJson(config, { - url, + const response = await ctx.client.requestJson({ + path, method: "GET", }); diff --git a/packages/commands/src/commands/memory/search.ts b/packages/commands/src/commands/memory/search.ts index 96acff4..c40911e 100644 --- a/packages/commands/src/commands/memory/search.ts +++ b/packages/commands/src/commands/memory/search.ts @@ -1,7 +1,6 @@ import { defineCommand, - requestJson, - memorySearchEndpoint, + memorySearchPath, detectOutputFormat, type FlagsDef, type Flags, @@ -38,7 +37,8 @@ export default defineCommand({ ], validate: (f: SearchFlags) => !f.query && !f.messages ? "Provide --query or --messages." : undefined, - async run(config, flags) { + async run(ctx) { + const { config, flags } = ctx; const userId = flags.userId; const body: MemorySearchRequest = { user_id: userId }; @@ -65,13 +65,12 @@ export default defineCommand({ const format = detectOutputFormat(config.output); if (config.dryRun) { - emitResult({ endpoint: memorySearchEndpoint(config.baseUrl), request: body }, format); + emitResult({ endpoint: ctx.client.url(memorySearchPath()), request: body }, format); return; } - const url = memorySearchEndpoint(config.baseUrl); - const response = await requestJson(config, { - url, + const response = await ctx.client.requestJson({ + path: memorySearchPath(), method: "POST", body, }); diff --git a/packages/commands/src/commands/memory/update.ts b/packages/commands/src/commands/memory/update.ts index 5ea1b6a..6951480 100644 --- a/packages/commands/src/commands/memory/update.ts +++ b/packages/commands/src/commands/memory/update.ts @@ -1,7 +1,6 @@ import { defineCommand, - requestJson, - memoryNodeEndpoint, + memoryNodePath, detectOutputFormat, type MemoryNodeUpdateRequest, } from "bailian-cli-core"; @@ -37,7 +36,8 @@ export default defineCommand({ }, }, exampleArgs: ['--node-id node_xxx --user-id user1 --content "updated memory content"'], - async run(config, flags) { + async run(ctx) { + const { config, flags } = ctx; const nodeId = flags.nodeId; const userId = flags.userId; const content = flags.content; @@ -52,15 +52,14 @@ export default defineCommand({ if (config.dryRun) { emitResult( - { endpoint: memoryNodeEndpoint(config.baseUrl, nodeId), method: "PATCH", request: body }, + { endpoint: ctx.client.url(memoryNodePath(nodeId)), method: "PATCH", request: body }, format, ); return; } - const url = memoryNodeEndpoint(config.baseUrl, nodeId); - const response = await requestJson<{ request_id: string }>(config, { - url, + const response = await ctx.client.requestJson<{ request_id: string }>({ + path: memoryNodePath(nodeId), method: "PATCH", body, }); diff --git a/packages/commands/src/commands/omni/chat.ts b/packages/commands/src/commands/omni/chat.ts index d204a31..7a0dff7 100644 --- a/packages/commands/src/commands/omni/chat.ts +++ b/packages/commands/src/commands/omni/chat.ts @@ -2,8 +2,7 @@ import { writeFileSync } from "fs"; import { extname } from "path"; import { defineCommand, - request, - chatEndpoint, + chatPath, parseSSE, detectOutputFormat, BailianError, @@ -12,10 +11,9 @@ import { type ChatMessageContent, type ChatRequest, type StreamChunk, - resolveFileUrl, } from "bailian-cli-core"; import { emitResult } from "bailian-cli-runtime"; -import { resolveOutputDir, resolveCredential } from "bailian-cli-core"; +import { resolveOutputDir } from "bailian-cli-core"; const OMNI_VOICES = ["Chelsie", "Cherry", "Ethan", "Serena", "Sunny", "Tina"]; @@ -145,7 +143,8 @@ export default defineCommand({ '--message "Hello" --text-only --output json', '--message "Read this passage aloud" --audio-out greeting.wav', ], - async run(config, flags) { + async run(ctx) { + const { config, flags } = ctx; // --- Parse messages --- const userMessages = flags.message; @@ -192,13 +191,12 @@ export default defineCommand({ const needsResolve = rawImageUrls.length > 0 || rawAudioUrls.length > 0 || rawVideoUrls.length > 0; if (needsResolve) { - const credential = await resolveCredential(config); for (const u of rawImageUrls) { - const resolved = await resolveFileUrl(u, credential.token, model); + const resolved = await ctx.client.uploadFile(u, model); imageUrls.push(resolved); } for (const u of rawAudioUrls) { - const resolved = await resolveFileUrl(u, credential.token, model); + const resolved = await ctx.client.uploadFile(u, model); audioInputs.push({ source: u, data: resolved }); } for (const u of rawVideoUrls) { @@ -211,11 +209,11 @@ export default defineCommand({ .filter(Boolean); // Resolve each frame URL for (const f of frames) { - const resolved = await resolveFileUrl(f, credential.token, model); + const resolved = await ctx.client.uploadFile(f, model); videoUrls.push(`frame:${resolved}`); } } else { - const resolved = await resolveFileUrl(u, credential.token, model); + const resolved = await ctx.client.uploadFile(u, model); videoUrls.push(resolved); } } @@ -291,9 +289,8 @@ export default defineCommand({ } // --- Stream request --- - const url = chatEndpoint(config.baseUrl); - const res = await request(config, { - url, + const res = await ctx.client.request({ + path: chatPath(), method: "POST", body, stream: true, diff --git a/packages/commands/src/commands/pipeline/run.ts b/packages/commands/src/commands/pipeline/run.ts index b221de0..036fa1f 100644 --- a/packages/commands/src/commands/pipeline/run.ts +++ b/packages/commands/src/commands/pipeline/run.ts @@ -46,7 +46,8 @@ export default defineCommand({ "--file workflow.json --events jsonl", "--file workflow.yaml --output json", ], - async run(config, flags) { + async run(ctx) { + const { config, flags } = ctx; const file = flags.file; initPipelineSteps(); diff --git a/packages/commands/src/commands/pipeline/validate.ts b/packages/commands/src/commands/pipeline/validate.ts index a018241..593e46a 100644 --- a/packages/commands/src/commands/pipeline/validate.ts +++ b/packages/commands/src/commands/pipeline/validate.ts @@ -18,7 +18,8 @@ export default defineCommand({ }, }, exampleArgs: ["--file workflow.yaml", "--file workflow.json --output json"], - async run(config, flags) { + async run(ctx) { + const { config, flags } = ctx; const file = flags.file; initPipelineSteps(); diff --git a/packages/commands/src/commands/quota/check.ts b/packages/commands/src/commands/quota/check.ts index 15eda6d..72d72af 100644 --- a/packages/commands/src/commands/quota/check.ts +++ b/packages/commands/src/commands/quota/check.ts @@ -1,10 +1,8 @@ import { defineCommand, - callConsoleGateway, effectiveConsoleGatewayConfig, - resolveConsoleGatewayCredential, detectOutputFormat, - type Config, + type Client, } from "bailian-cli-core"; import { emitResult } from "bailian-cli-runtime"; import { displayWidth, padEnd } from "bailian-cli-runtime"; @@ -91,22 +89,19 @@ function extractResponseData(result: Record): Record { +async function fetchAllModelsWithQpm(client: Client): Promise { const allModels: ModelWithQpm[] = []; let pageNo = 1; while (true) { - const raw = await callConsoleGateway(config, token, { - api: MODEL_LIST_API, - data: { - input: { - pageNo, - pageSize: 50, - group: false, - queryQpmInfo: true, - ignoreWorkspaceServiceSite: true, - supports: { selfServiceLimitIncrease: true }, - }, + const raw = await client.console(MODEL_LIST_API, { + input: { + pageNo, + pageSize: 50, + group: false, + queryQpmInfo: true, + ignoreWorkspaceServiceSite: true, + supports: { selfServiceLimitIncrease: true }, }, }); @@ -123,8 +118,7 @@ async function fetchAllModelsWithQpm(config: Config, token: string): Promise { @@ -132,22 +126,19 @@ async function fetchMonitorData( const startTime = now - windowMinutes * 60 * 1000; try { - const raw = await callConsoleGateway(config, token, { - api: MONITOR_API, - data: { - reqDTO: { - monitorType: "Advanced", - metricFilters: [ - { aggMethod: "sum_pm", metricName: "model_total_amount" }, - { aggMethod: "sum_pm", metricName: "model_call_count" }, - ], - labelFilters: { - resourceId: modelName, - resourceType: "model", - }, - startTime, - endTime: now, + const raw = await client.console(MONITOR_API, { + reqDTO: { + monitorType: "Advanced", + metricFilters: [ + { aggMethod: "sum_pm", metricName: "model_total_amount" }, + { aggMethod: "sum_pm", metricName: "model_call_count" }, + ], + labelFilters: { + resourceId: modelName, + resourceType: "model", }, + startTime, + endTime: now, }, }); @@ -263,7 +254,8 @@ export default defineCommand({ "--model qwen3.6-plus,qwen-turbo", "--output json", ], - async run(config, flags) { + async run(ctx) { + const { config, flags } = ctx; const modelFlag = flags.model || undefined; const rawPeriod = Number(flags.period) || 2; if (rawPeriod < 1) { @@ -284,9 +276,7 @@ export default defineCommand({ return; } - const credential = await resolveConsoleGatewayCredential(config); - - let models = await fetchAllModelsWithQpm(config, credential.token); + let models = await fetchAllModelsWithQpm(ctx.client); if (modelFlag) { const names = new Set( @@ -306,7 +296,7 @@ export default defineCommand({ } const monitorResults = await Promise.all( - models.map((m) => fetchMonitorData(config, credential.token, m.model, windowMinutes)), + models.map((m) => fetchMonitorData(ctx.client, m.model, windowMinutes)), ); const checkRows: CheckRow[] = models.map((m, idx) => { diff --git a/packages/commands/src/commands/quota/history.ts b/packages/commands/src/commands/quota/history.ts index 44c0c77..fe449a9 100644 --- a/packages/commands/src/commands/quota/history.ts +++ b/packages/commands/src/commands/quota/history.ts @@ -1,10 +1,4 @@ -import { - defineCommand, - callConsoleGateway, - resolveConsoleGatewayCredential, - detectOutputFormat, - BailianError, -} from "bailian-cli-core"; +import { defineCommand, detectOutputFormat, BailianError } from "bailian-cli-core"; import { emitResult } from "bailian-cli-runtime"; import { displayWidth, padEnd } from "bailian-cli-runtime"; @@ -117,7 +111,8 @@ export default defineCommand({ consoleSwitchAgent: { type: "number", valueHint: "", description: "Switch agent UID" }, }, exampleArgs: ["", "--page 2", "--page-size 20", "--model qwen-turbo", "--output json"], - async run(config, flags) { + async run(ctx) { + const { config, flags } = ctx; const page = Number(flags.page) || 1; const pageSize = Number(flags.pageSize) || 10; const modelFilter = flags.model || undefined; @@ -132,14 +127,9 @@ export default defineCommand({ return; } - const credential = await resolveConsoleGatewayCredential(config); - let result: unknown; try { - result = await callConsoleGateway(config, credential.token, { - api: HISTORY_API, - data: requestData, - }); + result = await ctx.client.console(HISTORY_API, requestData); } catch (err) { if (err instanceof BailianError && err.message.includes("NotLogined")) { process.stderr.write( diff --git a/packages/commands/src/commands/quota/list.ts b/packages/commands/src/commands/quota/list.ts index 88b5231..b259036 100644 --- a/packages/commands/src/commands/quota/list.ts +++ b/packages/commands/src/commands/quota/list.ts @@ -1,10 +1,4 @@ -import { - defineCommand, - callConsoleGateway, - resolveConsoleGatewayCredential, - detectOutputFormat, - type Config, -} from "bailian-cli-core"; +import { defineCommand, detectOutputFormat, type Client } from "bailian-cli-core"; import { emitResult } from "bailian-cli-runtime"; import { displayWidth, padEnd } from "bailian-cli-runtime"; @@ -65,8 +59,7 @@ function extractResponseData(result: Record): Record { const allModels: ModelWithQpm[] = []; @@ -84,10 +77,7 @@ async function fetchAllModelsWithQpm( input.supports = { selfServiceLimitIncrease: true }; } - const raw = await callConsoleGateway(config, token, { - api: MODEL_LIST_API, - data: { input }, - }); + const raw = await client.console(MODEL_LIST_API, { input }); const resp = extractResponseData(raw as Record); const list = (resp.list as ModelWithQpm[]) ?? []; @@ -177,7 +167,8 @@ export default defineCommand({ "--all", "--output json", ], - async run(config, flags) { + async run(ctx) { + const { config, flags } = ctx; const modelFlag = flags.model || undefined; const showAll = Boolean(flags.all); const format = detectOutputFormat(config.output); @@ -195,9 +186,7 @@ export default defineCommand({ return; } - const credential = await resolveConsoleGatewayCredential(config); - - let models = await fetchAllModelsWithQpm(config, credential.token, !showAll); + let models = await fetchAllModelsWithQpm(ctx.client, !showAll); if (modelFlag) { const names = new Set( diff --git a/packages/commands/src/commands/quota/request.ts b/packages/commands/src/commands/quota/request.ts index e33a55e..7dc035b 100644 --- a/packages/commands/src/commands/quota/request.ts +++ b/packages/commands/src/commands/quota/request.ts @@ -1,11 +1,4 @@ -import { - defineCommand, - callConsoleGateway, - resolveConsoleGatewayCredential, - detectOutputFormat, - BailianError, - type Config, -} from "bailian-cli-core"; +import { defineCommand, detectOutputFormat, BailianError, type Client } from "bailian-cli-core"; import { emitResult } from "bailian-cli-runtime"; const MODEL_LIST_API = "zeldaHttp.dashscopeModel./zelda/api/v1/modelCenter/listFoundationModels"; @@ -50,22 +43,18 @@ function extractResponseData(result: Record): Record } | undefined> { - const raw = await callConsoleGateway(config, token, { - api: MODEL_LIST_API, - data: { - input: { - pageNo: 1, - pageSize: 50, - name: modelName, - group: false, - queryQpmInfo: true, - ignoreWorkspaceServiceSite: true, - supports: { selfServiceLimitIncrease: true }, - }, + const raw = await client.console(MODEL_LIST_API, { + input: { + pageNo: 1, + pageSize: 50, + name: modelName, + group: false, + queryQpmInfo: true, + ignoreWorkspaceServiceSite: true, + supports: { selfServiceLimitIncrease: true }, }, }); @@ -107,7 +96,8 @@ export default defineCommand({ "--model qwen3.6-plus --tpm 8000000 --yes", "--model qwen-turbo --tpm 100000 --output json", ], - async run(config, flags) { + async run(ctx) { + const { config, flags } = ctx; const modelName = flags.model; if (!modelName) { process.stderr.write("Error: --model is required.\n"); @@ -134,9 +124,7 @@ export default defineCommand({ return; } - const credential = await resolveConsoleGatewayCredential(config); - - const modelInfo = await fetchModelQpmInfo(config, credential.token, modelName); + const modelInfo = await fetchModelQpmInfo(ctx.client, modelName); if (!modelInfo) { process.stderr.write( `Error: model "${modelName}" not found or does not support self-service quota increase.\n`, @@ -175,10 +163,7 @@ export default defineCommand({ requestData.input.confirmedDowngrade = true; } try { - return await callConsoleGateway(config, credential.token, { - api: UPDATE_LIMITS_API, - data: requestData, - }); + return await ctx.client.console(UPDATE_LIMITS_API, requestData); } catch (err) { if (err instanceof BailianError && err.message.includes("NotLogined")) { process.stderr.write( diff --git a/packages/commands/src/commands/search/web.ts b/packages/commands/src/commands/search/web.ts index 07822d0..ffe9228 100644 --- a/packages/commands/src/commands/search/web.ts +++ b/packages/commands/src/commands/search/web.ts @@ -1,8 +1,7 @@ import { defineCommand, detectOutputFormat, - mcpWebSearchEndpoint, - McpClient, + mcpWebSearchPath, type FlagsDef, } from "bailian-cli-core"; import { createSpinner } from "bailian-cli-runtime"; @@ -30,18 +29,18 @@ export default defineCommand({ "--list-tools", ], validate: (f) => (!f.listTools && !f.query ? "Missing required flag: --query" : undefined), - async run(config, flags) { - const mcpUrl = mcpWebSearchEndpoint(config.baseUrl); + async run(ctx) { + const { config, flags } = ctx; const format = detectOutputFormat(config.output); // --- List tools mode --- if (flags.listTools) { if (config.dryRun) { - emitResult({ endpoint: mcpUrl, action: "tools/list" }, format); + emitResult({ endpoint: ctx.client.url(mcpWebSearchPath()), action: "tools/list" }, format); return; } - const client = new McpClient(config, mcpUrl); + const client = ctx.client.mcp(mcpWebSearchPath()); await client.initialize(); const tools = await client.listTools(); @@ -55,7 +54,7 @@ export default defineCommand({ if (config.dryRun) { emitResult( { - endpoint: mcpUrl, + endpoint: ctx.client.url(mcpWebSearchPath()), action: "tools/call", tool: "bailian_web_search", arguments: { @@ -69,7 +68,7 @@ export default defineCommand({ } // Initialize MCP client - const client = new McpClient(config, mcpUrl); + const client = ctx.client.mcp(mcpWebSearchPath()); const spinner = createSpinner("Initializing search..."); if (!config.quiet) spinner.start(); diff --git a/packages/commands/src/commands/speech/recognize.ts b/packages/commands/src/commands/speech/recognize.ts index e006a59..eac24e0 100644 --- a/packages/commands/src/commands/speech/recognize.ts +++ b/packages/commands/src/commands/speech/recognize.ts @@ -4,18 +4,16 @@ import { defineCommand, ExitCode, detectOutputFormat, + type Client, type Config, type DashScopeASRRequest, type DashScopeASRTaskResult, type DashScopeAsyncResponse, - resolveFileUrl, - resolveCredential, trackingHeaders, stripUndefined, - taskEndpoint, - requestJson, + taskPath, + speechRecognizePath, type OutputFormat, - speechRecognizeEndpoint, type FlagsDef, type Flags, } from "bailian-cli-core"; @@ -71,7 +69,8 @@ export default defineCommand({ "--url https://example.com/audio.mp3 --out result.json", "--url https://example.com/audio.mp3 --no-wait --quiet", ], - async run(config, flags) { + async run(ctx) { + const { config, flags } = ctx; // Normalize --url to string[] (supports both single and repeated flags) let rawUrls: string[] = []; if (Array.isArray(flags.url)) { @@ -94,10 +93,7 @@ export default defineCommand({ const format = detectOutputFormat(config.output); // Auto-upload local files in parallel - const credential = await resolveCredential(config); - const resolvedUrls = await Promise.all( - rawUrls.map((u) => resolveFileUrl(u, credential.token, model)), - ); + const resolvedUrls = await Promise.all(rawUrls.map((u) => ctx.client.uploadFile(u, model))); const channelId = flags.channelId; const language = flags.language; const vocabularyId = flags.vocabularyId; @@ -128,22 +124,21 @@ export default defineCommand({ process.stderr.write(`[Model: ${model}] [Mode: async] [Files: ${resolvedUrls.length}]\n`); } - const url = speechRecognizeEndpoint(config.baseUrl); - await handleAsyncMode(config, url, body, flags, format, resolvedUrls.length); + await handleAsyncMode(ctx.client, config, body, flags, format, resolvedUrls.length); }, }); async function handleAsyncMode( + client: Client, config: Config, - url: string, body: DashScopeASRRequest, flags: RecognizeFlags, format: OutputFormat, fileCount: number, ): Promise { // Submit async task (always required for fun-asr) - const response = await requestJson(config, { - url, + const response = await client.requestJson({ + path: speechRecognizePath(), method: "POST", body, async: true, @@ -159,7 +154,7 @@ async function handleAsyncMode( // Poll until completion const pollInterval = flags.pollInterval ?? 2; - const pollUrl = taskEndpoint(config.baseUrl, taskId); + const pollUrl = client.url(taskPath(taskId)); const result = await poll(config, { url: pollUrl, diff --git a/packages/commands/src/commands/speech/synthesize.ts b/packages/commands/src/commands/speech/synthesize.ts index 01a8621..27cae16 100644 --- a/packages/commands/src/commands/speech/synthesize.ts +++ b/packages/commands/src/commands/speech/synthesize.ts @@ -4,17 +4,16 @@ import { defineCommand, ExitCode, detectOutputFormat, + type Client, type Config, type DashScopeTTSRequest, type DashScopeTTSResponse, type DashScopeTTSStreamChunk, stripUndefined, - requestJson, type OutputFormat, - speechSynthesizeEndpoint, + speechSynthesizePath, parseSSE, resolveOutputDir, - request, DOCS_HOSTS, type FlagsDef, type Flags, @@ -233,7 +232,8 @@ export default defineCommand({ if (!f.voice) return "Missing required flag: --voice"; return undefined; }, - async run(config, flags) { + async run(ctx) { + const { config, flags } = ctx; const model = flags.model || config.defaultSpeechModel || "cosyvoice-v3-flash"; // --list-voices: print voice list for the model and exit @@ -296,19 +296,17 @@ export default defineCommand({ process.stderr.write(`[Model: ${model}] [Voice: ${voice}]\n`); } - const url = speechSynthesizeEndpoint(config.baseUrl); - if (useStream) { - await handleStreamMode(config, url, body, flags, format); + await handleStreamMode(ctx.client, config, body, flags, format); } else { - await handleNonStreamMode(config, url, body, flags, format); + await handleNonStreamMode(ctx.client, config, body, flags, format); } }, }); async function handleNonStreamMode( + client: Client, config: Config, - url: string, body: DashScopeTTSRequest, flags: SynthesizeFlags, format: OutputFormat, @@ -316,7 +314,11 @@ async function handleNonStreamMode( const concurrent = getConcurrency(flags); const results = await runConcurrent(concurrent, config, () => - requestJson(config, { url, method: "POST", body }), + client.requestJson({ + path: speechSynthesizePath(), + method: "POST", + body, + }), ); const audioUrls = results.map((r) => r.output?.audio?.url).filter(Boolean) as string[]; @@ -373,14 +375,14 @@ async function handleNonStreamMode( } async function handleStreamMode( + client: Client, config: Config, - url: string, body: DashScopeTTSRequest, flags: SynthesizeFlags, format: OutputFormat, ): Promise { - const res = await request(config, { - url, + const res = await client.request({ + path: speechSynthesizePath(), method: "POST", body, stream: true, diff --git a/packages/commands/src/commands/text/chat.ts b/packages/commands/src/commands/text/chat.ts index d4fe1f0..4ca59d8 100644 --- a/packages/commands/src/commands/text/chat.ts +++ b/packages/commands/src/commands/text/chat.ts @@ -1,8 +1,6 @@ import { defineCommand, - request, - requestJson, - chatEndpoint, + chatPath, parseSSE, detectOutputFormat, type ChatMessage, @@ -122,7 +120,8 @@ export default defineCommand({ ], validate: (f) => !f.message && !f.messagesFile ? "Provide --message or --messages-file." : undefined, - async run(config, flags) { + async run(ctx) { + const { config, flags } = ctx; const { system, messages } = parseMessages(flags); const model = flags.model || config.defaultTextModel || "qwen3.7-max"; @@ -170,11 +169,9 @@ export default defineCommand({ return; } - const url = chatEndpoint(config.baseUrl); - if (shouldStream) { - const res = await request(config, { - url, + const res = await ctx.client.request({ + path: chatPath(), method: "POST", body, stream: true, @@ -229,8 +226,8 @@ export default defineCommand({ resultOut.write("\n"); } } else { - const response = await requestJson(config, { - url, + const response = await ctx.client.requestJson({ + path: chatPath(), method: "POST", body, }); diff --git a/packages/commands/src/commands/update.ts b/packages/commands/src/commands/update.ts index a226286..e890fa6 100644 --- a/packages/commands/src/commands/update.ts +++ b/packages/commands/src/commands/update.ts @@ -31,7 +31,8 @@ export default defineCommand({ description: "Update the CLI to the latest version", auth: "none", exampleArgs: [""], - async run(config) { + async run(ctx) { + const { config } = ctx; const npmPackage = config.npmPackage!; const binName = config.binName!; const currentVersion = config.clientVersion!; diff --git a/packages/commands/src/commands/usage/free.ts b/packages/commands/src/commands/usage/free.ts index dd8c0f3..475e01e 100644 --- a/packages/commands/src/commands/usage/free.ts +++ b/packages/commands/src/commands/usage/free.ts @@ -1,11 +1,4 @@ -import { - defineCommand, - callConsoleGateway, - resolveConsoleGatewayCredential, - fetchModelList, - detectOutputFormat, - type Config, -} from "bailian-cli-core"; +import { defineCommand, detectOutputFormat, fetchModelList, type Client } from "bailian-cli-core"; import { emitResult } from "bailian-cli-runtime"; import { displayWidth, padEnd } from "bailian-cli-runtime"; @@ -165,11 +158,14 @@ interface ModelInfo { type: string; } -async function fetchAllModels(config: Config, token: string): Promise { +async function fetchAllModels(client: Client): Promise { const allModels: Record[] = []; let page = 1; while (true) { - const result = await fetchModelList(config, token, { pageNo: page, pageSize: 50 }); + 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++; @@ -219,7 +215,8 @@ export default defineCommand({ "--model qwen-turbo --output json", "--model qwen3-max --console-region cn-beijing", ], - async run(config, flags) { + async run(ctx) { + const { config, flags } = ctx; const modelFlag = flags.model || undefined; const expiringDays = Number(flags.expiring) || 0; const VALID_SORT_FIELDS = ["remaining", "expires"] as const; @@ -263,10 +260,8 @@ export default defineCommand({ return; } - const credential = await resolveConsoleGatewayCredential(config); - if (!modelFlag) { - const modelInfos = await fetchAllModels(config, credential.token); + const modelInfos = await fetchAllModels(ctx.client); models = modelInfos.map((info) => info.name); for (const info of modelInfos) { typeMap.set(info.name, info.type); @@ -274,7 +269,9 @@ export default defineCommand({ requestData.queryFreeTierQuotaRequest.models = models; } else { const searchResults = await Promise.all( - models.map((name) => fetchModelList(config, credential.token, { name, pageSize: 50 })), + models.map((name) => + fetchModelList((api, data) => ctx.client.console(api, data), { name, pageSize: 50 }), + ), ); for (let idx = 0; idx < models.length; idx++) { const matched = searchResults[idx].models.find((item) => item.model === models[idx]); @@ -285,13 +282,9 @@ export default defineCommand({ } const [quotaResult, stopResult] = await Promise.all([ - callConsoleGateway(config, credential.token, { - api: FREE_TIER_API, - data: requestData, - }), - callConsoleGateway(config, credential.token, { - api: FREE_TIER_ONLY_STATUS_API, - data: { queryFreeTierOnlyStatusRequest: { models } }, + ctx.client.console(FREE_TIER_API, requestData), + ctx.client.console(FREE_TIER_ONLY_STATUS_API, { + queryFreeTierOnlyStatusRequest: { models }, }), ]); diff --git a/packages/commands/src/commands/usage/freetier.ts b/packages/commands/src/commands/usage/freetier.ts index 4823566..d1b636a 100644 --- a/packages/commands/src/commands/usage/freetier.ts +++ b/packages/commands/src/commands/usage/freetier.ts @@ -1,11 +1,4 @@ -import { - defineCommand, - callConsoleGateway, - resolveConsoleGatewayCredential, - fetchModelList, - detectOutputFormat, - type Config, -} from "bailian-cli-core"; +import { defineCommand, detectOutputFormat, fetchModelList, type Client } from "bailian-cli-core"; import { emitResult } from "bailian-cli-runtime"; const ACTIVATE_API = "zeldaEasy.broadscope-bailian.freeTrial.batchActivateFreeTierOnly"; @@ -57,8 +50,7 @@ const POLL_INTERVAL_MS = 500; const MAX_POLLS = 20; async function pollUntilDone( - config: Config, - token: string, + client: Client, api: string, requestKey: string, models: string[], @@ -70,10 +62,7 @@ async function pollUntilDone( [requestKey]: nextTaskId ? { taskId: nextTaskId } : { models }, }; - const raw = await callConsoleGateway(config, token, { - api, - data: requestData, - }); + const raw = await client.console(api, requestData); const resp = extractResponseData(raw as Record); if (resp.taskId && Object.keys(resp).length === 1) { @@ -86,11 +75,14 @@ async function pollUntilDone( return null; } -async function fetchAllModelNames(config: Config, token: string): Promise { +async function fetchAllModelNames(client: Client): Promise { const allModels: Record[] = []; let page = 1; while (true) { - const result = await fetchModelList(config, token, { pageNo: page, pageSize: 50 }); + 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++; @@ -139,7 +131,8 @@ export default defineCommand({ ], validate: (f) => !f.model && !f.all ? "Provide --model [,model2,...] or --all." : undefined, - async run(config, flags) { + async run(ctx) { + const { config, flags } = ctx; const modelFlag = flags.model || undefined; const off = Boolean(flags.off); const format = detectOutputFormat(config.output); @@ -174,21 +167,15 @@ export default defineCommand({ return; } - const credential = await resolveConsoleGatewayCredential(config); - if (!modelFlag) { - models = await fetchAllModelNames(config, credential.token); + models = await fetchAllModelNames(ctx.client); } if (off) { const [quotaResult, stopResult] = await Promise.all([ - callConsoleGateway(config, credential.token, { - api: FREE_TIER_API, - data: { queryFreeTierQuotaRequest: { models } }, - }), - callConsoleGateway(config, credential.token, { - api: FREE_TIER_ONLY_STATUS_API, - data: { queryFreeTierOnlyStatusRequest: { models } }, + ctx.client.console(FREE_TIER_API, { queryFreeTierQuotaRequest: { models } }), + ctx.client.console(FREE_TIER_ONLY_STATUS_API, { + queryFreeTierOnlyStatusRequest: { models }, }), ]); @@ -212,7 +199,7 @@ export default defineCommand({ ); continue; } - await pollUntilDone(config, credential.token, api, requestKey, [name]); + await pollUntilDone(ctx.client, api, requestKey, [name]); process.stdout.write(`Disabled auto-stop for "${name}".\n`); } return; @@ -220,7 +207,7 @@ export default defineCommand({ const jsonResults: unknown[] = []; for (const name of models) { - const result = await pollUntilDone(config, credential.token, api, requestKey, [name]); + const result = await pollUntilDone(ctx.client, api, requestKey, [name]); if (format === "json") { jsonResults.push(result); continue; diff --git a/packages/commands/src/commands/usage/stats.ts b/packages/commands/src/commands/usage/stats.ts index 9d67e8e..0cc6643 100644 --- a/packages/commands/src/commands/usage/stats.ts +++ b/packages/commands/src/commands/usage/stats.ts @@ -1,10 +1,4 @@ -import { - defineCommand, - callConsoleGateway, - resolveConsoleGatewayCredential, - detectOutputFormat, - type Config, -} from "bailian-cli-core"; +import { defineCommand, detectOutputFormat, type Config, type Client } from "bailian-cli-core"; import { emitResult } from "bailian-cli-runtime"; import { displayWidth, padEnd } from "bailian-cli-runtime"; @@ -65,8 +59,7 @@ const POLL_INTERVAL_MS = 500; const MAX_POLLS = 30; async function pollTelemetryApi( - config: Config, - token: string, + client: Client, api: string, reqDTO: Record, ): Promise { @@ -77,10 +70,7 @@ async function pollTelemetryApi( ? { reqDTO: { ...reqDTO, asyncTaskId: nextTaskId } } : { reqDTO }; - const raw = await callConsoleGateway(config, token, { - api, - data: requestData, - }); + const raw = await client.console(api, requestData); const resp = extractResponseData(raw as Record); @@ -325,7 +315,8 @@ export default defineCommand({ "--type Text --days 14", "--output json", ], - async run(config, flags) { + async run(ctx) { + const { config, flags } = ctx; const modelFlag = flags.model || undefined; const daysFlag = Number(flags.days) || 7; const typeFlag = flags.type || undefined; @@ -367,12 +358,8 @@ export default defineCommand({ return; } - const credential = await resolveConsoleGatewayCredential(config); - const results = await Promise.all( - models.map((model) => - pollTelemetryApi(config, credential.token, LIST_API, { ...baseReqDTO, model }), - ), + models.map((model) => pollTelemetryApi(ctx.client, LIST_API, { ...baseReqDTO, model })), ); const allItems: ModelStatisticItem[] = []; @@ -419,9 +406,7 @@ export default defineCommand({ return; } - const credential = await resolveConsoleGatewayCredential(config); - - const result = await pollTelemetryApi(config, credential.token, OVERVIEW_API, reqDTO); + const result = await pollTelemetryApi(ctx.client, OVERVIEW_API, reqDTO); if (!result) { process.stderr.write("Error: request timed out.\n"); process.exit(1); diff --git a/packages/commands/src/commands/video/download.ts b/packages/commands/src/commands/video/download.ts index ffc45a4..9c4c11c 100644 --- a/packages/commands/src/commands/video/download.ts +++ b/packages/commands/src/commands/video/download.ts @@ -1,7 +1,6 @@ import { defineCommand, - requestJson, - taskEndpoint, + taskPath, detectOutputFormat, type DashScopeTaskResponse, BailianError, @@ -12,7 +11,7 @@ import { emitResult, emitBare } from "bailian-cli-runtime"; export default defineCommand({ description: "Download a completed video by task ID", - auth: "none", + auth: "apiKey", usageArgs: "--task-id --out ", flags: { taskId: { @@ -27,7 +26,8 @@ export default defineCommand({ "--task-id 3b256896-xxxx --out video.mp4", "--task-id 3b256896-xxxx --out video.mp4 --quiet", ], - async run(config, flags) { + async run(ctx) { + const { config, flags } = ctx; const taskId = flags.taskId; const outPath = flags.out; @@ -39,9 +39,10 @@ export default defineCommand({ return; } - // Get task info to find video URL - const url = taskEndpoint(config.baseUrl, taskId); - const taskInfo = await requestJson(config, { url }); + // Get task info to find the video URL. + const taskInfo = await ctx.client.requestJson({ + path: taskPath(taskId), + }); if (taskInfo.output.task_status !== "SUCCEEDED") { throw new BailianError( diff --git a/packages/commands/src/commands/video/edit.ts b/packages/commands/src/commands/video/edit.ts index 2fed605..24ea86f 100644 --- a/packages/commands/src/commands/video/edit.ts +++ b/packages/commands/src/commands/video/edit.ts @@ -1,15 +1,12 @@ import { defineCommand, - requestJson, - videoGenerateEndpoint, - taskEndpoint, + videoGeneratePath, + taskPath, detectOutputFormat, type DashScopeVideoEditRequest, type DashScopeAsyncResponse, type DashScopeTaskResponse, resolveOutputDir, - resolveFileUrl, - resolveCredential, BailianError, ExitCode, resolveBooleanFlag, @@ -110,7 +107,8 @@ export default defineCommand({ '--video https://example.com/input.mp4 --prompt "Convert to anime style" --resolution 720P --download output.mp4', '--video https://example.com/input.mp4 --prompt "Put clothes on the kitten in the video" --watermark false', ], - async run(config, flags) { + async run(ctx) { + const { config, flags } = ctx; const videoUrl = flags.video; // prompt is optional for video edit per API spec @@ -120,8 +118,7 @@ export default defineCommand({ const format = detectOutputFormat(config.output); // Auto-upload local files - const credential = await resolveCredential(config); - const resolvedVideoUrl = await resolveFileUrl(videoUrl, credential.token, model); + const resolvedVideoUrl = await ctx.client.uploadFile(videoUrl, model); // --- Build media array --- const media: DashScopeVideoEditRequest["input"]["media"] = [ { type: "video", url: resolvedVideoUrl }, @@ -135,7 +132,7 @@ export default defineCommand({ .map((s) => s.trim()) .filter(Boolean); for (const imgUrl of images) { - const resolved = await resolveFileUrl(imgUrl, credential.token, model); + const resolved = await ctx.client.uploadFile(imgUrl, model); media.push({ type: "reference_image", url: resolved }); } } @@ -168,9 +165,8 @@ export default defineCommand({ } // --- Submit async task --- - const url = videoGenerateEndpoint(config.baseUrl); - const response = await requestJson(config, { - url, + const response = await ctx.client.requestJson({ + path: videoGeneratePath(), method: "POST", body, async: true, @@ -192,7 +188,7 @@ export default defineCommand({ // --- Poll until completion --- // Video editing is compute-intensive; default timeout = 600s (10 min) const pollInterval = flags.pollInterval ?? 15; - const pollUrl = taskEndpoint(config.baseUrl, taskId); + const pollUrl = ctx.client.url(taskPath(taskId)); const editTimeout = Math.max(config.timeout, 600); const result = await poll(config, { diff --git a/packages/commands/src/commands/video/generate.ts b/packages/commands/src/commands/video/generate.ts index bb17027..3b9de83 100644 --- a/packages/commands/src/commands/video/generate.ts +++ b/packages/commands/src/commands/video/generate.ts @@ -1,15 +1,12 @@ import { defineCommand, - requestJson, - videoGenerateEndpoint, - taskEndpoint, + videoGeneratePath, + taskPath, detectOutputFormat, type DashScopeVideoRequest, type DashScopeAsyncResponse, type DashScopeTaskResponse, resolveOutputDir, - resolveFileUrl, - resolveCredential, BailianError, ExitCode, resolveBooleanFlag, @@ -101,7 +98,8 @@ export default defineCommand({ '--prompt "Mountain landscape" --resolution 720P --duration 5', '--prompt "A cat playing with a ball" --watermark false', ], - async run(config, flags) { + async run(ctx) { + const { config, flags } = ctx; const prompt = flags.prompt; const model = @@ -115,8 +113,7 @@ export default defineCommand({ // Auto-upload local image file for i2v let resolvedImageUrl: string | undefined; if (imageUrl) { - const credential = await resolveCredential(config); - resolvedImageUrl = await resolveFileUrl(imageUrl, credential.token, model); + resolvedImageUrl = await ctx.client.uploadFile(imageUrl, model); } const watermark = resolveWatermark(flags.watermark); @@ -149,14 +146,13 @@ export default defineCommand({ // Submit async task(s) — supports --concurrent for parallel generation const concurrent = getConcurrency(flags); - const url = videoGenerateEndpoint(config.baseUrl); const responses = await runConcurrent( concurrent, config, () => - requestJson(config, { - url, + ctx.client.requestJson({ + path: videoGeneratePath(), method: "POST", body, async: true, @@ -180,7 +176,7 @@ export default defineCommand({ const pollInterval = flags.pollInterval ?? 5; const pollPromises = taskIds.map((taskId) => { - const pollUrl = taskEndpoint(config.baseUrl, taskId); + const pollUrl = ctx.client.url(taskPath(taskId)); return poll(config, { url: pollUrl, intervalSec: pollInterval, diff --git a/packages/commands/src/commands/video/ref.ts b/packages/commands/src/commands/video/ref.ts index d84d268..7643cd0 100644 --- a/packages/commands/src/commands/video/ref.ts +++ b/packages/commands/src/commands/video/ref.ts @@ -1,15 +1,12 @@ import { defineCommand, - requestJson, - videoGenerateEndpoint, - taskEndpoint, + videoGeneratePath, + taskPath, detectOutputFormat, type DashScopeVideoRefRequest, type DashScopeAsyncResponse, type DashScopeTaskResponse, resolveOutputDir, - resolveFileUrl, - resolveCredential, BailianError, ExitCode, resolveBooleanFlag, @@ -110,7 +107,8 @@ export default defineCommand({ !(f.image as string[] | undefined)?.length && !(f.refVideo as string[] | undefined)?.length ? "Provide at least one --image or --ref-video." : undefined, - async run(config, flags) { + async run(ctx) { + const { config, flags } = ctx; const prompt = flags.prompt; const images = flags.image || []; @@ -123,12 +121,11 @@ export default defineCommand({ const format = detectOutputFormat(config.output); // --- Resolve file URLs (auto-upload local files) --- - const credential = await resolveCredential(config); const media: DashScopeVideoRefRequest["input"]["media"] = []; // Add reference images for (let i = 0; i < images.length; i++) { - const resolved = await resolveFileUrl(images[i]!, credential.token, model); + const resolved = await ctx.client.uploadFile(images[i]!, model); const entry: DashScopeVideoRefRequest["input"]["media"][number] = { type: "reference_image", url: resolved, @@ -136,7 +133,7 @@ export default defineCommand({ // Pair voice by position if (imageVoices[i]) { - const resolvedVoice = await resolveFileUrl(imageVoices[i]!, credential.token, model); + const resolvedVoice = await ctx.client.uploadFile(imageVoices[i]!, model); entry.reference_voice = resolvedVoice; } @@ -145,7 +142,7 @@ export default defineCommand({ // Add reference videos for (let i = 0; i < refVideos.length; i++) { - const resolved = await resolveFileUrl(refVideos[i]!, credential.token, model); + const resolved = await ctx.client.uploadFile(refVideos[i]!, model); const entry: DashScopeVideoRefRequest["input"]["media"][number] = { type: "reference_video", url: resolved, @@ -153,7 +150,7 @@ export default defineCommand({ // Pair voice by position if (videoVoices[i]) { - const resolvedVoice = await resolveFileUrl(videoVoices[i]!, credential.token, model); + const resolvedVoice = await ctx.client.uploadFile(videoVoices[i]!, model); entry.reference_voice = resolvedVoice; } @@ -186,9 +183,8 @@ export default defineCommand({ } // --- Submit async task --- - const url = videoGenerateEndpoint(config.baseUrl); - const response = await requestJson(config, { - url, + const response = await ctx.client.requestJson({ + path: videoGeneratePath(), method: "POST", body, async: true, @@ -211,7 +207,7 @@ export default defineCommand({ // --- Poll until completion --- const pollInterval = flags.pollInterval ?? 15; - const pollUrl = taskEndpoint(config.baseUrl, taskId); + const pollUrl = ctx.client.url(taskPath(taskId)); const refTimeout = Math.max(config.timeout, 600); const result = await poll(config, { diff --git a/packages/commands/src/commands/video/task-get.ts b/packages/commands/src/commands/video/task-get.ts index 75d8946..1b1e003 100644 --- a/packages/commands/src/commands/video/task-get.ts +++ b/packages/commands/src/commands/video/task-get.ts @@ -1,7 +1,6 @@ import { defineCommand, - requestJson, - taskEndpoint, + taskPath, detectOutputFormat, type DashScopeTaskResponse, } from "bailian-cli-core"; @@ -18,7 +17,8 @@ export default defineCommand({ "--task-id 3b256896-3e70-xxxx-xxxx-xxxxxxxxxxxx", "--task-id 3b256896-3e70-xxxx --output json", ], - async run(config, flags) { + async run(ctx) { + const { config, flags } = ctx; const taskId = flags.taskId; const format = detectOutputFormat(config.output); @@ -28,8 +28,9 @@ export default defineCommand({ return; } - const url = taskEndpoint(config.baseUrl, taskId); - const response = await requestJson(config, { url }); + const response = await ctx.client.requestJson({ + path: taskPath(taskId), + }); if (config.quiet) { emitBare(response.output.task_status); diff --git a/packages/commands/src/commands/vision/describe.ts b/packages/commands/src/commands/vision/describe.ts index f5489c8..643db21 100644 --- a/packages/commands/src/commands/vision/describe.ts +++ b/packages/commands/src/commands/vision/describe.ts @@ -1,13 +1,10 @@ import { defineCommand, - requestJson, - chatEndpoint, + chatPath, detectOutputFormat, type ChatRequest, type ChatResponse, type ChatMessageContent, - resolveFileUrl, - resolveCredential, BailianError, ExitCode, isLocalFile, @@ -85,7 +82,8 @@ export default defineCommand({ !f.image && !(f.video as string[] | undefined)?.length ? "Provide --image or --video." : undefined, - async run(config, flags) { + async run(ctx) { + const { config, flags } = ctx; let image = flags.image; const videoInputs = flags.video ?? []; const model = flags.model || "qwen3-vl-plus"; @@ -121,8 +119,7 @@ export default defineCommand({ if (!existsSync(videoInput)) { throw new BailianError(`Video file not found: ${videoInput}`, ExitCode.USAGE); } - const credential = await resolveCredential(config); - videoUrl = await resolveFileUrl(videoInput, credential.token, model); + videoUrl = await ctx.client.uploadFile(videoInput, model); } contentArray.push({ type: "video_url", video_url: { url: videoUrl } }); @@ -138,8 +135,7 @@ export default defineCommand({ const { statSync } = await import("fs"); const fileSize = statSync(image).size; if (fileSize > 5 * 1024 * 1024) { - const credential = await resolveCredential(config); - finalImageUrl = await resolveFileUrl(image, credential.token, model); + finalImageUrl = await ctx.client.uploadFile(image, model); } } @@ -159,9 +155,8 @@ export default defineCommand({ ], }; - const url = chatEndpoint(config.baseUrl); - const response = await requestJson(config, { - url, + const response = await ctx.client.requestJson({ + path: chatPath(), method: "POST", body, }); diff --git a/packages/commands/src/commands/workspace/list.ts b/packages/commands/src/commands/workspace/list.ts index 94753dc..d8eaa13 100644 --- a/packages/commands/src/commands/workspace/list.ts +++ b/packages/commands/src/commands/workspace/list.ts @@ -1,9 +1,4 @@ -import { - defineCommand, - callConsoleGateway, - resolveConsoleGatewayCredential, - detectOutputFormat, -} from "bailian-cli-core"; +import { defineCommand, detectOutputFormat } from "bailian-cli-core"; import { emitResult } from "bailian-cli-runtime"; import { displayWidth, padEnd } from "bailian-cli-runtime"; @@ -92,21 +87,17 @@ export default defineCommand({ consoleSwitchAgent: { type: "number", valueHint: "", description: "Switch agent UID" }, }, exampleArgs: ["", "--list 5", "--output json"], - async run(config, flags) { + async run(ctx) { + const { config, flags } = ctx; const limit = Number(flags.list) || 0; const format = detectOutputFormat(config.output); - const credential = await resolveConsoleGatewayCredential(config); - if (config.dryRun) { emitResult({ api: LIST_WORKSPACES_API, data: {} }, format); return; } - const result = await callConsoleGateway(config, credential.token, { - api: LIST_WORKSPACES_API, - data: {}, - }); + const result = await ctx.client.console(LIST_WORKSPACES_API, {}); const resp = extractResponseData(result as Record); const dataArr = resp.data as Record[] | undefined; diff --git a/packages/core/src/advisor/intent.ts b/packages/core/src/advisor/intent.ts index e576fcd..e3af6cf 100644 --- a/packages/core/src/advisor/intent.ts +++ b/packages/core/src/advisor/intent.ts @@ -1,5 +1,5 @@ import { requestJson } from "../client/http.ts"; -import { chatEndpoint } from "../client/endpoints.ts"; +import { chatPath } from "../client/endpoints.ts"; import type { Config } from "../config/schema.ts"; import type { ChatResponse } from "../types/api.ts"; import { Complexities } from "./types.ts"; @@ -8,7 +8,7 @@ import { INTENT_MODEL, INTENT_SYSTEM_PROMPT } from "./constants/prompts.ts"; import { DEFAULT_INTENT } from "./constants/defaults.ts"; export async function analyzeIntent(config: Config, input: string): Promise { - const url = chatEndpoint(config.baseUrl); + const url = config.baseUrl + chatPath(); const body = { model: INTENT_MODEL, diff --git a/packages/core/src/advisor/recommend.ts b/packages/core/src/advisor/recommend.ts index 53244d8..43a608f 100644 --- a/packages/core/src/advisor/recommend.ts +++ b/packages/core/src/advisor/recommend.ts @@ -1,4 +1,4 @@ -import { chatEndpoint } from "../client/endpoints.ts"; +import { chatPath } from "../client/endpoints.ts"; import { request, requestJson } from "../client/http.ts"; import { parseSSE } from "../client/stream.ts"; import type { Config } from "../config/schema.ts"; @@ -238,7 +238,7 @@ export async function rankModels( body.enable_thinking = true; } - const url = chatEndpoint(config.baseUrl); + const url = config.baseUrl + chatPath(); let content: string; if (useThinkingModel) { diff --git a/packages/core/src/advisor/sources/api.ts b/packages/core/src/advisor/sources/api.ts index 3f227ba..8aa124e 100644 --- a/packages/core/src/advisor/sources/api.ts +++ b/packages/core/src/advisor/sources/api.ts @@ -1,4 +1,5 @@ import type { Config } from "../../config/schema.ts"; +import { callConsoleGateway } from "../../console/gateway.ts"; import { fetchModelList } from "../../console/models.ts"; import type { ModelProfile } from "../types.ts"; import type { ModelSource } from "./types.ts"; @@ -39,18 +40,16 @@ export class ApiSource implements ModelSource { } async load(): Promise { - const first = await fetchModelList(this.config, "", { - pageNo: 1, - pageSize: PAGE_SIZE, - }); + // Public model catalog — no console token (advisor runs unauthenticated). + const call = (api: string, data: Record) => + callConsoleGateway(this.config, "", { api, data }); + + const first = await fetchModelList(call, { pageNo: 1, pageSize: PAGE_SIZE }); const allRaw = [...first.models]; const totalPages = Math.ceil(first.total / PAGE_SIZE); for (let page = 2; page <= totalPages; page++) { - const result = await fetchModelList(this.config, "", { - pageNo: page, - pageSize: PAGE_SIZE, - }); + const result = await fetchModelList(call, { pageNo: page, pageSize: PAGE_SIZE }); allRaw.push(...result.models); } diff --git a/packages/core/src/auth/index.ts b/packages/core/src/auth/index.ts index eacb188..5759fc4 100644 --- a/packages/core/src/auth/index.ts +++ b/packages/core/src/auth/index.ts @@ -1,7 +1,3 @@ export { clearApiKey, loadApiKeyFromConfig, saveApiKeyToConfig } from "./credentials.ts"; -export { - resolveCredential, - resolveConsoleGatewayCredential, - CONSOLE_GATEWAY_NO_TOKEN_MESSAGE, -} from "./resolver.ts"; -export type { AuthMethod, ResolvedCredential } from "./types.ts"; +export { resolveApiKeyCredential, resolveConsoleCredential, describeAuth } from "./resolver.ts"; +export type { ApiKeyCredential, ConsoleCredential, AuthState, CredentialSource } from "./types.ts"; diff --git a/packages/core/src/auth/resolver.ts b/packages/core/src/auth/resolver.ts index 4d005f8..b4e3712 100644 --- a/packages/core/src/auth/resolver.ts +++ b/packages/core/src/auth/resolver.ts @@ -1,77 +1,57 @@ import type { Config } from "../config/schema.ts"; -import type { ResolvedCredential } from "./types.ts"; +import type { ApiKeyCredential, ConsoleCredential, AuthState } from "./types.ts"; import { BailianError } from "../errors/base.ts"; import { ExitCode } from "../errors/codes.ts"; -export async function resolveCredential(config: Config): Promise { - // 1. --api-key flag (explicit API key for this invocation) - if (config.apiKey) { - return { token: config.apiKey, method: "api-key", source: "flag" }; - } - - // 2. API key in config (DashScope sk-…); preferred over console token when both exist - if (config.fileApiKey) { - return { token: config.fileApiKey, method: "api-key", source: "config.json" }; - } - - // 3. access_token from env (temporary override) - if (config.accessTokenEnv) { - return { - token: config.accessTokenEnv, - method: "access-token", - source: "DASHSCOPE_ACCESS_TOKEN", - }; - } - - // 4. access_token from config (console callback) - if (config.fileAccessToken) { - return { - token: config.fileAccessToken, - method: "access-token", - source: "config.json", - }; - } - - // 5. API key from environment - if (process.env.DASHSCOPE_API_KEY) { - return { token: process.env.DASHSCOPE_API_KEY, method: "api-key", source: "DASHSCOPE_API_KEY" }; - } - - throw new BailianError( - "No credentials found.", - ExitCode.AUTH, - "Set DASHSCOPE_API_KEY environment variable, pass --api-key, or configure a key.", - ); -} +// Resolve the credential for a command's declared domain (model = api-key, +// console = access-token), by priority, or throw. Read only from `config`. /** - * Credential for Bailian **console** CLI gateway only (`callConsoleGateway`). - * DashScope API keys are not valid Bearer tokens for this gateway — use env/file - * `access_token` even when `api_key` is also present in config. + * Model-domain credential — always an API key. Priority: `--api-key` flag > + * `DASHSCOPE_API_KEY` env > config.json `api_key`. No access tokens here. */ -/** Thrown when `callConsoleGateway` has no usable console session token. */ -export const CONSOLE_GATEWAY_NO_TOKEN_MESSAGE = "No console access token found."; - -export async function resolveConsoleGatewayCredential(config: Config): Promise { - if (config.accessTokenEnv) { - return { - token: config.accessTokenEnv, - method: "access-token", - source: "DASHSCOPE_ACCESS_TOKEN", - }; - } +export async function resolveApiKeyCredential(config: Config): Promise { + const baseUrl = config.baseUrl; + if (config.apiKey) return { token: config.apiKey, baseUrl, source: "flag" }; + if (config.apiKeyEnv) return { token: config.apiKeyEnv, baseUrl, source: "env" }; + if (config.fileApiKey) return { token: config.fileApiKey, baseUrl, source: "config" }; + throw new BailianError( + "No API key found.", + ExitCode.AUTH, + "Set DASHSCOPE_API_KEY, pass --api-key, or run `bl auth login`.", + ); +} +/** Console-domain credential — an access token from `bl auth login --console`. */ +export async function resolveConsoleCredential(config: Config): Promise { if (config.fileAccessToken) { return { token: config.fileAccessToken, - method: "access-token", - source: "config.json", + region: config.consoleRegion ?? "cn-beijing", + site: config.consoleSite ?? "domestic", + switchAgent: config.consoleSwitchAgent, + source: "config", }; } - throw new BailianError( - CONSOLE_GATEWAY_NO_TOKEN_MESSAGE, + "No console access token found.", ExitCode.AUTH, - "Run `bl auth login --console` or set DASHSCOPE_ACCESS_TOKEN.", + "Run `bl auth login --console`.", ); } + +/** Full auth snapshot for `bl auth status` — what would resolve per domain (or undefined). */ +export async function describeAuth(config: Config): Promise { + const state: AuthState = {}; + try { + state.apiKey = await resolveApiKeyCredential(config); + } catch { + /* no model credential */ + } + try { + state.console = await resolveConsoleCredential(config); + } catch { + /* no console credential */ + } + return state; +} diff --git a/packages/core/src/auth/types.ts b/packages/core/src/auth/types.ts index 124a284..3418a59 100644 --- a/packages/core/src/auth/types.ts +++ b/packages/core/src/auth/types.ts @@ -1,7 +1,25 @@ -export type AuthMethod = "api-key" | "access-token"; +/** Where a resolved credential came from (shown by `bl auth status`). */ +export type CredentialSource = "flag" | "env" | "config"; -export interface ResolvedCredential { +/** Credential for the **model domain** (DashScope data plane). Always an API key. */ +export interface ApiKeyCredential { token: string; - method: AuthMethod; - source: string; + /** Base URL to send this key's requests to. */ + baseUrl: string; + source: CredentialSource; +} + +/** Credential for the **console domain** (Bailian console gateway). An access token + region/site. */ +export interface ConsoleCredential { + token: string; + region: string; + site: "domestic" | "international"; + switchAgent?: number; + source: CredentialSource; +} + +/** Full auth snapshot for display (`bl auth status`) — what would resolve per domain. */ +export interface AuthState { + apiKey?: ApiKeyCredential; + console?: ConsoleCredential; } diff --git a/packages/core/src/client/client.ts b/packages/core/src/client/client.ts new file mode 100644 index 0000000..2a01337 --- /dev/null +++ b/packages/core/src/client/client.ts @@ -0,0 +1,82 @@ +import type { Config } from "../config/schema.ts"; +import type { ApiKeyCredential, ConsoleCredential } from "../auth/types.ts"; +import { BailianError } from "../errors/base.ts"; +import { ExitCode } from "../errors/codes.ts"; +import { request, requestJson, type RequestOpts } from "./http.ts"; +import { resolveFileUrl } from "../files/upload.ts"; +import { McpClient } from "./mcp.ts"; +import { callConsoleGateway } from "../console/gateway.ts"; + +/** Like {@link RequestOpts} but with a `path` (Client prepends the credential's baseUrl). */ +export interface ClientRequestOpts extends Omit { + path: string; +} + +/** + * A command's network surface: call its methods to reach the API — the + * credential and base URL are already baked in, so commands never handle tokens + * or baseUrl. Model methods (`request`/`requestJson`/`uploadFile`/`mcp`) need an + * api key; `console` needs a console token; calling one without its credential + * throws. + */ +export class Client { + constructor( + private readonly config: Config, + private readonly apiCred?: ApiKeyCredential, + private readonly consoleCred?: ConsoleCredential, + ) {} + + private requireApi(): ApiKeyCredential { + if (!this.apiCred) { + throw new BailianError("This command needs a model-domain API key.", ExitCode.AUTH); + } + return this.apiCred; + } + + /** Model-domain base URL. Readable without a key (e.g. dry-run preview); real requests still need one. */ + get baseUrl(): string { + return this.apiCred?.baseUrl ?? this.config.baseUrl; + } + + /** Full URL for a model-domain {@link path}; build request/display URLs only through this. */ + url(path: string): string { + return this.baseUrl + path; + } + + private toOpts({ path, ...rest }: ClientRequestOpts): RequestOpts { + const cred = this.requireApi(); + return { + ...rest, + url: cred.baseUrl + path, + headers: { ...rest.headers, Authorization: `Bearer ${cred.token}` }, + noAuth: true, + }; + } + + request(opts: ClientRequestOpts): Promise { + return request(this.config, this.toOpts(opts)); + } + + requestJson(opts: ClientRequestOpts): Promise { + return requestJson(this.config, this.toOpts(opts)); + } + + /** Resolve a file arg: upload a local path to OSS (returns oss:// URL), or pass a URL through. */ + uploadFile(source: string, model: string, opts: { signal?: AbortSignal } = {}): Promise { + return resolveFileUrl(source, this.requireApi().token, model, opts); + } + + /** Open an MCP client. Accepts a path (prepended with the model baseUrl) or an absolute URL. */ + mcp(pathOrUrl: string): McpClient { + const url = /^https?:\/\//.test(pathOrUrl) ? pathOrUrl : this.requireApi().baseUrl + pathOrUrl; + return new McpClient(this.config, url, this.apiCred?.token); + } + + console(api: string, data: Record): Promise { + if (!this.consoleCred) { + throw new BailianError("This command needs a console access token.", ExitCode.AUTH); + } + // Pass only `api` + `data`; region / site / switchAgent come from config. + return callConsoleGateway(this.config, this.consoleCred.token, { api, data }) as Promise; + } +} diff --git a/packages/core/src/client/endpoints.ts b/packages/core/src/client/endpoints.ts index 7cb4ab2..c7853df 100644 --- a/packages/core/src/client/endpoints.ts +++ b/packages/core/src/client/endpoints.ts @@ -1,86 +1,78 @@ -// ---- Chat (OpenAI Compatible) ---- +// API path builders — return the path only; the Client prepends the +// credential's baseUrl. Commands never see baseUrl. -export function chatEndpoint(baseUrl: string): string { - return `${baseUrl}/compatible-mode/v1/chat/completions`; +// ---- Chat (OpenAI Compatible) ---- +export function chatPath(): string { + return "/compatible-mode/v1/chat/completions"; } // ---- Image Generation (DashScope) ---- - -export function imageEndpoint(baseUrl: string): string { - return `${baseUrl}/api/v1/services/aigc/image-generation/generation`; +export function imagePath(): string { + return "/api/v1/services/aigc/image-generation/generation"; } // Synchronous image generation (qwen-image-2.0 / qwen-image-max series) -export function imageSyncEndpoint(baseUrl: string): string { - return `${baseUrl}/api/v1/services/aigc/multimodal-generation/generation`; +export function imageSyncPath(): string { + return "/api/v1/services/aigc/multimodal-generation/generation"; } // ---- Video Generation (DashScope) ---- - -export function videoGenerateEndpoint(baseUrl: string): string { - return `${baseUrl}/api/v1/services/aigc/video-generation/video-synthesis`; +export function videoGeneratePath(): string { + return "/api/v1/services/aigc/video-generation/video-synthesis"; } // ---- Async Task Query ---- - -export function taskEndpoint(baseUrl: string, taskId: string): string { - return `${baseUrl}/api/v1/tasks/${encodeURIComponent(taskId)}`; +export function taskPath(taskId: string): string { + return `/api/v1/tasks/${encodeURIComponent(taskId)}`; } // ---- Application (Agent / Workflow) ---- - -export function appCompletionEndpoint(baseUrl: string, appId: string): string { - return `${baseUrl}/api/v1/apps/${encodeURIComponent(appId)}/completion`; +export function appCompletionPath(appId: string): string { + return `/api/v1/apps/${encodeURIComponent(appId)}/completion`; } // ---- Memory (DashScope v2) ---- - -export function memoryAddEndpoint(baseUrl: string): string { - return `${baseUrl}/api/v2/apps/memory/add`; +export function memoryAddPath(): string { + return "/api/v2/apps/memory/add"; } -export function memorySearchEndpoint(baseUrl: string): string { - return `${baseUrl}/api/v2/apps/memory/memory_nodes/search`; +export function memorySearchPath(): string { + return "/api/v2/apps/memory/memory_nodes/search"; } -export function memoryListEndpoint(baseUrl: string): string { - return `${baseUrl}/api/v2/apps/memory/memory_nodes`; +export function memoryListPath(): string { + return "/api/v2/apps/memory/memory_nodes"; } -export function memoryNodeEndpoint(baseUrl: string, nodeId: string): string { - return `${baseUrl}/api/v2/apps/memory/memory_nodes/${encodeURIComponent(nodeId)}`; +export function memoryNodePath(nodeId: string): string { + return `/api/v2/apps/memory/memory_nodes/${encodeURIComponent(nodeId)}`; } // ---- Speech Synthesis (TTS) ---- - -export function speechSynthesizeEndpoint(baseUrl: string): string { - return `${baseUrl}/api/v1/services/audio/tts/SpeechSynthesizer`; +export function speechSynthesizePath(): string { + return "/api/v1/services/audio/tts/SpeechSynthesizer"; } // ---- Speech Recognition (ASR) ---- - -export function speechRecognizeEndpoint(baseUrl: string): string { - return `${baseUrl}/api/v1/services/audio/asr/transcription`; +export function speechRecognizePath(): string { + return "/api/v1/services/audio/asr/transcription"; } // ---- Memory Profile (DashScope v2) ---- - -export function profileSchemaEndpoint(baseUrl: string): string { - return `${baseUrl}/api/v2/apps/memory/profile_schemas`; +export function profileSchemaPath(): string { + return "/api/v2/apps/memory/profile_schemas"; } -export function userProfileEndpoint(baseUrl: string, schemaId: string): string { - return `${baseUrl}/api/v2/apps/memory/profile_schemas/${encodeURIComponent(schemaId)}/profiles`; +export function userProfilePath(schemaId: string): string { + return `/api/v2/apps/memory/profile_schemas/${encodeURIComponent(schemaId)}/profiles`; } // ---- Knowledge Base Retrieve (DashScope) ---- - -export function knowledgeRetrieveEndpoint(baseUrl: string): string { - return `${baseUrl}/api/v1/indices/rag/index/retrieve`; +export function knowledgeRetrievePath(): string { + return "/api/v1/indices/rag/index/retrieve"; } // ---- MCP Services (Streamable HTTP) ---- - -export function mcpWebSearchEndpoint(baseUrl: string): string { - return `${baseUrl}/api/v1/mcps/WebSearch/mcp`; +export function mcpWebSearchPath(): string { + return "/api/v1/mcps/WebSearch/mcp"; } diff --git a/packages/core/src/client/http.ts b/packages/core/src/client/http.ts index 22490b3..0c3b21b 100644 --- a/packages/core/src/client/http.ts +++ b/packages/core/src/client/http.ts @@ -2,7 +2,7 @@ import type { Config } from "../config/schema.ts"; import type { ApiErrorBody } from "../errors/api.ts"; import { BailianError } from "../errors/base.ts"; import { ExitCode } from "../errors/codes.ts"; -import { resolveCredential } from "../auth/resolver.ts"; +import { resolveApiKeyCredential } from "../auth/resolver.ts"; import { mapApiError } from "../errors/api.ts"; import { maskToken } from "../utils/token.ts"; import { SOURCE_CONFIG, trackingHeaders } from "./headers.ts"; @@ -54,7 +54,7 @@ export async function request(config: Config, opts: RequestOpts): Promise` resolved via - * `resolveCredential`. Bailian MCPs all accept this; non-Bailian endpoints + * `resolveApiKeyCredential`. Bailian MCPs all accept this; non-Bailian endpoints * are out of scope for this client. */ import type { Config } from "../config/schema.ts"; import { BailianError } from "../errors/base.ts"; import { ExitCode } from "../errors/codes.ts"; -import { resolveCredential } from "../auth/resolver.ts"; +import { resolveApiKeyCredential } from "../auth/resolver.ts"; import { trackingHeaders } from "./headers.ts"; // ---- JSON-RPC 2.0 Types ---- @@ -58,9 +58,8 @@ export interface McpToolResult { * The path is `/api/v1/mcps//mcp`; the `serverCode` is taken * verbatim from `bl mcp list` (e.g. `WebSearch`, `market-cmapi00073529`). */ -export function bailianMcpUrl(baseUrl: string, serverCode: string): string { - const root = baseUrl.replace(/\/$/, ""); - return `${root}/api/v1/mcps/${serverCode}/mcp`; +export function bailianMcpPath(serverCode: string): string { + return `/api/v1/mcps/${serverCode}/mcp`; } // ---- MCP Client ---- @@ -72,15 +71,17 @@ export class McpClient { private config: Config; private authToken: string | undefined; - constructor(config: Config, url: string) { + constructor(config: Config, url: string, authToken?: string) { this.config = config; this.url = url; + this.authToken = authToken; } /** Initialize the MCP session. Must be called before any other method. */ async initialize(): Promise { - const credential = await resolveCredential(this.config); - this.authToken = credential.token; + if (!this.authToken) { + this.authToken = (await resolveApiKeyCredential(this.config)).token; + } const result = await this.rpc("initialize", { protocolVersion: "2025-03-26", diff --git a/packages/core/src/config/loader.ts b/packages/core/src/config/loader.ts index acd8b8f..16c2db1 100644 --- a/packages/core/src/config/loader.ts +++ b/packages/core/src/config/loader.ts @@ -32,8 +32,8 @@ export function loadConfig(flags: GlobalFlags): Config { const file = readConfigFile(); const apiKey = flags.apiKey || undefined; + const apiKeyEnv = process.env.DASHSCOPE_API_KEY?.trim() || undefined; const fileApiKey = file.api_key; - const accessTokenEnv = process.env.DASHSCOPE_ACCESS_TOKEN?.trim() || undefined; const fileAccessToken = file.access_token?.trim() || undefined; const baseUrl = flags.baseUrl || file.base_url || process.env.DASHSCOPE_BASE_URL || REGIONS.cn; @@ -56,7 +56,7 @@ export function loadConfig(flags: GlobalFlags): Config { return { apiKey, - accessTokenEnv, + apiKeyEnv, fileAccessToken, fileApiKey, configPath: getConfigPath(), diff --git a/packages/core/src/config/schema.ts b/packages/core/src/config/schema.ts index b25bd1e..2c6bd6f 100644 --- a/packages/core/src/config/schema.ts +++ b/packages/core/src/config/schema.ts @@ -104,9 +104,10 @@ export interface Config { binName?: string; /** npm package name for self-update (e.g. "bailian-cli", "bailian-cli-rag"), injected by createCli. */ npmPackage?: string; + /** `--api-key` flag (highest priority for the model domain). */ apiKey?: string; - /** `DASHSCOPE_ACCESS_TOKEN` env (explicit override). */ - accessTokenEnv?: string; + /** `DASHSCOPE_API_KEY` env (model domain). */ + apiKeyEnv?: string; /** `access_token` in config file (console login). */ fileAccessToken?: string; fileApiKey?: string; diff --git a/packages/core/src/console/models.ts b/packages/core/src/console/models.ts index 6b04949..9b7a427 100644 --- a/packages/core/src/console/models.ts +++ b/packages/core/src/console/models.ts @@ -1,6 +1,3 @@ -import { callConsoleGateway } from "./gateway.ts"; -import type { Config } from "../config/schema.ts"; - const MODEL_LIST_API = "zeldaHttp.dashscopeModel./zelda/api/v1/modelCenter/listFoundationModels"; export interface ModelListParams { @@ -16,27 +13,24 @@ export interface ModelListResult { models: Record[]; } +/** Page the console model-list API. `call` makes the gateway request (e.g. `client.console`). */ export async function fetchModelList( - config: Config, - token: string, + call: (api: string, data: Record) => Promise, params: ModelListParams = {}, ): Promise { const { pageNo = 1, pageSize = 50, name = "", providers = [], capabilities = [] } = params; - const result = (await callConsoleGateway(config, token, { - api: MODEL_LIST_API, - data: { - input: { - pageNo, - pageSize, - name, - providers, - inferenceProviders: [], - features: [], - group: true, - capabilities, - contextWindows: [], - }, + const result = (await call(MODEL_LIST_API, { + input: { + pageNo, + pageSize, + name, + providers, + inferenceProviders: [], + features: [], + group: true, + capabilities, + contextWindows: [], }, })) as any; diff --git a/packages/core/src/telemetry/tracker.ts b/packages/core/src/telemetry/tracker.ts index 8e5fec2..912e7e7 100644 --- a/packages/core/src/telemetry/tracker.ts +++ b/packages/core/src/telemetry/tracker.ts @@ -121,8 +121,9 @@ export async function trackCommandExecution( let authMethod: string | undefined; if (config.apiKey) authMethod = "api-key"; + else if (config.apiKeyEnv) authMethod = "api-key"; else if (config.fileApiKey) authMethod = "api-key"; - else if (config.accessTokenEnv || config.fileAccessToken) authMethod = "access-token"; + else if (config.fileAccessToken) authMethod = "access-token"; const event = createTrackingEvent({ command: commandPath.join(" "), diff --git a/packages/core/src/types/command.ts b/packages/core/src/types/command.ts index 1337ccb..066b50f 100644 --- a/packages/core/src/types/command.ts +++ b/packages/core/src/types/command.ts @@ -1,4 +1,5 @@ import type { Config } from "../config/schema.ts"; +import type { Client } from "../client/client.ts"; // ── Flag definitions ───────────────────────────────────────────────────────── // Flags are keyed by camelCase name (the key IS the parsed flag name, e.g. @@ -101,6 +102,18 @@ export type GlobalFlags = ParsedFlags; /** A command's full flags: global + its own flags, inferred in one pass. */ export type Flags = ParsedFlags; +/** + * What a command's `run` receives: use `client` for all network calls (its + * credential is already injected per the command's `auth`), `config` for + * settings, and `flags` for parsed arguments. Never handle tokens or baseUrl. + */ +export interface CommandContext { + /** Network surface; the credential for the command's `auth` is pre-injected. */ + client: Client; + config: Config; + flags: Flags; +} + // ── Command ────────────────────────────────────────────────────────────────── /** * A command. Generic over its flags `F` so `run`/`validate` receive precisely @@ -123,7 +136,7 @@ export interface Command { * parser — use this for rules spanning flags or depending on a flag's *value*. */ validate?: (flags: Flags) => string | undefined; - run: (config: Config, flags: Flags) => Promise; + run: (ctx: CommandContext) => Promise; } /** Type-erased command for heterogeneous storage (registry / context). */ diff --git a/packages/core/src/utils/env.ts b/packages/core/src/utils/env.ts index 17aff44..32c2130 100644 --- a/packages/core/src/utils/env.ts +++ b/packages/core/src/utils/env.ts @@ -1,28 +1,7 @@ /** * Environment detection utilities for bailian-cli. - * - * Used to determine whether the CLI is running in an interactive terminal - * (human user) or in a non-interactive environment (CI, agent, pipe, etc.), - * so commands can adjust their behavior accordingly. */ -/** - * Detects whether the current environment is interactive. - * - * Returns false when: - * - stdout or stdin is not a TTY - * - The --non-interactive flag was explicitly set - * - The process is running in a known CI environment (CI env var present) - * - * Returns true when stdout and stdin are both TTYs and --non-interactive - * was not passed. - */ -export function isInteractive(options?: { nonInteractive?: boolean }): boolean { - if (options?.nonInteractive === true) return false; - if (process.env.CI) return false; - return process.stdout.isTTY === true && process.stdin.isTTY === true; -} - /** * Detects whether the current process is running in a CI environment. */ diff --git a/packages/core/src/utils/index.ts b/packages/core/src/utils/index.ts index 25b9f30..33bfeb0 100644 --- a/packages/core/src/utils/index.ts +++ b/packages/core/src/utils/index.ts @@ -1,7 +1,6 @@ export { generateFilename } from "./filename.ts"; export { resolveOutputDir } from "./output-dir.ts"; export { maskToken } from "./token.ts"; -export { isInteractive } from "./env.ts"; export { isCI } from "./env.ts"; export { stripUndefined } from "./object.ts"; export { diff --git a/packages/runtime/package.json b/packages/runtime/package.json index 246f52b..5ec06ae 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -58,13 +58,9 @@ "node": ">=22.12.0" }, "inlinedDependencies": { - "@clack/core": "0.3.5", - "@clack/prompts": "0.7.0", "ajv": "8.20.0", "fast-deep-equal": "3.1.3", "fast-uri": "3.1.2", - "json-schema-traverse": "1.0.0", - "picocolors": "1.1.1", - "sisteransi": "1.0.5" + "json-schema-traverse": "1.0.0" } } diff --git a/packages/runtime/src/create-cli.ts b/packages/runtime/src/create-cli.ts index a3a4fe6..b8b946f 100644 --- a/packages/runtime/src/create-cli.ts +++ b/packages/runtime/src/create-cli.ts @@ -10,7 +10,7 @@ import { type RunContext, } from "./middleware.ts"; import type { AnyCommand, Config, GlobalFlags } from "bailian-cli-core"; -import { GLOBAL_FLAGS, UsageError, loadConfig, flushTelemetry } from "bailian-cli-core"; +import { GLOBAL_FLAGS, UsageError, loadConfig, flushTelemetry, Client } from "bailian-cli-core"; import { setupProxyFromEnv } from "./proxy.ts"; import { handleError } from "./error-handler.ts"; import { printWelcomeBanner, printQuickStart } from "./output/banner.ts"; @@ -78,12 +78,7 @@ export function createCli(commands: Record, opts: CliOptions let hasKey = false; try { const config = buildConfig(parseFlags(argv, GLOBAL_FLAGS)); - hasKey = !!( - config.apiKey || - config.fileApiKey || - config.fileAccessToken || - config.accessTokenEnv - ); + hasKey = !!(config.apiKey || config.apiKeyEnv || config.fileApiKey || config.fileAccessToken); } catch { /* unparseable global flags on the bare invocation — fall through to welcome */ } @@ -127,6 +122,7 @@ export function createCli(commands: Record, opts: CliOptions command: res.command, config, flags, + client: new Client(config), }; await runMiddleware(ctx); await flushTelemetry(1000); diff --git a/packages/runtime/src/error-handler.ts b/packages/runtime/src/error-handler.ts index c362372..0cf55a2 100644 --- a/packages/runtime/src/error-handler.ts +++ b/packages/runtime/src/error-handler.ts @@ -1,10 +1,4 @@ -import { - BailianError, - ExitCode, - detectOutputFormat, - type OutputFormat, - CONSOLE_GATEWAY_NO_TOKEN_MESSAGE, -} from "bailian-cli-core"; +import { BailianError, ExitCode, detectOutputFormat, type OutputFormat } from "bailian-cli-core"; import { API_KEY_PAGE } from "./urls.ts"; const LABEL_WIDTH = 13; @@ -30,10 +24,9 @@ function alignContinuation(text: string): string { function enhanceHint(err: BailianError): string | undefined { if (err.exitCode === ExitCode.AUTH) { - if ( - err.message === CONSOLE_GATEWAY_NO_TOKEN_MESSAGE || - err.hint?.includes("auth login --console") - ) { + // Console-domain auth errors already carry their own `--console` hint; don't + // append the api-key onboarding lines. + if (err.hint?.includes("auth login --console")) { return err.hint; } return [ diff --git a/packages/runtime/src/index.ts b/packages/runtime/src/index.ts index 3fc89e2..b86e0f7 100644 --- a/packages/runtime/src/index.ts +++ b/packages/runtime/src/index.ts @@ -26,7 +26,6 @@ export { BAILIAN_CONSOLE_ROOT, BAILIAN_CONSOLE, API_KEY_PAGE } from "./urls.ts"; // Output facilities consumed by commands export { emitResult, emitBare } from "./output/output.ts"; -export { promptText, promptSelect, promptConfirm, cmdUsage } from "./output/prompt.ts"; export { createSpinner, createProgressBar } from "./output/progress.ts"; export { printWelcomeBanner, printQuickStart } from "./output/banner.ts"; export { maybeShowStatusBar } from "./output/status-bar.ts"; @@ -37,7 +36,6 @@ export { poll } from "./utils/polling.ts"; export { downloadFile, formatBytes } from "./utils/download.ts"; export { runConcurrent, getConcurrency, downloadParallel } from "./utils/concurrent.ts"; export { resolveImageSize } from "./utils/image-size.ts"; -export { ensureApiKey } from "./utils/ensure-key.ts"; export { checkForUpdate, getPendingUpdateNotification, diff --git a/packages/runtime/src/middleware.ts b/packages/runtime/src/middleware.ts index c72bb3d..4460472 100644 --- a/packages/runtime/src/middleware.ts +++ b/packages/runtime/src/middleware.ts @@ -1,14 +1,17 @@ -import type { AnyCommand, Config, GlobalFlags } from "bailian-cli-core"; -import { resolveCredential, trackCommandExecution } from "bailian-cli-core"; -import { ensureApiKey } from "./utils/ensure-key.ts"; +import type { AnyCommand, Config, GlobalFlags, ApiKeyCredential } from "bailian-cli-core"; +import { + Client, + resolveApiKeyCredential, + resolveConsoleCredential, + trackCommandExecution, +} from "bailian-cli-core"; import { maybeShowStatusBar } from "./output/status-bar.ts"; import { checkForUpdate, getPendingUpdateNotification } from "./utils/update-checker.ts"; /** - * Everything a stage needs about the invocation in flight. Built once per `run` - * by the kernel and threaded through the middleware stack. The command itself - * still receives `(config, flags)` — this context is the pipeline's, not the - * command's — so adding cross-cutting concerns never touches command code. + * What each middleware stage gets for the invocation in flight: the matched + * `command` with its `path`/`config`/`flags`, and the `client` (populated by + * {@link authStage}). A stage reads these and may augment them before `next()`. */ export interface RunContext { readonly binName: string; @@ -19,6 +22,8 @@ export interface RunContext { readonly command: AnyCommand; config: Config; flags: GlobalFlags; + /** Network surface with the credential baked in — set by {@link authStage}. */ + client: Client; } /** Koa-style onion middleware: do work, call `next()`, do work after it returns. */ @@ -37,18 +42,24 @@ export function compose(stack: Middleware[]): (ctx: RunContext) => Promise } /** - * Prepare credentials for commands that need an API key. console / none - * commands resolve their own (or no) credential inside the command body. + * Bake the credential for the command's declared `auth` into `ctx.client`, and + * gate: no credential → throw before the command runs (skipped under --dry-run, + * which needs none). `auth: "none"` commands keep a credential-less client. */ export const authStage: Middleware = async (ctx, next) => { - if (ctx.command.auth === "apiKey" && !ctx.config.dryRun) { - await ensureApiKey(ctx.config); + const { command, config } = ctx; + if (command.auth === "apiKey") { + let cred: ApiKeyCredential | undefined; try { - const credential = await resolveCredential(ctx.config); - maybeShowStatusBar(ctx.config, credential.token, credential); - } catch { - /* no credential resolved — skip the status bar */ + cred = await resolveApiKeyCredential(config); + } catch (err) { + if (!config.dryRun) throw err; // dry-run only prints the request — no key needed } + ctx.client = new Client(config, cred); + if (cred) maybeShowStatusBar(config, cred.token, cred); + } else if (command.auth === "console" && !config.dryRun) { + const cred = await resolveConsoleCredential(config); + ctx.client = new Client(config, undefined, cred); } await next(); }; @@ -79,5 +90,5 @@ export const versionCheckStage: Middleware = async (ctx, next) => { } }; -/** Innermost stage: hand control to the command. */ -export const runCommandStage: Middleware = (ctx) => ctx.command.run(ctx.config, ctx.flags); +/** Innermost stage: hand control to the command with its full context. */ +export const runCommandStage: Middleware = (ctx) => ctx.command.run(ctx); diff --git a/packages/runtime/src/output/prompt.ts b/packages/runtime/src/output/prompt.ts deleted file mode 100644 index 012ca9c..0000000 --- a/packages/runtime/src/output/prompt.ts +++ /dev/null @@ -1,105 +0,0 @@ -/** - * Interactive prompt utilities. - * - * Wraps @clack/prompts with environment-awareness: - * - In interactive mode: shows prompts and lets users input values. - * - In non-interactive / CI / Agent mode: fails fast with a clear error. - * - * All functions here are no-ops (return undefined) when non-interactive, - * so callers must check isInteractive() first or handle the missing-value - * case explicitly. - */ - -import { isInteractive, type Config } from "bailian-cli-core"; - -/** - * Build a command-usage string prefixed with the product binary name, e.g. - * `bl --list-voices --model x`. Used for actionable hints inside error messages - * (the error boundary renders full command help separately). - */ -export function cmdUsage(config: Config, args = ""): string { - const bin = config.binName ?? ""; - return args ? `${bin} ${args}` : bin; -} - -// Dynamic import to avoid loading @clack/prompts in non-interactive envs unnecessarily -// (though for CLI tools the startup cost is usually acceptable) - -/** - * Prompt the user for a text value. - * Only call this when isInteractive() is true; otherwise the function returns - * undefined immediately so the caller can fail fast. - */ -export async function promptText(options: { - message: string; - defaultValue?: string; -}): Promise { - if (!isInteractive()) return undefined; - - const { defaultValue, message } = options; - const inquirer = (await import("@clack/prompts")) as { - text: (opts: { - message: string; - default?: string; - placeholder?: string; - }) => Promise; - }; - const val = await inquirer.text({ - message, - default: defaultValue, - placeholder: defaultValue, - }); - - // @clack/prompts returns a Symbol.cancel when the user presses Ctrl+C - if (typeof val === "symbol") return undefined; - return val as string; -} - -/** - * Like promptText but confirms with y/N before proceeding. - */ -export async function promptConfirm(options: { - message: string; - initialValue?: boolean; -}): Promise { - if (!isInteractive()) return undefined; - - const { message, initialValue } = options; - const inquirer = (await import("@clack/prompts")) as { - confirm: (opts: { message: string; initialValue?: boolean }) => Promise; - }; - const val = await inquirer.confirm({ message, initialValue }); - - if (typeof val === "symbol") return undefined; - return val as boolean; -} - -/** - * Prompt the user to select one value from a list. - * Only call this when isInteractive() is true; otherwise the function returns - * undefined immediately so the caller can fail fast. - */ -export async function promptSelect(options: { - message: string; - choices: Array<{ value: string; label: string; hint?: string }>; - defaultValue?: string; -}): Promise { - if (!isInteractive()) return undefined; - - const { message, choices, defaultValue } = options; - const clack = (await import("@clack/prompts")) as { - select: (opts: { - message: string; - initialValue?: string; - options: Array<{ value: string; label: string; hint?: string }>; - }) => Promise; - }; - const val = await clack.select({ - message, - initialValue: defaultValue, - options: choices, - }); - - if (typeof val === "symbol") return undefined; - return val as string; -} diff --git a/packages/runtime/src/output/status-bar.ts b/packages/runtime/src/output/status-bar.ts index 54abbee..be69215 100644 --- a/packages/runtime/src/output/status-bar.ts +++ b/packages/runtime/src/output/status-bar.ts @@ -1,5 +1,5 @@ import { homedir } from "os"; -import { maskToken, type Config, type ResolvedCredential } from "bailian-cli-core"; +import { maskToken, type Config, type ApiKeyCredential } from "bailian-cli-core"; const reset = "\x1b[0m"; const dim = "\x1b[2m"; @@ -14,13 +14,13 @@ function tildePath(p: string): string { export function maybeShowStatusBar( config: Config, token: string, - resolved?: ResolvedCredential, + resolved?: ApiKeyCredential, ): void { if (config.quiet || !process.stderr.isTTY) return; const filePath = config.configPath ? tildePath(config.configPath) : "~/.bailian/config.json"; const authTag = resolved - ? `${resolved.source} · ${resolved.method}` + ? `${resolved.source} · api-key` : config.apiKey ? "flag · api-key" : "config"; diff --git a/packages/runtime/src/pipeline/steps/bl-api.ts b/packages/runtime/src/pipeline/steps/bl-api.ts index 37d16dd..8a16f20 100644 --- a/packages/runtime/src/pipeline/steps/bl-api.ts +++ b/packages/runtime/src/pipeline/steps/bl-api.ts @@ -4,15 +4,15 @@ */ import { requestJson, - chatEndpoint, - imageEndpoint, - imageSyncEndpoint, - videoGenerateEndpoint, - taskEndpoint, - speechSynthesizeEndpoint, - speechRecognizeEndpoint, + chatPath, + imagePath, + imageSyncPath, + videoGeneratePath, + taskPath, + speechSynthesizePath, + speechRecognizePath, resolveFileUrl, - resolveCredential, + resolveApiKeyCredential, stripUndefined, resolveBooleanFlag, resolveWatermark, @@ -81,7 +81,7 @@ export async function textChat( } } - const url = chatEndpoint(config.baseUrl); + const url = config.baseUrl + chatPath(); const response = await requestJson(config, { url, method: "POST", @@ -118,7 +118,7 @@ export async function visionDescribe( if (input.video) { let videoUrl = input.video; if (isLocalFile(videoUrl)) { - const credential = await resolveCredential(config); + const credential = await resolveApiKeyCredential(config); videoUrl = await resolveFileUrl(videoUrl, credential.token, model, { signal: ctx.signal }); } contentArray.push({ type: "video_url", video_url: { url: videoUrl } }); @@ -128,7 +128,7 @@ export async function visionDescribe( for (const img of images) { let imageUrl = img; if (isLocalFile(img)) { - const credential = await resolveCredential(config); + const credential = await resolveApiKeyCredential(config); imageUrl = await resolveFileUrl(img, credential.token, model, { signal: ctx.signal }); } contentArray.push({ type: "image_url", image_url: { url: imageUrl } }); @@ -141,7 +141,7 @@ export async function visionDescribe( messages: [{ role: "user", content: contentArray }], }; - const url = chatEndpoint(config.baseUrl); + const url = config.baseUrl + chatPath(); return await requestJson(config, { url, method: "POST", @@ -208,7 +208,7 @@ export async function imageGenerate( }; if (useSync) { - const url = imageSyncEndpoint(config.baseUrl); + const url = config.baseUrl + imageSyncPath(); const response = await requestJson(config, { url, method: "POST", @@ -223,7 +223,7 @@ export async function imageGenerate( return { urls, request_id: response.request_id, ...(saved ? { saved } : {}) }; } else { // Async mode: submit then poll - const url = imageEndpoint(config.baseUrl); + const url = config.baseUrl + imagePath(); const asyncResp = await requestJson(config, { url, method: "POST", @@ -282,7 +282,7 @@ export async function imageEdit( for (const img of images) { let imageUrl = img; if (isLocalFile(img)) { - const credential = await resolveCredential(config); + const credential = await resolveApiKeyCredential(config); imageUrl = await resolveFileUrl(img, credential.token, model, { signal: ctx.signal }); } content.push({ image: imageUrl }); @@ -305,7 +305,7 @@ export async function imageEdit( }; if (useSync) { - const url = imageSyncEndpoint(config.baseUrl); + const url = config.baseUrl + imageSyncPath(); const response = await requestJson(config, { url, method: "POST", @@ -319,7 +319,7 @@ export async function imageEdit( const saved = await maybeDownloadImages(urls, input["out-dir"], input["out-prefix"]); return { urls, request_id: response.request_id, ...(saved ? { saved } : {}) }; } else { - const url = imageEndpoint(config.baseUrl); + const url = config.baseUrl + imagePath(); const asyncResp = await requestJson(config, { url, method: "POST", @@ -396,7 +396,7 @@ export async function videoGenerate( let resolvedImageUrl: string | undefined; if (input.image) { if (isLocalFile(input.image)) { - const credential = await resolveCredential(config); + const credential = await resolveApiKeyCredential(config); resolvedImageUrl = await resolveFileUrl(input.image, credential.token, model, { signal: ctx.signal, }); @@ -425,7 +425,7 @@ export async function videoGenerate( }; stripUndefined(body.parameters as Record); - const url = videoGenerateEndpoint(config.baseUrl); + const url = config.baseUrl + videoGeneratePath(); const asyncResp = await requestJson(config, { url, method: "POST", @@ -496,7 +496,7 @@ export async function speechSynthesize( }; stripUndefined(body.input as Record); - const url = speechSynthesizeEndpoint(config.baseUrl); + const url = config.baseUrl + speechSynthesizePath(); const response = await requestJson(config, { url, method: "POST", @@ -542,7 +542,7 @@ export async function speechRecognize( const fileUrls: string[] = []; for (const u of rawUrls) { if (isLocalFile(u)) { - const credential = await resolveCredential(config); + const credential = await resolveApiKeyCredential(config); fileUrls.push( await resolveFileUrl(u, credential.token, input.model || "fun-asr", { signal: ctx.signal, @@ -567,7 +567,7 @@ export async function speechRecognize( }; stripUndefined(body.parameters as Record); - const url = speechRecognizeEndpoint(config.baseUrl); + const url = config.baseUrl + speechRecognizePath(); const asyncResp = await requestJson(config, { url, method: "POST", @@ -644,7 +644,7 @@ async function pollTaskWithOptions( await delay(pollIntervalMs, ctx?.signal); attempt++; - const url = taskEndpoint(config.baseUrl, taskId); + const url = config.baseUrl + taskPath(taskId); const result = await requestJson(config, { url, method: "GET", diff --git a/packages/runtime/src/utils/ensure-key.ts b/packages/runtime/src/utils/ensure-key.ts deleted file mode 100644 index 972f5f9..0000000 --- a/packages/runtime/src/utils/ensure-key.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { - BailianError, - ExitCode, - isInteractive, - maskToken, - readConfigFile, - writeConfigFile, - type Config, -} from "bailian-cli-core"; -import { promptText, promptConfirm } from "../output/prompt.ts"; - -export async function ensureApiKey(config: Config): Promise { - if (config.apiKey || config.fileApiKey || config.accessTokenEnv || config.fileAccessToken) return; - - const envKey = process.env.DASHSCOPE_API_KEY; - let key: string | undefined; - - if (envKey) { - if (!isInteractive({ nonInteractive: config.nonInteractive })) { - key = envKey; - } else { - const use = await promptConfirm({ - message: `Found DASHSCOPE_API_KEY in environment (${maskToken(envKey)}). Save it to config file?`, - }); - if (use) key = envKey; - } - } - - if (!key) { - if (!isInteractive({ nonInteractive: config.nonInteractive })) { - throw new BailianError( - "No API key found.", - ExitCode.AUTH, - "Set DASHSCOPE_API_KEY environment variable, pass --api-key, or run interactively to be prompted.", - ); - } - const input = await promptText({ message: "Enter your DashScope API key:" }); - if (!input) throw new BailianError("API key is required.", ExitCode.AUTH); - key = input; - } - - const data: Record = { - ...(readConfigFile() as Record), - api_key: key, - }; - await writeConfigFile(data); - config.fileApiKey = key; - - const path = config.configPath ?? "~/.bailian/config.json"; - process.stderr.write(`API key saved to ${path}\n`); -}