feat: enhance credential handling for managed-agent and memory APIs

- Updated README.md to clarify API key usage and access restrictions for TokenPlan and pay-as-you-go keys.
- Introduced shared credential validation logic to prevent TokenPlan keys from being used in incompatible contexts.
- Enhanced error messaging for credential resolution failures in managed-agent and memory plugins.
- Added tests for credential classification and workspace endpoint composition.
- Updated documentation to reflect changes in credential handling and workspace-scoped agentstudio endpoint requirements.
This commit is contained in:
lisheng.lisheng
2026-08-15 16:50:08 +08:00
parent f919ebae3c
commit 50ed680ade
12 changed files with 476 additions and 50 deletions
@@ -50,6 +50,7 @@ export interface CredentialHost {
*/
export const CREDENTIALS_NOTE = [
"Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile).",
"The agentstudio endpoint is workspace-scoped: the base URL is composed from the workspace id (agents.yaml workspace_id > $BAILIAN_WORKSPACE_ID > bl's configured workspace_id) as https://{workspace}.cn-beijing.maas.aliyuncs.com/api/v1/agentstudio, and the key must belong to that workspace.",
"Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json.",
"Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env.",
];
@@ -85,13 +86,19 @@ export function prepareProviderEnv(): void {
* the block references them and the interpolated value is empty (a literal in
* agents.yaml is respected).
*
* `base_url` carries {@link AGENTSTUDIO_API_PATH} because the SDK appends resource
* paths onto it verbatim; a value already ending in the suffix is left as-is.
* It is filled even without a credential — `client.baseUrl` is readable
* credential-less (defaults to the CLI's model-domain base URL) — so offline
* commands (which skip the credential assert) still satisfy the SDK's
* "workspace_id or base_url" schema. With no credential the `api_key` is left
* untouched: online commands reject it via {@link assertProviderCredentials}.
* `base_url` is composed from the workspace when one is known — block
* `workspace_id` (agents.yaml literal or interpolated `${BAILIAN_WORKSPACE_ID}`)
* first, then bl's configured `workspace_id` — because agentstudio is served
* only on the workspace-scoped host; the bare model-domain origin 404s it
* (managed-agents API overview: `https://{workspace_id}.cn-beijing.maas.
* aliyuncs.com/api/v1/agentstudio`, region cn-beijing only). Only with no
* workspace at all does the model-domain origin get {@link AGENTSTUDIO_API_PATH}
* suffixed. A value already ending in the suffix is left as-is. base_url is
* filled even without a credential — `client.baseUrl` is readable
* credential-less — so offline commands (which skip the credential assert)
* still satisfy the SDK's "workspace_id or base_url" schema. With no
* credential the `api_key` is left untouched: online commands reject it via
* {@link assertProviderCredentials}.
*/
export function injectProviderCredentials(
providers: Record<string, unknown>,
@@ -103,16 +110,27 @@ export function injectProviderCredentials(
const cred = host.client.exportApiCredential();
if (cred) block.api_key = cred.token;
if ("base_url" in block && !block.base_url) {
// Defensive normalization: the auth chain already normalizes base_url to
// an origin, but never let a trailing slash produce "//api/v1/agentstudio".
const origin = host.client.baseUrl.replace(/\/+$/, "");
block.base_url = origin.endsWith(AGENTSTUDIO_API_PATH)
? origin
: `${origin}${AGENTSTUDIO_API_PATH}`;
if ("workspace_id" in block && !block.workspace_id) {
// agents.yaml interpolation already replaced `${BAILIAN_WORKSPACE_ID}` in
// file-based flows; the inline runtime passes an object config that never
// interpolates, so read the env var here too (prepareProviderEnv
// placeholders it to "" when unset). bl's configured workspace_id is the
// last resort.
block.workspace_id =
process.env.BAILIAN_WORKSPACE_ID?.trim() || host.settings.workspaceId || "";
}
if ("workspace_id" in block && !block.workspace_id && host.settings.workspaceId) {
block.workspace_id = host.settings.workspaceId;
if ("base_url" in block && !block.base_url) {
const workspaceId = typeof block.workspace_id === "string" ? block.workspace_id.trim() : "";
if (workspaceId) {
block.base_url = `https://${workspaceId}.cn-beijing.maas.aliyuncs.com${AGENTSTUDIO_API_PATH}`;
} else {
// Defensive normalization: the auth chain already normalizes base_url to
// an origin, but never let a trailing slash produce "//api/v1/agentstudio".
const origin = host.client.baseUrl.replace(/\/+$/, "");
block.base_url = origin.endsWith(AGENTSTUDIO_API_PATH)
? origin
: `${origin}${AGENTSTUDIO_API_PATH}`;
}
}
}
@@ -56,15 +56,17 @@ export function inlineStatePath(agentName: string): string {
/**
* The minimal in-memory project config that materializes into one cloud agent.
* `providers.bailian` carries empty `api_key`/`base_url` placeholders so
* {@link injectProviderCredentials} fills them from bl's auth chain (it only
* writes fields the block already declares).
* `providers.bailian` carries empty `api_key`/`base_url`/`workspace_id`
* placeholders so {@link injectProviderCredentials} fills them from bl's auth
* chain and workspace sources (it only writes fields the block already
* declares). `workspace_id` lets injection compose the workspace-scoped
* agentstudio host instead of the model-domain origin.
*/
export function buildInlineConfig(opts: InlineAgentOptions): Record<string, unknown> {
return {
version: "1",
providers: {
bailian: { api_key: "", base_url: "" },
bailian: { api_key: "", base_url: "", workspace_id: "" },
},
defaults: { provider: "bailian" },
environments: {
@@ -124,7 +124,8 @@ test("inject:已带后缀且尾斜杠的 base_url 去斜杠后原样保留", ()
expect(providers.bailian.base_url).toBe("https://x.maas.aliyuncs.com/api/v1/agentstudio");
});
test("inject:workspace_id 引用且为空时用 settings 填充;有字面量则保留", () => {
test("inject:workspace_id 引用且为空时按 env > settings 填充;有字面量则保留", () => {
delete process.env.BAILIAN_WORKSPACE_ID;
const empty = { bailian: { api_key: "", workspace_id: "" } };
injectProviderCredentials(
empty,
@@ -132,6 +133,16 @@ test("inject:workspace_id 引用且为空时用 settings 填充;有字面量则
);
expect(empty.bailian.workspace_id).toBe("ws-settings");
// 内联运行时(对象配置)不做 ${} 插值,env 变量在此补读。
process.env.BAILIAN_WORKSPACE_ID = "ws-env";
const fromEnv = { bailian: { api_key: "", workspace_id: "" } };
injectProviderCredentials(
fromEnv,
makeHost({ apiCred: bailianCred(), workspaceId: "ws-settings" }),
);
expect(fromEnv.bailian.workspace_id).toBe("ws-env");
delete process.env.BAILIAN_WORKSPACE_ID;
const literal = { bailian: { api_key: "", workspace_id: "ws-yaml" } };
injectProviderCredentials(
literal,
@@ -140,6 +151,37 @@ test("inject:workspace_id 引用且为空时用 settings 填充;有字面量则
expect(literal.bailian.workspace_id).toBe("ws-yaml");
});
test("inject:workspace 已知时 base_url 拼工作空间主机,而非模型域 origin", () => {
// agents.yaml 字面量 workspace_id + 空 base_url。
const literal = { bailian: { api_key: "", base_url: "", workspace_id: "ws-yaml" } };
injectProviderCredentials(literal, makeHost({ apiCred: bailianCred() }));
expect(literal.bailian.base_url).toBe(
"https://ws-yaml.cn-beijing.maas.aliyuncs.com/api/v1/agentstudio",
);
// 内联块:workspace_id 由 settings 填充后同样走工作空间主机。
const inline = { bailian: { api_key: "", base_url: "", workspace_id: "" } };
injectProviderCredentials(
inline,
makeHost({ apiCred: bailianCred(), workspaceId: "ws-settings" }),
);
expect(inline.bailian.workspace_id).toBe("ws-settings");
expect(inline.bailian.base_url).toBe(
"https://ws-settings.cn-beijing.maas.aliyuncs.com/api/v1/agentstudio",
);
// 显式 base_url 字面量永远优先于拼装。
const explicit = {
bailian: {
api_key: "",
base_url: "https://custom.example.com/api/v1/agentstudio",
workspace_id: "ws-yaml",
},
};
injectProviderCredentials(explicit, makeHost({ apiCred: bailianCred() }));
expect(explicit.bailian.base_url).toBe("https://custom.example.com/api/v1/agentstudio");
});
test("inject:无凭证时 api_key 保持不变,base_url 仍用 client 默认域名补齐(离线/范围外 schema 可用)", () => {
const providers = { bailian: { api_key: "", base_url: "" } };
injectProviderCredentials(providers, makeHost({}));
+33 -18
View File
@@ -28,16 +28,16 @@
- 百炼 API Key。**注意有两类且不可混用**:
| 类型 | 前缀 | 能访问 | 不能访问 |
| --------- | -------- | --------------------------------------- | -------------- |
| TokenPlan | `sk-sp-` | TokenPlan 网关(LLM / vision / 文生图) | 记忆库、知识库 |
| 按量付费 | `sk-ws-` | 记忆库、知识库、DashScope 全量接口 | TokenPlan 网关 |
| 类型 | 前缀 | 能访问 | 不能访问 |
| --------- | -------- | ----------------------------------------------------------- | ------------------------ |
| TokenPlan | `sk-sp-` | TokenPlan 网关(LLM / vision / 文生图) | 记忆库、知识库、远程任务 |
| 按量付费 | `sk-ws-` | 记忆库、知识库、远程任务(agentstudio)、DashScope 全量接口 | TokenPlan 网关 |
两者互相返回 `401 InvalidApiKey`,所以本包用**两个不同的环境变量**,不会互相踩:
```sh
export BAILIAN_TOKENPLAN_API_KEY=sk-sp-xxx # 只给 bailian-tokenplan provider
export DASHSCOPE_API_KEY=sk-ws-xxx # 给 bl、memory、RAG
export DASHSCOPE_API_KEY=sk-ws-xxx # 给 bl、memory、RAG、远程任务
```
只有一类 Key 也能用,只是能力范围相应缩小。若只有 TokenPlan Key:
@@ -48,7 +48,13 @@
export DASHSCOPE_BASE_URL=https://token-plan.cn-beijing.maas.aliyuncs.com
```
这样 LLM / vision / 文生图可用,memory 与 RAG 不可用(保持停用即可)。
这样 LLM / vision / 文生图可用(后两者经 `bl` 走 TokenPlan 网关);memory 与 RAG 保持停用即可。**远程任务仍可注册**,但它的凭证解析会看出这是 TokenPlan Key / 网关,调用 `bailian_run_remote_task` 时直接给出带修复指引的报错,而不是以前的 `Bailian API 404`。
**按量付费 Key 的解析顺序**(memory / RAG / 远程任务三处一致):行内 `config.apiKey` → `$DASHSCOPE_API_KEY`。
**远程任务的端点**另有讲究:managed-agent(agentstudio)API **只**在工作空间前缀主机上提供——`https://{workspaceId}.cn-beijing.maas.aliyuncs.com/api/v1/agentstudio`(普通 dashscope 主机与 TokenPlan 网关都 404),且 Key 只能访问**自己归属的工作空间**(不匹配时 403 `Endpoint.AccessDenied`)。端点解析顺序:行内 `baseUrl` → `$DASHSCOPE_BASE_URL` → 行内 `workspaceId` → `$BAILIAN_WORKSPACE_ID`(后两者自动拼成工作空间主机)。workspace ID 在百炼控制台右上角的工作空间下拉里看。
memory / RAG 是显式开启的插件,Key 缺失或误填 `sk-sp-` 会在启动期报错;远程任务默认启用,为避免拖垮 TokenPlan-only 环境,改为调用时报错。
---
@@ -125,10 +131,12 @@ DeepSeek 那两个模型在 TokenPlan 网关上传图**不报错但也看不见*
把一个任务甩到百炼云端的托管 agent 上跑,不占本地会话。**无需预先写 `agents.yaml` 或 `apply`**:工具首次被调用时,`bl managed-agent run` 会在你的账号里幂等创建一个 agent + cloud environment,之后复用。
- 模型自己按用户意图填 `instructions`(远程 agent 的角色),`task` 是要它做的事。例如你说「在云端帮我审计这个依赖树,它该懂安全」→ 模型调 `bailian_run_remote_task(task="审计依赖树", instructions="你是安全专家")`。
- **前提**:这条路走的是 managed-agent(agentstudio)服务,需要**按量付费 Key**(`sk-ws-`)+ dashscope 端点,且账号已开通 managed-agent。TokenPlan Key 不适用。若 `DASHSCOPE_API_KEY`/端点没配好,首次调用会返回 `Bailian API 404`。
- **前提**:这条路走的是 managed-agent(agentstudio)服务,需要**按量付费 Key**(`sk-ws-`)+ **工作空间端点**,且账号已开通 managed-agent。TokenPlan Key 不适用。
- **凭证解析**:Key 为 `config.apiKey` → `$DASHSCOPE_API_KEY`;端点为 `config.baseUrl` → `$DASHSCOPE_BASE_URL` → `config.workspaceId` → `$BAILIAN_WORKSPACE_ID`(后两者自动拼成 `https://{workspaceId}.cn-beijing.maas.aliyuncs.com`)。凡是解析出来的,都会显式下发给 `bl`,不会落到 `bl` 活动 config profile 的端点上——这正是旧版 `Bailian API 404` 的根因:agentstudio **只**在工作空间前缀主机上提供,TokenPlan 网关与普通 dashscope 主机都 404。
- **两个高频报错**:`404`=端点不是工作空间主机;`403 Endpoint.AccessDenied`=主机对了但这个 Key 不属于该工作空间。二者都会附带具体修复指引。Key 归属的工作空间在百炼控制台右上角下拉里看。
- 首次会创建云资源(可能计费、启动有延迟);同名 agent 后续复用。默认 agent 名 `dsh-remote-runner`,可在配置里改。
需要非默认的 agent 名 / 模型时:
需要非默认的 agent 名 / 模型 / 凭证时:
```yaml
- id: bailian-tool-managed-agent
@@ -136,6 +144,10 @@ DeepSeek 那两个模型在 TokenPlan 网关上传图**不报错但也看不见*
agent: my-runner
model: qwen3.8-max
timeoutMs: 600000
# 可选凭证(省略则按上面的解析顺序找):
# apiKey: sk-ws-xxxxxxxx
# workspaceId: llm-xxxxxxxx # 推荐:自动拼成工作空间端点
# baseUrl: https://llm-xxxxxxxx.cn-beijing.maas.aliyuncs.com # 或用完整端点
```
---
@@ -157,7 +169,7 @@ DeepSeek 那两个模型在 TokenPlan 网关上传图**不报错但也看不见*
workspaceId: llm-xxxxxxxx # 百炼控制台工作空间 ID
agentId: aid-xxxxxxxx # 知识库"检索服务"ID
maxResults: 10
# apiKey 省略则读 $DASHSCOPE_API_KEY
# apiKey 省略则读 $DASHSCOPE_API_KEY(须为按量付费 sk-ws-;误填 sk-sp- 会在启动期报错)
```
一个实例对一个知识库(`WebSearchRequest` 只带 `query` / `maxResults`,agentId 只能来自配置)。要多个知识库就插多行不同 `id`。
@@ -229,15 +241,18 @@ bl auth status
## 6. 常见问题
| 现象 | 原因 |
| -------------------------------------- | ---------------------------------------------------------------------- |
| LLM 路由 `401 InvalidApiKey` | `BAILIAN_TOKENPLAN_API_KEY` 没设,或误填了 `sk-ws-` 的按量付费 Key |
| memory / RAG `401 InvalidApiKey` | `DASHSCOPE_API_KEY` 误填了 `sk-sp-` 的 TokenPlan Key |
| `WEB_PROVIDER_AMBIGUOUS` | 有多个搜索 provider,需在 `web` 行 pin `searchProvider` |
| 粘图报 `MODEL_DOES_NOT_SUPPORT_IMAGES` | 当前模型不支持图片输入,换成上表标"是"的,或改用 vision 工具 |
| 工具报找不到 `bl` | `bl` 不在 PATH:`npm install -g bailian-cli` |
| 改了 patch 但没生效 | `config` 是整体替换,检查是否漏写了原有字段;再用 `--dump-config` 确认 |
| `memoryLibraryId does not exist` | 记忆库 ID 属于另一个账号,与当前 Key 不匹配 |
| 现象 | 原因 |
| -------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| LLM 路由 `401 InvalidApiKey` | `BAILIAN_TOKENPLAN_API_KEY` 没设,或误填了 `sk-ws-` 的按量付费 Key |
| memory / RAG 启动期报 TokenPlan Key | `DASHSCOPE_API_KEY` / `apiKey` 误填了 `sk-sp-` 的 TokenPlan Key |
| 远程任务 `Bailian API 404` | 端点不是工作空间前缀主机(TokenPlan 网关 / 普通 dashscope 主机都不提供 agentstudio);给该行配 `workspaceId`(或 `baseUrl`),或导出 `BAILIAN_WORKSPACE_ID` / `DASHSCOPE_BASE_URL` |
| 远程任务 `403 Endpoint.AccessDenied` | 主机是工作空间主机,但这个 Key 不属于该工作空间;换成 Key 归属工作空间的 ID(控制台右上角下拉),或用属于该工作空间的 Key |
| 远程任务调用即报 TokenPlan 提示 | `$DASHSCOPE_API_KEY` 是 `sk-sp-`;换按量付费 Key 或在行内配 `apiKey` |
| `WEB_PROVIDER_AMBIGUOUS` | 有多个搜索 provider,需在 `web` 行 pin `searchProvider` |
| 粘图报 `MODEL_DOES_NOT_SUPPORT_IMAGES` | 当前模型不支持图片输入,换成上表标"是"的,或改用 vision 工具 |
| 工具报找不到 `bl` | `bl` 不在 PATH:`npm install -g bailian-cli` |
| 改了 patch 但没生效 | `config` 是整体替换,检查是否漏写了原有字段;再用 `--dump-config` 确认 |
| `memoryLibraryId does not exist` | 记忆库 ID 属于另一个账号,与当前 Key 不匹配 |
---
+15
View File
@@ -79,18 +79,33 @@
# Enabled by default: the tool creates no resources at load time. It only
# provisions a cloud agent when the model actually calls it, and reuses it
# after — no deployment-specific ID to configure up front.
#
# Credentials: needs a pay-as-you-go key (sk-ws-), resolved from row config
# `apiKey`, then $DASHSCOPE_API_KEY. The agentstudio API is served only on
# the workspace-scoped host, so the endpoint resolves from row `baseUrl`,
# then $DASHSCOPE_BASE_URL, then row `workspaceId` / $BAILIAN_WORKSPACE_ID
# composed into https://{workspace}.cn-beijing.maas.aliyuncs.com — whatever
# resolves ships to bl explicitly, never leaving the endpoint to bl's
# active-profile base_url (a TokenPlan or bare model-domain origin 404s
# agentstudio). The key must belong to that workspace. A resolved TokenPlan
# key or TokenPlan endpoint rejects at call time with guidance (this row is
# enabled by default and must not break boot for TokenPlan-only setups).
- id: bailian-tool-managed-agent
name: bailian-cli-dsh/tool-managed-agent
# Disabled by default: the knowledge base to query is deployment-specific,
# and an enabled provider with no agentId would make `web_search` ambiguous
# for everyone. Set workspaceId + agentId and flip `disabled` to use it.
# Key: row config `apiKey`, then $DASHSCOPE_API_KEY (pay-as-you-go sk-ws-;
# a TokenPlan key is rejected at boot).
- id: bailian-web-search-rag
name: bailian-cli-dsh/web-search-rag
disabled: true
config: {}
# Disabled by default: memory add/search are billed per call.
# Key: row config `apiKey`, then $DASHSCOPE_API_KEY (pay-as-you-go sk-ws-;
# a missing key or a TokenPlan key fails the boot with an actionable message).
- id: bailian-memory
name: bailian-cli-dsh/memory
disabled: true
+12 -2
View File
@@ -26,6 +26,7 @@ import type { ContentBlock, Message } from "@deepseek-ai/dsh-llm";
import { createUserMessage } from "@deepseek-ai/dsh-llm";
import { defineTool } from "@deepseek-ai/dsh-tools";
import z from "@deepseek-ai/schemastery";
import { isTokenPlanKey, tokenPlanKeyRejection } from "../shared/credentials.ts";
import { dashScopeFetch, resolveApiKey, resolveBaseUrl } from "../shared/http.ts";
/** Cordis plugin name used by loader diagnostics. */
@@ -56,7 +57,12 @@ export interface Config {
}
export const Config: z<Config> = z.object({
apiKey: z.string().role("secret").description("DashScope key; defaults to $DASHSCOPE_API_KEY."),
apiKey: z
.string()
.role("secret")
.description(
"Pay-as-you-go DashScope key (sk-ws-); defaults to $DASHSCOPE_API_KEY. TokenPlan keys are rejected.",
),
baseUrl: z.string().description("DashScope base URL override."),
userId: z.string().description("Memory entity id owning these memories."),
memoryLibraryId: z.string().description("Memory library id; defaults to the account default."),
@@ -355,9 +361,13 @@ export function apply(ctx: Context, config: Config): void {
const apiKey = resolveApiKey(ctx, config.apiKey);
if (apiKey === undefined) {
throw new Error(
"bailian-memory: no DashScope API key. Set $DASHSCOPE_API_KEY or configure `apiKey`.",
"bailian-memory: no DashScope API key. Set `apiKey` in this row's config or export " +
"$DASHSCOPE_API_KEY (a pay-as-you-go sk-ws- key; the memory API 401s TokenPlan keys).",
);
}
if (isTokenPlanKey(apiKey)) {
throw new Error(tokenPlanKeyRejection(name, "the memory API"));
}
const client = new MemoryClient(
apiKey,
resolveBaseUrl(ctx, config.baseUrl),
+93
View File
@@ -0,0 +1,93 @@
/**
* Pure credential classification and pairing shared by the plugins that call
* pay-as-you-go DashScope APIs directly (memory, knowledge base) or through
* `bl managed-agent` (agentstudio). No runtime imports — this module is safe
* to load from tests and its rules are locked by `tests/credentials.test.ts`.
*
* TokenPlan keys (`sk-sp-`) and pay-as-you-go keys (`sk-ws-`) are not
* interchangeable: the TokenPlan gateway 401s a pay-as-you-go key, and the
* service APIs this package calls 401 or 404 a TokenPlan key. The LLM
* provider row keeps its TokenPlan key under a dedicated env name
* (`BAILIAN_TOKENPLAN_API_KEY`); every other plugin needs a pay-as-you-go key
* and rejects a TokenPlan one up front instead of failing at request time.
*
* @module bailian-cli-dsh/shared/credentials
*/
/**
* Standard DashScope model-domain endpoint. It serves the model APIs plus the
* memory v2 and knowledge indices the plugins call directly — but NOT
* `/api/v1/agentstudio`, which lives on the workspace-scoped host.
*/
export const DASHSCOPE_DEFAULT_BASE_URL = "https://dashscope.aliyuncs.com";
/** Key prefix that marks a TokenPlan key (which service APIs reject). */
export const TOKEN_PLAN_KEY_PREFIX = "sk-sp-";
/** Whether a key is shaped like a TokenPlan key (which service APIs reject). */
export function isTokenPlanKey(apiKey: string): boolean {
return apiKey.startsWith(TOKEN_PLAN_KEY_PREFIX);
}
/**
* Whether a base URL points at the TokenPlan gateway. That gateway serves the
* model-inference routes only — none of the service APIs this package calls,
* including `/api/v1/agentstudio`, so requests to it 404.
*/
export function isTokenPlanEndpoint(baseUrl: string): boolean {
try {
return new URL(baseUrl).hostname.startsWith("token-plan.");
} catch {
// An unparseable URL fails the request later with its own diagnostics;
// this check only classifies well-formed endpoints.
return false;
}
}
/**
* The standard error wording every plugin uses when it resolves a TokenPlan
* key, so all three surfaces fail with one recognizable, actionable message.
*/
export function tokenPlanKeyRejection(plugin: string, capability: string): string {
return (
`${plugin}: the resolved API key is a TokenPlan key (${TOKEN_PLAN_KEY_PREFIX}…), which ` +
`${capability} rejects. Use a pay-as-you-go key (sk-ws-): set \`apiKey\` in this row's ` +
"config or $DASHSCOPE_API_KEY. TokenPlan keys belong on $BAILIAN_TOKENPLAN_API_KEY, " +
"which only the `bailian-tokenplan` LLM provider reads."
);
}
/**
* Build the `--api-key` / `--base-url` flags handed to `bl managed-agent run`.
* Each resolved half ships independently:
*
* - A resolved key becomes `--api-key`, overriding bl's auth chain so an
* active TokenPlan profile cannot substitute its own key.
* - A resolved endpoint becomes `--base-url`, overriding the ACTIVE PROFILE's
* base_url — the half that fixes the classic `Bailian API 404`, where a
* TokenPlan (or bare model-domain) origin does not serve
* `/api/v1/agentstudio`.
*
* There is deliberately NO fallback endpoint: agentstudio is only served on
* the workspace-scoped host (see {@link workspaceEndpoint}), and an unknown
* workspace is a configuration gap, not a defaultable value. Unresolved halves
* emit nothing and bl's own auth chain decides them.
*/
export function credentialFlags(apiKey: string | undefined, baseUrl: string | undefined): string[] {
const flags: string[] = [];
if (baseUrl !== undefined && baseUrl.length > 0) flags.push("--base-url", baseUrl);
if (apiKey !== undefined && apiKey.length > 0) flags.push("--api-key", apiKey);
return flags;
}
/**
* Compose the workspace-scoped agentstudio host for a workspace id. The
* managed-agent API is served only from
* `https://{workspace}.cn-beijing.maas.aliyuncs.com/api/v1/agentstudio`
* (bl/the SDK append the resource path onto this origin); the plain
* dashscope origin 404s it, and a key only unlocks its own workspace's host
* (a mismatched one 403s `Endpoint.AccessDenied`).
*/
export function workspaceEndpoint(workspaceId: string): string {
return `https://${workspaceId}.cn-beijing.maas.aliyuncs.com`;
}
+6 -3
View File
@@ -6,8 +6,9 @@
*/
import type { Context } from "@deepseek-ai/cordis";
import { launchEnvironmentOf } from "@deepseek-ai/dsh-launch-environment";
import { DASHSCOPE_DEFAULT_BASE_URL } from "./credentials.ts";
export const DASHSCOPE_DEFAULT_BASE_URL = "https://dashscope.aliyuncs.com";
export { DASHSCOPE_DEFAULT_BASE_URL } from "./credentials.ts";
/** A non-2xx DashScope response, carrying the server's own wording. */
export class DashScopeError extends Error {
@@ -22,8 +23,10 @@ export class DashScopeError extends Error {
}
/**
* Resolve the DashScope key from explicit config, then the launch environment
* (process env, project `.env`, harness-home `.env`).
* Resolve the DashScope key: explicit row config first, then the launch
* environment (process env, project `.env`, harness-home `.env`). Callers
* that get `undefined` decide their own failure mode — opt-in plugins reject
* at boot, the managed-agent tool falls through to bl's own auth chain.
*/
export function resolveApiKey(ctx: Context, explicit?: string): string | undefined {
if (explicit !== undefined && explicit.length > 0) return explicit;
+152 -4
View File
@@ -14,13 +14,34 @@
* after, so no `agents.yaml` or prior `apply` is required. First use provisions
* cloud resources — it may incur cost and take longer to start.
*
* Credentials: agentstudio is a pay-as-you-go DashScope API served ONLY on the
* workspace-scoped host `https://{workspace}.cn-beijing.maas.aliyuncs.com`
* (the plain dashscope origin and the TokenPlan gateway both 404 it, and a key
* only unlocks its own workspace's host). `bl` resolves the key as
* `--api-key` > `$DASHSCOPE_API_KEY` > the active config profile, but a
* profile's `base_url` is NOT paired with an env-resolved key — an active
* TokenPlan profile therefore aims agentstudio at the TokenPlan gateway. So
* whenever this plugin resolves a key or an endpoint (row config, then launch
* env), it passes them explicitly; see {@link credentialFlags}. Endpoint
* resolution is `baseUrl`, then `$DASHSCOPE_BASE_URL`, then `workspaceId`
* composed into the workspace host (same for `$BAILIAN_WORKSPACE_ID`). With
* nothing resolvable here both halves are left to bl's own auth chain.
*
* @module bailian-cli-dsh/tool-managed-agent
*/
import type { Context } from "@deepseek-ai/cordis";
import { launchEnvironmentOf } from "@deepseek-ai/dsh-launch-environment";
import { defineTool } from "@deepseek-ai/dsh-tools";
import type {} from "@deepseek-ai/dsh-tools";
import z from "@deepseek-ai/schemastery";
import { runBlJson } from "../shared/bl.ts";
import {
credentialFlags,
isTokenPlanEndpoint,
isTokenPlanKey,
tokenPlanKeyRejection,
workspaceEndpoint,
} from "../shared/credentials.ts";
/** Cordis plugin name used by loader diagnostics. */
export const name = "bailian-tool-managed-agent";
@@ -36,6 +57,15 @@ export interface Config {
agent?: string;
/** Model for the remote agent. */
model?: string;
/** Pay-as-you-go DashScope key; defaults to `$DASHSCOPE_API_KEY`. */
apiKey?: string;
/**
* Workspace id the key belongs to; composed into the agentstudio host.
* Read from the console's top-right workspace switcher.
*/
workspaceId?: string;
/** Full agentstudio origin; wins over `workspaceId`. */
baseUrl?: string;
/** Cooperative budget; first-run provisioning of a cloud environment is slow. */
timeoutMs?: number;
}
@@ -43,11 +73,54 @@ export interface Config {
export const Config: z<Config> = z.object({
agent: z.string().description("Remote agent identity to create/reuse."),
model: z.string().description("Model for the remote agent."),
apiKey: z
.string()
.role("secret")
.description(
"Pay-as-you-go DashScope key (sk-ws-); defaults to $DASHSCOPE_API_KEY. TokenPlan keys are rejected.",
),
workspaceId: z
.string()
.description(
"Workspace the key belongs to (console top-right switcher); defaults to $BAILIAN_WORKSPACE_ID. " +
"Composed into https://{workspaceId}.cn-beijing.maas.aliyuncs.com.",
),
baseUrl: z
.string()
.description(
"Full agentstudio origin; overrides workspaceId. Defaults to $DASHSCOPE_BASE_URL.",
),
timeoutMs: z.natural().description("Cooperative timeout budget in milliseconds."),
});
const DEFAULT_TIMEOUT_MS = 600_000;
/**
* Resolve the managed-agent credentials: row config first, then the launch
* environment. Endpoint resolution: `baseUrl` (explicit origin) beats
* `workspaceId` (composed into the workspace-scoped host); env names mirror
* the same split. Agentstudio is only served on the workspace-scoped host, so
* an unresolved endpoint is left unset for bl to resolve (and the failure
* hints below explain the gap when bl cannot either).
*/
function resolveCredentials(ctx: Context, config: Config): { apiKey?: string; baseUrl?: string } {
const launchEnvironment = launchEnvironmentOf(ctx);
const env = (varName: string): string | undefined => {
const value = launchEnvironment.get(varName)?.value;
return value !== undefined && value.length > 0 ? value : undefined;
};
const apiKey = config.apiKey ?? env("DASHSCOPE_API_KEY");
const workspaceId = config.workspaceId ?? env("BAILIAN_WORKSPACE_ID");
const baseUrl =
config.baseUrl ??
env("DASHSCOPE_BASE_URL") ??
(workspaceId !== undefined ? workspaceEndpoint(workspaceId) : undefined);
return {
...(apiKey !== undefined ? { apiKey } : {}),
...(baseUrl !== undefined ? { baseUrl } : {}),
};
}
/** The `bl managed-agent run --output json` envelope: a session-event list. */
interface SessionRunResponse {
session_id?: string;
@@ -65,6 +138,26 @@ function assistantText(response: SessionRunResponse): string {
}
export function apply(ctx: Context, config: Config): void {
// Resolve credentials at boot so misconfigurations surface as one clear
// message instead of a cryptic 401/404 mid-task. This row is ENABLED BY
// DEFAULT, though, and TokenPlan-only setups legitimately keep
// $DASHSCOPE_API_KEY / $DASHSCOPE_BASE_URL aimed at the TokenPlan gateway
// for the vision/image tools — so a TokenPlan key or endpoint is not a boot
// error here: it becomes a per-call rejection with guidance, and everything
// else keeps working. (Opt-in plugins like bailian-memory reject at boot.)
const credentials = resolveCredentials(ctx, config);
const rejection =
credentials.apiKey !== undefined && isTokenPlanKey(credentials.apiKey)
? tokenPlanKeyRejection(name, "the managed-agent (agentstudio) API")
: credentials.baseUrl !== undefined && isTokenPlanEndpoint(credentials.baseUrl)
? `${name}: the resolved endpoint ${credentials.baseUrl} is the TokenPlan gateway, ` +
"which does not serve /api/v1/agentstudio (requests 404). Agentstudio lives on the " +
"workspace-scoped host: set `workspaceId` (the workspace your key belongs to, from " +
"the console's top-right switcher) or `baseUrl` in this row's config, or export " +
"BAILIAN_WORKSPACE_ID / DASHSCOPE_BASE_URL."
: undefined;
const credentialArgv = credentialFlags(credentials.apiKey, credentials.baseUrl);
ctx.tools.register(
defineTool({
name: "bailian_run_remote_task",
@@ -103,6 +196,7 @@ export function apply(ctx: Context, config: Config): void {
},
timeoutMs: config.timeoutMs ?? DEFAULT_TIMEOUT_MS,
async execute(args, exec) {
if (rejection !== undefined) throw new Error(rejection);
const argv = [
"managed-agent",
"run",
@@ -114,11 +208,22 @@ export function apply(ctx: Context, config: Config): void {
if (args.instructions !== undefined) argv.push("--instructions", args.instructions);
const model = args.model ?? config.model;
if (model !== undefined) argv.push("--model", model);
// Atomic credential pair: never let bl pair a key with its active
// profile's base_url (a TokenPlan profile 404s agentstudio).
argv.push(...credentialArgv);
const response = await runBlJson<SessionRunResponse>(ctx, argv, {
cwd: exec.agent?.session.header.cwd ?? process.cwd(),
signal: exec.signal,
});
let response: SessionRunResponse;
try {
response = await runBlJson<SessionRunResponse>(ctx, argv, {
cwd: exec.agent?.session.header.cwd ?? process.cwd(),
signal: exec.signal,
});
} catch (error) {
throw enrichProvisioningError(error, {
fellThroughToBlChain: credentials.apiKey === undefined,
endpointResolved: credentials.baseUrl !== undefined,
});
}
const answer = assistantText(response);
if (answer.length === 0) {
@@ -129,3 +234,46 @@ export function apply(ctx: Context, config: Config): void {
}),
);
}
/**
* Attach an actionable hint to the classic misconfiguration signatures.
* Agentstudio is only served on the workspace-scoped host, and a key only
* unlocks its own workspace, so the three failure modes each get targeted
* guidance: 404 = endpoint is not a workspace host; 403 `Endpoint.
* AccessDenied` = right shape of host but the wrong workspace for this key;
* 401 = TokenPlan key on a pay-as-you-go API. Anything else passes through.
*/
function enrichProvisioningError(
error: unknown,
context: { fellThroughToBlChain: boolean; endpointResolved: boolean },
): unknown {
if (!(error instanceof Error)) return error;
const message = error.message;
const workspaceHint =
"Agentstudio is served only on the workspace-scoped host " +
"https://{workspaceId}.cn-beijing.maas.aliyuncs.com, and a key only unlocks its own " +
"workspace. Set `workspaceId` (the workspace your key belongs to, from the console's " +
"top-right switcher) or `baseUrl` on the bailian-tool-managed-agent row, or export " +
"BAILIAN_WORKSPACE_ID / DASHSCOPE_BASE_URL.";
let hint: string | undefined;
if (message.includes("Endpoint.AccessDenied") || message.includes("403")) {
hint = `The host is workspace-scoped but this key belongs to a different workspace. ${workspaceHint}`;
} else if (message.includes("404")) {
hint = context.endpointResolved
? `The endpoint rejected /api/v1/agentstudio. ${workspaceHint}`
: context.fellThroughToBlChain
? "No key/endpoint resolved from this row's config or the environment, so bl used its " +
"own auth chain — its active profile endpoint is not the workspace host agentstudio " +
`needs. ${workspaceHint}`
: `The endpoint rejected /api/v1/agentstudio. ${workspaceHint}`;
} else if (message.includes("401")) {
hint =
"The managed-agent API rejected the key. It needs a pay-as-you-go key (sk-ws-); " +
"TokenPlan keys (sk-sp-) only serve the TokenPlan LLM gateway.";
}
if (hint === undefined) return error;
error.message = `${error.message}\n${hint}`;
return error;
}
+12 -2
View File
@@ -23,6 +23,7 @@ import type {
import { WebError } from "@deepseek-ai/dsh-web";
import { launchEnvironmentOf } from "@deepseek-ai/dsh-launch-environment";
import z from "@deepseek-ai/schemastery";
import { isTokenPlanKey, tokenPlanKeyRejection } from "../shared/credentials.ts";
import { dashScopeFetch, resolveApiKey } from "../shared/http.ts";
/** Cordis plugin name used by loader diagnostics. */
@@ -49,7 +50,9 @@ export const Config: z<Config> = z.object({
apiKey: z
.string()
.role("secret")
.description("DashScope API key; defaults to $DASHSCOPE_API_KEY."),
.description(
"Pay-as-you-go DashScope key (sk-ws-); defaults to $DASHSCOPE_API_KEY. TokenPlan keys are rejected.",
),
workspaceId: z.string().description("Bailian workspace id; defaults to $BAILIAN_WORKSPACE_ID."),
agentId: z.string().description("Retrieval service (agent) id identifying the knowledge base."),
maxResults: z.natural().description("Default source cap when the caller sets none."),
@@ -147,12 +150,19 @@ export class BailianKbSearchProvider implements WebSearchProvider {
}
export function apply(ctx: Context, config: Config): void {
const apiKey = resolveApiKey(ctx, config.apiKey);
// A TokenPlan key would register a provider that looks available and then 401s
// on every search; reject it at boot instead. An absent key stays soft:
// `available()` returns false and dsh falls back to another provider.
if (apiKey !== undefined && isTokenPlanKey(apiKey)) {
throw new Error(tokenPlanKeyRejection(name, "the knowledge-base API"));
}
const workspaceId =
config.workspaceId ?? launchEnvironmentOf(ctx).get("BAILIAN_WORKSPACE_ID")?.value ?? "";
ctx.web.registerSearchProvider(
new BailianKbSearchProvider({
apiKey: resolveApiKey(ctx, config.apiKey) ?? "",
apiKey: apiKey ?? "",
workspaceId,
agentId: config.agentId ?? "",
maxResults: config.maxResults ?? DEFAULT_MAX_RESULTS,
+57
View File
@@ -0,0 +1,57 @@
import { expect, test } from "vite-plus/test";
import {
credentialFlags,
DASHSCOPE_DEFAULT_BASE_URL,
isTokenPlanEndpoint,
isTokenPlanKey,
workspaceEndpoint,
} from "../src/shared/credentials.ts";
// 行为锁定:两类 Key(sk-sp- TokenPlan / sk-ws- 按量付费)不可混用,三个直连服务
// 模块(memory / RAG / managed-agent)都会拦下 TokenPlan Key,而不是等请求时
// 拿到难懂的 401/404。managed-agent 的凭证两半独立下发:解析出 key 就显式
// --api-key(不让 bl 用活动 profile 的 key),解析出端点就显式 --base-url
// (不让 bl 用活动 profile 的端点)。agentstudio 只在工作空间前缀主机上提供,
// 因此绝不存在"默认端点"——工作空间未知就是配置缺口,该报错而不是猜。
test("isTokenPlanKey classifies by prefix", () => {
expect(isTokenPlanKey("sk-sp-abc123")).toBe(true);
expect(isTokenPlanKey("sk-ws-abc123")).toBe(false);
expect(isTokenPlanKey("")).toBe(false);
});
test("isTokenPlanEndpoint classifies the gateway host", () => {
expect(isTokenPlanEndpoint("https://token-plan.cn-beijing.maas.aliyuncs.com")).toBe(true);
expect(
isTokenPlanEndpoint("https://token-plan.cn-beijing.maas.aliyuncs.com/compatible-mode/v1"),
).toBe(true);
expect(isTokenPlanEndpoint(DASHSCOPE_DEFAULT_BASE_URL)).toBe(false);
expect(isTokenPlanEndpoint(workspaceEndpoint("llm-x"))).toBe(false);
// 不可解析的 URL 交给后续请求自己报错,这里只做形状分类。
expect(isTokenPlanEndpoint("not a url")).toBe(false);
});
test("workspaceEndpoint composes the workspace-scoped agentstudio host", () => {
expect(workspaceEndpoint("llm-kpgesh4vqzf5gzv9")).toBe(
"https://llm-kpgesh4vqzf5gzv9.cn-beijing.maas.aliyuncs.com",
);
expect(workspaceEndpoint("ws_abc")).toBe("https://ws_abc.cn-beijing.maas.aliyuncs.com");
});
test("credentialFlags: each resolved half ships independently, no defaults", () => {
expect(credentialFlags(undefined, undefined)).toEqual([]);
expect(credentialFlags("", "")).toEqual([]);
// 只有 key:端点留给 bl 解析,绝不塞一个会 404 的默认主机。
expect(credentialFlags("sk-ws-abc", undefined)).toEqual(["--api-key", "sk-ws-abc"]);
// 只有端点:也下发,key 留给 bl 的 auth chain。
expect(credentialFlags(undefined, "https://ws.example.com")).toEqual([
"--base-url",
"https://ws.example.com",
]);
expect(credentialFlags("sk-ws-abc", "https://ws.example.com")).toEqual([
"--base-url",
"https://ws.example.com",
"--api-key",
"sk-ws-abc",
]);
});
@@ -53,6 +53,7 @@ Index: [index.md](index.md)
#### Notes
- Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile).
- The agentstudio endpoint is workspace-scoped: the base URL is composed from the workspace id (agents.yaml workspace_id > $BAILIAN_WORKSPACE_ID > bl's configured workspace_id) as https://{workspace}.cn-beijing.maas.aliyuncs.com/api/v1/agentstudio, and the key must belong to that workspace.
- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json.
- Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env.
@@ -87,6 +88,7 @@ bl managed-agent apply --provider bailian --yes
#### Notes
- Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile).
- The agentstudio endpoint is workspace-scoped: the base URL is composed from the workspace id (agents.yaml workspace_id > $BAILIAN_WORKSPACE_ID > bl's configured workspace_id) as https://{workspace}.cn-beijing.maas.aliyuncs.com/api/v1/agentstudio, and the key must belong to that workspace.
- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json.
- Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env.
@@ -153,6 +155,7 @@ bl managed-agent init --provider all
#### Notes
- Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile).
- The agentstudio endpoint is workspace-scoped: the base URL is composed from the workspace id (agents.yaml workspace_id > $BAILIAN_WORKSPACE_ID > bl's configured workspace_id) as https://{workspace}.cn-beijing.maas.aliyuncs.com/api/v1/agentstudio, and the key must belong to that workspace.
- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json.
- Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env.
- --no-refresh and --dry-run plan offline from local config and state: no remote requests, no state writes, provider keys are not checked.
@@ -194,6 +197,7 @@ bl managed-agent plan --no-refresh
#### Notes
- Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile).
- The agentstudio endpoint is workspace-scoped: the base URL is composed from the workspace id (agents.yaml workspace_id > $BAILIAN_WORKSPACE_ID > bl's configured workspace_id) as https://{workspace}.cn-beijing.maas.aliyuncs.com/api/v1/agentstudio, and the key must belong to that workspace.
- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json.
- Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env.
- Unlike `apply`, this creates/updates the cloud agent + environment on demand without --yes. The first run provisions cloud resources (may incur cost and take longer to start); later runs with the same --agent reuse them.
@@ -233,6 +237,7 @@ bl managed-agent run --prompt "Audit this dependency tree" --instructions "You a
#### Notes
- Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile).
- The agentstudio endpoint is workspace-scoped: the base URL is composed from the workspace id (agents.yaml workspace_id > $BAILIAN_WORKSPACE_ID > bl's configured workspace_id) as https://{workspace}.cn-beijing.maas.aliyuncs.com/api/v1/agentstudio, and the key must belong to that workspace.
- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json.
- Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env.
@@ -271,6 +276,7 @@ bl managed-agent session create --agent assistant --title 'debug run'
#### Notes
- Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile).
- The agentstudio endpoint is workspace-scoped: the base URL is composed from the workspace id (agents.yaml workspace_id > $BAILIAN_WORKSPACE_ID > bl's configured workspace_id) as https://{workspace}.cn-beijing.maas.aliyuncs.com/api/v1/agentstudio, and the key must belong to that workspace.
- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json.
- Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env.
@@ -303,6 +309,7 @@ bl managed-agent session delete --session-id sess_abc123
#### Notes
- Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile).
- The agentstudio endpoint is workspace-scoped: the base URL is composed from the workspace id (agents.yaml workspace_id > $BAILIAN_WORKSPACE_ID > bl's configured workspace_id) as https://{workspace}.cn-beijing.maas.aliyuncs.com/api/v1/agentstudio, and the key must belong to that workspace.
- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json.
- Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env.
@@ -337,6 +344,7 @@ bl managed-agent session events --session-id sess_abc123 --all
#### Notes
- Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile).
- The agentstudio endpoint is workspace-scoped: the base URL is composed from the workspace id (agents.yaml workspace_id > $BAILIAN_WORKSPACE_ID > bl's configured workspace_id) as https://{workspace}.cn-beijing.maas.aliyuncs.com/api/v1/agentstudio, and the key must belong to that workspace.
- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json.
- Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env.
@@ -368,6 +376,7 @@ bl managed-agent session get --session-id sess_abc123
#### Notes
- Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile).
- The agentstudio endpoint is workspace-scoped: the base URL is composed from the workspace id (agents.yaml workspace_id > $BAILIAN_WORKSPACE_ID > bl's configured workspace_id) as https://{workspace}.cn-beijing.maas.aliyuncs.com/api/v1/agentstudio, and the key must belong to that workspace.
- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json.
- Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env.
@@ -412,6 +421,7 @@ bl managed-agent session list --all
#### Notes
- Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile).
- The agentstudio endpoint is workspace-scoped: the base URL is composed from the workspace id (agents.yaml workspace_id > $BAILIAN_WORKSPACE_ID > bl's configured workspace_id) as https://{workspace}.cn-beijing.maas.aliyuncs.com/api/v1/agentstudio, and the key must belong to that workspace.
- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json.
- Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env.
- --output json emits one envelope: { session_id, provider, agent, events } — read session_id to chain `session send/get/events/delete`.
@@ -449,6 +459,7 @@ bl managed-agent session run --agent assistant --prompt "summarize this repo"
#### Notes
- Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile).
- The agentstudio endpoint is workspace-scoped: the base URL is composed from the workspace id (agents.yaml workspace_id > $BAILIAN_WORKSPACE_ID > bl's configured workspace_id) as https://{workspace}.cn-beijing.maas.aliyuncs.com/api/v1/agentstudio, and the key must belong to that workspace.
- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json.
- Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env.
@@ -479,6 +490,7 @@ bl managed-agent session send --session-id sess_abc123 --message "continue"
#### Notes
- Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile).
- The agentstudio endpoint is workspace-scoped: the base URL is composed from the workspace id (agents.yaml workspace_id > $BAILIAN_WORKSPACE_ID > bl's configured workspace_id) as https://{workspace}.cn-beijing.maas.aliyuncs.com/api/v1/agentstudio, and the key must belong to that workspace.
- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json.
- Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env.
- Providers without a skill listing API (e.g. ark) return an empty list.
@@ -525,6 +537,7 @@ bl managed-agent skill-list --source custom --provider bailian
#### Notes
- Bailian credentials come from bl's auth chain: --api-key > DASHSCOPE_API_KEY > `bl auth login` (active config profile).
- The agentstudio endpoint is workspace-scoped: the base URL is composed from the workspace id (agents.yaml workspace_id > $BAILIAN_WORKSPACE_ID > bl's configured workspace_id) as https://{workspace}.cn-beijing.maas.aliyuncs.com/api/v1/agentstudio, and the key must belong to that workspace.
- Other providers read the env vars referenced in agents.yaml (e.g. ${ANTHROPIC_API_KEY}), including .env and ~/.agents/config.json.
- Resolved credentials are injected into the SDK in-memory and cleared from the environment; they never persist in process env.