mirror of
https://github.com/modelstudioai/cli.git
synced 2026-09-14 19:49:23 +08:00
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:
@@ -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
@@ -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 不匹配 |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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,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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user