Skip to content

Commit 7ad14a7

Browse files
committed
Merge branch 'main' of github.com:modelstudioai/cli into fix/web-search-and-thinking
2 parents 8211268 + 7319f6d commit 7ad14a7

56 files changed

Lines changed: 4820 additions & 188 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/agents/auth-change.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,23 @@ defineCommand({ auth }) → runtime/authStage → ctx.client → command.run(ctx
5353

5454
命令不要直接解析 token、env 或 config。业务请求统一走 `ctx.client`;登录/配置命令通过 `ctx.authStore` / `ctx.configStore` 的窄接口操作落盘。
5555

56+
### 例外:agent 命令的分层鉴权与 SDK 凭证内存注入
57+
58+
`bl managed-agent *` 按调用链分两层:
59+
60+
- **离线命令**`init``validate``state list/show/rm`:`auth: "none"`,只读写本地文件,无需登录;引擎侧传 `credentials: "none"` 跳过凭证断言
61+
- **联网命令**`plan``apply``destroy``state import``skill-list`、全部 `session *`:统一声明 `auth: "apiKey"` 硬门禁 —— 无论目标 provider 是谁,authStage 都经 `resolveApiKey(sources)` 解析 bailian 凭证(flag > env > active profile config),缺失报统一 AUTH;引擎层 `assertProviderCredentials` 再对 agents.yaml 里**全部已声明 provider** 的空 key 拦截并给 provider 专属 hint。例外:`plan --no-refresh` / `plan --dry-run``credentials: "none"` 并强制 `refresh: false`(不联网、不回写 state,不查 provider key),其中 `--dry-run` 连登录也不要求(authStage 的 dry-run 豁免),`--no-refresh` 仍需登录。
62+
63+
凭证不以真实值写入 `process.env`,而是经 `packages/commands/src/commands/managed-agent/_engine/`**内存注入管道**(`resolveAgentProjectConfig`)注入 SDK,管道五步:
64+
65+
1. `prepareProviderEnv()` — 先 `bootstrapRuntimeCredentialsSync()`(SDK 把 `.env` / `~/.agents/config.json` 灌进 env,服务 claude/ark/qoder 等非 bailian provider),再把全部凭证类 env(`CREDENTIAL_ENV_KEYS`,含别名)中仍为 undefined 的占位为 `""`,使 agents.yaml 插值不因缺变量抛错
66+
2. `resolveProjectConfig` — 插值发生:bailian 插值拿到占位空串,claude/ark 拿到真实 env 值;随后 `normalizeInterpolatedProviderBlocks()` 把插值为空导致的 YAML `null` 归一为 `""`(避免离线命令下空 key 在 SDK zod 层报 "received null")
67+
3. `injectProviderCredentials()` — 用 `ctx.client.exportApiCredential()`(lint 限定 `managed-agent/_engine/**` 可用)覆写内存 config 对象的 bailian 块:有凭证时 `api_key` 无条件覆写;`base_url`(拼 `/api/v1/agentstudio` 后缀,无凭证时用 client 默认域名补齐以满足 schema)/`workspace_id`(取 `settings.workspaceId`)仅在引用且为空时填充
68+
4. `scrubCredentialEnv()` — 从 `process.env` 删除全部凭证变量(真实凭证此后只存于 config 对象 → provider adapter 实例内存,不驻留 env / 不被子进程继承)
69+
5. `assertProviderCredentials(providers)` — 任一已声明 provider 的 `api_key` 为空 → CLI 权威 `AUTH` 错误 + provider 专属 hint(取代 SDK 原始插值/zod 报错);离线命令传 `credentials: "none"` 整体跳过
70+
71+
`bl auth login` 仅管理 bailian(DashScope)凭证;claude/ark/qoder 的 key 从 env(shell / `.env` / `~/.agents/config.json`)经插值进入 config 对象,同样被清扫。禁止命令层直接 `readConfigFile` 裸读凭证;bailian 字段以 CLI 鉴权链为唯一信源。
72+
5673
## 必查清单
5774

5875
### A. core 层(类型 + 解析)

packages/cli/.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,7 @@ node_modules
22
dist
33
*.log
44
.DS_Store
5-
outputs/
5+
outputs/
6+
# agents
7+
agents.state.json
8+
.env

packages/cli/src/commands.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,23 @@ import {
8989
pluginLink,
9090
pluginList,
9191
pluginRemove,
92+
managedAgentInit,
93+
managedAgentValidate,
94+
managedAgentPlan,
95+
managedAgentApply,
96+
managedAgentDestroy,
97+
managedAgentStateList,
98+
managedAgentStateShow,
99+
managedAgentStateRm,
100+
managedAgentStateImport,
101+
managedAgentSessionCreate,
102+
managedAgentSessionList,
103+
managedAgentSessionGet,
104+
managedAgentSessionDelete,
105+
managedAgentSessionRun,
106+
managedAgentSessionSend,
107+
managedAgentSessionEvents,
108+
managedAgentSkillList,
92109
} from "bailian-cli-commands";
93110

94111
// Full bailian-cli product: every command, exposed under the `bl` binary.
@@ -186,4 +203,21 @@ export const commands: Record<string, AnyCommand> = {
186203
"plugin link": pluginLink,
187204
"plugin list": pluginList,
188205
"plugin remove": pluginRemove,
206+
"managed-agent init": managedAgentInit,
207+
"managed-agent validate": managedAgentValidate,
208+
"managed-agent plan": managedAgentPlan,
209+
"managed-agent apply": managedAgentApply,
210+
"managed-agent destroy": managedAgentDestroy,
211+
"managed-agent state list": managedAgentStateList,
212+
"managed-agent state show": managedAgentStateShow,
213+
"managed-agent state rm": managedAgentStateRm,
214+
"managed-agent state import": managedAgentStateImport,
215+
"managed-agent session create": managedAgentSessionCreate,
216+
"managed-agent session list": managedAgentSessionList,
217+
"managed-agent session get": managedAgentSessionGet,
218+
"managed-agent session delete": managedAgentSessionDelete,
219+
"managed-agent session run": managedAgentSessionRun,
220+
"managed-agent session send": managedAgentSessionSend,
221+
"managed-agent session events": managedAgentSessionEvents,
222+
"managed-agent skill-list": managedAgentSkillList,
189223
};

packages/commands/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
"check": "vp check"
4141
},
4242
"dependencies": {
43+
"@openagentpack/sdk": "0.3.1",
4344
"bailian-cli-core": "workspace:*",
4445
"bailian-cli-runtime": "workspace:*",
4546
"boxen": "catalog:",

packages/commands/src/commands/auth/login.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,11 @@ export default defineCommand({
2121
usageArgs:
2222
"--api-key <key> | --console | --open-api --access-key-id <id> --access-key-secret <secret>",
2323
flags: {
24-
apiKey: { type: "string", valueHint: "<key>", description: "Model API key to store" },
24+
apiKey: {
25+
type: "string",
26+
valueHint: "<key>",
27+
description: "Model API key to store",
28+
},
2529
baseUrl: {
2630
type: "string",
2731
valueHint: "<url>",

packages/commands/src/commands/config/set.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,12 @@ export default defineCommand({
1414
"Config key (base_url, output, output_dir, timeout, api_key, access_token, access_key_id, access_key_secret, security_token, default_*_model, workspace_id)",
1515
required: true,
1616
},
17-
value: { type: "string", valueHint: "<value>", description: "Value to set", required: true },
17+
value: {
18+
type: "string",
19+
valueHint: "<value>",
20+
description: "Value to set",
21+
required: true,
22+
},
1823
},
1924
exampleArgs: [
2025
"--key output --value json",
@@ -44,7 +49,9 @@ export default defineCommand({
4449
return;
4550
}
4651

47-
await ctx.configStore.write({ [resolvedKey]: coerced } as Partial<ConfigFile>);
52+
await ctx.configStore.write({
53+
[resolvedKey]: coerced,
54+
} as Partial<ConfigFile>);
4855

4956
if (!settings.quiet) {
5057
const shown = SECRET_KEYS.has(resolvedKey) ? maskToken(String(coerced)) : coerced;
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import type { ResourceAddress } from "@openagentpack/sdk";
2+
3+
/** Full state address: provider.type.name */
4+
export function formatResourceAddress(address: ResourceAddress): string {
5+
return `${address.provider}.${address.type}.${address.name}`;
6+
}
7+
8+
/** CLI display short label: type.name (provider) */
9+
export function formatResourceLabel(address: ResourceAddress): string {
10+
return `${address.type}.${address.name} (${address.provider})`;
11+
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import {
2+
createProjectRuntime,
3+
type LoadedProjectConfig,
4+
type ProjectRuntimeContext,
5+
resolveProjectConfig,
6+
UserError,
7+
} from "@openagentpack/sdk";
8+
import {
9+
assertProviderCredentials,
10+
type CredentialHost,
11+
injectProviderCredentials,
12+
normalizeInterpolatedProviderBlocks,
13+
prepareProviderEnv,
14+
scrubCredentialEnv,
15+
} from "./credentials.ts";
16+
import { loadFileState } from "./file-state-manager.ts";
17+
import { type HostContext, installSdkTransport } from "./transport.ts";
18+
19+
export { CREDENTIALS_NOTE, OFFLINE_NOTE } from "./credentials.ts";
20+
21+
/**
22+
* Whether this run requires provider keys:
23+
* - "all" (default) — online command: every provider declared in agents.yaml
24+
* must have a non-empty key after injection
25+
* - "none" — offline command (local config/state only), skip the check
26+
*/
27+
export type CredentialScope = "all" | "none";
28+
29+
interface AgentConfigOptions {
30+
resolveEnv?: boolean;
31+
projectName?: string;
32+
statePath?: string;
33+
credentials?: CredentialScope;
34+
}
35+
36+
/**
37+
* Resolve agents.yaml with credentials injected the bl way and scrubbed from the
38+
* environment — the shared credential spine for every SDK-engine command:
39+
* 1. prepare env (SDK bootstrap for non-bailian + placeholders so interpolation
40+
* never throws on a value we're about to supply/reject)
41+
* 2. resolve + interpolate the config
42+
* 3. override the bailian block with the CLI auth chain's credential (in-memory)
43+
* 4. scrub all credential vars from process.env (real values now live only in
44+
* the config object → provider adapters, never the environment)
45+
* 5. fail with a CLI-authoritative AUTH error if any provider's key is empty
46+
* (offline commands pass `credentials: "none"` to skip the check)
47+
*/
48+
export async function resolveAgentProjectConfig(
49+
host: CredentialHost,
50+
filePath: string,
51+
options: AgentConfigOptions = {},
52+
): Promise<LoadedProjectConfig> {
53+
prepareProviderEnv();
54+
const resolved = await resolveProjectConfig(filePath, options);
55+
normalizeInterpolatedProviderBlocks(resolved.config.providers);
56+
injectProviderCredentials(resolved.config.providers, host);
57+
scrubCredentialEnv();
58+
if ((options.credentials ?? "all") !== "none") {
59+
assertProviderCredentials(resolved.config.providers);
60+
}
61+
return resolved;
62+
}
63+
64+
/**
65+
* Build a full ProjectRuntimeContext from a config file path — the standard
66+
* entry point for agent commands that need the SDK engine. Mirrors OpenAgentPack
67+
* CLI's buildCliRuntime: resolve config → load local state → assemble runtime.
68+
* Takes the host context first so every SDK-engine command wires the
69+
* instrumented transport (UA / tracking headers / verbose) and the bl-resolved,
70+
* in-memory-injected credential ({@link resolveAgentProjectConfig}) by construction.
71+
*/
72+
export async function buildAgentRuntime(
73+
host: HostContext & CredentialHost,
74+
filePath: string,
75+
options: AgentConfigOptions = {},
76+
): Promise<ProjectRuntimeContext & { configPath: string }> {
77+
installSdkTransport(host);
78+
const { config, configPath, projectName } = await resolveAgentProjectConfig(
79+
host,
80+
filePath,
81+
options,
82+
);
83+
const state = await loadFileState(configPath, options.statePath, projectName);
84+
const ctx = createProjectRuntime({
85+
projectName,
86+
config,
87+
state,
88+
configPath,
89+
providers: config.providers,
90+
});
91+
return { ...ctx, configPath };
92+
}
93+
94+
/** Ensure a user-supplied --provider value is actually configured in agents.yaml. */
95+
export function assertProviderConfigured(
96+
ctx: ProjectRuntimeContext,
97+
provider: string | undefined,
98+
): void {
99+
if (!provider || provider === "all") return;
100+
if (ctx.providers.has(provider)) return;
101+
const available = Array.from(ctx.providers.keys()).join(", ") || "none";
102+
throw new UserError(
103+
`Provider '${provider}' is not configured. Available providers: ${available}.`,
104+
);
105+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/**
2+
* Redirect `console.log` / `console.info` to stderr while `fn` runs.
3+
*
4+
* The OpenAgentPack SDK's provider adapters emit progress/debug logging via
5+
* `console.log` (e.g. `[skill-upload]`), which would corrupt bl's stdout data
6+
* channel in `--output json` mode. Wrapping SDK calls that may log keeps stdout
7+
* a clean data channel. Restores the originals on completion.
8+
*/
9+
export async function withStdoutProtected<T>(fn: () => Promise<T>): Promise<T> {
10+
const originalLog = console.log;
11+
const originalInfo = console.info;
12+
const toStderr = (...args: unknown[]): void => {
13+
process.stderr.write(`${args.map((arg) => String(arg)).join(" ")}\n`);
14+
};
15+
console.log = toStderr;
16+
console.info = toStderr;
17+
try {
18+
return await fn();
19+
} finally {
20+
console.log = originalLog;
21+
console.info = originalInfo;
22+
}
23+
}

0 commit comments

Comments
 (0)