From 6329427b4d1e9695a9c337b15a4b5b657c33a283 Mon Sep 17 00:00:00 2001 From: chenanran555 Date: Tue, 21 Jul 2026 10:43:22 +0800 Subject: [PATCH 1/5] feat(agent): add agent command group with session and state management --- packages/cli/src/commands.ts | 32 ++ packages/commands/package.json | 1 + .../commands/agent/_engine/address-utils.ts | 11 + .../commands/agent/_engine/config-loader.ts | 43 ++ .../commands/agent/_engine/console-capture.ts | 23 + .../src/commands/agent/_engine/credentials.ts | 18 + .../src/commands/agent/_engine/errors.ts | 19 + .../src/commands/agent/_engine/feedback.ts | 10 + .../agent/_engine/file-state-manager.ts | 24 + .../src/commands/agent/_engine/pagination.ts | 25 + .../commands/agent/_engine/session-render.ts | 83 ++++ packages/commands/src/commands/agent/apply.ts | 119 +++++ .../commands/src/commands/agent/destroy.ts | 88 ++++ packages/commands/src/commands/agent/init.ts | 141 ++++++ packages/commands/src/commands/agent/plan.ts | 97 ++++ .../src/commands/agent/session-create.ts | 78 +++ .../src/commands/agent/session-delete.ts | 44 ++ .../src/commands/agent/session-events.ts | 76 +++ .../src/commands/agent/session-get.ts | 53 ++ .../src/commands/agent/session-list.ts | 72 +++ .../src/commands/agent/session-run.ts | 81 ++++ .../src/commands/agent/session-send.ts | 64 +++ .../src/commands/agent/state-import.ts | 61 +++ .../commands/src/commands/agent/state-list.ts | 52 ++ .../commands/src/commands/agent/state-rm.ts | 56 +++ .../commands/src/commands/agent/state-show.ts | 54 +++ .../commands/src/commands/agent/validate.ts | 54 +++ packages/commands/src/index.ts | 16 + pnpm-lock.yaml | 250 +++++++++- skills/bailian-cli/reference/agent.md | 451 ++++++++++++++++++ skills/bailian-cli/reference/index.md | 75 +-- 31 files changed, 2230 insertions(+), 41 deletions(-) create mode 100644 packages/commands/src/commands/agent/_engine/address-utils.ts create mode 100644 packages/commands/src/commands/agent/_engine/config-loader.ts create mode 100644 packages/commands/src/commands/agent/_engine/console-capture.ts create mode 100644 packages/commands/src/commands/agent/_engine/credentials.ts create mode 100644 packages/commands/src/commands/agent/_engine/errors.ts create mode 100644 packages/commands/src/commands/agent/_engine/feedback.ts create mode 100644 packages/commands/src/commands/agent/_engine/file-state-manager.ts create mode 100644 packages/commands/src/commands/agent/_engine/pagination.ts create mode 100644 packages/commands/src/commands/agent/_engine/session-render.ts create mode 100644 packages/commands/src/commands/agent/apply.ts create mode 100644 packages/commands/src/commands/agent/destroy.ts create mode 100644 packages/commands/src/commands/agent/init.ts create mode 100644 packages/commands/src/commands/agent/plan.ts create mode 100644 packages/commands/src/commands/agent/session-create.ts create mode 100644 packages/commands/src/commands/agent/session-delete.ts create mode 100644 packages/commands/src/commands/agent/session-events.ts create mode 100644 packages/commands/src/commands/agent/session-get.ts create mode 100644 packages/commands/src/commands/agent/session-list.ts create mode 100644 packages/commands/src/commands/agent/session-run.ts create mode 100644 packages/commands/src/commands/agent/session-send.ts create mode 100644 packages/commands/src/commands/agent/state-import.ts create mode 100644 packages/commands/src/commands/agent/state-list.ts create mode 100644 packages/commands/src/commands/agent/state-rm.ts create mode 100644 packages/commands/src/commands/agent/state-show.ts create mode 100644 packages/commands/src/commands/agent/validate.ts create mode 100644 skills/bailian-cli/reference/agent.md diff --git a/packages/cli/src/commands.ts b/packages/cli/src/commands.ts index a7001adb..2674eadc 100644 --- a/packages/cli/src/commands.ts +++ b/packages/cli/src/commands.ts @@ -83,6 +83,22 @@ import { pluginLink, pluginList, pluginRemove, + agentInit, + agentValidate, + agentPlan, + agentApply, + agentDestroy, + agentStateList, + agentStateShow, + agentStateRm, + agentStateImport, + agentSessionCreate, + agentSessionList, + agentSessionGet, + agentSessionDelete, + agentSessionRun, + agentSessionSend, + agentSessionEvents, } from "bailian-cli-commands"; // Full bailian-cli product: every command, exposed under the `bl` binary. @@ -174,4 +190,20 @@ export const commands: Record = { "plugin link": pluginLink, "plugin list": pluginList, "plugin remove": pluginRemove, + "agent init": agentInit, + "agent validate": agentValidate, + "agent plan": agentPlan, + "agent apply": agentApply, + "agent destroy": agentDestroy, + "agent state list": agentStateList, + "agent state show": agentStateShow, + "agent state rm": agentStateRm, + "agent state import": agentStateImport, + "agent session create": agentSessionCreate, + "agent session list": agentSessionList, + "agent session get": agentSessionGet, + "agent session delete": agentSessionDelete, + "agent session run": agentSessionRun, + "agent session send": agentSessionSend, + "agent session events": agentSessionEvents, }; diff --git a/packages/commands/package.json b/packages/commands/package.json index 209c2f66..df326631 100644 --- a/packages/commands/package.json +++ b/packages/commands/package.json @@ -40,6 +40,7 @@ "check": "vp check" }, "dependencies": { + "@openagentpack/sdk": "0.1.0", "bailian-cli-core": "workspace:*", "bailian-cli-runtime": "workspace:*", "boxen": "catalog:", diff --git a/packages/commands/src/commands/agent/_engine/address-utils.ts b/packages/commands/src/commands/agent/_engine/address-utils.ts new file mode 100644 index 00000000..ff8fb315 --- /dev/null +++ b/packages/commands/src/commands/agent/_engine/address-utils.ts @@ -0,0 +1,11 @@ +import type { ResourceAddress } from "@openagentpack/sdk"; + +/** Full state address: provider.type.name */ +export function formatResourceAddress(address: ResourceAddress): string { + return `${address.provider}.${address.type}.${address.name}`; +} + +/** CLI display short label: type.name (provider) */ +export function formatResourceLabel(address: ResourceAddress): string { + return `${address.type}.${address.name} (${address.provider})`; +} diff --git a/packages/commands/src/commands/agent/_engine/config-loader.ts b/packages/commands/src/commands/agent/_engine/config-loader.ts new file mode 100644 index 00000000..0e754948 --- /dev/null +++ b/packages/commands/src/commands/agent/_engine/config-loader.ts @@ -0,0 +1,43 @@ +import { + createProjectRuntime, + type ProjectRuntimeContext, + resolveProjectConfig, + UserError, +} from "@openagentpack/sdk"; +import { ensureCredentials } from "./credentials.ts"; +import { loadFileState } from "./file-state-manager.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. + */ +export async function buildAgentRuntime( + filePath: string, + options: { resolveEnv?: boolean; projectName?: string; statePath?: string } = {}, +): Promise { + ensureCredentials(); + const { config, configPath, projectName } = await resolveProjectConfig(filePath, options); + const state = await loadFileState(configPath, options.statePath, projectName); + const ctx = createProjectRuntime({ + projectName, + config, + state, + configPath, + providers: config.providers, + }); + return { ...ctx, configPath }; +} + +/** Ensure a user-supplied --provider value is actually configured in agents.yaml. */ +export function assertProviderConfigured( + ctx: ProjectRuntimeContext, + provider: string | undefined, +): void { + if (!provider || provider === "all") return; + if (ctx.providers.has(provider)) return; + const available = Array.from(ctx.providers.keys()).join(", ") || "none"; + throw new UserError( + `Provider '${provider}' is not configured. Available providers: ${available}.`, + ); +} diff --git a/packages/commands/src/commands/agent/_engine/console-capture.ts b/packages/commands/src/commands/agent/_engine/console-capture.ts new file mode 100644 index 00000000..aab32e19 --- /dev/null +++ b/packages/commands/src/commands/agent/_engine/console-capture.ts @@ -0,0 +1,23 @@ +/** + * Redirect `console.log` / `console.info` to stderr while `fn` runs. + * + * The OpenAgentPack SDK's provider adapters emit progress/debug logging via + * `console.log` (e.g. `[skill-upload]`), which would corrupt bl's stdout data + * channel in `--output json` mode. Wrapping SDK calls that may log keeps stdout + * a clean data channel. Restores the originals on completion. + */ +export async function withStdoutProtected(fn: () => Promise): Promise { + const originalLog = console.log; + const originalInfo = console.info; + const toStderr = (...args: unknown[]): void => { + process.stderr.write(`${args.map((arg) => String(arg)).join(" ")}\n`); + }; + console.log = toStderr; + console.info = toStderr; + try { + return await fn(); + } finally { + console.log = originalLog; + console.info = originalInfo; + } +} diff --git a/packages/commands/src/commands/agent/_engine/credentials.ts b/packages/commands/src/commands/agent/_engine/credentials.ts new file mode 100644 index 00000000..875d4331 --- /dev/null +++ b/packages/commands/src/commands/agent/_engine/credentials.ts @@ -0,0 +1,18 @@ +import { bootstrapRuntimeCredentialsSync } from "@openagentpack/sdk"; + +let bootstrapped = false; + +/** + * 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. + * + * 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. + */ +export function ensureCredentials(): void { + if (bootstrapped) return; + bootstrapped = true; + bootstrapRuntimeCredentialsSync(); +} diff --git a/packages/commands/src/commands/agent/_engine/errors.ts b/packages/commands/src/commands/agent/_engine/errors.ts new file mode 100644 index 00000000..18a40c82 --- /dev/null +++ b/packages/commands/src/commands/agent/_engine/errors.ts @@ -0,0 +1,19 @@ +import { UserError } from "@openagentpack/sdk"; +import { BailianError, ExitCode } from "bailian-cli-core"; + +/** + * 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). + */ +export async function withAgentErrors(fn: () => Promise): Promise { + try { + return await fn(); + } catch (error) { + if (error instanceof BailianError) throw error; + if (error instanceof UserError) throw new BailianError(error.message, ExitCode.USAGE); + if (error instanceof Error) throw new BailianError(error.message, ExitCode.GENERAL); + throw error; + } +} diff --git a/packages/commands/src/commands/agent/_engine/feedback.ts b/packages/commands/src/commands/agent/_engine/feedback.ts new file mode 100644 index 00000000..1cf876d3 --- /dev/null +++ b/packages/commands/src/commands/agent/_engine/feedback.ts @@ -0,0 +1,10 @@ +import type { RuntimeFeedbackEvent } from "@openagentpack/sdk"; + +/** + * Render SDK runtime feedback to stderr, keeping stdout a clean data channel. + * Used as the `onFeedback` sink for plan/apply so progress messages don't mix + * with structured output. + */ +export function renderAgentFeedback(event: RuntimeFeedbackEvent): void { + process.stderr.write(`${event.message}\n`); +} diff --git a/packages/commands/src/commands/agent/_engine/file-state-manager.ts b/packages/commands/src/commands/agent/_engine/file-state-manager.ts new file mode 100644 index 00000000..72a8fd06 --- /dev/null +++ b/packages/commands/src/commands/agent/_engine/file-state-manager.ts @@ -0,0 +1,24 @@ +import { basename, dirname, resolve } from "node:path"; +import { + type IStateManager, + LocalFileStateBackend, + StateManager, + type StateScope, +} from "@openagentpack/sdk"; + +function createStateScope(configPath: string, projectName?: string): StateScope { + const resolved = resolve(configPath); + return { projectId: projectName ?? basename(dirname(resolved)) }; +} + +/** Load or initialize a file-based StateManager (mirrors OpenAgentPack CLI). */ +export async function loadFileState( + configPath: string, + statePath?: string, + projectName?: string, +): Promise { + const resolved = resolve(configPath); + const backend = new LocalFileStateBackend({ configPath: resolved, statePath }); + const path = backend.getStatePath(createStateScope(resolved, projectName)); + return StateManager.load(path); +} diff --git a/packages/commands/src/commands/agent/_engine/pagination.ts b/packages/commands/src/commands/agent/_engine/pagination.ts new file mode 100644 index 00000000..df2fd934 --- /dev/null +++ b/packages/commands/src/commands/agent/_engine/pagination.ts @@ -0,0 +1,25 @@ +export interface PagedResult { + items: T[]; + hasMore: boolean; + nextPage?: string; +} + +/** Fetch the first page, then follow cursors while `all` is true. */ +export async function fetchAllPages( + fetchPage: (page?: string) => Promise>, + all?: boolean, +): Promise> { + const first = await fetchPage(); + const items = [...first.items]; + let hasMore = first.hasMore; + let nextPage = first.nextPage; + + while (all && nextPage) { + const next = await fetchPage(nextPage); + items.push(...next.items); + hasMore = next.hasMore; + nextPage = next.nextPage; + } + + return { items, hasMore, nextPage }; +} diff --git a/packages/commands/src/commands/agent/_engine/session-render.ts b/packages/commands/src/commands/agent/_engine/session-render.ts new file mode 100644 index 00000000..0b08da0a --- /dev/null +++ b/packages/commands/src/commands/agent/_engine/session-render.ts @@ -0,0 +1,83 @@ +import { + type CollectedSessionEvents, + isTerminalSessionStatus, + type ProviderSessionEvent, +} from "@openagentpack/sdk"; +import { sanitizeSessionEvent, sanitizeSessionEvents } from "@openagentpack/sdk/session-events"; + +/** Skip user echo + thinking noise in live rendering (mirrors OpenAgentPack CLI). */ +function shouldRenderLiveEvent(event: ProviderSessionEvent): boolean { + return event.type !== "thinking" && !(event.type === "message" && event.role === "user"); +} + +function writeJsonLine(value: unknown): void { + process.stdout.write(`${JSON.stringify(value)}\n`); +} + +/** Assistant text → stdout (data channel); everything else → stderr (diagnostics). */ +function renderEvent(event: ProviderSessionEvent): void { + if (!shouldRenderLiveEvent(event)) return; + if (event.type === "message" && event.content) { + process.stdout.write(event.content); + } else if (event.type === "tool_use") { + process.stderr.write(`\n[tool] ${event.tool_name}\n`); + } else if (event.type === "tool_result" && event.content) { + const preview = + event.content.length > 200 ? `${event.content.slice(0, 200)}...` : event.content; + process.stderr.write(`${preview}\n`); + } else if (event.type === "status") { + if (event.status === "running") process.stderr.write("\n[session running]\n"); + } else if (event.type === "error") { + process.stderr.write(`\n[error] ${event.content ?? "unknown error"}\n`); + } +} + +function renderTerminalStatus(status: string, json: boolean): void { + if (json) return; + process.stderr.write(`\n[session ${status}]\n`); +} + +/** Consume an SSE stream, rendering live (text) or as JSONL (json). */ +export async function streamAndRenderEvents( + events: AsyncIterable, + json: boolean, +): Promise { + for await (const event of events) { + if (json) writeJsonLine(sanitizeSessionEvent(event)); + else renderEvent(event); + if (event.type === "status" && isTerminalSessionStatus(event.status)) { + renderTerminalStatus(event.status ?? "", json); + break; + } + } +} + +/** Render a polled (non-streaming) collected result. */ +export function renderCollectedEvents(result: CollectedSessionEvents, json: boolean): void { + if (json) { + process.stdout.write( + `${JSON.stringify( + { + events: sanitizeSessionEvents(result.result.events), + has_more: result.result.has_more, + next_page: result.result.next_page, + }, + null, + 2, + )}\n`, + ); + return; + } + for (const event of result.result.events) renderEvent(event); + renderTerminalStatus(result.terminalStatus, json); +} + +/** Split a comma-separated --memory-stores value. */ +export function parseMemoryStores(value?: string): string[] | undefined { + return value + ? value + .split(",") + .map((entry) => entry.trim()) + .filter(Boolean) + : undefined; +} diff --git a/packages/commands/src/commands/agent/apply.ts b/packages/commands/src/commands/agent/apply.ts new file mode 100644 index 00000000..809c9f56 --- /dev/null +++ b/packages/commands/src/commands/agent/apply.ts @@ -0,0 +1,119 @@ +import { + BailianError, + defineCommand, + detectOutputFormat, + ExitCode, + type FlagsDef, +} from "bailian-cli-core"; +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 { withStdoutProtected } from "./_engine/console-capture.ts"; +import { withAgentErrors } from "./_engine/errors.ts"; +import { renderAgentFeedback } from "./_engine/feedback.ts"; + +const APPLY_FLAGS = { + file: { + type: "string", + valueHint: "", + description: "Config file path (default: agents.yaml)", + }, + provider: { + type: "string", + valueHint: "", + description: "Target provider (default: all configured)", + }, + yes: { + type: "switch", + description: "Confirm and apply without an interactive prompt (required to mutate)", + }, + noRefresh: { + type: "switch", + description: "Skip refreshing state from remote before planning", + }, + concurrency: { + type: "number", + valueHint: "", + description: "Max independent resources to apply in parallel (default 6, max 10)", + }, +} satisfies FlagsDef; + +export default defineCommand({ + description: "Apply planned changes to create/update/delete agent resources", + auth: "none", + usageArgs: "[--file ] [--provider ] [--yes] [--concurrency ]", + flags: APPLY_FLAGS, + exampleArgs: ["--yes", "--provider bailian --yes"], + async run(ctx) { + const { settings, flags } = ctx; + const format = detectOutputFormat(settings.output); + const file = flags.file ?? "agents.yaml"; + + const planned = await withAgentErrors(() => + withStdoutProtected(async () => { + const runtime = await buildAgentRuntime(file); + assertProviderConfigured(runtime, flags.provider); + return planProjectContext(runtime, { + provider: flags.provider, + refresh: !flags.noRefresh, + quiet: true, + onFeedback: renderAgentFeedback, + }); + }), + ); + + const plan = planned.plan; + if (plan.diagnostics.some((diag) => diag.severity === "error")) { + for (const diag of plan.diagnostics) { + if (diag.severity === "error") emitBare(`[error] ${diag.code}: ${diag.message}`); + } + throw new BailianError("Cannot apply: resolve the errors above first.", ExitCode.GENERAL); + } + + const actionable = plan.actions.filter((action) => action.action !== "no-op"); + if (actionable.length === 0) { + emitBare("No changes. Infrastructure is up-to-date."); + return; + } + + const creates = actionable.filter((action) => action.action === "create").length; + const updates = actionable.filter((action) => action.action === "update").length; + const deletes = planned.destructiveActions; + + for (const action of actionable) { + const icon = action.action === "create" ? "+" : action.action === "update" ? "~" : "-"; + emitBare(` ${icon} ${formatResourceLabel(action.address)}`); + } + + if (!flags.yes) { + throw new BailianError( + `Refusing to apply ${actionable.length} change(s) (${creates} create, ${updates} update, ${deletes.length} destroy) without confirmation.`, + ExitCode.USAGE, + "Review with `bl agent plan`, then re-run with --yes to apply.", + ); + } + + const result = await withAgentErrors(() => + withStdoutProtected(() => + executePlannedProject(planned, { + onFeedback: renderAgentFeedback, + policy: "force", + concurrency: flags.concurrency, + }), + ), + ); + + const succeeded = result.results.filter((entry) => entry.status === "success").length; + const failed = result.results.filter((entry) => entry.status === "failed").length; + const skipped = result.results.filter((entry) => entry.status === "skipped").length; + + if (format === "json") { + emitResult({ succeeded, failed, skipped, results: result.results }, format); + } else { + emitBare(`\nApply finished: ${succeeded} succeeded, ${failed} failed, ${skipped} skipped.`); + } + + if (failed > 0) throw new BailianError("Apply failed.", ExitCode.GENERAL); + }, +}); diff --git a/packages/commands/src/commands/agent/destroy.ts b/packages/commands/src/commands/agent/destroy.ts new file mode 100644 index 00000000..52fd3952 --- /dev/null +++ b/packages/commands/src/commands/agent/destroy.ts @@ -0,0 +1,88 @@ +import { + BailianError, + defineCommand, + detectOutputFormat, + ExitCode, + type FlagsDef, +} from "bailian-cli-core"; +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 { withStdoutProtected } from "./_engine/console-capture.ts"; +import { withAgentErrors } from "./_engine/errors.ts"; + +const DESTROY_FLAGS = { + file: { + type: "string", + valueHint: "", + description: "Config file path (default: agents.yaml)", + }, + yes: { + type: "switch", + description: "Confirm and destroy without an interactive prompt (required)", + }, + cascade: { + type: "switch", + description: "Auto-delete dependent resources (e.g. sessions referencing an environment)", + }, +} satisfies FlagsDef; + +export default defineCommand({ + description: "Destroy all managed agent resources tracked in state", + auth: "none", + usageArgs: "[--file ] [--yes] [--cascade]", + flags: DESTROY_FLAGS, + exampleArgs: ["--yes", "--yes --cascade"], + async run(ctx) { + const { settings, flags } = ctx; + const format = detectOutputFormat(settings.output); + const file = flags.file ?? "agents.yaml"; + + const planned = await withAgentErrors(() => + withStdoutProtected(async () => { + const runtime = await buildAgentRuntime(file); + return planDestroyProjectContext(runtime); + }), + ); + + const resources = planned.resources; + if (resources.length === 0) { + emitBare("No resources in state. Nothing to destroy."); + return; + } + + for (const resource of resources) { + emitBare(` - ${formatResourceLabel(resource.address)} [${resource.remote_id}]`); + } + + if (!flags.yes) { + throw new BailianError( + `Refusing to destroy ${resources.length} resource(s) without confirmation.`, + ExitCode.USAGE, + "Re-run with --yes to destroy (add --cascade to remove dependents).", + ); + } + + const result = await withAgentErrors(() => + withStdoutProtected(() => + destroyPlannedProjectResources(planned, { + cascade: flags.cascade, + onCascadeRequired: async () => Boolean(flags.cascade), + onResourceResult: (item) => { + const label = formatResourceLabel(item.resource.address); + process.stderr.write(` ${item.status === "success" ? "✓" : "✗"} ${label}\n`); + }, + }), + ), + ); + + if (format === "json") { + emitResult({ destroyed: result.destroyed, total: result.resources.length }, format); + } else { + emitBare( + `\nDestroy complete. ${result.destroyed}/${result.resources.length} resources removed.`, + ); + } + }, +}); diff --git a/packages/commands/src/commands/agent/init.ts b/packages/commands/src/commands/agent/init.ts new file mode 100644 index 00000000..dc16f65c --- /dev/null +++ b/packages/commands/src/commands/agent/init.ts @@ -0,0 +1,141 @@ +import { existsSync } from "node:fs"; +import { readFile, writeFile } from "node:fs/promises"; +import { + BailianError, + defineCommand, + detectOutputFormat, + ExitCode, + type FlagsDef, +} from "bailian-cli-core"; +import { emitBare, emitResult } from "bailian-cli-runtime"; + +const GITIGNORE_ADDITIONS = ` +# agents +agents.state.json +.env +`; + +const PROVIDERS = ["bailian", "claude", "qoder", "ark", "all"] as const; + +const PROVIDER_BLOCKS: Record = { + bailian: ` bailian:\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}`, +}; + +const SINGLE_MODEL: Record = { + bailian: ` model: qwen3.7-max`, + claude: ` model: claude-sonnet-4-6`, + qoder: ` model: ultimate`, + ark: ` model: doubao-seed-2-1-pro-260628`, +}; + +function buildTemplate(options: { provider: string; agentName: string }): string { + const providerBlock = + options.provider === "all" + ? `${PROVIDER_BLOCKS.bailian}\n${PROVIDER_BLOCKS.claude}\n${PROVIDER_BLOCKS.qoder}\n${PROVIDER_BLOCKS.ark}` + : PROVIDER_BLOCKS[options.provider]!; + + const modelBlock = + options.provider === "all" + ? ` model:\n bailian: qwen3.7-max\n claude: claude-sonnet-4-6\n qoder: ultimate\n ark: doubao-seed-2-1-pro-260628` + : SINGLE_MODEL[options.provider]!; + + const toolBlock = + options.provider === "bailian" + ? "[bash, read, glob, grep]" + : "[read, glob, grep, web_search, web_fetch]"; + + return `version: "1" + +providers: +${providerBlock} + +defaults: + provider: ${options.provider === "all" ? "all" : options.provider} + +environments: + dev: + config: + type: cloud + networking: + type: unrestricted + +agents: + ${options.agentName}: + description: "General-purpose assistant" +${modelBlock} + instructions: | + You are a helpful assistant. + environment: dev + tools: + builtin: ${toolBlock} +`; +} + +const INIT_FLAGS = { + provider: { + type: "string", + valueHint: "", + description: "Provider: bailian, claude, qoder, ark, all (default: bailian)", + choices: PROVIDERS, + }, + agentName: { + type: "string", + valueHint: "", + description: "Name of the first agent (default: assistant)", + }, + file: { + type: "string", + valueHint: "", + description: "Output config path (default: agents.yaml)", + }, + force: { + type: "switch", + description: "Overwrite an existing config file", + }, +} satisfies FlagsDef; + +export default defineCommand({ + description: "Create a new agents.yaml template", + auth: "none", + usageArgs: "[--provider ] [--agent-name ] [--file ] [--force]", + flags: INIT_FLAGS, + exampleArgs: ["", "--provider bailian --agent-name assistant", "--provider all"], + async run(ctx) { + const { settings, flags } = ctx; + const format = detectOutputFormat(settings.output); + const provider = flags.provider ?? "bailian"; + const agentName = flags.agentName ?? "assistant"; + const file = flags.file ?? "agents.yaml"; + + if (existsSync(file) && !flags.force) { + throw new BailianError( + `${file} already exists.`, + ExitCode.USAGE, + "Pass --force to overwrite.", + ); + } + + const template = buildTemplate({ provider, agentName }); + await writeFile(file, template, "utf8"); + + const gitignorePath = ".gitignore"; + if (existsSync(gitignorePath)) { + const content = await readFile(gitignorePath, "utf8"); + if (!content.includes("agents.state.json")) { + await writeFile(gitignorePath, content + GITIGNORE_ADDITIONS, "utf8"); + } + } else { + await writeFile(gitignorePath, `${GITIGNORE_ADDITIONS.trim()}\n`, "utf8"); + } + + if (format === "json") { + emitResult({ created: file, provider, agent: agentName }, format); + } else { + emitBare(`Created ${file}`); + emitBare("Next: edit agents.yaml, then run `bl agent plan`."); + } + }, +}); diff --git a/packages/commands/src/commands/agent/plan.ts b/packages/commands/src/commands/agent/plan.ts new file mode 100644 index 00000000..4ded10e7 --- /dev/null +++ b/packages/commands/src/commands/agent/plan.ts @@ -0,0 +1,97 @@ +import { + BailianError, + defineCommand, + detectOutputFormat, + ExitCode, + type FlagsDef, +} from "bailian-cli-core"; +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 { withStdoutProtected } from "./_engine/console-capture.ts"; +import { withAgentErrors } from "./_engine/errors.ts"; +import { renderAgentFeedback } from "./_engine/feedback.ts"; + +const PLAN_FLAGS = { + file: { + type: "string", + valueHint: "", + description: "Config file path (default: agents.yaml)", + }, + provider: { + type: "string", + valueHint: "", + description: "Target provider (default: all configured)", + }, + noRefresh: { + type: "switch", + description: "Skip refreshing state from remote before planning", + }, + refreshOnly: { + type: "switch", + description: "Refresh state and show drift without planning remote mutations", + }, +} satisfies FlagsDef; + +export default defineCommand({ + description: "Show what changes would be applied to agent infrastructure", + auth: "none", + usageArgs: "[--file ] [--provider ] [--no-refresh] [--refresh-only]", + flags: PLAN_FLAGS, + exampleArgs: ["", "--provider bailian", "--no-refresh"], + async run(ctx) { + const { settings, flags } = ctx; + const format = detectOutputFormat(settings.output); + const file = flags.file ?? "agents.yaml"; + + const planned = await withAgentErrors(() => + withStdoutProtected(async () => { + const runtime = await buildAgentRuntime(file); + assertProviderConfigured(runtime, flags.provider); + return planProjectContext(runtime, { + provider: flags.provider, + refresh: !flags.noRefresh, + quiet: format === "json", + onFeedback: format === "json" ? undefined : renderAgentFeedback, + }); + }), + ); + + const plan = planned.plan; + const hasErrors = plan.diagnostics.some((diag) => diag.severity === "error"); + + if (format === "json") { + emitResult(plan, format); + if (hasErrors) throw new BailianError("Plan contains errors.", ExitCode.GENERAL); + return; + } + + for (const diag of plan.diagnostics) { + emitBare(`[${diag.severity}] ${diag.code}: ${diag.message}`); + } + if (hasErrors) throw new BailianError("Plan contains errors.", ExitCode.GENERAL); + + const creates = plan.actions.filter((action) => action.action === "create"); + const updates = plan.actions.filter((action) => action.action === "update"); + const deletes = plan.actions.filter((action) => action.action === "delete"); + + if (creates.length + updates.length + deletes.length === 0) { + emitBare("No changes. Infrastructure is up-to-date."); + if (flags.refreshOnly) emitBare("Refresh-only mode: no remote mutations were performed."); + return; + } + + emitBare("\nPlanned actions:\n"); + for (const action of creates) emitBare(` + ${formatResourceLabel(action.address)}`); + for (const action of updates) { + emitBare(` ~ ${formatResourceLabel(action.address)}`); + if (action.reason) emitBare(` ${action.reason}`); + } + for (const action of deletes) emitBare(` - ${formatResourceLabel(action.address)}`); + emitBare( + `\nPlan: ${creates.length} to create, ${updates.length} to update, ${deletes.length} to destroy.`, + ); + if (flags.refreshOnly) emitBare("Refresh-only mode: no remote mutations will be performed."); + }, +}); diff --git a/packages/commands/src/commands/agent/session-create.ts b/packages/commands/src/commands/agent/session-create.ts new file mode 100644 index 00000000..faf082e8 --- /dev/null +++ b/packages/commands/src/commands/agent/session-create.ts @@ -0,0 +1,78 @@ +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 { withStdoutProtected } from "./_engine/console-capture.ts"; +import { withAgentErrors } from "./_engine/errors.ts"; +import { parseMemoryStores } from "./_engine/session-render.ts"; + +const SESSION_CREATE_FLAGS = { + file: { + type: "string", + valueHint: "", + description: "Config file path (default: agents.yaml)", + }, + agent: { + type: "string", + valueHint: "", + description: "Agent name (auto-detected when only one agent is configured)", + }, + environment: { + type: "string", + valueHint: "", + description: "Override agent's declared environment", + }, + vault: { type: "string", valueHint: "", description: "Override agent's declared vault" }, + memoryStores: { + type: "string", + valueHint: "", + description: "Override agent's memory stores (comma-separated)", + }, + title: { type: "string", valueHint: "", description: "Session title" }, + provider: { + type: "string", + valueHint: "<name>", + description: "Target provider (multi-provider agents)", + }, +} satisfies FlagsDef; + +export default defineCommand({ + description: "Create a new session for an agent", + auth: "none", + usageArgs: "[--agent <name>] [--environment <name>] [--title <title>] [--file <path>]", + flags: SESSION_CREATE_FLAGS, + exampleArgs: ["", "--agent assistant", "--agent assistant --title 'debug run'"], + async run(ctx) { + const { settings, flags } = ctx; + const format = detectOutputFormat(settings.output); + const file = flags.file ?? "agents.yaml"; + + const run = await withAgentErrors(() => + withStdoutProtected(async () => { + const runtime = await buildAgentRuntime(file); + return createSessionForAgent(runtime, { + agent: flags.agent, + provider: flags.provider, + environment: flags.environment, + vault: flags.vault, + memoryStores: parseMemoryStores(flags.memoryStores), + title: flags.title, + }); + }), + ); + + const { agentName, session } = run; + if (format === "json") { + emitResult({ agent: agentName, session }, format); + return; + } + emitBare(`Session created: ${session.id}`); + emitBare(` Agent: ${agentName}`); + emitBare(` Environment: ${session.environment_id}`); + emitBare(` Status: ${session.status}`); + if (session.vault_ids.length) emitBare(` Vaults: ${session.vault_ids.join(", ")}`); + if (session.memory_store_ids.length) { + emitBare(` Memory: ${session.memory_store_ids.join(", ")}`); + } + }, +}); diff --git a/packages/commands/src/commands/agent/session-delete.ts b/packages/commands/src/commands/agent/session-delete.ts new file mode 100644 index 00000000..e85e754b --- /dev/null +++ b/packages/commands/src/commands/agent/session-delete.ts @@ -0,0 +1,44 @@ +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 { withStdoutProtected } from "./_engine/console-capture.ts"; +import { withAgentErrors } from "./_engine/errors.ts"; + +const SESSION_DELETE_FLAGS = { + sessionId: { + type: "string", + valueHint: "<id>", + description: "Session ID (required)", + required: true, + }, + file: { + type: "string", + valueHint: "<path>", + description: "Config file path (default: agents.yaml)", + }, + provider: { type: "string", valueHint: "<name>", description: "Target provider" }, +} satisfies FlagsDef; + +export default defineCommand({ + description: "Delete a session", + auth: "none", + usageArgs: "--session-id <id> [--provider <name>] [--file <path>]", + flags: SESSION_DELETE_FLAGS, + exampleArgs: ["--session-id sess_abc123"], + async run(ctx) { + const { settings, flags } = ctx; + const format = detectOutputFormat(settings.output); + const file = flags.file ?? "agents.yaml"; + + await withAgentErrors(() => + withStdoutProtected(async () => { + const runtime = await buildAgentRuntime(file); + await deleteSession(runtime, flags.sessionId, flags.provider); + }), + ); + + if (format === "json") emitResult({ deleted: flags.sessionId }, format); + else emitBare(`Session ${flags.sessionId} deleted.`); + }, +}); diff --git a/packages/commands/src/commands/agent/session-events.ts b/packages/commands/src/commands/agent/session-events.ts new file mode 100644 index 00000000..8887a351 --- /dev/null +++ b/packages/commands/src/commands/agent/session-events.ts @@ -0,0 +1,76 @@ +import { defineCommand, detectOutputFormat, type FlagsDef } from "bailian-cli-core"; +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 { withStdoutProtected } from "./_engine/console-capture.ts"; +import { withAgentErrors } from "./_engine/errors.ts"; +import { fetchAllPages } from "./_engine/pagination.ts"; + +const SESSION_EVENTS_FLAGS = { + sessionId: { + type: "string", + valueHint: "<id>", + description: "Session ID (required)", + required: true, + }, + file: { + type: "string", + 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" }, +} satisfies FlagsDef; + +export default defineCommand({ + description: "List event history for a session", + auth: "none", + usageArgs: "--session-id <id> [--limit <n>] [--all] [--file <path>]", + flags: SESSION_EVENTS_FLAGS, + exampleArgs: ["--session-id sess_abc123", "--session-id sess_abc123 --all"], + async run(ctx) { + const { settings, flags } = ctx; + const format = detectOutputFormat(settings.output); + const file = flags.file ?? "agents.yaml"; + + const { items: events, hasMore } = await withAgentErrors(() => + withStdoutProtected(async () => { + const runtime = await buildAgentRuntime(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 }; + }, flags.all); + }), + ); + + if (format === "json") { + emitResult({ events: sanitizeSessionEvents(events), has_more: hasMore }, format); + return; + } + if (events.length === 0) { + emitBare("No events found."); + return; + } + + const headers = ["#", "TYPE", "CONTENT"]; + const rows = events.map((event, index) => { + let preview = ""; + if (event.type === "message") preview = (event.content ?? "").slice(0, 60); + else if (event.type === "tool_use") preview = event.tool_name ?? ""; + else if (event.type === "tool_result") preview = (event.content ?? "").slice(0, 60); + else if (event.type === "status") preview = event.status ?? ""; + else if (event.type === "error") preview = (event.content ?? "").slice(0, 60); + else preview = event.raw_type; + return [String(index + 1), event.type, preview]; + }); + for (const line of formatTable(headers, rows)) emitBare(line); + emitBare(`\nTotal: ${events.length}`); + if (hasMore) emitBare("More events available. Use --all to fetch all."); + }, +}); diff --git a/packages/commands/src/commands/agent/session-get.ts b/packages/commands/src/commands/agent/session-get.ts new file mode 100644 index 00000000..203facb8 --- /dev/null +++ b/packages/commands/src/commands/agent/session-get.ts @@ -0,0 +1,53 @@ +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 { withStdoutProtected } from "./_engine/console-capture.ts"; +import { withAgentErrors } from "./_engine/errors.ts"; + +const SESSION_GET_FLAGS = { + sessionId: { + type: "string", + valueHint: "<id>", + description: "Session ID (required)", + required: true, + }, + file: { + type: "string", + valueHint: "<path>", + description: "Config file path (default: agents.yaml)", + }, + provider: { type: "string", valueHint: "<name>", description: "Target provider" }, +} satisfies FlagsDef; + +export default defineCommand({ + description: "Get details of a session", + auth: "none", + usageArgs: "--session-id <id> [--provider <name>] [--file <path>]", + flags: SESSION_GET_FLAGS, + exampleArgs: ["--session-id sess_abc123"], + async run(ctx) { + const { settings, flags } = ctx; + const format = detectOutputFormat(settings.output); + const file = flags.file ?? "agents.yaml"; + + const session = await withAgentErrors(() => + withStdoutProtected(async () => { + const runtime = await buildAgentRuntime(file); + return getSession(runtime, flags.sessionId, flags.provider); + }), + ); + + if (format === "json") { + emitResult(session, format); + return; + } + emitBare(` ID: ${session.id}`); + emitBare(` Agent: ${session.agent_id}`); + emitBare(` Environment: ${session.environment_id}`); + emitBare(` Status: ${session.status}`); + if (session.title) emitBare(` Title: ${session.title}`); + emitBare(` Created: ${session.created_at}`); + emitBare(` Updated: ${session.updated_at}`); + }, +}); diff --git a/packages/commands/src/commands/agent/session-list.ts b/packages/commands/src/commands/agent/session-list.ts new file mode 100644 index 00000000..0686c6b0 --- /dev/null +++ b/packages/commands/src/commands/agent/session-list.ts @@ -0,0 +1,72 @@ +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 { withStdoutProtected } from "./_engine/console-capture.ts"; +import { withAgentErrors } from "./_engine/errors.ts"; +import { fetchAllPages } from "./_engine/pagination.ts"; + +const SESSION_LIST_FLAGS = { + file: { + type: "string", + 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" }, +} satisfies FlagsDef; + +export default defineCommand({ + description: "List sessions from the provider", + auth: "none", + usageArgs: "[--agent <name>] [--all] [--provider <name>] [--file <path>]", + flags: SESSION_LIST_FLAGS, + exampleArgs: ["", "--agent assistant", "--all"], + async run(ctx) { + const { settings, flags } = ctx; + const format = detectOutputFormat(settings.output); + const file = flags.file ?? "agents.yaml"; + + const { items: summaries, hasMore } = await withAgentErrors(() => + withStdoutProtected(async () => { + const runtime = await buildAgentRuntime(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 }; + }, flags.all); + }), + ); + + const sessions = summaries.map((summary) => summary.session); + if (format === "json") { + emitResult({ sessions, has_more: hasMore }, format); + return; + } + if (sessions.length === 0) { + emitBare("No sessions found."); + return; + } + + const agentNames = new Map( + summaries + .filter((summary) => summary.agentName) + .map((summary) => [summary.session.id, summary.agentName!]), + ); + const headers = ["ID", "TITLE", "AGENT", "STATUS", "CREATED"]; + const rows = sessions.map((session) => [ + session.id, + (session.title ?? "").slice(0, 20), + agentNames.get(session.id) ?? session.agent_id.slice(0, 12), + session.status, + session.created_at, + ]); + for (const line of formatTable(headers, rows)) emitBare(line); + emitBare(`\nTotal: ${sessions.length}`); + if (hasMore) emitBare("More sessions available. Use --all to fetch all."); + }, +}); diff --git a/packages/commands/src/commands/agent/session-run.ts b/packages/commands/src/commands/agent/session-run.ts new file mode 100644 index 00000000..5f75e59c --- /dev/null +++ b/packages/commands/src/commands/agent/session-run.ts @@ -0,0 +1,81 @@ +import { defineCommand, detectOutputFormat, type FlagsDef } from "bailian-cli-core"; +import { startSessionRun, startSessionRunPolling } from "@openagentpack/sdk"; +import { buildAgentRuntime } from "./_engine/config-loader.ts"; +import { withStdoutProtected } from "./_engine/console-capture.ts"; +import { withAgentErrors } from "./_engine/errors.ts"; +import { + parseMemoryStores, + renderCollectedEvents, + streamAndRenderEvents, +} from "./_engine/session-render.ts"; + +const SESSION_RUN_FLAGS = { + prompt: { + type: "string", + valueHint: "<text>", + description: "Prompt to send (required)", + required: true, + }, + file: { + type: "string", + valueHint: "<path>", + description: "Config file path (default: agents.yaml)", + }, + agent: { + type: "string", + valueHint: "<name>", + description: "Agent name (auto-detected when only one agent is configured)", + }, + environment: { + type: "string", + valueHint: "<name>", + description: "Override agent's declared environment", + }, + 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" }, +} satisfies FlagsDef; + +export default defineCommand({ + description: "Create a session, send a message, and stream the response", + auth: "none", + usageArgs: "--prompt <text> [--agent <name>] [--no-stream] [--file <path>]", + flags: SESSION_RUN_FLAGS, + exampleArgs: ['--prompt "hello"', '--agent assistant --prompt "summarize this repo"'], + async run(ctx) { + const { settings, flags } = ctx; + const format = detectOutputFormat(settings.output); + const file = flags.file ?? "agents.yaml"; + const asJson = format === "json"; + + const runOptions = { + agent: flags.agent, + provider: flags.provider, + environment: flags.environment, + vault: flags.vault, + memoryStores: parseMemoryStores(flags.memoryStores), + title: flags.title, + }; + + await withAgentErrors(() => + withStdoutProtected(async () => { + const runtime = await buildAgentRuntime(file); + if (flags.noStream) { + const run = await startSessionRunPolling(runtime, flags.prompt, runOptions); + if (!asJson) process.stderr.write(`Session created: ${run.session.id}\n`); + renderCollectedEvents(run, asJson); + } else { + const run = await startSessionRun(runtime, flags.prompt, runOptions); + if (!asJson) process.stderr.write(`Session created: ${run.session.id}\n`); + await streamAndRenderEvents(run.events, asJson); + } + }), + ); + }, +}); diff --git a/packages/commands/src/commands/agent/session-send.ts b/packages/commands/src/commands/agent/session-send.ts new file mode 100644 index 00000000..a0b2611a --- /dev/null +++ b/packages/commands/src/commands/agent/session-send.ts @@ -0,0 +1,64 @@ +import { defineCommand, detectOutputFormat, type FlagsDef } from "bailian-cli-core"; +import { sendSessionMessagePolling, sendSessionMessageStreaming } from "@openagentpack/sdk"; +import { buildAgentRuntime } 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"; + +const SESSION_SEND_FLAGS = { + sessionId: { + type: "string", + valueHint: "<id>", + description: "Session ID (required)", + required: true, + }, + message: { + type: "string", + valueHint: "<text>", + description: "Message to send (required)", + required: true, + }, + file: { + type: "string", + 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" }, +} satisfies FlagsDef; + +export default defineCommand({ + description: "Send a message to an existing session and stream the response", + auth: "none", + usageArgs: "--session-id <id> --message <text> [--no-stream] [--file <path>]", + flags: SESSION_SEND_FLAGS, + exampleArgs: ['--session-id sess_abc123 --message "continue"'], + async run(ctx) { + const { settings, flags } = ctx; + const format = detectOutputFormat(settings.output); + const file = flags.file ?? "agents.yaml"; + const asJson = format === "json"; + + await withAgentErrors(() => + withStdoutProtected(async () => { + const runtime = await buildAgentRuntime(file); + if (flags.noStream) { + const result = await sendSessionMessagePolling(runtime, flags.sessionId, flags.message, { + provider: flags.provider, + }); + renderCollectedEvents(result, asJson); + } else { + const events = await sendSessionMessageStreaming( + runtime, + flags.sessionId, + flags.message, + { + provider: flags.provider, + }, + ); + await streamAndRenderEvents(events, asJson); + } + }), + ); + }, +}); diff --git a/packages/commands/src/commands/agent/state-import.ts b/packages/commands/src/commands/agent/state-import.ts new file mode 100644 index 00000000..8d4fabae --- /dev/null +++ b/packages/commands/src/commands/agent/state-import.ts @@ -0,0 +1,61 @@ +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 { withStdoutProtected } from "./_engine/console-capture.ts"; +import { withAgentErrors } from "./_engine/errors.ts"; + +const STATE_IMPORT_FLAGS = { + address: { + type: "string", + valueHint: "<provider.type.name>", + description: "Resource state address (required)", + required: true, + }, + remoteId: { + type: "string", + valueHint: "<id>", + description: "Existing remote resource ID to import (required)", + required: true, + }, + resourceVersion: { + type: "number", + valueHint: "<n>", + description: "Resource version (for versioned resources like agents)", + }, + file: { + type: "string", + valueHint: "<path>", + description: "Config file path (default: agents.yaml)", + }, +} satisfies FlagsDef; + +export default defineCommand({ + description: "Import an existing remote resource into agents state", + auth: "none", + usageArgs: + "--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"], + async run(ctx) { + const { settings, flags } = ctx; + const format = detectOutputFormat(settings.output); + const file = flags.file ?? "agents.yaml"; + + await withAgentErrors(() => + withStdoutProtected(async () => { + const runtime = await buildAgentRuntime(file); + const parsed = parseStateAddress(flags.address, { requireProvider: true }); + await importResource(runtime, parsed, flags.remoteId, { + resourceVersion: flags.resourceVersion, + }); + }), + ); + + if (format === "json") { + emitResult({ imported: flags.address, remote_id: flags.remoteId }, format); + } else { + emitBare(`Imported ${flags.address} (remote_id: ${flags.remoteId}) into state.`); + } + }, +}); diff --git a/packages/commands/src/commands/agent/state-list.ts b/packages/commands/src/commands/agent/state-list.ts new file mode 100644 index 00000000..68d26ed5 --- /dev/null +++ b/packages/commands/src/commands/agent/state-list.ts @@ -0,0 +1,52 @@ +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 { withStdoutProtected } from "./_engine/console-capture.ts"; +import { withAgentErrors } from "./_engine/errors.ts"; + +const STATE_LIST_FLAGS = { + file: { + type: "string", + valueHint: "<path>", + description: "Config file path (default: agents.yaml)", + }, +} satisfies FlagsDef; + +export default defineCommand({ + description: "List resources tracked in agents state", + auth: "none", + usageArgs: "[--file <path>]", + flags: STATE_LIST_FLAGS, + exampleArgs: ["", "--file agents.yaml"], + async run(ctx) { + const { settings, flags } = ctx; + const format = detectOutputFormat(settings.output); + const file = flags.file ?? "agents.yaml"; + + const resources = await withAgentErrors(() => + withStdoutProtected(async () => { + const runtime = await buildAgentRuntime(file); + return runtime.state.listResources(); + }), + ); + + if (format === "json") { + emitResult({ resources }, format); + return; + } + if (resources.length === 0) { + emitBare("No resources tracked in state."); + return; + } + + const headers = ["TYPE", "NAME", "PROVIDER", "REMOTE ID"]; + const rows = resources.map((resource) => [ + resource.address.type, + resource.address.name, + resource.address.provider, + resource.remote_id ?? "(local)", + ]); + for (const line of formatTable(headers, rows)) emitBare(line); + emitBare(`\nTotal: ${resources.length}`); + }, +}); diff --git a/packages/commands/src/commands/agent/state-rm.ts b/packages/commands/src/commands/agent/state-rm.ts new file mode 100644 index 00000000..44022d14 --- /dev/null +++ b/packages/commands/src/commands/agent/state-rm.ts @@ -0,0 +1,56 @@ +import { + BailianError, + defineCommand, + detectOutputFormat, + ExitCode, + type FlagsDef, +} from "bailian-cli-core"; +import { emitBare, emitResult } from "bailian-cli-runtime"; +import { parseStateAddress } from "@openagentpack/sdk"; +import { buildAgentRuntime } from "./_engine/config-loader.ts"; +import { withStdoutProtected } from "./_engine/console-capture.ts"; +import { withAgentErrors } from "./_engine/errors.ts"; + +const STATE_RM_FLAGS = { + address: { + type: "string", + valueHint: "<provider.type.name>", + description: "Resource state address (required)", + required: true, + }, + file: { + type: "string", + valueHint: "<path>", + description: "Config file path (default: agents.yaml)", + }, +} satisfies FlagsDef; + +export default defineCommand({ + description: "Remove a resource from state without destroying it remotely", + auth: "none", + usageArgs: "--address <provider.type.name> [--file <path>]", + flags: STATE_RM_FLAGS, + exampleArgs: ["--address bailian.agent.assistant"], + async run(ctx) { + const { settings, flags } = ctx; + const format = detectOutputFormat(settings.output); + const file = flags.file ?? "agents.yaml"; + + await withAgentErrors(() => + withStdoutProtected(async () => { + const runtime = await buildAgentRuntime(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); + } + runtime.state.removeResource(found.address); + await runtime.state.save(); + }), + ); + + const message = `Removed ${flags.address} from state (remote resource not deleted).`; + if (format === "json") emitResult({ removed: flags.address }, format); + else emitBare(message); + }, +}); diff --git a/packages/commands/src/commands/agent/state-show.ts b/packages/commands/src/commands/agent/state-show.ts new file mode 100644 index 00000000..5a5f2dd3 --- /dev/null +++ b/packages/commands/src/commands/agent/state-show.ts @@ -0,0 +1,54 @@ +import { + BailianError, + defineCommand, + detectOutputFormat, + ExitCode, + type FlagsDef, +} from "bailian-cli-core"; +import { emitBare, emitResult } from "bailian-cli-runtime"; +import { parseStateAddress } from "@openagentpack/sdk"; +import { buildAgentRuntime } from "./_engine/config-loader.ts"; +import { withStdoutProtected } from "./_engine/console-capture.ts"; +import { withAgentErrors } from "./_engine/errors.ts"; + +const STATE_SHOW_FLAGS = { + address: { + type: "string", + valueHint: "<provider.type.name>", + description: "Resource state address (required)", + required: true, + }, + file: { + type: "string", + valueHint: "<path>", + description: "Config file path (default: agents.yaml)", + }, +} satisfies FlagsDef; + +export default defineCommand({ + description: "Show details of a resource in agents state", + auth: "none", + usageArgs: "--address <provider.type.name> [--file <path>]", + flags: STATE_SHOW_FLAGS, + exampleArgs: ["--address bailian.agent.assistant"], + async run(ctx) { + const { settings, flags } = ctx; + const format = detectOutputFormat(settings.output); + const file = flags.file ?? "agents.yaml"; + + const found = await withAgentErrors(() => + withStdoutProtected(async () => { + const runtime = await buildAgentRuntime(file); + const parsed = parseStateAddress(flags.address, { requireProvider: false }); + return runtime.state.findResource(parsed); + }), + ); + + if (!found) { + throw new BailianError(`Resource not found: ${flags.address}`, ExitCode.GENERAL); + } + + if (format === "json") emitResult(found, format); + else emitBare(JSON.stringify(found, null, 2)); + }, +}); diff --git a/packages/commands/src/commands/agent/validate.ts b/packages/commands/src/commands/agent/validate.ts new file mode 100644 index 00000000..195c02b0 --- /dev/null +++ b/packages/commands/src/commands/agent/validate.ts @@ -0,0 +1,54 @@ +import { + BailianError, + defineCommand, + detectOutputFormat, + ExitCode, + type FlagsDef, +} from "bailian-cli-core"; +import { emitBare, emitResult } from "bailian-cli-runtime"; +import { resolveProjectConfig, validateProjectConfig } from "@openagentpack/sdk"; +import { ensureCredentials } from "./_engine/credentials.ts"; +import { withAgentErrors } from "./_engine/errors.ts"; + +const VALIDATE_FLAGS = { + file: { + type: "string", + valueHint: "<path>", + description: "Config file path (default: agents.yaml)", + }, +} satisfies FlagsDef; + +export default defineCommand({ + description: "Validate an agents.yaml configuration (offline)", + auth: "none", + usageArgs: "[--file <path>]", + flags: VALIDATE_FLAGS, + exampleArgs: ["", "--file agents.yaml"], + async run(ctx) { + const { settings, flags } = ctx; + const format = detectOutputFormat(settings.output); + const file = flags.file ?? "agents.yaml"; + + const diagnostics = await withAgentErrors(async () => { + ensureCredentials(); + const { config } = await resolveProjectConfig(file); + return validateProjectConfig(config); + }); + + const errorCount = diagnostics.filter((diag) => diag.severity === "error").length; + + if (format === "json") { + emitResult({ valid: errorCount === 0, diagnostics }, format); + } else { + for (const diag of diagnostics) { + const where = diag.resource ? ` (${diag.resource.type}.${diag.resource.name})` : ""; + emitBare(`[${diag.severity}] ${diag.message}${where}`); + } + if (errorCount === 0) emitBare("Configuration is valid."); + } + + if (errorCount > 0) { + throw new BailianError(`Validation failed with ${errorCount} error(s).`, ExitCode.GENERAL); + } + }, +}); diff --git a/packages/commands/src/index.ts b/packages/commands/src/index.ts index 2579847f..a3094786 100644 --- a/packages/commands/src/index.ts +++ b/packages/commands/src/index.ts @@ -86,6 +86,22 @@ export { default as tokenPlanListSeats } from "./commands/token-plan/list-seats. export { default as tokenPlanCreateKey } from "./commands/token-plan/create-key.ts"; export { default as tokenPlanAssignSeats } from "./commands/token-plan/assign-seats.ts"; export { default as tokenPlanAddMember } from "./commands/token-plan/add-member.ts"; +export { default as agentInit } from "./commands/agent/init.ts"; +export { default as agentValidate } from "./commands/agent/validate.ts"; +export { default as agentPlan } from "./commands/agent/plan.ts"; +export { default as agentApply } from "./commands/agent/apply.ts"; +export { default as agentDestroy } from "./commands/agent/destroy.ts"; +export { default as agentStateList } from "./commands/agent/state-list.ts"; +export { default as agentStateShow } from "./commands/agent/state-show.ts"; +export { default as agentStateRm } from "./commands/agent/state-rm.ts"; +export { default as agentStateImport } from "./commands/agent/state-import.ts"; +export { default as agentSessionCreate } from "./commands/agent/session-create.ts"; +export { default as agentSessionList } from "./commands/agent/session-list.ts"; +export { default as agentSessionGet } from "./commands/agent/session-get.ts"; +export { default as agentSessionDelete } from "./commands/agent/session-delete.ts"; +export { default as agentSessionRun } from "./commands/agent/session-run.ts"; +export { default as agentSessionSend } from "./commands/agent/session-send.ts"; +export { default as agentSessionEvents } from "./commands/agent/session-events.ts"; export { default as pluginInstall } from "./commands/plugin/install.ts"; export { default as pluginLink } from "./commands/plugin/link.ts"; export { default as pluginList } from "./commands/plugin/list.ts"; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a4340666..4213c8ae 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -50,7 +50,7 @@ importers: version: 4.23.0 vite-plus: specifier: 'catalog:' - version: 0.1.22(@types/node@25.6.0)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.0)(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.0)(yaml@2.8.3))(yaml@2.8.3) + version: 0.1.22(@types/node@25.6.0)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.0)(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.0)(yaml@2.9.0))(yaml@2.9.0) packages/cli: dependencies: @@ -100,6 +100,9 @@ importers: packages/commands: dependencies: + '@openagentpack/sdk': + specifier: 0.1.0 + version: 0.1.0 bailian-cli-core: specifier: workspace:* version: link:../core @@ -171,7 +174,7 @@ importers: version: 6.0.3 vite-plus: specifier: 0.1.22 - version: 0.1.22(@types/node@24.12.2)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.0)(typescript@6.0.3)(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.0)(yaml@2.8.3))(yaml@2.8.3) + version: 0.1.22(@types/node@24.12.2)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.0)(typescript@6.0.3)(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.0)(yaml@2.9.0))(yaml@2.9.0) packages/kscli: dependencies: @@ -440,6 +443,10 @@ packages: '@emnapi/core': ^1.7.1 '@emnapi/runtime': ^1.7.1 + '@openagentpack/sdk@0.1.0': + resolution: {integrity: sha512-6IsFwOyuLnB/WIW9CGtpaJaRcGylYIs6UPW/Yz1gZveiuTTFdEWOi8LQ52JmOHB10J1lar+TrW2DJBEejxxu0Q==} + engines: {node: '>=22'} + '@oxc-project/runtime@0.129.0': resolution: {integrity: sha512-0+S67blQakgeNqoKGozOUp5rQBrz2ynXZ2QIINXZPiafsD0YL0UogB9hAWc1S7k6VSNwKYC/N7MqT0V6IzpHkQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1073,6 +1080,9 @@ packages: resolution: {integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==} engines: {node: '>=10'} + core-util-is@1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + detect-libc@2.1.2: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} @@ -1115,10 +1125,19 @@ packages: resolution: {integrity: sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==} engines: {node: '>=18'} + immediate@3.0.6: + resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} + isarray@1.0.0: + resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + jiti@2.6.1: resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true @@ -1126,6 +1145,12 @@ packages: json-schema-traverse@1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + jszip@3.10.1: + resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==} + + lie@3.3.0: + resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==} + lightningcss-android-arm64@1.32.0: resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} engines: {node: '>= 12.0.0'} @@ -1231,6 +1256,9 @@ packages: oxlint-tsgolint: optional: true + pako@1.0.11: + resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} + pend@1.2.0: resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} @@ -1253,6 +1281,12 @@ packages: resolution: {integrity: sha512-W62t/Se6rA0Az3DfCL0AqJwXuKwBeYg6nOaIgzP+xZ7N5BFCI7DYi1qs6ygUYT6rvfi6t9k65UMLJC+PHZpDAA==} engines: {node: ^10 || ^12 || >=14} + process-nextick-args@2.0.1: + resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + + readable-stream@2.3.8: + resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} + require-from-string@2.0.2: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} @@ -1262,6 +1296,12 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} hasBin: true + safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + + setimmediate@1.0.5: + resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} + sirv@3.0.2: resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==} engines: {node: '>=18'} @@ -1284,6 +1324,9 @@ packages: resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} engines: {node: '>=18'} + string_decoder@1.1.1: + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -1338,6 +1381,9 @@ packages: resolution: {integrity: sha512-RNHlB4fxZK0IrkhBsxhlbx7s8kFWwr7rzzOqj5nvZugw3ig3RsB7KW3zVlV0eu8POl+rx5d1hmL7rRg0z1owow==} engines: {node: '>=22.19.0'} + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + vite-plus@0.1.22: resolution: {integrity: sha512-fCCmEKjI+Hv74PdL/MKcrBkdYPHFNcqD5568KxwN0sa4SGxtcbs55i/577LxKs0w5zIjuLRZZ0zQPu9MO+9itg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1411,10 +1457,18 @@ packages: engines: {node: '>= 14.6'} hasBin: true + yaml@2.9.0: + resolution: {integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==} + engines: {node: '>= 14.6'} + hasBin: true + yauzl@3.4.0: resolution: {integrity: sha512-jIH9yLR9wqr0wOS0TpBvo/g/2UgZH5qePVbjgRliiF0BYvOZyaBknKsF+x9Iht0O6sqgnB93rCICdOZFecJuDw==} engines: {node: '>=12'} + zod@4.4.3: + resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} + snapshots: '@clack/core@0.3.5': @@ -1529,6 +1583,12 @@ snapshots: '@tybys/wasm-util': 0.10.1 optional: true + '@openagentpack/sdk@0.1.0': + dependencies: + jszip: 3.10.1 + yaml: 2.9.0 + zod: 4.4.3 + '@oxc-project/runtime@0.129.0': {} '@oxc-project/types@0.127.0': {} @@ -1794,7 +1854,22 @@ snapshots: typescript: 6.0.3 yaml: 2.8.3 - '@voidzero-dev/vite-plus-core@0.1.22(@types/node@25.6.0)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.0)(typescript@6.0.3)(yaml@2.8.3)': + '@voidzero-dev/vite-plus-core@0.1.22(@types/node@24.12.2)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.0)(typescript@6.0.3)(yaml@2.9.0)': + dependencies: + '@oxc-project/runtime': 0.129.0 + '@oxc-project/types': 0.129.0 + lightningcss: 1.32.0 + postcss: 8.5.12 + optionalDependencies: + '@types/node': 24.12.2 + esbuild: 0.28.1 + fsevents: 2.3.3 + jiti: 2.6.1 + tsx: 4.23.0 + typescript: 6.0.3 + yaml: 2.9.0 + + '@voidzero-dev/vite-plus-core@0.1.22(@types/node@25.6.0)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.0)(typescript@6.0.3)(yaml@2.9.0)': dependencies: '@oxc-project/runtime': 0.129.0 '@oxc-project/types': 0.129.0 @@ -1807,7 +1882,7 @@ snapshots: jiti: 2.6.1 tsx: 4.23.0 typescript: 6.0.3 - yaml: 2.8.3 + yaml: 2.9.0 '@voidzero-dev/vite-plus-darwin-arm64@0.1.22': optional: true @@ -1867,11 +1942,51 @@ snapshots: - utf-8-validate - yaml - '@voidzero-dev/vite-plus-test@0.1.22(@types/node@25.6.0)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.0)(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.0)(yaml@2.8.3))(yaml@2.8.3)': + '@voidzero-dev/vite-plus-test@0.1.22(@types/node@24.12.2)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.0)(typescript@6.0.3)(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.0)(yaml@2.9.0))(yaml@2.9.0)': + dependencies: + '@standard-schema/spec': 1.1.0 + '@types/chai': 5.2.3 + '@voidzero-dev/vite-plus-core': 0.1.22(@types/node@24.12.2)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.0)(typescript@6.0.3)(yaml@2.9.0) + es-module-lexer: 1.7.0 + obug: 2.1.1 + pixelmatch: 7.2.0 + pngjs: 7.0.0 + sirv: 3.0.2 + std-env: 4.1.0 + tinybench: 2.9.0 + tinyexec: 1.1.2 + tinyglobby: 0.2.16 + vite: 8.0.10(@types/node@24.12.2)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.0)(yaml@2.9.0) + ws: 8.20.0 + optionalDependencies: + '@types/node': 24.12.2 + transitivePeerDependencies: + - '@arethetypeswrong/core' + - '@tsdown/css' + - '@tsdown/exe' + - '@vitejs/devtools' + - bufferutil + - esbuild + - jiti + - less + - publint + - sass + - sass-embedded + - stylus + - sugarss + - terser + - tsx + - typescript + - unplugin-unused + - unrun + - utf-8-validate + - yaml + + '@voidzero-dev/vite-plus-test@0.1.22(@types/node@25.6.0)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.0)(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.0)(yaml@2.9.0))(yaml@2.9.0)': dependencies: '@standard-schema/spec': 1.1.0 '@types/chai': 5.2.3 - '@voidzero-dev/vite-plus-core': 0.1.22(@types/node@25.6.0)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.0)(typescript@6.0.3)(yaml@2.8.3) + '@voidzero-dev/vite-plus-core': 0.1.22(@types/node@25.6.0)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.0)(typescript@6.0.3)(yaml@2.9.0) es-module-lexer: 1.7.0 obug: 2.1.1 pixelmatch: 7.2.0 @@ -1881,7 +1996,7 @@ snapshots: tinybench: 2.9.0 tinyexec: 1.1.2 tinyglobby: 0.2.16 - vite: 8.0.10(@types/node@25.6.0)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.0)(yaml@2.8.3) + vite: 8.0.10(@types/node@25.6.0)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.0)(yaml@2.9.0) ws: 8.20.0 optionalDependencies: '@types/node': 25.6.0 @@ -1949,6 +2064,8 @@ snapshots: cli-boxes@3.0.0: {} + core-util-is@1.0.3: {} + detect-libc@2.1.2: {} emoji-regex@10.6.0: {} @@ -1999,13 +2116,30 @@ snapshots: get-east-asian-width@1.6.0: {} + immediate@3.0.6: {} + + inherits@2.0.4: {} + is-fullwidth-code-point@3.0.0: {} + isarray@1.0.0: {} + jiti@2.6.1: optional: true json-schema-traverse@1.0.0: {} + jszip@3.10.1: + dependencies: + lie: 3.3.0 + pako: 1.0.11 + readable-stream: 2.3.8 + setimmediate: 1.0.5 + + lie@3.3.0: + dependencies: + immediate: 3.0.6 + lightningcss-android-arm64@1.32.0: optional: true @@ -2117,6 +2251,8 @@ snapshots: '@oxlint/binding-win32-x64-msvc': 1.63.0 oxlint-tsgolint: 0.22.1 + pako@1.0.11: {} + pend@1.2.0: {} picocolors@1.1.1: {} @@ -2135,6 +2271,18 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + process-nextick-args@2.0.1: {} + + readable-stream@2.3.8: + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 1.0.0 + process-nextick-args: 2.0.1 + safe-buffer: 5.1.2 + string_decoder: 1.1.1 + util-deprecate: 1.0.2 + require-from-string@2.0.2: {} rolldown@1.0.0-rc.17: @@ -2158,6 +2306,10 @@ snapshots: '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.17 '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.17 + safe-buffer@5.1.2: {} + + setimmediate@1.0.5: {} + sirv@3.0.2: dependencies: '@polka/url': 1.0.0-next.29 @@ -2182,6 +2334,10 @@ snapshots: get-east-asian-width: 1.6.0 strip-ansi: 7.2.0 + string_decoder@1.1.1: + dependencies: + safe-buffer: 5.1.2 + strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 @@ -2222,6 +2378,8 @@ snapshots: undici@8.4.1: {} + util-deprecate@1.0.2: {} + vite-plus@0.1.22(@types/node@24.12.2)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.0)(typescript@6.0.3)(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.0)(yaml@2.8.3))(yaml@2.8.3): dependencies: '@oxc-project/types': 0.129.0 @@ -2271,12 +2429,61 @@ snapshots: - vite - yaml - vite-plus@0.1.22(@types/node@25.6.0)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.0)(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.0)(yaml@2.8.3))(yaml@2.8.3): + vite-plus@0.1.22(@types/node@24.12.2)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.0)(typescript@6.0.3)(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.0)(yaml@2.9.0))(yaml@2.9.0): dependencies: '@oxc-project/types': 0.129.0 '@oxlint/plugins': 1.61.0 - '@voidzero-dev/vite-plus-core': 0.1.22(@types/node@25.6.0)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.0)(typescript@6.0.3)(yaml@2.8.3) - '@voidzero-dev/vite-plus-test': 0.1.22(@types/node@25.6.0)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.0)(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.0)(yaml@2.8.3))(yaml@2.8.3) + '@voidzero-dev/vite-plus-core': 0.1.22(@types/node@24.12.2)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.0)(typescript@6.0.3)(yaml@2.9.0) + '@voidzero-dev/vite-plus-test': 0.1.22(@types/node@24.12.2)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.0)(typescript@6.0.3)(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.0)(yaml@2.9.0))(yaml@2.9.0) + oxfmt: 0.48.0 + oxlint: 1.63.0(oxlint-tsgolint@0.22.1) + oxlint-tsgolint: 0.22.1 + optionalDependencies: + '@voidzero-dev/vite-plus-darwin-arm64': 0.1.22 + '@voidzero-dev/vite-plus-darwin-x64': 0.1.22 + '@voidzero-dev/vite-plus-linux-arm64-gnu': 0.1.22 + '@voidzero-dev/vite-plus-linux-arm64-musl': 0.1.22 + '@voidzero-dev/vite-plus-linux-x64-gnu': 0.1.22 + '@voidzero-dev/vite-plus-linux-x64-musl': 0.1.22 + '@voidzero-dev/vite-plus-win32-arm64-msvc': 0.1.22 + '@voidzero-dev/vite-plus-win32-x64-msvc': 0.1.22 + transitivePeerDependencies: + - '@arethetypeswrong/core' + - '@edge-runtime/vm' + - '@opentelemetry/api' + - '@tsdown/css' + - '@tsdown/exe' + - '@types/node' + - '@vitejs/devtools' + - '@vitest/coverage-istanbul' + - '@vitest/coverage-v8' + - '@vitest/ui' + - bufferutil + - esbuild + - happy-dom + - jiti + - jsdom + - less + - publint + - sass + - sass-embedded + - stylus + - sugarss + - terser + - tsx + - typescript + - unplugin-unused + - unrun + - utf-8-validate + - vite + - yaml + + vite-plus@0.1.22(@types/node@25.6.0)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.0)(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.0)(yaml@2.9.0))(yaml@2.9.0): + dependencies: + '@oxc-project/types': 0.129.0 + '@oxlint/plugins': 1.61.0 + '@voidzero-dev/vite-plus-core': 0.1.22(@types/node@25.6.0)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.0)(typescript@6.0.3)(yaml@2.9.0) + '@voidzero-dev/vite-plus-test': 0.1.22(@types/node@25.6.0)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.0)(typescript@6.0.3)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.0)(yaml@2.9.0))(yaml@2.9.0) oxfmt: 0.48.0 oxlint: 1.63.0(oxlint-tsgolint@0.22.1) oxlint-tsgolint: 0.22.1 @@ -2335,7 +2542,22 @@ snapshots: tsx: 4.23.0 yaml: 2.8.3 - vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.0)(yaml@2.8.3): + vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.0)(yaml@2.9.0): + dependencies: + lightningcss: 1.32.0 + picomatch: 4.0.4 + postcss: 8.5.12 + rolldown: 1.0.0-rc.17 + tinyglobby: 0.2.16 + optionalDependencies: + '@types/node': 24.12.2 + esbuild: 0.28.1 + fsevents: 2.3.3 + jiti: 2.6.1 + tsx: 4.23.0 + yaml: 2.9.0 + + vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.1)(jiti@2.6.1)(tsx@4.23.0)(yaml@2.9.0): dependencies: lightningcss: 1.32.0 picomatch: 4.0.4 @@ -2348,7 +2570,7 @@ snapshots: fsevents: 2.3.3 jiti: 2.6.1 tsx: 4.23.0 - yaml: 2.8.3 + yaml: 2.9.0 widest-line@5.0.0: dependencies: @@ -2364,6 +2586,10 @@ snapshots: yaml@2.8.3: {} + yaml@2.9.0: {} + yauzl@3.4.0: dependencies: pend: 1.2.0 + + zod@4.4.3: {} diff --git a/skills/bailian-cli/reference/agent.md b/skills/bailian-cli/reference/agent.md new file mode 100644 index 00000000..c7abb325 --- /dev/null +++ b/skills/bailian-cli/reference/agent.md @@ -0,0 +1,451 @@ +# `bl agent` commands + +> Auto-generated from `packages/cli/src/commands.ts`. Do not edit by hand. +> Regenerate: `pnpm --filter bailian-cli run generate:reference`. + +Index: [index.md](index.md) + +## Commands in this group + +| Command | Description | +| ------------------------- | ------------------------------------------------------------- | +| `bl agent apply` | Apply planned changes to create/update/delete agent resources | +| `bl agent destroy` | Destroy all managed agent resources tracked in state | +| `bl agent init` | Create a new agents.yaml template | +| `bl agent plan` | Show what changes would be applied to agent infrastructure | +| `bl agent session create` | Create a new session for an agent | +| `bl agent session delete` | Delete a session | +| `bl agent session events` | List event history for a session | +| `bl agent session get` | Get details of a session | +| `bl agent session list` | List sessions from the provider | +| `bl agent session run` | Create a session, send a message, and stream the response | +| `bl agent session send` | Send a message to an existing session and stream the response | +| `bl agent state import` | Import an existing remote resource into agents state | +| `bl agent state list` | List resources tracked in agents state | +| `bl agent state rm` | Remove a resource from state without destroying it remotely | +| `bl agent state show` | Show details of a resource in agents state | +| `bl agent validate` | Validate an agents.yaml configuration (offline) | + +## Command details + +### `bl agent apply` + +| Field | Value | +| --------------- | -------------------------------------------------------------------------------- | +| **Name** | `agent apply` | +| **Description** | Apply planned changes to create/update/delete agent resources | +| **Usage** | `bl agent apply [--file <path>] [--provider <name>] [--yes] [--concurrency <n>]` | + +#### Flags + +| Flag | Type | Required | Description | +| ------------------- | ------ | -------- | -------------------------------------------------------------------- | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | +| `--provider <name>` | string | no | Target provider (default: all configured) | +| `--yes` | switch | no | Confirm and apply without an interactive prompt (required to mutate) | +| `--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) | + +#### Examples + +```bash +bl agent apply --yes +``` + +```bash +bl agent apply --provider bailian --yes +``` + +### `bl agent destroy` + +| Field | Value | +| --------------- | ------------------------------------------------------ | +| **Name** | `agent destroy` | +| **Description** | Destroy all managed agent resources tracked in state | +| **Usage** | `bl agent destroy [--file <path>] [--yes] [--cascade]` | + +#### Flags + +| Flag | Type | Required | Description | +| --------------- | ------ | -------- | -------------------------------------------------------------------------- | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | +| `--yes` | switch | no | Confirm and destroy without an interactive prompt (required) | +| `--cascade` | switch | no | Auto-delete dependent resources (e.g. sessions referencing an environment) | + +#### Examples + +```bash +bl agent destroy --yes +``` + +```bash +bl agent destroy --yes --cascade +``` + +### `bl agent init` + +| Field | Value | +| --------------- | ----------------------------------------------------------------------------------- | +| **Name** | `agent init` | +| **Description** | Create a new agents.yaml template | +| **Usage** | `bl agent init [--provider <name>] [--agent-name <name>] [--file <path>] [--force]` | + +#### Flags + +| Flag | Type | Required | Description | +| ----------------------------------------------- | ------ | -------- | ------------------------------------------------------------- | +| `--provider <bailian\|claude\|qoder\|ark\|all>` | string | no | Provider: bailian, claude, qoder, ark, all (default: bailian) | +| `--agent-name <name>` | string | no | Name of the first agent (default: assistant) | +| `--file <path>` | string | no | Output config path (default: agents.yaml) | +| `--force` | switch | no | Overwrite an existing config file | + +#### Examples + +```bash +bl agent init +``` + +```bash +bl agent init --provider bailian --agent-name assistant +``` + +```bash +bl agent init --provider all +``` + +### `bl agent plan` + +| Field | Value | +| --------------- | ----------------------------------------------------------------------------------- | +| **Name** | `agent plan` | +| **Description** | Show what changes would be applied to agent infrastructure | +| **Usage** | `bl agent plan [--file <path>] [--provider <name>] [--no-refresh] [--refresh-only]` | + +#### Flags + +| Flag | Type | Required | Description | +| ------------------- | ------ | -------- | -------------------------------------------------------------- | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | +| `--provider <name>` | string | no | Target provider (default: all configured) | +| `--no-refresh` | switch | no | Skip refreshing state from remote before planning | +| `--refresh-only` | switch | no | Refresh state and show drift without planning remote mutations | + +#### Examples + +```bash +bl agent plan +``` + +```bash +bl agent plan --provider bailian +``` + +```bash +bl agent plan --no-refresh +``` + +### `bl agent session create` + +| Field | Value | +| --------------- | --------------------------------------------------------------------------------------------------- | +| **Name** | `agent session create` | +| **Description** | Create a new session for an agent | +| **Usage** | `bl agent session create [--agent <name>] [--environment <name>] [--title <title>] [--file <path>]` | + +#### Flags + +| Flag | Type | Required | Description | +| ------------------------- | ------ | -------- | ------------------------------------------------------------ | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | +| `--agent <name>` | string | no | Agent name (auto-detected when only one agent is configured) | +| `--environment <name>` | string | no | Override agent's declared environment | +| `--vault <name>` | string | no | Override agent's declared vault | +| `--memory-stores <names>` | string | no | Override agent's memory stores (comma-separated) | +| `--title <title>` | string | no | Session title | +| `--provider <name>` | string | no | Target provider (multi-provider agents) | + +#### Examples + +```bash +bl agent session create +``` + +```bash +bl agent session create --agent assistant +``` + +```bash +bl agent session create --agent assistant --title 'debug run' +``` + +### `bl agent session delete` + +| Field | Value | +| --------------- | ------------------------------------------------------------------------------- | +| **Name** | `agent session delete` | +| **Description** | Delete a session | +| **Usage** | `bl agent session delete --session-id <id> [--provider <name>] [--file <path>]` | + +#### Flags + +| Flag | Type | Required | Description | +| ------------------- | ------ | -------- | --------------------------------------- | +| `--session-id <id>` | string | yes | Session ID (required) | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | +| `--provider <name>` | string | no | Target provider | + +#### Examples + +```bash +bl agent session delete --session-id sess_abc123 +``` + +### `bl agent session events` + +| Field | Value | +| --------------- | --------------------------------------------------------------------------------- | +| **Name** | `agent session events` | +| **Description** | List event history for a session | +| **Usage** | `bl agent session events --session-id <id> [--limit <n>] [--all] [--file <path>]` | + +#### Flags + +| Flag | Type | Required | Description | +| ------------------- | ------ | -------- | --------------------------------------- | +| `--session-id <id>` | string | yes | Session ID (required) | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | +| `--provider <name>` | string | no | Target provider | +| `--limit <n>` | number | no | Maximum number of events to fetch | +| `--all` | switch | no | Fetch all pages by following the cursor | + +#### Examples + +```bash +bl agent session events --session-id sess_abc123 +``` + +```bash +bl agent session events --session-id sess_abc123 --all +``` + +### `bl agent session get` + +| Field | Value | +| --------------- | ---------------------------------------------------------------------------- | +| **Name** | `agent session get` | +| **Description** | Get details of a session | +| **Usage** | `bl agent session get --session-id <id> [--provider <name>] [--file <path>]` | + +#### Flags + +| Flag | Type | Required | Description | +| ------------------- | ------ | -------- | --------------------------------------- | +| `--session-id <id>` | string | yes | Session ID (required) | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | +| `--provider <name>` | string | no | Target provider | + +#### Examples + +```bash +bl agent session get --session-id sess_abc123 +``` + +### `bl agent session list` + +| Field | Value | +| --------------- | ------------------------------------------------------------------------------------ | +| **Name** | `agent session list` | +| **Description** | List sessions from the provider | +| **Usage** | `bl agent session list [--agent <name>] [--all] [--provider <name>] [--file <path>]` | + +#### Flags + +| Flag | Type | Required | Description | +| ------------------- | ------ | -------- | --------------------------------------- | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | +| `--agent <name>` | string | no | Filter by agent name | +| `--all` | switch | no | Fetch all pages by following the cursor | +| `--provider <name>` | string | no | Target provider | + +#### Examples + +```bash +bl agent session list +``` + +```bash +bl agent session list --agent assistant +``` + +```bash +bl agent session list --all +``` + +### `bl agent session run` + +| Field | Value | +| --------------- | ------------------------------------------------------------------------------------- | +| **Name** | `agent session run` | +| **Description** | Create a session, send a message, and stream the response | +| **Usage** | `bl agent session run --prompt <text> [--agent <name>] [--no-stream] [--file <path>]` | + +#### Flags + +| Flag | Type | Required | Description | +| ------------------------- | ------ | -------- | ------------------------------------------------------------ | +| `--prompt <text>` | string | yes | Prompt to send (required) | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | +| `--agent <name>` | string | no | Agent name (auto-detected when only one agent is configured) | +| `--environment <name>` | string | no | Override agent's declared environment | +| `--vault <name>` | string | no | Override agent's declared vault | +| `--memory-stores <names>` | string | no | Override agent's memory stores (comma-separated) | +| `--title <title>` | string | no | Session title | +| `--provider <name>` | string | no | Target provider | +| `--no-stream` | switch | no | Use polling instead of SSE streaming | + +#### Examples + +```bash +bl agent session run --prompt "hello" +``` + +```bash +bl agent session run --agent assistant --prompt "summarize this repo" +``` + +### `bl agent session send` + +| Field | Value | +| --------------- | ---------------------------------------------------------------------------------------- | +| **Name** | `agent session send` | +| **Description** | Send a message to an existing session and stream the response | +| **Usage** | `bl agent session send --session-id <id> --message <text> [--no-stream] [--file <path>]` | + +#### Flags + +| Flag | Type | Required | Description | +| ------------------- | ------ | -------- | --------------------------------------- | +| `--session-id <id>` | string | yes | Session ID (required) | +| `--message <text>` | string | yes | Message to send (required) | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | +| `--provider <name>` | string | no | Target provider | +| `--no-stream` | switch | no | Use polling instead of SSE streaming | + +#### Examples + +```bash +bl agent session send --session-id sess_abc123 --message "continue" +``` + +### `bl agent state import` + +| Field | Value | +| --------------- | ---------------------------------------------------------------------------------------------------------------- | +| **Name** | `agent state import` | +| **Description** | Import an existing remote resource into agents state | +| **Usage** | `bl agent state import --address <provider.type.name> --remote-id <id> [--resource-version <n>] [--file <path>]` | + +#### Flags + +| Flag | Type | Required | Description | +| -------------------------------- | ------ | -------- | ------------------------------------------------------ | +| `--address <provider.type.name>` | string | yes | Resource state address (required) | +| `--remote-id <id>` | string | yes | Existing remote resource ID to import (required) | +| `--resource-version <n>` | number | no | Resource version (for versioned resources like agents) | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | + +#### Examples + +```bash +bl agent state import --address bailian.agent.assistant --remote-id agent-abc123 +``` + +### `bl agent state list` + +| Field | Value | +| --------------- | -------------------------------------- | +| **Name** | `agent state list` | +| **Description** | List resources tracked in agents state | +| **Usage** | `bl agent state list [--file <path>]` | + +#### Flags + +| Flag | Type | Required | Description | +| --------------- | ------ | -------- | --------------------------------------- | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | + +#### Examples + +```bash +bl agent state list +``` + +```bash +bl agent state list --file agents.yaml +``` + +### `bl agent state rm` + +| Field | Value | +| --------------- | ------------------------------------------------------------------ | +| **Name** | `agent state rm` | +| **Description** | Remove a resource from state without destroying it remotely | +| **Usage** | `bl agent state rm --address <provider.type.name> [--file <path>]` | + +#### Flags + +| Flag | Type | Required | Description | +| -------------------------------- | ------ | -------- | --------------------------------------- | +| `--address <provider.type.name>` | string | yes | Resource state address (required) | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | + +#### Examples + +```bash +bl agent state rm --address bailian.agent.assistant +``` + +### `bl agent state show` + +| Field | Value | +| --------------- | -------------------------------------------------------------------- | +| **Name** | `agent state show` | +| **Description** | Show details of a resource in agents state | +| **Usage** | `bl agent state show --address <provider.type.name> [--file <path>]` | + +#### Flags + +| Flag | Type | Required | Description | +| -------------------------------- | ------ | -------- | --------------------------------------- | +| `--address <provider.type.name>` | string | yes | Resource state address (required) | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | + +#### Examples + +```bash +bl agent state show --address bailian.agent.assistant +``` + +### `bl agent validate` + +| Field | Value | +| --------------- | ----------------------------------------------- | +| **Name** | `agent validate` | +| **Description** | Validate an agents.yaml configuration (offline) | +| **Usage** | `bl agent validate [--file <path>]` | + +#### Flags + +| Flag | Type | Required | Description | +| --------------- | ------ | -------- | --------------------------------------- | +| `--file <path>` | string | no | Config file path (default: agents.yaml) | + +#### Examples + +```bash +bl agent validate +``` + +```bash +bl agent validate --file agents.yaml +``` diff --git a/skills/bailian-cli/reference/index.md b/skills/bailian-cli/reference/index.md index 6a19b713..f6b3a022 100644 --- a/skills/bailian-cli/reference/index.md +++ b/skills/bailian-cli/reference/index.md @@ -11,6 +11,22 @@ Use this index for the full quick index and global flags. | 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 agent apply` | Apply planned changes to create/update/delete agent resources | [agent.md](agent.md) | +| `bl agent destroy` | Destroy all managed agent resources tracked in state | [agent.md](agent.md) | +| `bl agent init` | Create a new agents.yaml template | [agent.md](agent.md) | +| `bl agent plan` | Show what changes would be applied to agent infrastructure | [agent.md](agent.md) | +| `bl agent session create` | Create a new session for an agent | [agent.md](agent.md) | +| `bl agent session delete` | Delete a session | [agent.md](agent.md) | +| `bl agent session events` | List event history for a session | [agent.md](agent.md) | +| `bl agent session get` | Get details of a session | [agent.md](agent.md) | +| `bl agent session list` | List sessions from the provider | [agent.md](agent.md) | +| `bl agent session run` | Create a session, send a message, and stream the response | [agent.md](agent.md) | +| `bl agent session send` | Send a message to an existing session and stream the response | [agent.md](agent.md) | +| `bl agent state import` | Import an existing remote resource into agents state | [agent.md](agent.md) | +| `bl agent state list` | List resources tracked in agents state | [agent.md](agent.md) | +| `bl agent state rm` | Remove a resource from state without destroying it remotely | [agent.md](agent.md) | +| `bl agent state show` | Show details of a resource in agents state | [agent.md](agent.md) | +| `bl agent validate` | Validate an agents.yaml configuration (offline) | [agent.md](agent.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 login` | Authenticate with API key, console browser login, or OpenAPI AK/SK (credentials can coexist) | [auth.md](auth.md) | @@ -96,35 +112,36 @@ Use this index for the full quick index and global flags. ## By group -| Group | Commands | Reference | -| ------------ | ---------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------ | -| `advisor` | `recommend` | [advisor.md](advisor.md) | -| `app` | `call`, `list` | [app.md](app.md) | -| `auth` | `login`, `logout`, `status` | [auth.md](auth.md) | -| `config` | `set`, `show` | [config.md](config.md) | -| `console` | `call` | [console.md](console.md) | -| `dataset` | `delete`, `get`, `list`, `upload`, `validate` | [dataset.md](dataset.md) | -| `deploy` | `audio create`, `delete`, `get`, `image create`, `list`, `models`, `scale`, `text create`, `update` | [deploy.md](deploy.md) | -| `file` | `upload` | [file.md](file.md) | -| `finetune` | `audio create`, `cancel`, `capability`, `checkpoints`, `delete`, `export`, `get`, `image create`, `list`, `logs`, `text create`, `watch` | [finetune.md](finetune.md) | -| `image` | `edit`, `generate` | [image.md](image.md) | -| `knowledge` | `chat`, `retrieve`, `search` | [knowledge.md](knowledge.md) | -| `mcp` | `call`, `list`, `tools` | [mcp.md](mcp.md) | -| `memory` | `add`, `delete`, `list`, `profile create`, `profile get`, `search`, `update` | [memory.md](memory.md) | -| `model` | `list` | [model.md](model.md) | -| `omni` | `(root)` | [omni.md](omni.md) | -| `pipeline` | `run`, `validate` | [pipeline.md](pipeline.md) | -| `plugin` | `install`, `link`, `list`, `remove` | [plugin.md](plugin.md) | -| `quota` | `check`, `history`, `list`, `request` | [quota.md](quota.md) | -| `search` | `web` | [search.md](search.md) | -| `speech` | `recognize`, `synthesize` | [speech.md](speech.md) | -| `text` | `chat` | [text.md](text.md) | -| `token-plan` | `add-member`, `assign-seats`, `create-key`, `list-seats` | [token-plan.md](token-plan.md) | -| `update` | `(root)` | [update.md](update.md) | -| `usage` | `free`, `freetier`, `stats`, `summary` | [usage.md](usage.md) | -| `video` | `download`, `edit`, `generate`, `ref`, `task get` | [video.md](video.md) | -| `vision` | `describe` | [vision.md](vision.md) | -| `workspace` | `list` | [workspace.md](workspace.md) | +| Group | Commands | Reference | +| ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------ | +| `advisor` | `recommend` | [advisor.md](advisor.md) | +| `agent` | `apply`, `destroy`, `init`, `plan`, `session create`, `session delete`, `session events`, `session get`, `session list`, `session run`, `session send`, `state import`, `state list`, `state rm`, `state show`, `validate` | [agent.md](agent.md) | +| `app` | `call`, `list` | [app.md](app.md) | +| `auth` | `login`, `logout`, `status` | [auth.md](auth.md) | +| `config` | `set`, `show` | [config.md](config.md) | +| `console` | `call` | [console.md](console.md) | +| `dataset` | `delete`, `get`, `list`, `upload`, `validate` | [dataset.md](dataset.md) | +| `deploy` | `audio create`, `delete`, `get`, `image create`, `list`, `models`, `scale`, `text create`, `update` | [deploy.md](deploy.md) | +| `file` | `upload` | [file.md](file.md) | +| `finetune` | `audio create`, `cancel`, `capability`, `checkpoints`, `delete`, `export`, `get`, `image create`, `list`, `logs`, `text create`, `watch` | [finetune.md](finetune.md) | +| `image` | `edit`, `generate` | [image.md](image.md) | +| `knowledge` | `chat`, `retrieve`, `search` | [knowledge.md](knowledge.md) | +| `mcp` | `call`, `list`, `tools` | [mcp.md](mcp.md) | +| `memory` | `add`, `delete`, `list`, `profile create`, `profile get`, `search`, `update` | [memory.md](memory.md) | +| `model` | `list` | [model.md](model.md) | +| `omni` | `(root)` | [omni.md](omni.md) | +| `pipeline` | `run`, `validate` | [pipeline.md](pipeline.md) | +| `plugin` | `install`, `link`, `list`, `remove` | [plugin.md](plugin.md) | +| `quota` | `check`, `history`, `list`, `request` | [quota.md](quota.md) | +| `search` | `web` | [search.md](search.md) | +| `speech` | `recognize`, `synthesize` | [speech.md](speech.md) | +| `text` | `chat` | [text.md](text.md) | +| `token-plan` | `add-member`, `assign-seats`, `create-key`, `list-seats` | [token-plan.md](token-plan.md) | +| `update` | `(root)` | [update.md](update.md) | +| `usage` | `free`, `freetier`, `stats`, `summary` | [usage.md](usage.md) | +| `video` | `download`, `edit`, `generate`, `ref`, `task get` | [video.md](video.md) | +| `vision` | `describe` | [vision.md](vision.md) | +| `workspace` | `list` | [workspace.md](workspace.md) | ## Global flags From d6bd38a46a685dd185d76326d34dbab99699b1eb Mon Sep 17 00:00:00 2001 From: chenanran555 <car534511@alibaba-inc.com> Date: Wed, 22 Jul 2026 13:40:28 +0800 Subject: [PATCH 2/5] =?UTF-8?q?feat(agent):=20agent=E7=9B=B8=E5=85=B3cli?= =?UTF-8?q?=E5=91=BD=E4=BB=A4=E7=9A=84client=E5=B1=82=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=EF=BC=8C=E5=AF=B9=E9=BD=90cli=20client=E7=9A=84=E5=9F=BA?= =?UTF-8?q?=E7=A1=80=E8=83=BD=E5=8A=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/agents/auth-change.md | 4 + packages/cli/.gitignore | 5 +- packages/cli/agents.yaml | 26 +++++ .../commands/agent/_engine/config-loader.ts | 13 ++- .../src/commands/agent/_engine/credentials.ts | 47 ++++++++- .../src/commands/agent/_engine/errors.ts | 44 ++++++++- .../src/commands/agent/_engine/transport.ts | 29 ++++++ packages/commands/src/commands/agent/apply.ts | 9 +- .../commands/src/commands/agent/destroy.ts | 5 +- packages/commands/src/commands/agent/init.ts | 7 +- packages/commands/src/commands/agent/plan.ts | 9 +- .../src/commands/agent/session-create.ts | 11 ++- .../src/commands/agent/session-delete.ts | 11 ++- .../src/commands/agent/session-events.ts | 28 ++++-- .../src/commands/agent/session-get.ts | 11 ++- .../src/commands/agent/session-list.ts | 28 ++++-- .../src/commands/agent/session-run.ts | 22 ++++- .../src/commands/agent/session-send.ts | 16 +++- .../src/commands/agent/state-import.ts | 9 +- .../commands/src/commands/agent/state-list.ts | 5 +- .../commands/src/commands/agent/state-rm.ts | 9 +- .../commands/src/commands/agent/state-show.ts | 9 +- .../commands/src/commands/agent/validate.ts | 3 +- packages/commands/tests/agent-errors.test.ts | 88 +++++++++++++++++ .../commands/tests/credentials-bridge.test.ts | 95 +++++++++++++++++++ packages/core/src/client/index.ts | 3 +- .../core/src/client/instrumented-fetch.ts | 76 +++++++++++++++ .../core/tests/instrumented-fetch.test.ts | 84 ++++++++++++++++ skills/bailian-cli/reference/agent.md | 75 +++++++++++++++ 29 files changed, 723 insertions(+), 58 deletions(-) create mode 100644 packages/cli/agents.yaml create mode 100644 packages/commands/src/commands/agent/_engine/transport.ts create mode 100644 packages/commands/tests/agent-errors.test.ts create mode 100644 packages/commands/tests/credentials-bridge.test.ts create mode 100644 packages/core/src/client/instrumented-fetch.ts create mode 100644 packages/core/tests/instrumented-fetch.test.ts diff --git a/docs/agents/auth-change.md b/docs/agents/auth-change.md index fde61017..ccc1aa8e 100644 --- a/docs/agents/auth-change.md +++ b/docs/agents/auth-change.md @@ -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 层(类型 + 解析) diff --git a/packages/cli/.gitignore b/packages/cli/.gitignore index 3e9713e3..fccd164c 100644 --- a/packages/cli/.gitignore +++ b/packages/cli/.gitignore @@ -2,4 +2,7 @@ node_modules dist *.log .DS_Store -outputs/ \ No newline at end of file +outputs/ +# agents +agents.state.json +.env diff --git a/packages/cli/agents.yaml b/packages/cli/agents.yaml new file mode 100644 index 00000000..e86f410b --- /dev/null +++ b/packages/cli/agents.yaml @@ -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] diff --git a/packages/commands/src/commands/agent/_engine/config-loader.ts b/packages/commands/src/commands/agent/_engine/config-loader.ts index 0e754948..433549d9 100644 --- a/packages/commands/src/commands/agent/_engine/config-loader.ts +++ b/packages/commands/src/commands/agent/_engine/config-loader.ts @@ -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); diff --git a/packages/commands/src/commands/agent/_engine/credentials.ts b/packages/commands/src/commands/agent/_engine/credentials.ts index 875d4331..1c1cc63e 100644 --- a/packages/commands/src/commands/agent/_engine/credentials.ts +++ b/packages/commands/src/commands/agent/_engine/credentials.ts @@ -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(); } diff --git a/packages/commands/src/commands/agent/_engine/errors.ts b/packages/commands/src/commands/agent/_engine/errors.ts index 18a40c82..b02d54e5 100644 --- a/packages/commands/src/commands/agent/_engine/errors.ts +++ b/packages/commands/src/commands/agent/_engine/errors.ts @@ -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; } diff --git a/packages/commands/src/commands/agent/_engine/transport.ts b/packages/commands/src/commands/agent/_engine/transport.ts new file mode 100644 index 00000000..1d99e5ce --- /dev/null +++ b/packages/commands/src/commands/agent/_engine/transport.ts @@ -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; +} diff --git a/packages/commands/src/commands/agent/apply.ts b/packages/commands/src/commands/agent/apply.ts index 809c9f56..cd66f1a6 100644 --- a/packages/commands/src/commands/agent/apply.ts +++ b/packages/commands/src/commands/agent/apply.ts @@ -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, diff --git a/packages/commands/src/commands/agent/destroy.ts b/packages/commands/src/commands/agent/destroy.ts index 52fd3952..6b5e6fa6 100644 --- a/packages/commands/src/commands/agent/destroy.ts +++ b/packages/commands/src/commands/agent/destroy.ts @@ -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); }), ); diff --git a/packages/commands/src/commands/agent/init.ts b/packages/commands/src/commands/agent/init.ts index dc16f65c..467caee2 100644 --- a/packages/commands/src/commands/agent/init.ts +++ b/packages/commands/src/commands/agent/init.ts @@ -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`."); } }, diff --git a/packages/commands/src/commands/agent/plan.ts b/packages/commands/src/commands/agent/plan.ts index 4ded10e7..ceec1103 100644 --- a/packages/commands/src/commands/agent/plan.ts +++ b/packages/commands/src/commands/agent/plan.ts @@ -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, diff --git a/packages/commands/src/commands/agent/session-create.ts b/packages/commands/src/commands/agent/session-create.ts index faf082e8..af6a92d6 100644 --- a/packages/commands/src/commands/agent/session-create.ts +++ b/packages/commands/src/commands/agent/session-create.ts @@ -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, diff --git a/packages/commands/src/commands/agent/session-delete.ts b/packages/commands/src/commands/agent/session-delete.ts index e85e754b..e12f53df 100644 --- a/packages/commands/src/commands/agent/session-delete.ts +++ b/packages/commands/src/commands/agent/session-delete.ts @@ -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); }), ); diff --git a/packages/commands/src/commands/agent/session-events.ts b/packages/commands/src/commands/agent/session-events.ts index 8887a351..7a6cac7b 100644 --- a/packages/commands/src/commands/agent/session-events.ts +++ b/packages/commands/src/commands/agent/session-events.ts @@ -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); }), ); diff --git a/packages/commands/src/commands/agent/session-get.ts b/packages/commands/src/commands/agent/session-get.ts index 203facb8..6da3e67f 100644 --- a/packages/commands/src/commands/agent/session-get.ts +++ b/packages/commands/src/commands/agent/session-get.ts @@ -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); }), ); diff --git a/packages/commands/src/commands/agent/session-list.ts b/packages/commands/src/commands/agent/session-list.ts index 0686c6b0..375feeb5 100644 --- a/packages/commands/src/commands/agent/session-list.ts +++ b/packages/commands/src/commands/agent/session-list.ts @@ -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); }), ); diff --git a/packages/commands/src/commands/agent/session-run.ts b/packages/commands/src/commands/agent/session-run.ts index 5f75e59c..34824a3f 100644 --- a/packages/commands/src/commands/agent/session-run.ts +++ b/packages/commands/src/commands/agent/session-run.ts @@ -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`); diff --git a/packages/commands/src/commands/agent/session-send.ts b/packages/commands/src/commands/agent/session-send.ts index a0b2611a..643f9fe3 100644 --- a/packages/commands/src/commands/agent/session-send.ts +++ b/packages/commands/src/commands/agent/session-send.ts @@ -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, diff --git a/packages/commands/src/commands/agent/state-import.ts b/packages/commands/src/commands/agent/state-import.ts index 8d4fabae..69c33299 100644 --- a/packages/commands/src/commands/agent/state-import.ts +++ b/packages/commands/src/commands/agent/state-import.ts @@ -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, }); diff --git a/packages/commands/src/commands/agent/state-list.ts b/packages/commands/src/commands/agent/state-list.ts index 68d26ed5..49ff8a8d 100644 --- a/packages/commands/src/commands/agent/state-list.ts +++ b/packages/commands/src/commands/agent/state-list.ts @@ -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(); }), ); diff --git a/packages/commands/src/commands/agent/state-rm.ts b/packages/commands/src/commands/agent/state-rm.ts index 44022d14..633d5db1 100644 --- a/packages/commands/src/commands/agent/state-rm.ts +++ b/packages/commands/src/commands/agent/state-rm.ts @@ -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); diff --git a/packages/commands/src/commands/agent/state-show.ts b/packages/commands/src/commands/agent/state-show.ts index 5a5f2dd3..316a1a68 100644 --- a/packages/commands/src/commands/agent/state-show.ts +++ b/packages/commands/src/commands/agent/state-show.ts @@ -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); }), ); diff --git a/packages/commands/src/commands/agent/validate.ts b/packages/commands/src/commands/agent/validate.ts index 195c02b0..7a746810 100644 --- a/packages/commands/src/commands/agent/validate.ts +++ b/packages/commands/src/commands/agent/validate.ts @@ -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); diff --git a/packages/commands/tests/agent-errors.test.ts b/packages/commands/tests/agent-errors.test.ts new file mode 100644 index 00000000..c93e3b9e --- /dev/null +++ b/packages/commands/tests/agent-errors.test.ts @@ -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(); +}); diff --git a/packages/commands/tests/credentials-bridge.test.ts b/packages/commands/tests/credentials-bridge.test.ts new file mode 100644 index 00000000..9e18f141 --- /dev/null +++ b/packages/commands/tests/credentials-bridge.test.ts @@ -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(); + }); +}); diff --git a/packages/core/src/client/index.ts b/packages/core/src/client/index.ts index b4308e39..03bfc836 100644 --- a/packages/core/src/client/index.ts +++ b/packages/core/src/client/index.ts @@ -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, diff --git a/packages/core/src/client/instrumented-fetch.ts b/packages/core/src/client/instrumented-fetch.ts new file mode 100644 index 00000000..da27495b --- /dev/null +++ b/packages/core/src/client/instrumented-fetch.ts @@ -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; + }; +} diff --git a/packages/core/tests/instrumented-fetch.test.ts b/packages/core/tests/instrumented-fetch.test.ts new file mode 100644 index 00000000..501c2622 --- /dev/null +++ b/packages/core/tests/instrumented-fetch.test.ts @@ -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(); +}); diff --git a/skills/bailian-cli/reference/agent.md b/skills/bailian-cli/reference/agent.md index c7abb325..01df0f79 100644 --- a/skills/bailian-cli/reference/agent.md +++ b/skills/bailian-cli/reference/agent.md @@ -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 From 1c9dac24e975a3a6e9606c80036514eb61986194 Mon Sep 17 00:00:00 2001 From: chenanran555 <car534511@alibaba-inc.com> Date: Wed, 22 Jul 2026 14:20:24 +0800 Subject: [PATCH 3/5] =?UTF-8?q?feat(cma):=20login=E6=97=B6=E5=88=9D?= =?UTF-8?q?=E5=A7=8B=E5=8C=96agent=E7=9B=B8=E5=85=B3=E7=9A=84baseUrl?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/commands/agent/_engine/credentials.ts | 16 +++-- packages/commands/src/commands/agent/init.ts | 4 +- packages/commands/src/commands/auth/login.ts | 18 ++++- packages/commands/src/commands/config/set.ts | 15 +++- .../commands/tests/credentials-bridge.test.ts | 68 +++++++++++++++++-- packages/core/src/auth/store.ts | 1 + packages/core/src/config/schema.ts | 9 +++ skills/bailian-cli/reference/agent.md | 60 ++++++++-------- skills/bailian-cli/reference/auth.md | 19 +++--- skills/bailian-cli/reference/config.md | 8 +-- 10 files changed, 156 insertions(+), 62 deletions(-) diff --git a/packages/commands/src/commands/agent/_engine/credentials.ts b/packages/commands/src/commands/agent/_engine/credentials.ts index 1c1cc63e..92fb203e 100644 --- a/packages/commands/src/commands/agent/_engine/credentials.ts +++ b/packages/commands/src/commands/agent/_engine/credentials.ts @@ -11,16 +11,17 @@ let bootstrapped = false; * 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>`.", + "Credentials come from the env vars referenced in agents.yaml (e.g. ${DASHSCOPE_API_KEY}, ${BAILIAN_BASE_URL}).", + "For the bailian provider, bl fills these from your login as a fallback: `bl auth login --api-key <key> --agentstudio-base-url <url>`.", ]; /** * 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 *`. + * the bailian provider. bl persists `api_key` / `agentstudio_base_url` in + * `~/.bailian/config.json` (via `bl auth login`); mirror them onto + * `DASHSCOPE_API_KEY` / `BAILIAN_BASE_URL` so users don't have to re-declare the + * same credentials for `bl agent *`. `workspace_id` is still bridged for configs + * that predate the base_url flow (the SDK accepts either). * * 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 @@ -33,6 +34,9 @@ export function bridgeBailianCredentials(): void { if (!process.env.DASHSCOPE_API_KEY?.trim() && file.api_key) { process.env.DASHSCOPE_API_KEY = file.api_key; } + if (!process.env.BAILIAN_BASE_URL?.trim() && file.agentstudio_base_url) { + process.env.BAILIAN_BASE_URL = file.agentstudio_base_url; + } if (!process.env.BAILIAN_WORKSPACE_ID?.trim() && file.workspace_id) { process.env.BAILIAN_WORKSPACE_ID = file.workspace_id; } diff --git a/packages/commands/src/commands/agent/init.ts b/packages/commands/src/commands/agent/init.ts index 467caee2..78ee8d45 100644 --- a/packages/commands/src/commands/agent/init.ts +++ b/packages/commands/src/commands/agent/init.ts @@ -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 # 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}`, + bailian: ` bailian:\n # bl auth login --api-key <key> sets DASHSCOPE_API_KEY; --agentstudio-base-url <url> sets BAILIAN_BASE_URL\n api_key: \${DASHSCOPE_API_KEY}\n base_url: \${BAILIAN_BASE_URL}`, 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}`, @@ -137,7 +137,7 @@ export default defineCommand({ 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.", + "Credentials: run `bl auth login --api-key <key> --agentstudio-base-url <url>`, or set DASHSCOPE_API_KEY / BAILIAN_BASE_URL.", ); } emitBare("Next: edit agents.yaml, then run `bl agent plan`."); diff --git a/packages/commands/src/commands/auth/login.ts b/packages/commands/src/commands/auth/login.ts index ebf9ac9d..3e6e1cd1 100644 --- a/packages/commands/src/commands/auth/login.ts +++ b/packages/commands/src/commands/auth/login.ts @@ -19,12 +19,22 @@ export default defineCommand({ usageArgs: "--api-key <key> | --console | --open-api --access-key-id <id> --access-key-secret <secret>", flags: { - apiKey: { type: "string", valueHint: "<key>", description: "DashScope API key to store" }, + apiKey: { + type: "string", + valueHint: "<key>", + description: "DashScope API key to store", + }, baseUrl: { type: "string", valueHint: "<url>", description: "DashScope API base URL (used with --api-key for validation)", }, + agentstudioBaseUrl: { + type: "string", + valueHint: "<url>", + description: + "Bailian AgentStudio base URL for `bl agent` commands (sets BAILIAN_BASE_URL; used with --api-key)", + }, console: { type: "switch", description: @@ -65,6 +75,9 @@ export default defineCommand({ if (!apiKeyMode && hasValue(f.baseUrl)) { return "Use --base-url only with --api-key"; } + if (!apiKeyMode && hasValue(f.agentstudioBaseUrl)) { + return "Use --agentstudio-base-url only with --api-key"; + } if (!consoleMode && hasValue(f.consoleSite)) { return "Use --console-site only with --console"; } @@ -131,6 +144,9 @@ export default defineCommand({ if (baseUrl) { await store.login({ base_url: baseUrl }); } + if (flags.agentstudioBaseUrl) { + await store.login({ agentstudio_base_url: flags.agentstudioBaseUrl }); + } await validateAndPersistApiKey(deps, key, baseUrl || store.resolveBaseUrl()); }, }); diff --git a/packages/commands/src/commands/config/set.ts b/packages/commands/src/commands/config/set.ts index 99b3e831..7a3cd81a 100644 --- a/packages/commands/src/commands/config/set.ts +++ b/packages/commands/src/commands/config/set.ts @@ -23,6 +23,7 @@ const VALID_KEYS = [ "default_speech_model", "default_omni_model", "workspace_id", + "agentstudio_base_url", ]; // Keys whose values are secrets. Their stored value must never be echoed back in @@ -44,6 +45,7 @@ const KEY_ALIASES: Record<string, string> = { "default-speech-model": "default_speech_model", "default-omni-model": "default_omni_model", "workspace-id": "workspace_id", + "agentstudio-base-url": "agentstudio_base_url", }; export default defineCommand({ @@ -55,10 +57,15 @@ export default defineCommand({ type: "string", valueHint: "<key>", description: - "Config key (base_url, output, output_dir, timeout, api_key, access_token, access_key_id, access_key_secret, default_*_model, workspace_id)", + "Config key (base_url, output, output_dir, timeout, api_key, access_token, access_key_id, access_key_secret, default_*_model, workspace_id, agentstudio_base_url)", + required: true, + }, + value: { + type: "string", + valueHint: "<value>", + description: "Value to set", required: true, }, - value: { type: "string", valueHint: "<value>", description: "Value to set", required: true }, }, exampleArgs: [ "--key output --value json", @@ -106,7 +113,9 @@ export default defineCommand({ } const coerced = resolvedKey === "timeout" ? Number(value) : value; - await ctx.configStore.write({ [resolvedKey]: coerced } as Partial<ConfigFile>); + await ctx.configStore.write({ + [resolvedKey]: coerced, + } as Partial<ConfigFile>); if (!settings.quiet) { const shown = SECRET_KEYS.has(resolvedKey) ? maskToken(String(coerced)) : coerced; diff --git a/packages/commands/tests/credentials-bridge.test.ts b/packages/commands/tests/credentials-bridge.test.ts index 9e18f141..46f35ca0 100644 --- a/packages/commands/tests/credentials-bridge.test.ts +++ b/packages/commands/tests/credentials-bridge.test.ts @@ -5,20 +5,29 @@ 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 保存恢复隔离,验证优先级与不抛错语义。 + * bridgeBailianCredentials 把 ~/.bailian/config.json 的 api_key / agentstudio_base_url + * / workspace_id 作为最低优先级兜底填入 DASHSCOPE_API_KEY / BAILIAN_BASE_URL / + * 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 }; + config?: { + api_key?: string; + workspace_id?: string; + agentstudio_base_url?: string; + }; + env?: { + DASHSCOPE_API_KEY?: string; + BAILIAN_WORKSPACE_ID?: string; + BAILIAN_BASE_URL?: 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 savedBaseUrl = process.env.BAILIAN_BASE_URL; const dir = mkdtempSync(join(tmpdir(), "bl-cred-bridge-")); process.env.BAILIAN_CONFIG_DIR = dir; @@ -31,6 +40,8 @@ async function inScenario( 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; + if (scenario.env?.BAILIAN_BASE_URL === undefined) delete process.env.BAILIAN_BASE_URL; + else process.env.BAILIAN_BASE_URL = scenario.env.BAILIAN_BASE_URL; try { assert(); @@ -38,6 +49,7 @@ async function inScenario( restore("BAILIAN_CONFIG_DIR", savedConfigDir); restore("DASHSCOPE_API_KEY", savedApiKey); restore("BAILIAN_WORKSPACE_ID", savedWorkspace); + restore("BAILIAN_BASE_URL", savedBaseUrl); rmSync(dir, { recursive: true, force: true }); } } @@ -51,7 +63,10 @@ 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" }, + env: { + DASHSCOPE_API_KEY: "sk-from-env", + BAILIAN_WORKSPACE_ID: "ws-from-env", + }, }, () => { bridgeBailianCredentials(); @@ -63,7 +78,10 @@ test("bridge:env 已有值时不被 bl config 覆盖(最低优先级)", async () test("bridge:env 缺失且 bl config 有值时填充", async () => { await inScenario( - { config: { api_key: "sk-from-config", workspace_id: "ws-from-config" }, env: {} }, + { + config: { api_key: "sk-from-config", workspace_id: "ws-from-config" }, + env: {}, + }, () => { bridgeBailianCredentials(); expect(process.env.DASHSCOPE_API_KEY).toBe("sk-from-config"); @@ -93,3 +111,39 @@ test("bridge:env 与 bl config 皆缺失时不抛错且不写入", async () => { expect(process.env.BAILIAN_WORKSPACE_ID).toBeUndefined(); }); }); + +test("bridge:agentstudio_base_url 兜底填入 BAILIAN_BASE_URL", async () => { + await inScenario( + { + config: { + api_key: "sk-from-config", + agentstudio_base_url: "https://ws-x.cn-beijing.maas.aliyuncs.com/api/v1/agentstudio", + }, + env: {}, + }, + () => { + bridgeBailianCredentials(); + expect(process.env.BAILIAN_BASE_URL).toBe( + "https://ws-x.cn-beijing.maas.aliyuncs.com/api/v1/agentstudio", + ); + }, + ); +}); + +test("bridge:env 已有 BAILIAN_BASE_URL 时不被 bl config 覆盖", async () => { + await inScenario( + { + config: { + api_key: "sk-from-config", + agentstudio_base_url: "https://from-config.aliyuncs.com/api/v1/agentstudio", + }, + env: { + BAILIAN_BASE_URL: "https://from-env.aliyuncs.com/api/v1/agentstudio", + }, + }, + () => { + bridgeBailianCredentials(); + expect(process.env.BAILIAN_BASE_URL).toBe("https://from-env.aliyuncs.com/api/v1/agentstudio"); + }, + ); +}); diff --git a/packages/core/src/auth/store.ts b/packages/core/src/auth/store.ts index 45600e4d..94970611 100644 --- a/packages/core/src/auth/store.ts +++ b/packages/core/src/auth/store.ts @@ -18,6 +18,7 @@ export type AuthPersistPatch = Pick< | "access_key_id" | "access_key_secret" | "base_url" + | "agentstudio_base_url" | "console_site" | "console_region" | "console_switch_agent" diff --git a/packages/core/src/config/schema.ts b/packages/core/src/config/schema.ts index b76b9316..3c30e414 100644 --- a/packages/core/src/config/schema.ts +++ b/packages/core/src/config/schema.ts @@ -23,6 +23,13 @@ export interface ConfigFile { /** Alibaba Cloud OpenAPI AccessKey secret from `bl auth login --open-api`. */ access_key_secret?: string; base_url?: string; + /** + * Bailian AgentStudio API base URL for `bl agent` commands, e.g. + * `https://<workspace>.cn-beijing.maas.aliyuncs.com/api/v1/agentstudio`. + * Distinct from `base_url` (the DashScope model API): the agent path bridges + * this to the SDK's `BAILIAN_BASE_URL`, so a workspace_id is not required. + */ + agentstudio_base_url?: string; output?: "text" | "json"; output_dir?: string; timeout?: number; @@ -78,6 +85,8 @@ export function parseConfigFile(raw: unknown): ConfigFile { ) out.access_key_secret = obj.openapi_access_key_secret; if (typeof obj.base_url === "string" && isHttpUrl(obj.base_url)) out.base_url = obj.base_url; + if (typeof obj.agentstudio_base_url === "string" && isHttpUrl(obj.agentstudio_base_url)) + out.agentstudio_base_url = obj.agentstudio_base_url; if (typeof obj.output === "string" && VALID_OUTPUTS.has(obj.output)) out.output = obj.output as ConfigFile["output"]; if (typeof obj.output_dir === "string" && obj.output_dir.length > 0) diff --git a/skills/bailian-cli/reference/agent.md b/skills/bailian-cli/reference/agent.md index 01df0f79..bd73e5dc 100644 --- a/skills/bailian-cli/reference/agent.md +++ b/skills/bailian-cli/reference/agent.md @@ -48,8 +48,8 @@ Index: [index.md](index.md) #### 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>`. +- Credentials come from the env vars referenced in agents.yaml (e.g. ${DASHSCOPE_API_KEY}, ${BAILIAN_BASE_URL}). +- For the bailian provider, bl fills these from your login as a fallback: `bl auth login --api-key <key> --agentstudio-base-url <url>`. #### Examples @@ -79,8 +79,8 @@ bl agent apply --provider bailian --yes #### 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>`. +- Credentials come from the env vars referenced in agents.yaml (e.g. ${DASHSCOPE_API_KEY}, ${BAILIAN_BASE_URL}). +- For the bailian provider, bl fills these from your login as a fallback: `bl auth login --api-key <key> --agentstudio-base-url <url>`. #### Examples @@ -142,8 +142,8 @@ bl agent init --provider all #### 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>`. +- Credentials come from the env vars referenced in agents.yaml (e.g. ${DASHSCOPE_API_KEY}, ${BAILIAN_BASE_URL}). +- For the bailian provider, bl fills these from your login as a fallback: `bl auth login --api-key <key> --agentstudio-base-url <url>`. #### Examples @@ -181,8 +181,8 @@ bl agent plan --no-refresh #### 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>`. +- Credentials come from the env vars referenced in agents.yaml (e.g. ${DASHSCOPE_API_KEY}, ${BAILIAN_BASE_URL}). +- For the bailian provider, bl fills these from your login as a fallback: `bl auth login --api-key <key> --agentstudio-base-url <url>`. #### Examples @@ -216,8 +216,8 @@ bl agent session create --agent assistant --title 'debug run' #### 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>`. +- Credentials come from the env vars referenced in agents.yaml (e.g. ${DASHSCOPE_API_KEY}, ${BAILIAN_BASE_URL}). +- For the bailian provider, bl fills these from your login as a fallback: `bl auth login --api-key <key> --agentstudio-base-url <url>`. #### Examples @@ -245,8 +245,8 @@ bl agent session delete --session-id sess_abc123 #### 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>`. +- Credentials come from the env vars referenced in agents.yaml (e.g. ${DASHSCOPE_API_KEY}, ${BAILIAN_BASE_URL}). +- For the bailian provider, bl fills these from your login as a fallback: `bl auth login --api-key <key> --agentstudio-base-url <url>`. #### Examples @@ -276,8 +276,8 @@ bl agent session events --session-id sess_abc123 --all #### 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>`. +- Credentials come from the env vars referenced in agents.yaml (e.g. ${DASHSCOPE_API_KEY}, ${BAILIAN_BASE_URL}). +- For the bailian provider, bl fills these from your login as a fallback: `bl auth login --api-key <key> --agentstudio-base-url <url>`. #### Examples @@ -304,8 +304,8 @@ bl agent session get --session-id sess_abc123 #### 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>`. +- Credentials come from the env vars referenced in agents.yaml (e.g. ${DASHSCOPE_API_KEY}, ${BAILIAN_BASE_URL}). +- For the bailian provider, bl fills these from your login as a fallback: `bl auth login --api-key <key> --agentstudio-base-url <url>`. #### Examples @@ -345,8 +345,8 @@ bl agent session list --all #### 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>`. +- Credentials come from the env vars referenced in agents.yaml (e.g. ${DASHSCOPE_API_KEY}, ${BAILIAN_BASE_URL}). +- For the bailian provider, bl fills these from your login as a fallback: `bl auth login --api-key <key> --agentstudio-base-url <url>`. #### Examples @@ -378,8 +378,8 @@ bl agent session run --agent assistant --prompt "summarize this repo" #### 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>`. +- Credentials come from the env vars referenced in agents.yaml (e.g. ${DASHSCOPE_API_KEY}, ${BAILIAN_BASE_URL}). +- For the bailian provider, bl fills these from your login as a fallback: `bl auth login --api-key <key> --agentstudio-base-url <url>`. #### Examples @@ -406,8 +406,8 @@ bl agent session send --session-id sess_abc123 --message "continue" #### 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>`. +- Credentials come from the env vars referenced in agents.yaml (e.g. ${DASHSCOPE_API_KEY}, ${BAILIAN_BASE_URL}). +- For the bailian provider, bl fills these from your login as a fallback: `bl auth login --api-key <key> --agentstudio-base-url <url>`. #### Examples @@ -431,8 +431,8 @@ bl agent state import --address bailian.agent.assistant --remote-id agent-abc123 #### 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>`. +- Credentials come from the env vars referenced in agents.yaml (e.g. ${DASHSCOPE_API_KEY}, ${BAILIAN_BASE_URL}). +- For the bailian provider, bl fills these from your login as a fallback: `bl auth login --api-key <key> --agentstudio-base-url <url>`. #### Examples @@ -461,8 +461,8 @@ bl agent state list --file 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>`. +- Credentials come from the env vars referenced in agents.yaml (e.g. ${DASHSCOPE_API_KEY}, ${BAILIAN_BASE_URL}). +- For the bailian provider, bl fills these from your login as a fallback: `bl auth login --api-key <key> --agentstudio-base-url <url>`. #### Examples @@ -487,8 +487,8 @@ bl agent state rm --address bailian.agent.assistant #### 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>`. +- Credentials come from the env vars referenced in agents.yaml (e.g. ${DASHSCOPE_API_KEY}, ${BAILIAN_BASE_URL}). +- For the bailian provider, bl fills these from your login as a fallback: `bl auth login --api-key <key> --agentstudio-base-url <url>`. #### Examples @@ -512,8 +512,8 @@ bl agent state show --address bailian.agent.assistant #### 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>`. +- Credentials come from the env vars referenced in agents.yaml (e.g. ${DASHSCOPE_API_KEY}, ${BAILIAN_BASE_URL}). +- For the bailian provider, bl fills these from your login as a fallback: `bl auth login --api-key <key> --agentstudio-base-url <url>`. #### Examples diff --git a/skills/bailian-cli/reference/auth.md b/skills/bailian-cli/reference/auth.md index eda1496f..2da50b90 100644 --- a/skills/bailian-cli/reference/auth.md +++ b/skills/bailian-cli/reference/auth.md @@ -25,15 +25,16 @@ Index: [index.md](index.md) #### Flags -| Flag | Type | Required | Description | -| ------------------------------ | ------ | -------- | ------------------------------------------------------------------------------------- | -| `--api-key <key>` | string | no | DashScope API key to store | -| `--base-url <url>` | string | no | DashScope API base URL (used with --api-key for validation) | -| `--console` | switch | no | Sign in via browser; use --console-site to choose domestic (default) or international | -| `--console-site <site>` | string | no | Console site: domestic, international | -| `--open-api` | switch | no | Store Alibaba Cloud OpenAPI AK/SK credentials | -| `--access-key-id <id>` | string | no | Alibaba Cloud Access Key ID to store | -| `--access-key-secret <secret>` | string | no | Alibaba Cloud Access Key Secret to store | +| Flag | Type | Required | Description | +| ------------------------------ | ------ | -------- | ------------------------------------------------------------------------------------------------- | +| `--api-key <key>` | string | no | DashScope API key to store | +| `--base-url <url>` | string | no | DashScope API base URL (used with --api-key for validation) | +| `--agentstudio-base-url <url>` | string | no | Bailian AgentStudio base URL for `bl agent` commands (sets BAILIAN_BASE_URL; used with --api-key) | +| `--console` | switch | no | Sign in via browser; use --console-site to choose domestic (default) or international | +| `--console-site <site>` | string | no | Console site: domestic, international | +| `--open-api` | switch | no | Store Alibaba Cloud OpenAPI AK/SK credentials | +| `--access-key-id <id>` | string | no | Alibaba Cloud Access Key ID to store | +| `--access-key-secret <secret>` | string | no | Alibaba Cloud Access Key Secret to store | #### Examples diff --git a/skills/bailian-cli/reference/config.md b/skills/bailian-cli/reference/config.md index eb03de24..a08f0d23 100644 --- a/skills/bailian-cli/reference/config.md +++ b/skills/bailian-cli/reference/config.md @@ -24,10 +24,10 @@ Index: [index.md](index.md) #### Flags -| Flag | Type | Required | Description | -| ----------------- | ------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------- | -| `--key <key>` | string | yes | Config key (base*url, output, output_dir, timeout, api_key, access_token, access_key_id, access_key_secret, default*\*\_model, workspace_id) | -| `--value <value>` | string | yes | Value to set | +| Flag | Type | Required | Description | +| ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `--key <key>` | string | yes | Config key (base*url, output, output_dir, timeout, api_key, access_token, access_key_id, access_key_secret, default*\*\_model, workspace_id, agentstudio_base_url) | +| `--value <value>` | string | yes | Value to set | #### Examples From 9e59b013268555dd5290bf7b34070b960d219a83 Mon Sep 17 00:00:00 2001 From: chenanran555 <car534511@alibaba-inc.com> Date: Wed, 22 Jul 2026 17:01:14 +0800 Subject: [PATCH 4/5] feat: update openagentpack sdk --- packages/cli/agents.yaml | 26 -------------------------- packages/commands/package.json | 2 +- pnpm-lock.yaml | 14 +++++--------- 3 files changed, 6 insertions(+), 36 deletions(-) delete mode 100644 packages/cli/agents.yaml diff --git a/packages/cli/agents.yaml b/packages/cli/agents.yaml deleted file mode 100644 index e86f410b..00000000 --- a/packages/cli/agents.yaml +++ /dev/null @@ -1,26 +0,0 @@ -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] diff --git a/packages/commands/package.json b/packages/commands/package.json index 2aed8b58..57f5b03a 100644 --- a/packages/commands/package.json +++ b/packages/commands/package.json @@ -40,7 +40,7 @@ "check": "vp check" }, "dependencies": { - "@openagentpack/sdk": "0.1.0", + "@openagentpack/sdk": "0.3.0-beta-8d9edcd-20260722", "bailian-cli-core": "workspace:*", "bailian-cli-runtime": "workspace:*", "boxen": "catalog:", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 58771368..2d5419c0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -40,10 +40,6 @@ catalogs: specifier: ^3.4.0 version: 3.4.0 -overrides: - vite: npm:@voidzero-dev/vite-plus-core@latest - vitest: npm:@voidzero-dev/vite-plus-test@latest - importers: .: @@ -104,8 +100,8 @@ importers: packages/commands: dependencies: '@openagentpack/sdk': - specifier: 0.1.0 - version: 0.1.0 + specifier: 0.3.0-beta-8d9edcd-20260722 + version: 0.3.0-beta-8d9edcd-20260722 bailian-cli-core: specifier: workspace:* version: link:../core @@ -449,8 +445,8 @@ packages: '@emnapi/core': ^1.7.1 '@emnapi/runtime': ^1.7.1 - '@openagentpack/sdk@0.1.0': - resolution: {integrity: sha512-6IsFwOyuLnB/WIW9CGtpaJaRcGylYIs6UPW/Yz1gZveiuTTFdEWOi8LQ52JmOHB10J1lar+TrW2DJBEejxxu0Q==} + '@openagentpack/sdk@0.3.0-beta-8d9edcd-20260722': + resolution: {integrity: sha512-WqF8srhE4Gu2fBRb6jFpuCNq+Bkzp/acPRW2TNsWOpb1qSwi7vRfrv2tBf8iMDXtDFOQP4jb/j7QS5/1/X5ShQ==} engines: {node: '>=22'} '@oxc-project/runtime@0.129.0': @@ -1593,7 +1589,7 @@ snapshots: '@tybys/wasm-util': 0.10.1 optional: true - '@openagentpack/sdk@0.1.0': + '@openagentpack/sdk@0.3.0-beta-8d9edcd-20260722': dependencies: jszip: 3.10.1 yaml: 2.9.0 From 1da3367de8d18f0a8af9b969089056200fb264fe Mon Sep 17 00:00:00 2001 From: chenanran555 <car534511@alibaba-inc.com> Date: Wed, 22 Jul 2026 17:44:50 +0800 Subject: [PATCH 5/5] feat: fix ci --- pnpm-lock.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2d5419c0..bed1456b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -40,6 +40,10 @@ catalogs: specifier: ^3.4.0 version: 3.4.0 +overrides: + vite: npm:@voidzero-dev/vite-plus-core@latest + vitest: npm:@voidzero-dev/vite-plus-test@latest + importers: .: