@ai-zen/agents-sdk 0.5.2 → 0.5.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.5.4] - 2026-08-05
4
+
5
+ ### 🚀 新功能
6
+
7
+ - **`ExecAsyncTool`(exec_async)统一走 shell 解析** — 移除 Unix 平台手动分词逻辑,所有平台均经 `spawn(command, [], { shell: true })` 执行。工具描述明确告知 agent:其本身不捕获输出,如需留存/查看输出可在命令中使用 shell 重定向写入文件(`>` / `>>`),并支持管道(`|`)等 shell 语法
8
+
9
+ ### 🎯 优化
10
+
11
+ - **`ExecTool`(exec)的 `timeout` 改为必填** — schema `required` 由 `["command"]` 扩为 `["command", "timeout"]`,`call()` 类型签名 `timeout: number`(必填),并在运行时双校验(缺失/非正数/非有限数即抛错)。`timeout` 必填后,命令始终受超时保护,避免无超时长跑
12
+ - **超时原因明确告知 agent** — `exec` 超时被终止(`error.killed` 为 true)时返回结构新增 `terminated: "timeout"` 字段。弥补 Node 不会自动在 stderr 写入超时提示的限制,使 agent 能明确判断命令因超时被终止,从而灵活应对
13
+
14
+ ## [0.5.3] - 2026-08-05
15
+
16
+ ### 🚀 新功能
17
+
18
+ - **初始化时默认释放含 socket-pty 的 mcp.json** — `ConfigManager.bootstrap()` 新增 `ensureDefaultMcpConfig()`,首启在 `~/.ai-zen/mcp.json` 写入默认 MCP 配置(含 socket-pty 终端 server,`command: npx -y @ai-zen/socket-pty mcp`),文件已存在则幂等不覆盖;新增 `readMcpConfig()` / `writeMcpConfig()` 读写方法;新增并导出 `DEFAULT_MCP_CONFIG` 常量
19
+ - **`load_mcp` 透传 server description** — `McpServerConfig` 新增 `description?` 字段,`discoverMcpServers` 解析不再丢弃;`load_mcp` 的 `server` 参数枚举按 `- {id}: {description}` 拼接各 server 说明供 LLM 参考(与 `load_skill` 同构)
20
+ - **新增 `ContextGuardPlugin` 上下文安全护栏** — 在每次内循环**发请求前**(`onInnerLoopStart`,Core 中位于 try 块外)检测上一轮 `usage.prompt_tokens`,超过 `maxTokens × ratio`(默认 1.2,即超阈 20%)时抛出 `ContextOverflowError` 中断对话,防止读入超大文件导致上下文失控。与 `AutoMigratePlugin` 职责分离、区间互补;无需改动 Core
21
+
22
+ ### 🆕 新增导出
23
+
24
+ - `DEFAULT_MCP_CONFIG`(constants)、`ContextGuardPlugin`、`ContextOverflowError`
25
+
3
26
  ## [0.5.2] - 2026-08-03
4
27
 
5
28
  ### 🚀 新功能
@@ -16,6 +16,7 @@ function normalizeConfig(id, raw) {
16
16
  const config = {
17
17
  id,
18
18
  transport,
19
+ description: raw.description,
19
20
  command: raw.command,
20
21
  args: raw.args,
21
22
  env: raw.env,
@@ -5,7 +5,7 @@ export class ExecAsyncTool extends SdkCallbackTool {
5
5
  super({
6
6
  function: {
7
7
  name: "exec_async",
8
- description: "异步执行命令,启动程序后立即返回,不等待执行结果。适用于启动长期运行的进程(如服务器、监听程序、GUI应用等)或不需要等待结果的命令。注意:该工具不会捕获命令的输出,如果需要获取输出请使用 exec。",
8
+ description: "异步执行命令,启动程序后立即返回,不等待执行结果。适用于启动长期运行的进程(如服务器、监听程序、GUI应用等)或不需要等待结果的命令。注意:该工具本身不捕获命令的输出;若需获取/留存输出,可在命令中使用 shell 重定向写入文件(如 `> /path/file`,支持 `>>` 追加、`|` 管道)。命令经 shell 解析执行。",
9
9
  parameters: {
10
10
  type: "object",
11
11
  properties: {
@@ -33,62 +33,15 @@ export class ExecAsyncTool extends SdkCallbackTool {
33
33
  const command = input.command;
34
34
  const cwd = input.cwd ? this.resolve(input.cwd) : this.env.cwd;
35
35
  const detached = input.detached ?? false;
36
- // Windows 用 shell 模式,其他平台直接 spawn 避免 shell 安全问题
37
- const useShell = process.platform === "win32";
36
+ // 统一走 shell:允许 shell 语法(重定向 > / >>、管道 | 等)
38
37
  return new Promise((resolve) => {
39
- let child;
40
- if (useShell) {
41
- child = spawn(command, [], {
42
- cwd,
43
- detached,
44
- stdio: "ignore",
45
- shell: true,
46
- windowsHide: true,
47
- });
48
- }
49
- else {
50
- // Unix: 解析命令和参数,直接 spawn
51
- const args = [];
52
- let current = "";
53
- let inQuote = null;
54
- for (const ch of command) {
55
- if (inQuote) {
56
- if (ch === "\\") {
57
- current += command[command.indexOf(ch) + 1] ?? "";
58
- continue;
59
- }
60
- if (ch === inQuote) {
61
- inQuote = null;
62
- if (current)
63
- args.push(current);
64
- current = "";
65
- continue;
66
- }
67
- current += ch;
68
- }
69
- else {
70
- if (ch === '"' || ch === "'") {
71
- inQuote = ch;
72
- }
73
- else if (ch === " ") {
74
- if (current) {
75
- args.push(current);
76
- current = "";
77
- }
78
- }
79
- else {
80
- current += ch;
81
- }
82
- }
83
- }
84
- if (current)
85
- args.push(current);
86
- child = spawn(args[0], args.slice(1), {
87
- cwd,
88
- detached,
89
- stdio: "ignore",
90
- });
91
- }
38
+ const child = spawn(command, [], {
39
+ cwd,
40
+ detached,
41
+ stdio: "ignore",
42
+ shell: true,
43
+ windowsHide: true,
44
+ });
92
45
  let resolved = false;
93
46
  child.on("error", (err) => {
94
47
  if (resolved)
@@ -4,6 +4,6 @@ export declare class ExecTool extends SdkCallbackTool {
4
4
  constructor(env: ToolEnv);
5
5
  call(input: {
6
6
  command: string;
7
- timeout?: number;
7
+ timeout: number;
8
8
  }): Promise<string>;
9
9
  }
@@ -15,10 +15,10 @@ export class ExecTool extends SdkCallbackTool {
15
15
  },
16
16
  timeout: {
17
17
  type: "number",
18
- description: "超时时间(毫秒),超时后会终止进程。默认无超时。建议长时间运行的命令使用 exec_async 异步执行。",
18
+ description: "超时时间(毫秒),必填。超时后会终止进程;超过该时长仍未完成即视为超时并终止。建议长时间运行的命令使用 exec_async 异步执行。",
19
19
  },
20
20
  },
21
- required: ["command"],
21
+ required: ["command", "timeout"],
22
22
  additionalProperties: false,
23
23
  },
24
24
  },
@@ -27,17 +27,24 @@ export class ExecTool extends SdkCallbackTool {
27
27
  }
28
28
  async call(input) {
29
29
  const command = input.command;
30
- const timeout = input.timeout;
30
+ const { timeout } = input;
31
+ // 运行时双校验:timeout 必填且必须为正数
32
+ if (typeof timeout !== "number" || !Number.isFinite(timeout) || timeout <= 0) {
33
+ throw new Error(`exec: 参数 timeout 为必填项,且必须是正数(毫秒),当前值: ${JSON.stringify(timeout)}`);
34
+ }
31
35
  const result = await new Promise((resolve) => {
32
36
  const child = exec(command, {
33
37
  cwd: this.env.cwd,
34
- timeout: timeout && timeout > 0 ? timeout : undefined,
38
+ timeout,
35
39
  }, (error, stdout, stderr) => {
40
+ const killed = error?.killed ?? false;
36
41
  resolve({
37
42
  stdout,
38
43
  stderr,
39
44
  exitCode: error?.code ?? (error ? 1 : null),
40
- killed: error?.killed ?? false,
45
+ killed,
46
+ // 明确告知 agent 命令是因超时被终止(Node 不会自动在 stderr 写入该提示)
47
+ terminated: killed ? "timeout" : undefined,
41
48
  });
42
49
  });
43
50
  });
@@ -16,6 +16,11 @@ export function createLoadMcpTool(mcpManager, filteredMcps) {
16
16
  }
17
17
  const ids = filteredMcps.map((s) => s.id);
18
18
  const param = createDisclosureParam(ids, "选择一个 MCP 服务器", EMPTY_HINT);
19
+ // 拼接各 MCP 服务器的描述供 LLM 参考(对齐 load_skill 的处理方式)
20
+ const mcpDescriptions = filteredMcps
21
+ .map((s) => ` - ${s.id}: ${s.description || "无描述"}`)
22
+ .join("\n");
23
+ const serverIdDescription = `${param.description}\n\n各 MCP 服务器说明:\n${mcpDescriptions || " 无可用 MCP 服务器"}`;
19
24
  return new CallbackTool({
20
25
  function: {
21
26
  name: "load_mcp",
@@ -25,7 +30,7 @@ export function createLoadMcpTool(mcpManager, filteredMcps) {
25
30
  properties: {
26
31
  server: {
27
32
  type: "string",
28
- description: param.description,
33
+ description: serverIdDescription,
29
34
  ...(param.enum ? { enum: param.enum } : {}),
30
35
  },
31
36
  },
@@ -37,7 +37,7 @@ export declare class ConfigManager {
37
37
  */
38
38
  ensureDefaultSubAgent(): Promise<AgentDefinition | null>;
39
39
  /**
40
- * 一键初始化:目录 + config.json + 默认 Agent + 默认 SubAgent。
40
+ * 一对初始化:目录 + config.json + 默认 Agent + 默认 SubAgent + 默认 MCP。
41
41
  * 已有文件不会被覆盖。
42
42
  */
43
43
  bootstrap(): Promise<{
@@ -45,5 +45,27 @@ export declare class ConfigManager {
45
45
  agent: AgentDefinition | null;
46
46
  subAgent: AgentDefinition | null;
47
47
  }>;
48
+ /** mcp.json 路径:{basePath}/mcp.json */
49
+ get mcpPath(): string;
50
+ /**
51
+ * 确保 basePath/mcp.json 存在。不存在时写入出厂默认 MCP 配置(含 socket-pty)。
52
+ * 已有文件不被覆盖,尊重用户已配置的 MCP 服务器。
53
+ * @returns 返回当前生效的 MCP 配置原始内容
54
+ */
55
+ ensureDefaultMcpConfig(): Promise<Record<string, unknown>>;
56
+ /**
57
+ * 读取 basePath/mcp.json,不存在时返回空结构。
58
+ */
59
+ readMcpConfig(): Promise<{
60
+ mcpServers: Record<string, unknown>;
61
+ }>;
62
+ /**
63
+ * 原子写入 mcp.json。
64
+ */
65
+ writeMcpConfig(config: {
66
+ mcpServers: Record<string, unknown>;
67
+ }): Promise<void>;
68
+ /** 原子写 JSON 文件(先写 .tmp 再 rename,避免半成品)。 */
69
+ private writeJson;
48
70
  }
49
- export { DEFAULT_AGENT_ID, DEFAULT_AGENT_DEFINITION, DEFAULT_SUBAGENT_ID, DEFAULT_SUBAGENT_DEFINITION, DEFAULT_APP_CONFIG, CONFIG_SUB_DIRS, } from "./constants.js";
71
+ export { DEFAULT_AGENT_ID, DEFAULT_AGENT_DEFINITION, DEFAULT_SUBAGENT_ID, DEFAULT_SUBAGENT_DEFINITION, DEFAULT_APP_CONFIG, DEFAULT_MCP_CONFIG, CONFIG_SUB_DIRS, } from "./constants.js";
@@ -1,6 +1,6 @@
1
1
  import { promises as fs } from "node:fs";
2
2
  import { dirname, join } from "node:path";
3
- import { DEFAULT_AGENT_ID, DEFAULT_AGENT_DEFINITION, DEFAULT_SUBAGENT_ID, DEFAULT_SUBAGENT_DEFINITION, DEFAULT_APP_CONFIG, CONFIG_SUB_DIRS, } from "./constants.js";
3
+ import { DEFAULT_AGENT_ID, DEFAULT_AGENT_DEFINITION, DEFAULT_SUBAGENT_ID, DEFAULT_SUBAGENT_DEFINITION, DEFAULT_APP_CONFIG, DEFAULT_MCP_CONFIG, CONFIG_SUB_DIRS, } from "./constants.js";
4
4
  /**
5
5
  * 配置管理器 — 负责配置文件的读写、目录初始化、默认实体创建。
6
6
  */
@@ -143,7 +143,7 @@ export class ConfigManager {
143
143
  return definition;
144
144
  }
145
145
  /**
146
- * 一键初始化:目录 + config.json + 默认 Agent + 默认 SubAgent。
146
+ * 一对初始化:目录 + config.json + 默认 Agent + 默认 SubAgent + 默认 MCP。
147
147
  * 已有文件不会被覆盖。
148
148
  */
149
149
  async bootstrap() {
@@ -151,7 +151,72 @@ export class ConfigManager {
151
151
  const config = await this.ensureDefaultConfig();
152
152
  const agent = await this.ensureDefaultAgent();
153
153
  const subAgent = await this.ensureDefaultSubAgent();
154
+ await this.ensureDefaultMcpConfig();
154
155
  return { config, agent, subAgent };
155
156
  }
157
+ // -----------------------------------------------------------------------
158
+ // MCP 配置(~/.ai-zen/mcp.json)
159
+ // -----------------------------------------------------------------------
160
+ /** mcp.json 路径:{basePath}/mcp.json */
161
+ get mcpPath() {
162
+ return join(this.basePath, "mcp.json");
163
+ }
164
+ /**
165
+ * 确保 basePath/mcp.json 存在。不存在时写入出厂默认 MCP 配置(含 socket-pty)。
166
+ * 已有文件不被覆盖,尊重用户已配置的 MCP 服务器。
167
+ * @returns 返回当前生效的 MCP 配置原始内容
168
+ */
169
+ async ensureDefaultMcpConfig() {
170
+ const mcpPath = this.mcpPath;
171
+ try {
172
+ await fs.access(mcpPath);
173
+ }
174
+ catch {
175
+ // 不存在 → 写入默认
176
+ await this.ensureDirs();
177
+ await this.writeJson(mcpPath, DEFAULT_MCP_CONFIG);
178
+ return { ...DEFAULT_MCP_CONFIG };
179
+ }
180
+ // 已存在 → 不覆盖,读取返回
181
+ return JSON.parse(await fs.readFile(mcpPath, "utf-8"));
182
+ }
183
+ /**
184
+ * 读取 basePath/mcp.json,不存在时返回空结构。
185
+ */
186
+ async readMcpConfig() {
187
+ const mcpPath = this.mcpPath;
188
+ try {
189
+ await fs.access(mcpPath);
190
+ }
191
+ catch {
192
+ return { mcpServers: {} };
193
+ }
194
+ try {
195
+ return JSON.parse(await fs.readFile(mcpPath, "utf-8"));
196
+ }
197
+ catch {
198
+ return { mcpServers: {} };
199
+ }
200
+ }
201
+ /**
202
+ * 原子写入 mcp.json。
203
+ */
204
+ async writeMcpConfig(config) {
205
+ await this.ensureDirs();
206
+ await this.writeJson(this.mcpPath, config);
207
+ }
208
+ /** 原子写 JSON 文件(先写 .tmp 再 rename,避免半成品)。 */
209
+ async writeJson(path, data) {
210
+ const dir = dirname(path);
211
+ try {
212
+ await fs.access(dir);
213
+ }
214
+ catch {
215
+ await fs.mkdir(dir, { recursive: true });
216
+ }
217
+ const tmpPath = path + ".tmp";
218
+ await fs.writeFile(tmpPath, JSON.stringify(data, null, 2) + "\n", "utf-8");
219
+ await fs.rename(tmpPath, path);
220
+ }
156
221
  }
157
- export { DEFAULT_AGENT_ID, DEFAULT_AGENT_DEFINITION, DEFAULT_SUBAGENT_ID, DEFAULT_SUBAGENT_DEFINITION, DEFAULT_APP_CONFIG, CONFIG_SUB_DIRS, } from "./constants.js";
222
+ export { DEFAULT_AGENT_ID, DEFAULT_AGENT_DEFINITION, DEFAULT_SUBAGENT_ID, DEFAULT_SUBAGENT_DEFINITION, DEFAULT_APP_CONFIG, DEFAULT_MCP_CONFIG, CONFIG_SUB_DIRS, } from "./constants.js";
@@ -9,6 +9,15 @@ export declare const DEFAULT_SUBAGENT_ID = "sub-agent-default";
9
9
  export declare const DEFAULT_SUBAGENT_DEFINITION: Omit<AgentDefinition, "createdAt" | "updatedAt">;
10
10
  /** SDK 出厂默认 AppConfig。CLI/Desktop 首次初始化时使用。 */
11
11
  export declare const DEFAULT_APP_CONFIG: AppConfig;
12
+ /**
13
+ * SDK 出厂默认 MCP 服务器配置(mcp.json 内容,业界标准格式)。
14
+ * 首次初始化时释放到 ~/.ai-zen/mcp.json,让用户开箱即用 socket-pty 终端能力。
15
+ *
16
+ * 若文件已存在则不覆盖(尊重用户已有配置)。
17
+ */
18
+ export declare const DEFAULT_MCP_CONFIG: {
19
+ mcpServers: Record<string, unknown>;
20
+ };
12
21
  /**
13
22
  * 标准共享子目录列表(不包含运行时目录)。
14
23
  *
@@ -179,6 +179,25 @@ export const DEFAULT_APP_CONFIG = {
179
179
  defaultMigrationModel: "deepseek-v4-flash",
180
180
  };
181
181
  // ---------------------------------------------------------------------------
182
+ // 默认 MCP 配置(socket-pty 终端 MCP)
183
+ // ---------------------------------------------------------------------------
184
+ /**
185
+ * SDK 出厂默认 MCP 服务器配置(mcp.json 内容,业界标准格式)。
186
+ * 首次初始化时释放到 ~/.ai-zen/mcp.json,让用户开箱即用 socket-pty 终端能力。
187
+ *
188
+ * 若文件已存在则不覆盖(尊重用户已有配置)。
189
+ */
190
+ export const DEFAULT_MCP_CONFIG = {
191
+ mcpServers: {
192
+ "socket-pty": {
193
+ type: "stdio",
194
+ command: "npx",
195
+ args: ["-y", "@ai-zen/socket-pty", "mcp"],
196
+ description: "可托管的伪终端(pty):spawn/read/wait/write/resize/status/kill",
197
+ },
198
+ },
199
+ };
200
+ // ---------------------------------------------------------------------------
182
201
  // 目录
183
202
  // ---------------------------------------------------------------------------
184
203
  /**
package/dist/index.d.ts CHANGED
@@ -9,7 +9,7 @@ export { discoverSkills, readSkill } from "./capabilities/discovery/skills.js";
9
9
  export type { SkillInfo, Frontmatter } from "./capabilities/discovery/skills.js";
10
10
  export { discoverMcpServers } from "./capabilities/discovery/mcp.js";
11
11
  export { discoverUserTools } from "./capabilities/discovery/usertools.js";
12
- export { ConfigManager, DEFAULT_AGENT_ID, DEFAULT_AGENT_DEFINITION, DEFAULT_SUBAGENT_ID, DEFAULT_SUBAGENT_DEFINITION, DEFAULT_APP_CONFIG, CONFIG_SUB_DIRS, } from "./config/ConfigManager.js";
12
+ export { ConfigManager, DEFAULT_AGENT_ID, DEFAULT_AGENT_DEFINITION, DEFAULT_SUBAGENT_ID, DEFAULT_SUBAGENT_DEFINITION, DEFAULT_APP_CONFIG, DEFAULT_MCP_CONFIG, CONFIG_SUB_DIRS, } from "./config/ConfigManager.js";
13
13
  export { EntityRepository } from "./shared/EntityRepository.js";
14
14
  export { AgentRepository } from "./crud/AgentRepository.js";
15
15
  export { Provider } from "./runtime/Provider.js";
@@ -26,6 +26,8 @@ export type { BuildMigrationAgentOptions } from "./runtime/TaskMigrationService.
26
26
  export { AutoMigratePlugin } from "./plugin/AutoMigratePlugin.js";
27
27
  export type { AutoMigrateOptions } from "./plugin/AutoMigratePlugin.js";
28
28
  export { AutoRefreshToolsPlugin } from "./plugin/AutoRefreshToolsPlugin.js";
29
+ export { ContextGuardPlugin } from "./plugin/ContextGuardPlugin.js";
30
+ export type { ContextGuardOptions } from "./plugin/ContextGuardPlugin.js";
29
31
  export { BUILTIN_TOOL_CLASSES } from "./capabilities/implements/builtin/index.js";
30
32
  export { CwdTool, ReadFileTool, WriteFileTool, ExecTool, MkdirTool, RmTool, GlobTool, LsTool, ExistTool, FindTextTool, DownloadFileTool, RenameTool, CopyTool, BatchEditTool, EditTool, ExecAsyncTool, SleepTool, } from "./capabilities/implements/builtin/index.js";
31
33
  export { GenerateImageTool } from "./capabilities/implements/builtin/GenerateImageTool.js";
@@ -34,4 +36,4 @@ export { createLoadMcpTool, createCallMcpTool, createReadMcpResourceTool } from
34
36
  export { createSubAgentTool } from "./capabilities/implements/subAgentTools.js";
35
37
  export { getLogger, setLogger } from "./shared/logger.js";
36
38
  export type { Logger, LogFunctions } from "./shared/logger.js";
37
- export { SdkError } from "./shared/errors.js";
39
+ export { SdkError, ContextOverflowError } from "./shared/errors.js";
package/dist/index.js CHANGED
@@ -8,7 +8,7 @@ export { discoverSkills, readSkill } from "./capabilities/discovery/skills.js";
8
8
  export { discoverMcpServers } from "./capabilities/discovery/mcp.js";
9
9
  export { discoverUserTools } from "./capabilities/discovery/usertools.js";
10
10
  // 配置 — ConfigManager
11
- export { ConfigManager, DEFAULT_AGENT_ID, DEFAULT_AGENT_DEFINITION, DEFAULT_SUBAGENT_ID, DEFAULT_SUBAGENT_DEFINITION, DEFAULT_APP_CONFIG, CONFIG_SUB_DIRS, } from "./config/ConfigManager.js";
11
+ export { ConfigManager, DEFAULT_AGENT_ID, DEFAULT_AGENT_DEFINITION, DEFAULT_SUBAGENT_ID, DEFAULT_SUBAGENT_DEFINITION, DEFAULT_APP_CONFIG, DEFAULT_MCP_CONFIG, CONFIG_SUB_DIRS, } from "./config/ConfigManager.js";
12
12
  // CRUD — 实体仓储(仅 Agent 定义等能力实体)
13
13
  export { EntityRepository } from "./shared/EntityRepository.js";
14
14
  export { AgentRepository } from "./crud/AgentRepository.js";
@@ -23,6 +23,7 @@ export { TaskMigrationService } from "./runtime/TaskMigrationService.js";
23
23
  // 插件
24
24
  export { AutoMigratePlugin } from "./plugin/AutoMigratePlugin.js";
25
25
  export { AutoRefreshToolsPlugin } from "./plugin/AutoRefreshToolsPlugin.js";
26
+ export { ContextGuardPlugin } from "./plugin/ContextGuardPlugin.js";
26
27
  // 工具 — 内置工具类 + 动态工具工厂
27
28
  export { BUILTIN_TOOL_CLASSES } from "./capabilities/implements/builtin/index.js";
28
29
  export { CwdTool, ReadFileTool, WriteFileTool, ExecTool, MkdirTool, RmTool, GlobTool, LsTool, ExistTool, FindTextTool, DownloadFileTool, RenameTool, CopyTool, BatchEditTool, EditTool, ExecAsyncTool, SleepTool, } from "./capabilities/implements/builtin/index.js";
@@ -32,4 +33,4 @@ export { createLoadMcpTool, createCallMcpTool, createReadMcpResourceTool } from
32
33
  export { createSubAgentTool } from "./capabilities/implements/subAgentTools.js";
33
34
  // 共享
34
35
  export { getLogger, setLogger } from "./shared/logger.js";
35
- export { SdkError } from "./shared/errors.js";
36
+ export { SdkError, ContextOverflowError } from "./shared/errors.js";
@@ -0,0 +1,34 @@
1
+ import type { AgentPlugin, SendContext } from "../runtime/SdkAgent.js";
2
+ export interface ContextGuardOptions {
3
+ /** 上下文告警阈值(token)。用量超过 maxTokens × ratio 即中断。 */
4
+ maxTokens: number;
5
+ /**
6
+ * 超出比例。用量 > maxTokens × ratio 时判定为「严重超限」(如读入超大文件),
7
+ * 在请求前抛出 ContextOverflowError 中断对话。默认 1.2(即 +20%)。
8
+ */
9
+ ratio?: number;
10
+ }
11
+ /**
12
+ * 上下文安全护栏插件。
13
+ *
14
+ * 在每次内循环**发请求前**检测上一轮用量(usage.prompt_tokens):
15
+ * - 若用量超过 maxTokens × ratio,说明上下文已严重超限(通常因读入超大文件),
16
+ * 在请求前抛出 ContextOverflowError,**中断对话**,交由上层向用户报错。
17
+ *
18
+ * 与迁移插件职责分离:
19
+ * - ContextGuardPlugin 只做「严重超限即中断」的安全兜底,绝不继续对话/迁移;
20
+ * - AutoMigratePlugin 在正常范围内(超 maxTokens 但未达硬上限)做交接迁移。
21
+ *
22
+ * ```ts
23
+ * agent.use(new ContextGuardPlugin({ maxTokens: 250_000 }));
24
+ * agent.use(new AutoMigratePlugin({
25
+ * maxTokens: 250_000,
26
+ * migrationAgent,
27
+ * }));
28
+ * ```
29
+ */
30
+ export declare class ContextGuardPlugin implements AgentPlugin {
31
+ private options;
32
+ constructor(options: ContextGuardOptions);
33
+ onInnerLoopStart(ctx: SendContext): Promise<void>;
34
+ }
@@ -0,0 +1,37 @@
1
+ import { ContextOverflowError } from "../shared/errors.js";
2
+ /**
3
+ * 上下文安全护栏插件。
4
+ *
5
+ * 在每次内循环**发请求前**检测上一轮用量(usage.prompt_tokens):
6
+ * - 若用量超过 maxTokens × ratio,说明上下文已严重超限(通常因读入超大文件),
7
+ * 在请求前抛出 ContextOverflowError,**中断对话**,交由上层向用户报错。
8
+ *
9
+ * 与迁移插件职责分离:
10
+ * - ContextGuardPlugin 只做「严重超限即中断」的安全兜底,绝不继续对话/迁移;
11
+ * - AutoMigratePlugin 在正常范围内(超 maxTokens 但未达硬上限)做交接迁移。
12
+ *
13
+ * ```ts
14
+ * agent.use(new ContextGuardPlugin({ maxTokens: 250_000 }));
15
+ * agent.use(new AutoMigratePlugin({
16
+ * maxTokens: 250_000,
17
+ * migrationAgent,
18
+ * }));
19
+ * ```
20
+ */
21
+ export class ContextGuardPlugin {
22
+ options;
23
+ constructor(options) {
24
+ this.options = options;
25
+ }
26
+ async onInnerLoopStart(ctx) {
27
+ const { agent } = ctx;
28
+ const { maxTokens, ratio = 1.2 } = this.options;
29
+ // 首轮请求前尚无用量数据,跳过(只能在发出首轮请求、拿到 usage 后才有据可查)
30
+ const promptTokens = agent.lastUsage?.prompt_tokens;
31
+ if (promptTokens == null)
32
+ return;
33
+ if (promptTokens > maxTokens * ratio) {
34
+ throw new ContextOverflowError(promptTokens, maxTokens, ratio);
35
+ }
36
+ }
37
+ }
@@ -6,3 +6,19 @@ export declare class SdkError extends Error {
6
6
  readonly code: string;
7
7
  constructor(code: string, message: string, cause?: Error);
8
8
  }
9
+ /**
10
+ * 上下文超限错误。由 ContextGuardPlugin 在请求前抛出,用于中断对话。
11
+ * 触发条件通常是上下文 token 用量远超阈值(如读入超大文件),
12
+ * 继续对话已无意义且会持续放大费用/延迟,故由上层捕获后向用户显式报错。
13
+ */
14
+ export declare class ContextOverflowError extends Error {
15
+ /** 触发中断时的刚用量(usage.prompt_tokens) */
16
+ readonly promptTokens: number;
17
+ /** 配置的告警阈值(maxTokens) */
18
+ readonly maxTokens: number;
19
+ /** 实际生效的硬上限(maxTokens × ratio,越界即中断) */
20
+ readonly threshold: number;
21
+ /** 超出比例(如 1.2 表示 20%) */
22
+ readonly ratio: number;
23
+ constructor(promptTokens: number, maxTokens: number, ratio: number);
24
+ }
@@ -13,3 +13,28 @@ export class SdkError extends Error {
13
13
  }
14
14
  }
15
15
  }
16
+ /**
17
+ * 上下文超限错误。由 ContextGuardPlugin 在请求前抛出,用于中断对话。
18
+ * 触发条件通常是上下文 token 用量远超阈值(如读入超大文件),
19
+ * 继续对话已无意义且会持续放大费用/延迟,故由上层捕获后向用户显式报错。
20
+ */
21
+ export class ContextOverflowError extends Error {
22
+ /** 触发中断时的刚用量(usage.prompt_tokens) */
23
+ promptTokens;
24
+ /** 配置的告警阈值(maxTokens) */
25
+ maxTokens;
26
+ /** 实际生效的硬上限(maxTokens × ratio,越界即中断) */
27
+ threshold;
28
+ /** 超出比例(如 1.2 表示 20%) */
29
+ ratio;
30
+ constructor(promptTokens, maxTokens, ratio) {
31
+ const threshold = maxTokens * ratio;
32
+ super(`🛑 上下文严重超限:已用 ${promptTokens} token,超过硬上限 ${threshold}(${Math.round((ratio - 1) * 100)}%,配置上限 ${maxTokens})。` +
33
+ `当前对话可能因读入超大文件而失控,已中断,请检查并重新开始。`);
34
+ this.name = "ContextOverflowError";
35
+ this.promptTokens = promptTokens;
36
+ this.maxTokens = maxTokens;
37
+ this.threshold = threshold;
38
+ this.ratio = ratio;
39
+ }
40
+ }
@@ -91,6 +91,8 @@ export interface McpServerConfig {
91
91
  transport: "stdio" | "http" | "sse";
92
92
  /** 是否禁用,默认为 false */
93
93
  disabled?: boolean;
94
+ /** 服务器描述(供 load_mcp 呈现给 LLM,非连接必需) */
95
+ description?: string;
94
96
  command?: string;
95
97
  args?: string[];
96
98
  env?: Record<string, string>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-zen/agents-sdk",
3
- "version": "0.5.2",
3
+ "version": "0.5.4",
4
4
  "description": "AI-Zen Agents SDK — shared business logic for CLI and Desktop",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",