From 98ba3279fa8abedfa615f2e4b5682dd52d96f598 Mon Sep 17 00:00:00 2001 From: clh02467605 Date: Fri, 14 Aug 2026 15:27:26 +0800 Subject: [PATCH] fix(runtime): expose errno in fetch-failed JSON cause.code --- packages/runtime/src/error-handler.ts | 3 +- packages/runtime/tests/error-handler.test.ts | 85 +++++++++++++ skills/bailian-cli/reference/index.md | 120 +++++++++---------- skills/bailian-cli/reference/usage.md | 25 ++-- 4 files changed, 160 insertions(+), 73 deletions(-) create mode 100644 packages/runtime/tests/error-handler.test.ts diff --git a/packages/runtime/src/error-handler.ts b/packages/runtime/src/error-handler.ts index 11494ef..b3d8920 100644 --- a/packages/runtime/src/error-handler.ts +++ b/packages/runtime/src/error-handler.ts @@ -78,11 +78,12 @@ function fromFetchFailed(err: TypeError): BailianError { if (causeMsg && causeMsg !== code) detailParts.push(causeMsg); const detail = detailParts.length > 0 ? detailParts.join(": ") : "unknown cause"; + // Prefer the errno (ENOTFOUND, …) so JSON toJSON() exposes cause.code for agents. return new BailianError( `Network request failed: ${detail}`, ExitCode.NETWORK, pickNetworkHint(code), - { cause: err }, + { cause: cause ?? err }, ); } diff --git a/packages/runtime/tests/error-handler.test.ts b/packages/runtime/tests/error-handler.test.ts new file mode 100644 index 0000000..a128657 --- /dev/null +++ b/packages/runtime/tests/error-handler.test.ts @@ -0,0 +1,85 @@ +import { ExitCode } from "bailian-cli-core"; +import { expect, test } from "vite-plus/test"; +import { handleError } from "../src/error-handler.ts"; + +test("handleError: fetch failed JSON includes cause.code from errno", () => { + const previousOutput = process.env.DASHSCOPE_OUTPUT; + process.env.DASHSCOPE_OUTPUT = "json"; + + let stderr = ""; + const originalWrite = process.stderr.write.bind(process.stderr); + const originalExit = process.exit; + process.stderr.write = ((chunk: string | Uint8Array) => { + stderr += String(chunk); + return true; + }) as typeof process.stderr.write; + process.exit = ((code?: number) => { + throw new Error(`process.exit:${code ?? 0}`); + }) as typeof process.exit; + + const root = Object.assign(new Error("getaddrinfo ENOTFOUND example.invalid"), { + code: "ENOTFOUND", + }); + const fetchFailed = new TypeError("fetch failed", { cause: root }); + + try { + expect(() => handleError(fetchFailed, "bl")).toThrow( + new RegExp(`process\\.exit:${ExitCode.NETWORK}`), + ); + const payload = JSON.parse(stderr.trim()) as { + error: { code: number; message: string; cause?: { message: string; code?: string } }; + }; + expect(payload.error.code).toBe(ExitCode.NETWORK); + expect(payload.error.message).toMatch(/ENOTFOUND/); + expect(payload.error.cause).toEqual({ + message: root.message, + code: "ENOTFOUND", + }); + } finally { + process.stderr.write = originalWrite; + process.exit = originalExit; + if (previousOutput === undefined) { + delete process.env.DASHSCOPE_OUTPUT; + } else { + process.env.DASHSCOPE_OUTPUT = previousOutput; + } + } +}); + +test("handleError: fetch failed without nested cause still maps to NETWORK", () => { + const previousOutput = process.env.DASHSCOPE_OUTPUT; + process.env.DASHSCOPE_OUTPUT = "json"; + + let stderr = ""; + const originalWrite = process.stderr.write.bind(process.stderr); + const originalExit = process.exit; + process.stderr.write = ((chunk: string | Uint8Array) => { + stderr += String(chunk); + return true; + }) as typeof process.stderr.write; + process.exit = ((code?: number) => { + throw new Error(`process.exit:${code ?? 0}`); + }) as typeof process.exit; + + const fetchFailed = new TypeError("fetch failed"); + + try { + expect(() => handleError(fetchFailed, "bl")).toThrow( + new RegExp(`process\\.exit:${ExitCode.NETWORK}`), + ); + const payload = JSON.parse(stderr.trim()) as { + error: { code: number; message: string; cause?: { message: string; code?: string } }; + }; + expect(payload.error.code).toBe(ExitCode.NETWORK); + expect(payload.error.message).toMatch(/unknown cause/); + expect(payload.error.cause).toEqual({ message: "fetch failed" }); + } finally { + process.stderr.write = originalWrite; + process.exit = originalExit; + if (previousOutput === undefined) { + delete process.env.DASHSCOPE_OUTPUT; + } else { + process.env.DASHSCOPE_OUTPUT = previousOutput; + } + } +}); diff --git a/skills/bailian-cli/reference/index.md b/skills/bailian-cli/reference/index.md index 2fa87ae..e9df5cc 100644 --- a/skills/bailian-cli/reference/index.md +++ b/skills/bailian-cli/reference/index.md @@ -9,66 +9,66 @@ Use this index for the skill-scoped quick index and global flags. ## Quick index -| Command | Description | Detail | -| ------------------------------- | ---------------------------------------------------------------------------------------------- | ------------------------------ | -| `bl advisor recommend` | Recommend the best models for your use case (intent analysis → candidate recall → LLM ranking) | [advisor.md](advisor.md) | -| `bl app call` | Call a Bailian application (agent or workflow) | [app.md](app.md) | -| `bl app list` | List Bailian applications | [app.md](app.md) | -| `bl auth generate-access-token` | Generate a CLI access token using OpenAPI AK/SK | [auth.md](auth.md) | -| `bl auth login` | Authenticate with API key, console browser login, or OpenAPI AK/SK (credentials can coexist) | [auth.md](auth.md) | -| `bl auth logout` | Clear stored credentials; full logout also clears the model Base URL | [auth.md](auth.md) | -| `bl auth status` | Show current authentication state | [auth.md](auth.md) | -| `bl config agent` | Configure a coding agent to use DashScope API | [config.md](config.md) | -| `bl config list` | List config profiles and show the active profile | [config.md](config.md) | -| `bl config set` | Set a config value | [config.md](config.md) | -| `bl config show` | Display current configuration | [config.md](config.md) | -| `bl config ui` | Open a local web UI to manage config profiles | [config.md](config.md) | -| `bl config use` | Set the active config profile | [config.md](config.md) | -| `bl console call` | Call a Bailian console API via the CLI gateway | [console.md](console.md) | -| `bl file upload` | Upload a local file to DashScope temporary storage (48h) | [file.md](file.md) | -| `bl knowledge chat` | Chat with a Bailian knowledge base (RAG Q&A with streaming) | [knowledge.md](knowledge.md) | -| `bl knowledge retrieve` | Retrieve from a Bailian knowledge base (deprecated, use `search` instead) | [knowledge.md](knowledge.md) | -| `bl knowledge search` | Search a Bailian knowledge base (RAG semantic retrieval) | [knowledge.md](knowledge.md) | -| `bl mcp call` | Call a tool on an MCP server (tools/call) | [mcp.md](mcp.md) | -| `bl mcp list` | List MCP servers activated under your Bailian account | [mcp.md](mcp.md) | -| `bl mcp tools` | List tools exposed by an MCP server (tools/list) | [mcp.md](mcp.md) | -| `bl memory add` | Add memory from messages or custom content | [memory.md](memory.md) | -| `bl memory delete` | Delete a memory node | [memory.md](memory.md) | -| `bl memory list` | List memory nodes for a user | [memory.md](memory.md) | -| `bl memory profile create` | Create a user profile schema for memory profiling | [memory.md](memory.md) | -| `bl memory profile get` | Get user profile by schema ID and user ID | [memory.md](memory.md) | -| `bl memory search` | Search memory nodes by query or messages | [memory.md](memory.md) | -| `bl memory update` | Update a memory node content | [memory.md](memory.md) | -| `bl model list` | Browse model families or show detailed model info in the Bailian model marketplace | [model.md](model.md) | -| `bl pipeline run` | Run a pipeline workflow definition | [pipeline.md](pipeline.md) | -| `bl pipeline validate` | Validate a pipeline definition without executing | [pipeline.md](pipeline.md) | -| `bl plugin install` | Install or upgrade an allowlisted Command Pack | [plugin.md](plugin.md) | -| `bl plugin link` | Link an allowlisted local Command Pack for development | [plugin.md](plugin.md) | -| `bl plugin list` | List installed Command Packs and their load status | [plugin.md](plugin.md) | -| `bl plugin remove` | Remove an installed Command Pack | [plugin.md](plugin.md) | -| `bl quota check` | Check current usage against rate limits | [quota.md](quota.md) | -| `bl quota history` | View quota change history | [quota.md](quota.md) | -| `bl quota list` | View model RPM/TPM rate limits | [quota.md](quota.md) | -| `bl quota request` | Request a temporary quota increase | [quota.md](quota.md) | -| `bl search web` | Search the web using DashScope MCP WebSearch service | [search.md](search.md) | -| `bl skill add` | Install skills from the Bailian skill registry into local agents | [skill.md](skill.md) | -| `bl skill init` | Install all bailian-\* skills (one-shot bootstrap for new environments) | [skill.md](skill.md) | -| `bl skill list` | List registry skills and diff against local installs | [skill.md](skill.md) | -| `bl skill remove` | Remove locally installed skills (registry is untouched) | [skill.md](skill.md) | -| `bl skill update` | Update installed skills to the latest registry versions | [skill.md](skill.md) | -| `bl text chat` | Send a chat completion (OpenAI compatible, DashScope) | [text.md](text.md) | -| `bl token-plan add-member` | Add a member to a Token Plan organization | [token-plan.md](token-plan.md) | -| `bl token-plan assign-seats` | Batch assign Token Plan seats to members | [token-plan.md](token-plan.md) | -| `bl token-plan create-key` | Create a Token Plan API key for a seat | [token-plan.md](token-plan.md) | -| `bl token-plan list-seats` | List Token Plan subscription seat details | [token-plan.md](token-plan.md) | -| `bl update` | Update the CLI to the latest or a specified version | [update.md](update.md) | -| `bl usage free` | Query free-tier quota for models (all models if --model is omitted) | [usage.md](usage.md) | -| `bl usage freetier` | Enable or disable auto-stop for free-tier models. Enables by default; use --off to disable | [usage.md](usage.md) | -| `bl usage stats` | Query model usage statistics | [usage.md](usage.md) | -| `bl usage summary` | Show a unified usage summary: free-tier quota and recent usage overview | [usage.md](usage.md) | -| `bl usage token-plan` | Show Token Plan quota usage | [usage.md](usage.md) | -| `bl workspace init` | Initialize Bailian workspace and activate postpaid services | [workspace.md](workspace.md) | -| `bl workspace list` | List all workspaces | [workspace.md](workspace.md) | +| Command | Authentication | Description | Detail | +| ------------------------------- | -------------- | ---------------------------------------------------------------------------------------------- | ------------------------------ | +| `bl advisor recommend` | API Key | Recommend the best models for your use case (intent analysis → candidate recall → LLM ranking) | [advisor.md](advisor.md) | +| `bl app call` | API Key | Call a Bailian application (agent or workflow) | [app.md](app.md) | +| `bl app list` | Console | List Bailian applications | [app.md](app.md) | +| `bl auth generate-access-token` | No Auth | Generate a CLI access token using OpenAPI AK/SK | [auth.md](auth.md) | +| `bl auth login` | No Auth | Authenticate with API key, console browser login, or OpenAPI AK/SK (credentials can coexist) | [auth.md](auth.md) | +| `bl auth logout` | No Auth | Clear stored credentials; full logout also clears the model Base URL | [auth.md](auth.md) | +| `bl auth status` | No Auth | Show current authentication state | [auth.md](auth.md) | +| `bl config agent` | No Auth | Configure a coding agent to use DashScope API | [config.md](config.md) | +| `bl config list` | No Auth | List config profiles and show the active profile | [config.md](config.md) | +| `bl config set` | No Auth | Set a config value | [config.md](config.md) | +| `bl config show` | No Auth | Display current configuration | [config.md](config.md) | +| `bl config ui` | No Auth | Open a local web UI to manage config profiles | [config.md](config.md) | +| `bl config use` | No Auth | Set the active config profile | [config.md](config.md) | +| `bl console call` | Console | Call a Bailian console API via the CLI gateway | [console.md](console.md) | +| `bl file upload` | API Key | Upload a local file to DashScope temporary storage (48h) | [file.md](file.md) | +| `bl knowledge chat` | API Key | Chat with a Bailian knowledge base (RAG Q&A with streaming) | [knowledge.md](knowledge.md) | +| `bl knowledge retrieve` | API Key | Retrieve from a Bailian knowledge base (deprecated, use `search` instead) | [knowledge.md](knowledge.md) | +| `bl knowledge search` | API Key | Search a Bailian knowledge base (RAG semantic retrieval) | [knowledge.md](knowledge.md) | +| `bl mcp call` | API Key | Call a tool on an MCP server (tools/call) | [mcp.md](mcp.md) | +| `bl mcp list` | Console | List MCP servers activated under your Bailian account | [mcp.md](mcp.md) | +| `bl mcp tools` | API Key | List tools exposed by an MCP server (tools/list) | [mcp.md](mcp.md) | +| `bl memory add` | API Key | Add memory from messages or custom content | [memory.md](memory.md) | +| `bl memory delete` | API Key | Delete a memory node | [memory.md](memory.md) | +| `bl memory list` | API Key | List memory nodes for a user | [memory.md](memory.md) | +| `bl memory profile create` | API Key | Create a user profile schema for memory profiling | [memory.md](memory.md) | +| `bl memory profile get` | API Key | Get user profile by schema ID and user ID | [memory.md](memory.md) | +| `bl memory search` | API Key | Search memory nodes by query or messages | [memory.md](memory.md) | +| `bl memory update` | API Key | Update a memory node content | [memory.md](memory.md) | +| `bl model list` | Console | Browse model families or show detailed model info in the Bailian model marketplace | [model.md](model.md) | +| `bl pipeline run` | No Auth | Run a pipeline workflow definition | [pipeline.md](pipeline.md) | +| `bl pipeline validate` | No Auth | Validate a pipeline definition without executing | [pipeline.md](pipeline.md) | +| `bl plugin install` | No Auth | Install or upgrade an allowlisted Command Pack | [plugin.md](plugin.md) | +| `bl plugin link` | No Auth | Link an allowlisted local Command Pack for development | [plugin.md](plugin.md) | +| `bl plugin list` | No Auth | List installed Command Packs and their load status | [plugin.md](plugin.md) | +| `bl plugin remove` | No Auth | Remove an installed Command Pack | [plugin.md](plugin.md) | +| `bl quota check` | Console | Check current usage against rate limits | [quota.md](quota.md) | +| `bl quota history` | Console | View quota change history | [quota.md](quota.md) | +| `bl quota list` | Console | View model RPM/TPM rate limits | [quota.md](quota.md) | +| `bl quota request` | Console | Request a temporary quota increase | [quota.md](quota.md) | +| `bl search web` | API Key | Search the web using DashScope MCP WebSearch service | [search.md](search.md) | +| `bl skill add` | No Auth | Install skills from the Bailian skill registry into local agents | [skill.md](skill.md) | +| `bl skill init` | No Auth | Install all bailian-\* skills (one-shot bootstrap for new environments) | [skill.md](skill.md) | +| `bl skill list` | No Auth | List registry skills and diff against local installs | [skill.md](skill.md) | +| `bl skill remove` | No Auth | Remove locally installed skills (registry is untouched) | [skill.md](skill.md) | +| `bl skill update` | No Auth | Update installed skills to the latest registry versions | [skill.md](skill.md) | +| `bl text chat` | API Key | Send a chat completion (OpenAI compatible, DashScope) | [text.md](text.md) | +| `bl token-plan add-member` | AK/SK | Add a member to a Token Plan organization | [token-plan.md](token-plan.md) | +| `bl token-plan assign-seats` | AK/SK | Batch assign Token Plan seats to members | [token-plan.md](token-plan.md) | +| `bl token-plan create-key` | AK/SK | Create a Token Plan API key for a seat | [token-plan.md](token-plan.md) | +| `bl token-plan list-seats` | AK/SK | List Token Plan subscription seat details | [token-plan.md](token-plan.md) | +| `bl update` | No Auth | Update the CLI to the latest or a specified version | [update.md](update.md) | +| `bl usage free` | Console | Query free-tier quota for models (all models if --model is omitted) | [usage.md](usage.md) | +| `bl usage freetier` | Console | Enable or disable auto-stop for free-tier models. Enables by default; use --off to disable | [usage.md](usage.md) | +| `bl usage stats` | Console | Query model usage statistics | [usage.md](usage.md) | +| `bl usage summary` | Console | Show a unified usage summary: free-tier quota and recent usage overview | [usage.md](usage.md) | +| `bl usage token-plan` | Console | Show Token Plan quota usage | [usage.md](usage.md) | +| `bl workspace init` | No Auth | Initialize Bailian workspace and activate postpaid services | [workspace.md](workspace.md) | +| `bl workspace list` | Console | List all workspaces | [workspace.md](workspace.md) | ## By group diff --git a/skills/bailian-cli/reference/usage.md b/skills/bailian-cli/reference/usage.md index 1fed601..80bf57c 100644 --- a/skills/bailian-cli/reference/usage.md +++ b/skills/bailian-cli/reference/usage.md @@ -7,13 +7,13 @@ Index: [index.md](index.md) ## Commands in this group -| Command | Description | -| --------------------- | ------------------------------------------------------------------------------------------ | -| `bl usage free` | Query free-tier quota for models (all models if --model is omitted) | -| `bl usage freetier` | Enable or disable auto-stop for free-tier models. Enables by default; use --off to disable | -| `bl usage stats` | Query model usage statistics | -| `bl usage summary` | Show a unified usage summary: free-tier quota and recent usage overview | -| `bl usage token-plan` | Show Token Plan quota usage | +| Command | Authentication | Description | +| --------------------- | -------------- | ------------------------------------------------------------------------------------------ | +| `bl usage free` | Console | Query free-tier quota for models (all models if --model is omitted) | +| `bl usage freetier` | Console | Enable or disable auto-stop for free-tier models. Enables by default; use --off to disable | +| `bl usage stats` | Console | Query model usage statistics | +| `bl usage summary` | Console | Show a unified usage summary: free-tier quota and recent usage overview | +| `bl usage token-plan` | Console | Show Token Plan quota usage | ## Command details @@ -207,11 +207,12 @@ bl usage summary --output json ### `bl usage token-plan` -| Field | Value | -| --------------- | ----------------------------- | -| **Name** | `usage token-plan` | -| **Description** | Show Token Plan quota usage | -| **Usage** | `bl usage token-plan [flags]` | +| Field | Value | +| ------------------ | ----------------------------- | +| **Name** | `usage token-plan` | +| **Description** | Show Token Plan quota usage | +| **Authentication** | Console | +| **Usage** | `bl usage token-plan [flags]` | #### Flags