feat(agent): agent相关cli命令的client层功能,对齐cli client的基础能力

This commit is contained in:
chenanran555
2026-07-22 13:40:28 +08:00
parent 6329427b4d
commit d6bd38a46a
29 changed files with 723 additions and 58 deletions
+4
View File
@@ -50,6 +50,10 @@ defineCommand({ auth }) → runtime/authStage → ctx.client → command.run(ctx
命令不要直接解析 token、env 或 config。业务请求统一走 `ctx.client`;登录/配置命令通过 `ctx.authStore` / `ctx.configStore` 的窄接口操作落盘。
### 例外:agent 命令的 SDK 凭证桥接
`bl agent *` 命令声明 `auth: "none"`,凭证由 `@openagentpack/sdk` 自主从 env 解析(agents.yaml 的 `${DASHSCOPE_API_KEY}` / `${BAILIAN_WORKSPACE_ID}` 插值)。为让 bl 登录态复用,`packages/commands/src/commands/agent/_engine/credentials.ts` 的 `bridgeBailianCredentials()` 会**直接 `readConfigFile()`**,把 config 的 `api_key` / `workspace_id` 作为最低优先级兜底填入对应 env,仅填空值,不覆盖已有。这是唯一允许命令层直接读 config 的场景(SDK 只认 env,不走 `ctx.client`);优先级链:`~/.agents/config.json` > shell env > `.env` > `~/.bailian/config.json`。
## 必查清单
### A. core 层(类型 + 解析)
+4 -1
View File
@@ -2,4 +2,7 @@ node_modules
dist
*.log
.DS_Store
outputs/
outputs/
# agents
agents.state.json
.env
+26
View File
@@ -0,0 +1,26 @@
version: "1"
providers:
bailian:
api_key: ${DASHSCOPE_API_KEY}
workspace_id: ${BAILIAN_WORKSPACE_ID}
defaults:
provider: bailian
environments:
dev:
config:
type: cloud
networking:
type: unrestricted
agents:
assistant:
description: "General-purpose assistant"
model: qwen3.7-max
instructions: |
You are a helpful assistant.
environment: dev
tools:
builtin: [bash, read, glob, grep]
@@ -6,16 +6,27 @@ import {
} from "@openagentpack/sdk";
import { ensureCredentials } from "./credentials.ts";
import { loadFileState } from "./file-state-manager.ts";
import { type HostContext, installSdkTransport } from "./transport.ts";
export { CREDENTIALS_NOTE } from "./credentials.ts";
/**
* Build a full ProjectRuntimeContext from a config file path — the standard
* entry point for agent commands that need the SDK engine. Mirrors OpenAgentPack
* CLI's buildCliRuntime: resolve config → load local state → assemble runtime.
* Takes the host context first so every SDK-engine command wires the
* instrumented transport (UA / tracking headers / verbose) by construction.
*/
export async function buildAgentRuntime(
host: HostContext,
filePath: string,
options: { resolveEnv?: boolean; projectName?: string; statePath?: string } = {},
options: {
resolveEnv?: boolean;
projectName?: string;
statePath?: string;
} = {},
): Promise<ProjectRuntimeContext & { configPath: string }> {
installSdkTransport(host);
ensureCredentials();
const { config, configPath, projectName } = await resolveProjectConfig(filePath, options);
const state = await loadFileState(configPath, options.statePath, projectName);
@@ -1,18 +1,59 @@
import { bootstrapRuntimeCredentialsSync } from "@openagentpack/sdk";
import { readConfigFile } from "bailian-cli-core";
let bootstrapped = false;
/**
* Shared `--help` note documenting where agent commands get provider
* credentials. Mirrors the credential-source hint bl's native commands surface
* (knowledge / usage / token-plan), adapted for the SDK's env-based resolution
* and the {@link bridgeBailianCredentials} fallback. Attach to every command
* that loads agents.yaml. `bl` prefix is safe: agent commands ship on `bl` only.
*/
export const CREDENTIALS_NOTE = [
"Credentials come from the env vars referenced in agents.yaml (e.g. ${DASHSCOPE_API_KEY}, ${BAILIAN_WORKSPACE_ID}).",
"For the bailian provider, bl fills these from your login as a fallback: `bl auth login` (API key) and `bl config set workspace_id <id>`.",
];
/**
* Bridge bl's own login state into the env vars the OpenAgentPack SDK reads for
* the bailian provider. bl persists `api_key` / `workspace_id` in
* `~/.bailian/config.json` (via `bl auth login` / `bl config set`); mirror them
* onto `DASHSCOPE_API_KEY` / `BAILIAN_WORKSPACE_ID` so users don't have to
* re-declare the same credentials for `bl agent *`.
*
* Lowest priority: only fills a var that is still unset, so anything already in
* the environment (shell export, `.env`, or `~/.agents/config.json` — which the
* SDK bootstrap has already applied) wins. Missing values are left alone; the
* SDK surfaces its own error when interpolation can't resolve, and non-bailian
* providers (claude/qoder/ark) don't need DashScope credentials at all.
*/
export function bridgeBailianCredentials(): void {
const file = readConfigFile();
if (!process.env.DASHSCOPE_API_KEY?.trim() && file.api_key) {
process.env.DASHSCOPE_API_KEY = file.api_key;
}
if (!process.env.BAILIAN_WORKSPACE_ID?.trim() && file.workspace_id) {
process.env.BAILIAN_WORKSPACE_ID = file.workspace_id;
}
}
/**
* Lazily load `.env` and `~/.agents/config.json` into `process.env` so the
* OpenAgentPack SDK can resolve provider credentials (e.g. DASHSCOPE_API_KEY,
* BAILIAN_WORKSPACE_ID). Safe to call repeatedly — only the first call does I/O.
* BAILIAN_WORKSPACE_ID), then bridge bl's own config as a fallback. Safe to call
* repeatedly — only the first call does I/O.
*
* Effective precedence for bailian provider fields:
* ~/.agents/config.json > shell env > .env > ~/.bailian/config.json
*
* NOTE: agent commands declare `auth: "none"` and let the SDK own credential
* resolution. This is the documented tradeoff of the wholesale SDK integration;
* bl's own `--api-key` / `bl auth login` flow is bridged separately as a follow-up.
* resolution. The bl-config bridge is a best-effort fallback; it never overrides
* a value the SDK bootstrap already resolved.
*/
export function ensureCredentials(): void {
if (bootstrapped) return;
bootstrapped = true;
bootstrapRuntimeCredentialsSync();
bridgeBailianCredentials();
}
@@ -1,11 +1,46 @@
import { UserError } from "@openagentpack/sdk";
import { BailianError, ExitCode } from "bailian-cli-core";
import { type ApiErrorBody, BailianError, ExitCode, mapApiError } from "bailian-cli-core";
/**
* Structural shape of the SDK's `ApiError` (thrown by provider clients on HTTP
* 4xx/5xx). Matched on fields instead of `instanceof` because the installed SDK
* version does not export the class yet, and structural matching keeps this
* check stable across SDK versions either way.
*/
interface SdkApiErrorLike extends Error {
statusCode: number;
responseBody: string;
}
function isSdkApiError(error: Error): error is SdkApiErrorLike {
const candidate = error as Partial<SdkApiErrorLike>;
return typeof candidate.statusCode === "number" && typeof candidate.responseBody === "string";
}
/**
* The SDK embeds the raw response body in its error message; recover the
* structured fields (message / code / request_id) when the body is JSON so
* `mapApiError` surfaces a clean server message plus api metadata. Non-JSON
* bodies pass through verbatim as the message.
*/
function parseSdkResponseBody(raw: string): ApiErrorBody {
try {
const parsed: unknown = JSON.parse(raw);
if (parsed && typeof parsed === "object") return parsed as ApiErrorBody;
} catch {
/* non-JSON body */
}
return { message: raw.trim() || undefined };
}
/**
* Run an SDK-backed operation, translating SDK error types into BailianError so
* bl's error handler produces the right exit code and hint formatting.
* SDK `UserError` → USAGE/GENERAL; any other Error → GENERAL (message passed
* through, per bl's "don't translate server errors" boundary).
* SDK `UserError` → USAGE; SDK `ApiError` (server HTTP error) → GENERAL via
* `mapApiError` (server message passed through verbatim, with
* httpStatus/apiCode/requestId metadata for --output json); any other Error →
* GENERAL (message passed through, per bl's "don't translate server errors"
* boundary).
*/
export async function withAgentErrors<T>(fn: () => Promise<T>): Promise<T> {
try {
@@ -13,6 +48,9 @@ export async function withAgentErrors<T>(fn: () => Promise<T>): Promise<T> {
} catch (error) {
if (error instanceof BailianError) throw error;
if (error instanceof UserError) throw new BailianError(error.message, ExitCode.USAGE);
if (error instanceof Error && isSdkApiError(error)) {
throw mapApiError(error.statusCode, parseSdkResponseBody(error.responseBody));
}
if (error instanceof Error) throw new BailianError(error.message, ExitCode.GENERAL);
throw error;
}
@@ -0,0 +1,29 @@
import * as sdk from "@openagentpack/sdk";
import {
createInstrumentedFetch,
type FetchImplementation,
type Identity,
type Settings,
} from "bailian-cli-core";
/** The slice of CommandContext the transport wrapper needs (UA identity + verbose). */
export interface HostContext {
identity: Identity;
settings: Settings;
}
let installed = false;
/**
* Route the SDK's provider-client requests through the CLI's instrumented
* fetch (UA, host-gated tracking headers, --verbose logging). Feature-detected:
* `setDefaultFetch` landed after @openagentpack/sdk 0.1.0 — on older versions
* this is a silent no-op and the SDK keeps using the global fetch as before.
*/
export function installSdkTransport(host: HostContext): void {
if (installed) return;
const setDefaultFetch = (sdk as Record<string, unknown>).setDefaultFetch;
if (typeof setDefaultFetch !== "function") return;
(setDefaultFetch as (fetchImpl: FetchImplementation) => void)(createInstrumentedFetch(host));
installed = true;
}
@@ -8,7 +8,11 @@ import {
import { emitBare, emitResult } from "bailian-cli-runtime";
import { executePlannedProject, planProjectContext } from "@openagentpack/sdk";
import { formatResourceLabel } from "./_engine/address-utils.ts";
import { assertProviderConfigured, buildAgentRuntime } from "./_engine/config-loader.ts";
import {
assertProviderConfigured,
buildAgentRuntime,
CREDENTIALS_NOTE,
} from "./_engine/config-loader.ts";
import { withStdoutProtected } from "./_engine/console-capture.ts";
import { withAgentErrors } from "./_engine/errors.ts";
import { renderAgentFeedback } from "./_engine/feedback.ts";
@@ -45,6 +49,7 @@ export default defineCommand({
usageArgs: "[--file <path>] [--provider <name>] [--yes] [--concurrency <n>]",
flags: APPLY_FLAGS,
exampleArgs: ["--yes", "--provider bailian --yes"],
notes: CREDENTIALS_NOTE,
async run(ctx) {
const { settings, flags } = ctx;
const format = detectOutputFormat(settings.output);
@@ -52,7 +57,7 @@ export default defineCommand({
const planned = await withAgentErrors(() =>
withStdoutProtected(async () => {
const runtime = await buildAgentRuntime(file);
const runtime = await buildAgentRuntime(ctx, file);
assertProviderConfigured(runtime, flags.provider);
return planProjectContext(runtime, {
provider: flags.provider,
@@ -8,7 +8,7 @@ import {
import { emitBare, emitResult } from "bailian-cli-runtime";
import { destroyPlannedProjectResources, planDestroyProjectContext } from "@openagentpack/sdk";
import { formatResourceLabel } from "./_engine/address-utils.ts";
import { buildAgentRuntime } from "./_engine/config-loader.ts";
import { buildAgentRuntime, CREDENTIALS_NOTE } from "./_engine/config-loader.ts";
import { withStdoutProtected } from "./_engine/console-capture.ts";
import { withAgentErrors } from "./_engine/errors.ts";
@@ -34,6 +34,7 @@ export default defineCommand({
usageArgs: "[--file <path>] [--yes] [--cascade]",
flags: DESTROY_FLAGS,
exampleArgs: ["--yes", "--yes --cascade"],
notes: CREDENTIALS_NOTE,
async run(ctx) {
const { settings, flags } = ctx;
const format = detectOutputFormat(settings.output);
@@ -41,7 +42,7 @@ export default defineCommand({
const planned = await withAgentErrors(() =>
withStdoutProtected(async () => {
const runtime = await buildAgentRuntime(file);
const runtime = await buildAgentRuntime(ctx, file);
return planDestroyProjectContext(runtime);
}),
);
+6 -1
View File
@@ -18,7 +18,7 @@ agents.state.json
const PROVIDERS = ["bailian", "claude", "qoder", "ark", "all"] as const;
const PROVIDER_BLOCKS: Record<string, string> = {
bailian: ` bailian:\n api_key: \${DASHSCOPE_API_KEY}\n workspace_id: \${BAILIAN_WORKSPACE_ID}`,
bailian: ` bailian:\n # bl auth login sets DASHSCOPE_API_KEY; bl config set workspace_id <id> sets BAILIAN_WORKSPACE_ID\n api_key: \${DASHSCOPE_API_KEY}\n workspace_id: \${BAILIAN_WORKSPACE_ID}`,
claude: ` claude:\n api_key: \${ANTHROPIC_API_KEY}`,
qoder: ` qoder:\n api_key: \${QODER_PAT}\n gateway: "https://api.qoder.com/api/v1/cloud"`,
ark: ` ark:\n api_key: \${ARK_API_KEY}`,
@@ -135,6 +135,11 @@ export default defineCommand({
emitResult({ created: file, provider, agent: agentName }, format);
} else {
emitBare(`Created ${file}`);
if (provider === "bailian" || provider === "all") {
emitBare(
"Credentials: run `bl auth login` and `bl config set workspace_id <id>`, or set DASHSCOPE_API_KEY / BAILIAN_WORKSPACE_ID.",
);
}
emitBare("Next: edit agents.yaml, then run `bl agent plan`.");
}
},
+7 -2
View File
@@ -8,7 +8,11 @@ import {
import { emitBare, emitResult } from "bailian-cli-runtime";
import { planProjectContext } from "@openagentpack/sdk";
import { formatResourceLabel } from "./_engine/address-utils.ts";
import { assertProviderConfigured, buildAgentRuntime } from "./_engine/config-loader.ts";
import {
assertProviderConfigured,
buildAgentRuntime,
CREDENTIALS_NOTE,
} from "./_engine/config-loader.ts";
import { withStdoutProtected } from "./_engine/console-capture.ts";
import { withAgentErrors } from "./_engine/errors.ts";
import { renderAgentFeedback } from "./_engine/feedback.ts";
@@ -40,6 +44,7 @@ export default defineCommand({
usageArgs: "[--file <path>] [--provider <name>] [--no-refresh] [--refresh-only]",
flags: PLAN_FLAGS,
exampleArgs: ["", "--provider bailian", "--no-refresh"],
notes: CREDENTIALS_NOTE,
async run(ctx) {
const { settings, flags } = ctx;
const format = detectOutputFormat(settings.output);
@@ -47,7 +52,7 @@ export default defineCommand({
const planned = await withAgentErrors(() =>
withStdoutProtected(async () => {
const runtime = await buildAgentRuntime(file);
const runtime = await buildAgentRuntime(ctx, file);
assertProviderConfigured(runtime, flags.provider);
return planProjectContext(runtime, {
provider: flags.provider,
@@ -1,7 +1,7 @@
import { defineCommand, detectOutputFormat, type FlagsDef } from "bailian-cli-core";
import { emitBare, emitResult } from "bailian-cli-runtime";
import { createSessionForAgent } from "@openagentpack/sdk";
import { buildAgentRuntime } from "./_engine/config-loader.ts";
import { buildAgentRuntime, CREDENTIALS_NOTE } from "./_engine/config-loader.ts";
import { withStdoutProtected } from "./_engine/console-capture.ts";
import { withAgentErrors } from "./_engine/errors.ts";
import { parseMemoryStores } from "./_engine/session-render.ts";
@@ -22,7 +22,11 @@ const SESSION_CREATE_FLAGS = {
valueHint: "<name>",
description: "Override agent's declared environment",
},
vault: { type: "string", valueHint: "<name>", description: "Override agent's declared vault" },
vault: {
type: "string",
valueHint: "<name>",
description: "Override agent's declared vault",
},
memoryStores: {
type: "string",
valueHint: "<names>",
@@ -42,6 +46,7 @@ export default defineCommand({
usageArgs: "[--agent <name>] [--environment <name>] [--title <title>] [--file <path>]",
flags: SESSION_CREATE_FLAGS,
exampleArgs: ["", "--agent assistant", "--agent assistant --title 'debug run'"],
notes: CREDENTIALS_NOTE,
async run(ctx) {
const { settings, flags } = ctx;
const format = detectOutputFormat(settings.output);
@@ -49,7 +54,7 @@ export default defineCommand({
const run = await withAgentErrors(() =>
withStdoutProtected(async () => {
const runtime = await buildAgentRuntime(file);
const runtime = await buildAgentRuntime(ctx, file);
return createSessionForAgent(runtime, {
agent: flags.agent,
provider: flags.provider,
@@ -1,7 +1,7 @@
import { defineCommand, detectOutputFormat, type FlagsDef } from "bailian-cli-core";
import { emitBare, emitResult } from "bailian-cli-runtime";
import { deleteSession } from "@openagentpack/sdk";
import { buildAgentRuntime } from "./_engine/config-loader.ts";
import { buildAgentRuntime, CREDENTIALS_NOTE } from "./_engine/config-loader.ts";
import { withStdoutProtected } from "./_engine/console-capture.ts";
import { withAgentErrors } from "./_engine/errors.ts";
@@ -17,7 +17,11 @@ const SESSION_DELETE_FLAGS = {
valueHint: "<path>",
description: "Config file path (default: agents.yaml)",
},
provider: { type: "string", valueHint: "<name>", description: "Target provider" },
provider: {
type: "string",
valueHint: "<name>",
description: "Target provider",
},
} satisfies FlagsDef;
export default defineCommand({
@@ -26,6 +30,7 @@ export default defineCommand({
usageArgs: "--session-id <id> [--provider <name>] [--file <path>]",
flags: SESSION_DELETE_FLAGS,
exampleArgs: ["--session-id sess_abc123"],
notes: CREDENTIALS_NOTE,
async run(ctx) {
const { settings, flags } = ctx;
const format = detectOutputFormat(settings.output);
@@ -33,7 +38,7 @@ export default defineCommand({
await withAgentErrors(() =>
withStdoutProtected(async () => {
const runtime = await buildAgentRuntime(file);
const runtime = await buildAgentRuntime(ctx, file);
await deleteSession(runtime, flags.sessionId, flags.provider);
}),
);
@@ -2,7 +2,7 @@ import { defineCommand, detectOutputFormat, type FlagsDef } from "bailian-cli-co
import { emitBare, emitResult, formatTable } from "bailian-cli-runtime";
import { listSessionEvents } from "@openagentpack/sdk";
import { sanitizeSessionEvents } from "@openagentpack/sdk/session-events";
import { buildAgentRuntime } from "./_engine/config-loader.ts";
import { buildAgentRuntime, CREDENTIALS_NOTE } from "./_engine/config-loader.ts";
import { withStdoutProtected } from "./_engine/console-capture.ts";
import { withAgentErrors } from "./_engine/errors.ts";
import { fetchAllPages } from "./_engine/pagination.ts";
@@ -19,9 +19,20 @@ const SESSION_EVENTS_FLAGS = {
valueHint: "<path>",
description: "Config file path (default: agents.yaml)",
},
provider: { type: "string", valueHint: "<name>", description: "Target provider" },
limit: { type: "number", valueHint: "<n>", description: "Maximum number of events to fetch" },
all: { type: "switch", description: "Fetch all pages by following the cursor" },
provider: {
type: "string",
valueHint: "<name>",
description: "Target provider",
},
limit: {
type: "number",
valueHint: "<n>",
description: "Maximum number of events to fetch",
},
all: {
type: "switch",
description: "Fetch all pages by following the cursor",
},
} satisfies FlagsDef;
export default defineCommand({
@@ -30,6 +41,7 @@ export default defineCommand({
usageArgs: "--session-id <id> [--limit <n>] [--all] [--file <path>]",
flags: SESSION_EVENTS_FLAGS,
exampleArgs: ["--session-id sess_abc123", "--session-id sess_abc123 --all"],
notes: CREDENTIALS_NOTE,
async run(ctx) {
const { settings, flags } = ctx;
const format = detectOutputFormat(settings.output);
@@ -37,14 +49,18 @@ export default defineCommand({
const { items: events, hasMore } = await withAgentErrors(() =>
withStdoutProtected(async () => {
const runtime = await buildAgentRuntime(file);
const runtime = await buildAgentRuntime(ctx, file);
return fetchAllPages(async (page) => {
const result = await listSessionEvents(runtime, flags.sessionId, {
provider: flags.provider,
limit: flags.limit,
page_token: page,
});
return { items: result.events, hasMore: result.has_more, nextPage: result.next_page };
return {
items: result.events,
hasMore: result.has_more,
nextPage: result.next_page,
};
}, flags.all);
}),
);
@@ -1,7 +1,7 @@
import { defineCommand, detectOutputFormat, type FlagsDef } from "bailian-cli-core";
import { emitBare, emitResult } from "bailian-cli-runtime";
import { getSession } from "@openagentpack/sdk";
import { buildAgentRuntime } from "./_engine/config-loader.ts";
import { buildAgentRuntime, CREDENTIALS_NOTE } from "./_engine/config-loader.ts";
import { withStdoutProtected } from "./_engine/console-capture.ts";
import { withAgentErrors } from "./_engine/errors.ts";
@@ -17,7 +17,11 @@ const SESSION_GET_FLAGS = {
valueHint: "<path>",
description: "Config file path (default: agents.yaml)",
},
provider: { type: "string", valueHint: "<name>", description: "Target provider" },
provider: {
type: "string",
valueHint: "<name>",
description: "Target provider",
},
} satisfies FlagsDef;
export default defineCommand({
@@ -26,6 +30,7 @@ export default defineCommand({
usageArgs: "--session-id <id> [--provider <name>] [--file <path>]",
flags: SESSION_GET_FLAGS,
exampleArgs: ["--session-id sess_abc123"],
notes: CREDENTIALS_NOTE,
async run(ctx) {
const { settings, flags } = ctx;
const format = detectOutputFormat(settings.output);
@@ -33,7 +38,7 @@ export default defineCommand({
const session = await withAgentErrors(() =>
withStdoutProtected(async () => {
const runtime = await buildAgentRuntime(file);
const runtime = await buildAgentRuntime(ctx, file);
return getSession(runtime, flags.sessionId, flags.provider);
}),
);
@@ -1,7 +1,7 @@
import { defineCommand, detectOutputFormat, type FlagsDef } from "bailian-cli-core";
import { emitBare, emitResult, formatTable } from "bailian-cli-runtime";
import { listSessionSummaries } from "@openagentpack/sdk";
import { buildAgentRuntime } from "./_engine/config-loader.ts";
import { buildAgentRuntime, CREDENTIALS_NOTE } from "./_engine/config-loader.ts";
import { withStdoutProtected } from "./_engine/console-capture.ts";
import { withAgentErrors } from "./_engine/errors.ts";
import { fetchAllPages } from "./_engine/pagination.ts";
@@ -12,9 +12,20 @@ const SESSION_LIST_FLAGS = {
valueHint: "<path>",
description: "Config file path (default: agents.yaml)",
},
agent: { type: "string", valueHint: "<name>", description: "Filter by agent name" },
all: { type: "switch", description: "Fetch all pages by following the cursor" },
provider: { type: "string", valueHint: "<name>", description: "Target provider" },
agent: {
type: "string",
valueHint: "<name>",
description: "Filter by agent name",
},
all: {
type: "switch",
description: "Fetch all pages by following the cursor",
},
provider: {
type: "string",
valueHint: "<name>",
description: "Target provider",
},
} satisfies FlagsDef;
export default defineCommand({
@@ -23,6 +34,7 @@ export default defineCommand({
usageArgs: "[--agent <name>] [--all] [--provider <name>] [--file <path>]",
flags: SESSION_LIST_FLAGS,
exampleArgs: ["", "--agent assistant", "--all"],
notes: CREDENTIALS_NOTE,
async run(ctx) {
const { settings, flags } = ctx;
const format = detectOutputFormat(settings.output);
@@ -30,14 +42,18 @@ export default defineCommand({
const { items: summaries, hasMore } = await withAgentErrors(() =>
withStdoutProtected(async () => {
const runtime = await buildAgentRuntime(file);
const runtime = await buildAgentRuntime(ctx, file);
return fetchAllPages(async (page) => {
const result = await listSessionSummaries(runtime, {
agent: flags.agent,
provider: flags.provider,
filter: page ? { page } : undefined,
});
return { items: result.summaries, hasMore: result.hasMore, nextPage: result.nextPage };
return {
items: result.summaries,
hasMore: result.hasMore,
nextPage: result.nextPage,
};
}, flags.all);
}),
);
@@ -1,6 +1,6 @@
import { defineCommand, detectOutputFormat, type FlagsDef } from "bailian-cli-core";
import { startSessionRun, startSessionRunPolling } from "@openagentpack/sdk";
import { buildAgentRuntime } from "./_engine/config-loader.ts";
import { buildAgentRuntime, CREDENTIALS_NOTE } from "./_engine/config-loader.ts";
import { withStdoutProtected } from "./_engine/console-capture.ts";
import { withAgentErrors } from "./_engine/errors.ts";
import {
@@ -31,15 +31,26 @@ const SESSION_RUN_FLAGS = {
valueHint: "<name>",
description: "Override agent's declared environment",
},
vault: { type: "string", valueHint: "<name>", description: "Override agent's declared vault" },
vault: {
type: "string",
valueHint: "<name>",
description: "Override agent's declared vault",
},
memoryStores: {
type: "string",
valueHint: "<names>",
description: "Override agent's memory stores (comma-separated)",
},
title: { type: "string", valueHint: "<title>", description: "Session title" },
provider: { type: "string", valueHint: "<name>", description: "Target provider" },
noStream: { type: "switch", description: "Use polling instead of SSE streaming" },
provider: {
type: "string",
valueHint: "<name>",
description: "Target provider",
},
noStream: {
type: "switch",
description: "Use polling instead of SSE streaming",
},
} satisfies FlagsDef;
export default defineCommand({
@@ -48,6 +59,7 @@ export default defineCommand({
usageArgs: "--prompt <text> [--agent <name>] [--no-stream] [--file <path>]",
flags: SESSION_RUN_FLAGS,
exampleArgs: ['--prompt "hello"', '--agent assistant --prompt "summarize this repo"'],
notes: CREDENTIALS_NOTE,
async run(ctx) {
const { settings, flags } = ctx;
const format = detectOutputFormat(settings.output);
@@ -65,7 +77,7 @@ export default defineCommand({
await withAgentErrors(() =>
withStdoutProtected(async () => {
const runtime = await buildAgentRuntime(file);
const runtime = await buildAgentRuntime(ctx, file);
if (flags.noStream) {
const run = await startSessionRunPolling(runtime, flags.prompt, runOptions);
if (!asJson) process.stderr.write(`Session created: ${run.session.id}\n`);
@@ -1,6 +1,6 @@
import { defineCommand, detectOutputFormat, type FlagsDef } from "bailian-cli-core";
import { sendSessionMessagePolling, sendSessionMessageStreaming } from "@openagentpack/sdk";
import { buildAgentRuntime } from "./_engine/config-loader.ts";
import { buildAgentRuntime, CREDENTIALS_NOTE } from "./_engine/config-loader.ts";
import { withStdoutProtected } from "./_engine/console-capture.ts";
import { withAgentErrors } from "./_engine/errors.ts";
import { renderCollectedEvents, streamAndRenderEvents } from "./_engine/session-render.ts";
@@ -23,8 +23,15 @@ const SESSION_SEND_FLAGS = {
valueHint: "<path>",
description: "Config file path (default: agents.yaml)",
},
provider: { type: "string", valueHint: "<name>", description: "Target provider" },
noStream: { type: "switch", description: "Use polling instead of SSE streaming" },
provider: {
type: "string",
valueHint: "<name>",
description: "Target provider",
},
noStream: {
type: "switch",
description: "Use polling instead of SSE streaming",
},
} satisfies FlagsDef;
export default defineCommand({
@@ -33,6 +40,7 @@ export default defineCommand({
usageArgs: "--session-id <id> --message <text> [--no-stream] [--file <path>]",
flags: SESSION_SEND_FLAGS,
exampleArgs: ['--session-id sess_abc123 --message "continue"'],
notes: CREDENTIALS_NOTE,
async run(ctx) {
const { settings, flags } = ctx;
const format = detectOutputFormat(settings.output);
@@ -41,7 +49,7 @@ export default defineCommand({
await withAgentErrors(() =>
withStdoutProtected(async () => {
const runtime = await buildAgentRuntime(file);
const runtime = await buildAgentRuntime(ctx, file);
if (flags.noStream) {
const result = await sendSessionMessagePolling(runtime, flags.sessionId, flags.message, {
provider: flags.provider,
@@ -1,7 +1,7 @@
import { defineCommand, detectOutputFormat, type FlagsDef } from "bailian-cli-core";
import { emitBare, emitResult } from "bailian-cli-runtime";
import { importResource, parseStateAddress } from "@openagentpack/sdk";
import { buildAgentRuntime } from "./_engine/config-loader.ts";
import { buildAgentRuntime, CREDENTIALS_NOTE } from "./_engine/config-loader.ts";
import { withStdoutProtected } from "./_engine/console-capture.ts";
import { withAgentErrors } from "./_engine/errors.ts";
@@ -37,6 +37,7 @@ export default defineCommand({
"--address <provider.type.name> --remote-id <id> [--resource-version <n>] [--file <path>]",
flags: STATE_IMPORT_FLAGS,
exampleArgs: ["--address bailian.agent.assistant --remote-id agent-abc123"],
notes: CREDENTIALS_NOTE,
async run(ctx) {
const { settings, flags } = ctx;
const format = detectOutputFormat(settings.output);
@@ -44,8 +45,10 @@ export default defineCommand({
await withAgentErrors(() =>
withStdoutProtected(async () => {
const runtime = await buildAgentRuntime(file);
const parsed = parseStateAddress(flags.address, { requireProvider: true });
const runtime = await buildAgentRuntime(ctx, file);
const parsed = parseStateAddress(flags.address, {
requireProvider: true,
});
await importResource(runtime, parsed, flags.remoteId, {
resourceVersion: flags.resourceVersion,
});
@@ -1,6 +1,6 @@
import { defineCommand, detectOutputFormat, type FlagsDef } from "bailian-cli-core";
import { emitBare, emitResult, formatTable } from "bailian-cli-runtime";
import { buildAgentRuntime } from "./_engine/config-loader.ts";
import { buildAgentRuntime, CREDENTIALS_NOTE } from "./_engine/config-loader.ts";
import { withStdoutProtected } from "./_engine/console-capture.ts";
import { withAgentErrors } from "./_engine/errors.ts";
@@ -18,6 +18,7 @@ export default defineCommand({
usageArgs: "[--file <path>]",
flags: STATE_LIST_FLAGS,
exampleArgs: ["", "--file agents.yaml"],
notes: CREDENTIALS_NOTE,
async run(ctx) {
const { settings, flags } = ctx;
const format = detectOutputFormat(settings.output);
@@ -25,7 +26,7 @@ export default defineCommand({
const resources = await withAgentErrors(() =>
withStdoutProtected(async () => {
const runtime = await buildAgentRuntime(file);
const runtime = await buildAgentRuntime(ctx, file);
return runtime.state.listResources();
}),
);
@@ -7,7 +7,7 @@ import {
} from "bailian-cli-core";
import { emitBare, emitResult } from "bailian-cli-runtime";
import { parseStateAddress } from "@openagentpack/sdk";
import { buildAgentRuntime } from "./_engine/config-loader.ts";
import { buildAgentRuntime, CREDENTIALS_NOTE } from "./_engine/config-loader.ts";
import { withStdoutProtected } from "./_engine/console-capture.ts";
import { withAgentErrors } from "./_engine/errors.ts";
@@ -31,6 +31,7 @@ export default defineCommand({
usageArgs: "--address <provider.type.name> [--file <path>]",
flags: STATE_RM_FLAGS,
exampleArgs: ["--address bailian.agent.assistant"],
notes: CREDENTIALS_NOTE,
async run(ctx) {
const { settings, flags } = ctx;
const format = detectOutputFormat(settings.output);
@@ -38,8 +39,10 @@ export default defineCommand({
await withAgentErrors(() =>
withStdoutProtected(async () => {
const runtime = await buildAgentRuntime(file);
const parsed = parseStateAddress(flags.address, { requireProvider: false });
const runtime = await buildAgentRuntime(ctx, file);
const parsed = parseStateAddress(flags.address, {
requireProvider: false,
});
const found = runtime.state.findResource(parsed);
if (!found) {
throw new BailianError(`Resource not found: ${flags.address}`, ExitCode.GENERAL);
@@ -7,7 +7,7 @@ import {
} from "bailian-cli-core";
import { emitBare, emitResult } from "bailian-cli-runtime";
import { parseStateAddress } from "@openagentpack/sdk";
import { buildAgentRuntime } from "./_engine/config-loader.ts";
import { buildAgentRuntime, CREDENTIALS_NOTE } from "./_engine/config-loader.ts";
import { withStdoutProtected } from "./_engine/console-capture.ts";
import { withAgentErrors } from "./_engine/errors.ts";
@@ -31,6 +31,7 @@ export default defineCommand({
usageArgs: "--address <provider.type.name> [--file <path>]",
flags: STATE_SHOW_FLAGS,
exampleArgs: ["--address bailian.agent.assistant"],
notes: CREDENTIALS_NOTE,
async run(ctx) {
const { settings, flags } = ctx;
const format = detectOutputFormat(settings.output);
@@ -38,8 +39,10 @@ export default defineCommand({
const found = await withAgentErrors(() =>
withStdoutProtected(async () => {
const runtime = await buildAgentRuntime(file);
const parsed = parseStateAddress(flags.address, { requireProvider: false });
const runtime = await buildAgentRuntime(ctx, file);
const parsed = parseStateAddress(flags.address, {
requireProvider: false,
});
return runtime.state.findResource(parsed);
}),
);
@@ -7,7 +7,7 @@ import {
} from "bailian-cli-core";
import { emitBare, emitResult } from "bailian-cli-runtime";
import { resolveProjectConfig, validateProjectConfig } from "@openagentpack/sdk";
import { ensureCredentials } from "./_engine/credentials.ts";
import { CREDENTIALS_NOTE, ensureCredentials } from "./_engine/credentials.ts";
import { withAgentErrors } from "./_engine/errors.ts";
const VALIDATE_FLAGS = {
@@ -24,6 +24,7 @@ export default defineCommand({
usageArgs: "[--file <path>]",
flags: VALIDATE_FLAGS,
exampleArgs: ["", "--file agents.yaml"],
notes: CREDENTIALS_NOTE,
async run(ctx) {
const { settings, flags } = ctx;
const format = detectOutputFormat(settings.output);
@@ -0,0 +1,88 @@
import { UserError } from "@openagentpack/sdk";
import { BailianError, ExitCode } from "bailian-cli-core";
import { expect, test } from "vite-plus/test";
import { withAgentErrors } from "../src/commands/agent/_engine/errors.ts";
/**
* Structural stand-in for the SDK's internal `ApiError` (not exported by the
* installed SDK version): withAgentErrors matches on statusCode/responseBody
* fields, so any Error carrying them must map through mapApiError.
*/
class FakeSdkApiError extends Error {
constructor(
readonly statusCode: number,
readonly responseBody: string,
) {
super(`Bailian API ${statusCode}: ${responseBody}`);
}
}
async function catchMapped(error: unknown): Promise<BailianError> {
try {
await withAgentErrors(() => Promise.reject(error));
} catch (mapped) {
expect(mapped).toBeInstanceOf(BailianError);
return mapped as BailianError;
}
throw new Error("expected withAgentErrors to throw");
}
test("BailianError passes through untouched", async () => {
const original = new BailianError("already mapped", ExitCode.AUTH);
const mapped = await catchMapped(original);
expect(mapped).toBe(original);
});
test("SDK UserError maps to USAGE", async () => {
const mapped = await catchMapped(new UserError("bad agents.yaml"));
expect(mapped.exitCode).toBe(ExitCode.USAGE);
expect(mapped.message).toBe("bad agents.yaml");
});
test("SDK ApiError with DashScope-style JSON body surfaces clean message and api metadata", async () => {
const body = JSON.stringify({
code: "InvalidParameter",
message: "agent name already exists",
request_id: "req-123",
});
const mapped = await catchMapped(new FakeSdkApiError(400, body));
expect(mapped.exitCode).toBe(ExitCode.GENERAL);
expect(mapped.message).toBe("agent name already exists");
expect(mapped.api).toEqual({
httpStatus: 400,
apiCode: "InvalidParameter",
requestId: "req-123",
});
});
test("SDK ApiError with OpenAI-style error envelope extracts message and type", async () => {
const body = JSON.stringify({
error: { message: "model does not exist", type: "invalid_request_error" },
request_id: "req-456",
});
const mapped = await catchMapped(new FakeSdkApiError(404, body));
expect(mapped.exitCode).toBe(ExitCode.GENERAL);
expect(mapped.message).toBe("model does not exist");
expect(mapped.api?.apiCode).toBe("invalid_request_error");
expect(mapped.api?.requestId).toBe("req-456");
});
test("SDK ApiError with non-JSON body passes raw text through as message", async () => {
const mapped = await catchMapped(new FakeSdkApiError(502, "Bad Gateway"));
expect(mapped.exitCode).toBe(ExitCode.GENERAL);
expect(mapped.message).toBe("Bad Gateway");
expect(mapped.api?.httpStatus).toBe(502);
});
test("SDK ApiError with empty body falls back to HTTP status message", async () => {
const mapped = await catchMapped(new FakeSdkApiError(503, ""));
expect(mapped.message).toBe("HTTP 503");
expect(mapped.api?.httpStatus).toBe(503);
});
test("plain Error maps to GENERAL with message passed through", async () => {
const mapped = await catchMapped(new Error("boom"));
expect(mapped.exitCode).toBe(ExitCode.GENERAL);
expect(mapped.message).toBe("boom");
expect(mapped.api).toBeUndefined();
});
@@ -0,0 +1,95 @@
import { mkdtempSync, rmSync, writeFileSync } from "fs";
import { tmpdir } from "os";
import { join } from "path";
import { expect, test } from "vite-plus/test";
import { bridgeBailianCredentials } from "../src/commands/agent/_engine/credentials.ts";
/**
* bridgeBailianCredentials 把 ~/.bailian/config.json 的 api_key / workspace_id
* 作为最低优先级兜底填入 DASHSCOPE_API_KEY / BAILIAN_WORKSPACE_ID。
* 用临时 config dir + env 保存恢复隔离,验证优先级与不抛错语义。
*/
async function inScenario(
scenario: {
config?: { api_key?: string; workspace_id?: string };
env?: { DASHSCOPE_API_KEY?: string; BAILIAN_WORKSPACE_ID?: string };
},
assert: () => void,
): Promise<void> {
const savedConfigDir = process.env.BAILIAN_CONFIG_DIR;
const savedApiKey = process.env.DASHSCOPE_API_KEY;
const savedWorkspace = process.env.BAILIAN_WORKSPACE_ID;
const dir = mkdtempSync(join(tmpdir(), "bl-cred-bridge-"));
process.env.BAILIAN_CONFIG_DIR = dir;
if (scenario.config) {
writeFileSync(join(dir, "config.json"), JSON.stringify(scenario.config), "utf-8");
}
// 显式设置/清除 env,避免继承宿主环境干扰断言
if (scenario.env?.DASHSCOPE_API_KEY === undefined) delete process.env.DASHSCOPE_API_KEY;
else process.env.DASHSCOPE_API_KEY = scenario.env.DASHSCOPE_API_KEY;
if (scenario.env?.BAILIAN_WORKSPACE_ID === undefined) delete process.env.BAILIAN_WORKSPACE_ID;
else process.env.BAILIAN_WORKSPACE_ID = scenario.env.BAILIAN_WORKSPACE_ID;
try {
assert();
} finally {
restore("BAILIAN_CONFIG_DIR", savedConfigDir);
restore("DASHSCOPE_API_KEY", savedApiKey);
restore("BAILIAN_WORKSPACE_ID", savedWorkspace);
rmSync(dir, { recursive: true, force: true });
}
}
function restore(key: string, value: string | undefined): void {
if (value === undefined) delete process.env[key];
else process.env[key] = value;
}
test("bridge:env 已有值时不被 bl config 覆盖(最低优先级)", async () => {
await inScenario(
{
config: { api_key: "sk-from-config", workspace_id: "ws-from-config" },
env: { DASHSCOPE_API_KEY: "sk-from-env", BAILIAN_WORKSPACE_ID: "ws-from-env" },
},
() => {
bridgeBailianCredentials();
expect(process.env.DASHSCOPE_API_KEY).toBe("sk-from-env");
expect(process.env.BAILIAN_WORKSPACE_ID).toBe("ws-from-env");
},
);
});
test("bridge:env 缺失且 bl config 有值时填充", async () => {
await inScenario(
{ config: { api_key: "sk-from-config", workspace_id: "ws-from-config" }, env: {} },
() => {
bridgeBailianCredentials();
expect(process.env.DASHSCOPE_API_KEY).toBe("sk-from-config");
expect(process.env.BAILIAN_WORKSPACE_ID).toBe("ws-from-config");
},
);
});
test("bridge:仅缺失项被填,已有项保留(逐字段独立)", async () => {
await inScenario(
{
config: { api_key: "sk-from-config", workspace_id: "ws-from-config" },
env: { DASHSCOPE_API_KEY: "sk-from-env" },
},
() => {
bridgeBailianCredentials();
expect(process.env.DASHSCOPE_API_KEY).toBe("sk-from-env");
expect(process.env.BAILIAN_WORKSPACE_ID).toBe("ws-from-config");
},
);
});
test("bridge:env 与 bl config 皆缺失时不抛错且不写入", async () => {
await inScenario({ env: {} }, () => {
expect(() => bridgeBailianCredentials()).not.toThrow();
expect(process.env.DASHSCOPE_API_KEY).toBeUndefined();
expect(process.env.BAILIAN_WORKSPACE_ID).toBeUndefined();
});
});
+2 -1
View File
@@ -19,8 +19,9 @@ export {
videoGeneratePath,
} from "./endpoints.ts";
export { CHANNEL, SOURCE_CONFIG, TAGS, trackingHeaders } from "./headers.ts";
export type { RequestOpts } from "./http.ts";
export type { HttpDeps, RequestOpts } from "./http.ts";
export { request, requestJson } from "./http.ts";
export { createInstrumentedFetch, type FetchImplementation } from "./instrumented-fetch.ts";
export { Client, type ClientRequestOpts, type ClientOpenApiQueryOpts } from "./client.ts";
export {
buildAcsCanonicalQuery,
@@ -0,0 +1,76 @@
import { maskToken } from "../utils/token.ts";
import type { HttpDeps } from "./http.ts";
import { trackingHeaders } from "./headers.ts";
/**
* fetch-compatible signature, structurally identical to the SDK-side `FetchLike`
* seam. Declared locally so core stays free of SDK imports.
*/
export type FetchImplementation = (
input: string | URL | Request,
init?: RequestInit,
) => Promise<Response>;
/**
* Tracking headers are DashScope-specific: only attach them to Alibaba Cloud
* hosts, never to third-party providers (Anthropic / Ark / Qoder) that an
* embedded SDK may also call through this fetch.
*/
function isAlibabaCloudHost(url: string): boolean {
try {
const { hostname } = new URL(url);
return hostname === "aliyuncs.com" || hostname.endsWith(".aliyuncs.com");
} catch {
return false;
}
}
function requestUrl(input: string | URL | Request): string {
if (typeof input === "string") return input;
if (input instanceof URL) return input.href;
return input.url;
}
/**
* A transparent fetch wrapper carrying the client-layer cross-cutting request
* concerns (UA, tracking headers, `--verbose` logging) for network stacks that
* bypass {@link request} — e.g. an embedded SDK's provider clients. Deliberately
* transport-only: no auth injection, no baseUrl handling, no timeout, and no
* error mapping, so the caller's response semantics (status handling, SSE,
* conflict detection) stay intact.
*/
export function createInstrumentedFetch(deps: HttpDeps): FetchImplementation {
return async (input, init = {}) => {
const url = requestUrl(input);
const headers = new Headers(
init.headers ?? (input instanceof Request ? input.headers : undefined),
);
if (!headers.has("user-agent")) {
headers.set("User-Agent", `${deps.identity.clientName}/${deps.identity.version}`);
}
if (isAlibabaCloudHost(url)) {
for (const [name, value] of Object.entries(trackingHeaders())) {
headers.set(name, value);
}
}
if (deps.settings.verbose) {
console.error(`> ${init.method ?? "GET"} ${url}`);
const auth = headers.get("authorization");
if (auth) console.error(`> Auth: ${maskToken(auth.replace(/^Bearer /, ""))}`);
}
const res = await fetch(input, { ...init, headers });
if (deps.settings.verbose) {
console.error(`< ${res.status} ${res.statusText}`);
const reqId = res.headers.get("x-request-id");
if (reqId) {
console.error(`request_id: ${reqId}`);
}
}
return res;
};
}
@@ -0,0 +1,84 @@
import { expect, test } from "vite-plus/test";
import type { Identity, Settings } from "../src/index.ts";
import { createInstrumentedFetch, SOURCE_CONFIG } from "../src/index.ts";
const identity: Identity = {
binName: "bl",
clientName: "bailian-cli",
version: "1.2.3",
npmPackage: "bailian-cli",
};
const settings: Settings = { timeout: 60, verbose: false } as Settings;
interface CapturedRequest {
url: string;
headers: Headers;
}
/** Run the wrapper against a stubbed globalThis.fetch and capture what reaches it. */
async function capture(
input: string | URL | Request,
init?: RequestInit,
): Promise<CapturedRequest> {
const originalFetch = globalThis.fetch;
let captured: CapturedRequest | undefined;
globalThis.fetch = (async (fetchInput: string | URL | Request, fetchInit?: RequestInit) => {
captured = {
url:
typeof fetchInput === "string"
? fetchInput
: fetchInput instanceof URL
? fetchInput.href
: fetchInput.url,
headers: new Headers(fetchInit?.headers),
};
return new Response("{}", { status: 200 });
}) as unknown as typeof fetch;
try {
await createInstrumentedFetch({ identity, settings })(input, init);
} finally {
globalThis.fetch = originalFetch;
}
if (!captured) throw new Error("stubbed fetch was not called");
return captured;
}
test("adds UA and tracking header on Alibaba Cloud hosts", async () => {
const { headers } = await capture(
"https://ws-1.cn-beijing.maas.aliyuncs.com/api/v1/agentstudio/agents",
{ method: "POST", headers: { Authorization: "Bearer k" } },
);
expect(headers.get("user-agent")).toBe("bailian-cli/1.2.3");
expect(headers.get("x-dashscope-source-config")).toBe(SOURCE_CONFIG);
expect(headers.get("authorization")).toBe("Bearer k");
});
test("adds UA but no tracking header on third-party hosts", async () => {
const { headers } = await capture("https://api.anthropic.com/v1/messages", {
method: "POST",
});
expect(headers.get("user-agent")).toBe("bailian-cli/1.2.3");
expect(headers.get("x-dashscope-source-config")).toBeNull();
});
test("does not override a caller-provided User-Agent", async () => {
const { headers } = await capture("https://dashscope.aliyuncs.com/api/v1/tasks/t1", {
headers: { "User-Agent": "custom/9.9" },
});
expect(headers.get("user-agent")).toBe("custom/9.9");
});
test("does not invent a Content-Type (FormData boundary safety)", async () => {
const { headers } = await capture("https://dashscope.aliyuncs.com/api/v1/files", {
method: "POST",
});
expect(headers.get("content-type")).toBeNull();
});
test("passes non-URL-parseable inputs through without tracking headers", async () => {
const { url, headers } = await capture("/relative/path");
expect(url).toBe("/relative/path");
expect(headers.get("x-dashscope-source-config")).toBeNull();
});
+75
View File
@@ -46,6 +46,11 @@ Index: [index.md](index.md)
| `--no-refresh` | switch | no | Skip refreshing state from remote before planning |
| `--concurrency <n>` | number | no | Max independent resources to apply in parallel (default 6, max 10) |
#### Notes
- Credentials come from the env vars referenced in agents.yaml (e.g. ${DASHSCOPE_API_KEY}, ${BAILIAN_WORKSPACE_ID}).
- For the bailian provider, bl fills these from your login as a fallback: `bl auth login` (API key) and `bl config set workspace_id <id>`.
#### Examples
```bash
@@ -72,6 +77,11 @@ bl agent apply --provider bailian --yes
| `--yes` | switch | no | Confirm and destroy without an interactive prompt (required) |
| `--cascade` | switch | no | Auto-delete dependent resources (e.g. sessions referencing an environment) |
#### Notes
- Credentials come from the env vars referenced in agents.yaml (e.g. ${DASHSCOPE_API_KEY}, ${BAILIAN_WORKSPACE_ID}).
- For the bailian provider, bl fills these from your login as a fallback: `bl auth login` (API key) and `bl config set workspace_id <id>`.
#### Examples
```bash
@@ -130,6 +140,11 @@ bl agent init --provider all
| `--no-refresh` | switch | no | Skip refreshing state from remote before planning |
| `--refresh-only` | switch | no | Refresh state and show drift without planning remote mutations |
#### Notes
- Credentials come from the env vars referenced in agents.yaml (e.g. ${DASHSCOPE_API_KEY}, ${BAILIAN_WORKSPACE_ID}).
- For the bailian provider, bl fills these from your login as a fallback: `bl auth login` (API key) and `bl config set workspace_id <id>`.
#### Examples
```bash
@@ -164,6 +179,11 @@ bl agent plan --no-refresh
| `--title <title>` | string | no | Session title |
| `--provider <name>` | string | no | Target provider (multi-provider agents) |
#### Notes
- Credentials come from the env vars referenced in agents.yaml (e.g. ${DASHSCOPE_API_KEY}, ${BAILIAN_WORKSPACE_ID}).
- For the bailian provider, bl fills these from your login as a fallback: `bl auth login` (API key) and `bl config set workspace_id <id>`.
#### Examples
```bash
@@ -194,6 +214,11 @@ bl agent session create --agent assistant --title 'debug run'
| `--file <path>` | string | no | Config file path (default: agents.yaml) |
| `--provider <name>` | string | no | Target provider |
#### Notes
- Credentials come from the env vars referenced in agents.yaml (e.g. ${DASHSCOPE_API_KEY}, ${BAILIAN_WORKSPACE_ID}).
- For the bailian provider, bl fills these from your login as a fallback: `bl auth login` (API key) and `bl config set workspace_id <id>`.
#### Examples
```bash
@@ -218,6 +243,11 @@ bl agent session delete --session-id sess_abc123
| `--limit <n>` | number | no | Maximum number of events to fetch |
| `--all` | switch | no | Fetch all pages by following the cursor |
#### Notes
- Credentials come from the env vars referenced in agents.yaml (e.g. ${DASHSCOPE_API_KEY}, ${BAILIAN_WORKSPACE_ID}).
- For the bailian provider, bl fills these from your login as a fallback: `bl auth login` (API key) and `bl config set workspace_id <id>`.
#### Examples
```bash
@@ -244,6 +274,11 @@ bl agent session events --session-id sess_abc123 --all
| `--file <path>` | string | no | Config file path (default: agents.yaml) |
| `--provider <name>` | string | no | Target provider |
#### Notes
- Credentials come from the env vars referenced in agents.yaml (e.g. ${DASHSCOPE_API_KEY}, ${BAILIAN_WORKSPACE_ID}).
- For the bailian provider, bl fills these from your login as a fallback: `bl auth login` (API key) and `bl config set workspace_id <id>`.
#### Examples
```bash
@@ -267,6 +302,11 @@ bl agent session get --session-id sess_abc123
| `--all` | switch | no | Fetch all pages by following the cursor |
| `--provider <name>` | string | no | Target provider |
#### Notes
- Credentials come from the env vars referenced in agents.yaml (e.g. ${DASHSCOPE_API_KEY}, ${BAILIAN_WORKSPACE_ID}).
- For the bailian provider, bl fills these from your login as a fallback: `bl auth login` (API key) and `bl config set workspace_id <id>`.
#### Examples
```bash
@@ -303,6 +343,11 @@ bl agent session list --all
| `--provider <name>` | string | no | Target provider |
| `--no-stream` | switch | no | Use polling instead of SSE streaming |
#### Notes
- Credentials come from the env vars referenced in agents.yaml (e.g. ${DASHSCOPE_API_KEY}, ${BAILIAN_WORKSPACE_ID}).
- For the bailian provider, bl fills these from your login as a fallback: `bl auth login` (API key) and `bl config set workspace_id <id>`.
#### Examples
```bash
@@ -331,6 +376,11 @@ bl agent session run --agent assistant --prompt "summarize this repo"
| `--provider <name>` | string | no | Target provider |
| `--no-stream` | switch | no | Use polling instead of SSE streaming |
#### Notes
- Credentials come from the env vars referenced in agents.yaml (e.g. ${DASHSCOPE_API_KEY}, ${BAILIAN_WORKSPACE_ID}).
- For the bailian provider, bl fills these from your login as a fallback: `bl auth login` (API key) and `bl config set workspace_id <id>`.
#### Examples
```bash
@@ -354,6 +404,11 @@ bl agent session send --session-id sess_abc123 --message "continue"
| `--resource-version <n>` | number | no | Resource version (for versioned resources like agents) |
| `--file <path>` | string | no | Config file path (default: agents.yaml) |
#### Notes
- Credentials come from the env vars referenced in agents.yaml (e.g. ${DASHSCOPE_API_KEY}, ${BAILIAN_WORKSPACE_ID}).
- For the bailian provider, bl fills these from your login as a fallback: `bl auth login` (API key) and `bl config set workspace_id <id>`.
#### Examples
```bash
@@ -374,6 +429,11 @@ bl agent state import --address bailian.agent.assistant --remote-id agent-abc123
| --------------- | ------ | -------- | --------------------------------------- |
| `--file <path>` | string | no | Config file path (default: agents.yaml) |
#### Notes
- Credentials come from the env vars referenced in agents.yaml (e.g. ${DASHSCOPE_API_KEY}, ${BAILIAN_WORKSPACE_ID}).
- For the bailian provider, bl fills these from your login as a fallback: `bl auth login` (API key) and `bl config set workspace_id <id>`.
#### Examples
```bash
@@ -399,6 +459,11 @@ bl agent state list --file agents.yaml
| `--address <provider.type.name>` | string | yes | Resource state address (required) |
| `--file <path>` | string | no | Config file path (default: agents.yaml) |
#### Notes
- Credentials come from the env vars referenced in agents.yaml (e.g. ${DASHSCOPE_API_KEY}, ${BAILIAN_WORKSPACE_ID}).
- For the bailian provider, bl fills these from your login as a fallback: `bl auth login` (API key) and `bl config set workspace_id <id>`.
#### Examples
```bash
@@ -420,6 +485,11 @@ bl agent state rm --address bailian.agent.assistant
| `--address <provider.type.name>` | string | yes | Resource state address (required) |
| `--file <path>` | string | no | Config file path (default: agents.yaml) |
#### Notes
- Credentials come from the env vars referenced in agents.yaml (e.g. ${DASHSCOPE_API_KEY}, ${BAILIAN_WORKSPACE_ID}).
- For the bailian provider, bl fills these from your login as a fallback: `bl auth login` (API key) and `bl config set workspace_id <id>`.
#### Examples
```bash
@@ -440,6 +510,11 @@ bl agent state show --address bailian.agent.assistant
| --------------- | ------ | -------- | --------------------------------------- |
| `--file <path>` | string | no | Config file path (default: agents.yaml) |
#### Notes
- Credentials come from the env vars referenced in agents.yaml (e.g. ${DASHSCOPE_API_KEY}, ${BAILIAN_WORKSPACE_ID}).
- For the bailian provider, bl fills these from your login as a fallback: `bl auth login` (API key) and `bl config set workspace_id <id>`.
#### Examples
```bash