@ai-zen/agents-sdk 0.7.1 → 0.8.0

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,27 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.8.0] - 2026-08-24
4
+
5
+ ### ⚠️ Breaking
6
+
7
+ - **`TaskMigrationService` is now an instantiated, self-contained migration service** — it holds no model call of its own: `migrate()` reuses the passed `agent`'s own `client` / `model` / `modelConfig` to generate the handoff document, so it needs no `Provider` and no standalone "migration Agent" object (and no duplicated `config + modelId` model wiring). `migrate()` is an instance method that serializes history, calls the model to generate the handoff document, and replaces `agent.messages`.
8
+ - `TaskMigrationServiceOptions` = `{ client?, model?, modelConfig?, onBeforeMigrate?, onMigrated?, logger? }` — pass `client`/`model`/`modelConfig` to override the model call used for the handoff; when omitted, `migrate()` falls back to the passed `agent`'s own config
9
+ - `migrate({ agent, promptTokens?, maxTokens? })` — `agent` is the only runtime param (its `client`/`model`/`modelConfig` back the model call); the migration hooks are flat on the service instance (no `hooks` wrapper)
10
+ - **`AutoMigratePlugin` narrowed to a trigger-only role** — constructor takes `{ service, maxTokens }`; it only detects token overflow in `onAfterSend` and delegates to `service.migrate({ agent, promptTokens, maxTokens })`. No hooks on the plugin.
11
+ - **Removed** — `TaskMigrationService.createAgentDefinition` (the service calls the model directly, so a standalone migration Agent definition is unnecessary) and `BuildMigrationAgentOptions`; `MigrationContext` no longer carries `migrationAgent` (records `model` instead).
12
+ - **New exports** — `MigrationContext`, `TaskMigrationServiceOptions`.
13
+
14
+ ### 🛠 Optimized
15
+
16
+ - `serializeMessages` moved from `AutoMigratePlugin` private to `TaskMigrationService` public static.
17
+ - The pure-tool statics (`createPrompt` / `createPostMessages` / `serializeMessages`) remain `static`; the instance `migrate()` reuses the passed `agent`'s model client. The "should migrate" check (`promptTokens > maxTokens`) lives in `AutoMigratePlugin` (the trigger side), not on the service.
18
+ - Migration failure still leaves `agent.messages` unchanged (atomic); hook errors are caught and logged, never interrupting the migration flow.
19
+
20
+ ### ✅ Tests
21
+
22
+ - `TaskMigrationService`: statics (`createPrompt` / `createPostMessages` / `serializeMessages`) + `migrate` (model call, hook contexts, failure atomicity) — all green.
23
+ - `AutoMigratePlugin`: trigger-only behavior (overflow → `service.migrate`; below threshold / no usage → no model call).
24
+
3
25
  ## [0.7.1] - 2026-08-25
4
26
 
5
27
  ### 🚀 New Features
package/README.md CHANGED
@@ -63,7 +63,8 @@ const provider = await Provider.create({
63
63
  ...paths,
64
64
  });
65
65
  const agent = createAgent(provider, "my-agent");
66
- agent.use(new AutoMigratePlugin({ maxTokens, migrationAgent, onMigrated }));
66
+ const migrationService = new TaskMigrationService({ onMigrated }); // migrates via the agent's own model client
67
+ agent.use(new AutoMigratePlugin({ service: migrationService, maxTokens }));
67
68
  agent.use(new AutoRefreshToolsPlugin());
68
69
  await agent.init();
69
70
  await agent.send("Hello");
@@ -80,7 +81,7 @@ await agent.send("Hello");
80
81
  | `runtime` | ✅ Implemented — Provider, Capabilities, createAgent, MCP connection management, task migration |
81
82
  | `plugin` | ✅ Implemented — AutoMigratePlugin / AutoRefreshToolsPlugin / ContextGuardPlugin / UnknownToolHintPlugin |
82
83
  | `shared` | ✅ Implemented — SdkError + injectable Logger |
83
- | Tests | ✅ 445 passing, 51 files, all green (incl. real-API chat & viewImage e2e) |
84
+ | Tests | ✅ 463 passing, 51 files, all green (incl. real-API chat & viewImage e2e) |
84
85
 
85
86
  ## Built-in Tools
86
87
 
@@ -117,7 +118,7 @@ Conditionally injected based on the active model / config:
117
118
 
118
119
  | Plugin | Description |
119
120
  |--------|-------------|
120
- | `AutoMigratePlugin` | Automatically triggers task migration when the context overflows, generates a handoff document, and transparently replaces the Agent |
121
+ | `AutoMigratePlugin` | Automatically triggers task migration when the context overflows; delegates the actual migration to the injected `TaskMigrationService` instance, which reuses the agent's own model client |
121
122
  | `AutoRefreshToolsPlugin` | Re-scans the file system before each `send()` to refresh the tool list |
122
123
  | `ContextGuardPlugin` | Context safety guard — before each request, throws `ContextOverflowError` (interrupting the conversation) when the previous round's `usage.prompt_tokens` exceeds `maxTokens × ratio` (default 1.5), preventing context runaway from reading oversized files |
123
124
  | `UnknownToolHintPlugin` | Smarter unknown-tool hints — when the LLM calls a nonexistent tool, guides it to `call_mcp_tool` / points out permission issues based on MCP config (opt-in via `agent.use`) |
@@ -133,3 +134,4 @@ See the project-root [`PRINCIPLES.md`](../../PRINCIPLES.md):
133
134
  5. Occam's razor
134
135
  6. Refactor as you go; keep clean layering
135
136
  7. Tests are the foundation
137
+ he foundation
package/README.zh.md CHANGED
@@ -62,7 +62,8 @@ const provider = await Provider.create({
62
62
  ...paths,
63
63
  });
64
64
  const agent = createAgent(provider, "my-agent");
65
- agent.use(new AutoMigratePlugin({ maxTokens, migrationAgent, onMigrated }));
65
+ const migrationService = new TaskMigrationService({ onMigrated }); // 迁移复用传入 agent 自身的模型调用
66
+ agent.use(new AutoMigratePlugin({ service: migrationService, maxTokens }));
66
67
  agent.use(new AutoRefreshToolsPlugin());
67
68
  await agent.init();
68
69
  await agent.send("你好");
@@ -116,7 +117,7 @@ await agent.send("你好");
116
117
 
117
118
  | 插件 | 说明 |
118
119
  |------|------|
119
- | `AutoMigratePlugin` | 上下文超限时自动触发任务迁移,生成交接文档,透明替换 Agent |
120
+ | `AutoMigratePlugin` | 上下文超限时自动触发任务迁移,生成交接文档,透明替换 Agent(委托注入的 `TaskMigrationService`,复用 Agent 自身的模型调用) |
120
121
  | `AutoRefreshToolsPlugin` | 每次 `send()` 前重新扫描文件系统,刷新工具列表 |
121
122
  | `ContextGuardPlugin` | 上下文安全护栏 — 每轮发请求前检测上一轮 `usage.prompt_tokens`,超过 `maxTokens × ratio`(默认 1.5,即 +50%)时抛 `ContextOverflowError` 中断对话,防止读入超大文件导致上下文失控 |
122
123
  | `UnknownToolHintPlugin` | 未知工具智能提示 — LLM 调用不存在的工具时,根据 MCP 配置引导使用 `call_mcp_tool` / 提示权限问题(调用方显式 `agent.use` 注册) |
package/dist/index.d.ts CHANGED
@@ -23,7 +23,7 @@ export type { McpConnectOptions } from "./runtime/McpConnectionManager.js";
23
23
  export { SdkCallbackTool } from "./runtime/SdkCallbackTool.js";
24
24
  export type { SdkCallbackToolOptions } from "./runtime/SdkCallbackTool.js";
25
25
  export { TaskMigrationService } from "./runtime/TaskMigrationService.js";
26
- export type { BuildMigrationAgentOptions } from "./runtime/TaskMigrationService.js";
26
+ export type { MigrationContext, TaskMigrationServiceOptions, } from "./runtime/TaskMigrationService.js";
27
27
  export { AutoMigratePlugin } from "./plugin/AutoMigratePlugin.js";
28
28
  export type { AutoMigrateOptions } from "./plugin/AutoMigratePlugin.js";
29
29
  export { AutoRefreshToolsPlugin } from "./plugin/AutoRefreshToolsPlugin.js";
@@ -1,24 +1,21 @@
1
1
  import type { AgentPlugin, SendContext } from "@ai-zen/agents-core";
2
- import { SdkAgent } from "../runtime/SdkAgent.js";
2
+ import { TaskMigrationService } from "../runtime/TaskMigrationService.js";
3
3
  export interface AutoMigrateOptions {
4
+ /** 迁移服务实例(持有迁移前后钩子;生成交接文档时复用传入 agent 的模型调用) */
5
+ service: TaskMigrationService;
6
+ /** 触发迁移的上下文阈值 */
4
7
  maxTokens: number;
5
- migrationAgent: SdkAgent;
6
- /** 迁移开始前触发(promptTokens 刚超限时),此时 agent.messages 还是完整旧历史。适用于保存旧对话或向用户展示迁移提示。 */
7
- onBeforeMigrate?: (promptTokens: number, maxTokens: number, agent: SdkAgent) => void;
8
- /** 迁移完成后触发,此时交接文档已注入 agent.messages。适用于 UI 更新等后处理。 */
9
- onMigrated?: (handoffDoc: string, agent: SdkAgent) => void;
10
8
  }
11
9
  /**
12
- * 自动迁移插件。
10
+ * 自动迁移插件 — 只负责「触发」,实际迁移逻辑统一委托给持有的 `TaskMigrationService` 实例。
13
11
  *
14
- * 当 token 使用量超过 maxTokens 时,自动将对话历史交给 migrationAgent 生成交接文档,
15
- * 然后用交接文档替换当前 agent 的消息列表,实现无缝迁移。
12
+ * 当 token 使用量超过 maxTokens 时触发迁移。`service.migrate()` 需要 `agent`(当前对话)作为
13
+ * 唯一运行时参数,其余(模型、迁移前后钩子)已由服务在构造时持有。
16
14
  *
17
15
  * ```ts
18
16
  * agent.use(new AutoMigratePlugin({
17
+ * service: new TaskMigrationService({ onMigrated }),
19
18
  * maxTokens: 250_000,
20
- * migrationAgent: anotherAgent,
21
- * onMigrated: (doc, agent) => { ... },
22
19
  * }));
23
20
  * ```
24
21
  */
@@ -26,6 +23,4 @@ export declare class AutoMigratePlugin implements AgentPlugin {
26
23
  private options;
27
24
  constructor(options: AutoMigrateOptions);
28
25
  onAfterSend(ctx: SendContext): Promise<void>;
29
- private serializeMessages;
30
- private getLastAssistantContent;
31
26
  }
@@ -1,18 +1,15 @@
1
- import { AgentNS } from "@ai-zen/agents-core";
2
- import { TaskMigrationService } from "../runtime/TaskMigrationService.js";
3
1
  import { getLogger } from "../shared/logger.js";
4
2
  const log = getLogger();
5
3
  /**
6
- * 自动迁移插件。
4
+ * 自动迁移插件 — 只负责「触发」,实际迁移逻辑统一委托给持有的 `TaskMigrationService` 实例。
7
5
  *
8
- * 当 token 使用量超过 maxTokens 时,自动将对话历史交给 migrationAgent 生成交接文档,
9
- * 然后用交接文档替换当前 agent 的消息列表,实现无缝迁移。
6
+ * 当 token 使用量超过 maxTokens 时触发迁移。`service.migrate()` 需要 `agent`(当前对话)作为
7
+ * 唯一运行时参数,其余(模型、迁移前后钩子)已由服务在构造时持有。
10
8
  *
11
9
  * ```ts
12
10
  * agent.use(new AutoMigratePlugin({
11
+ * service: new TaskMigrationService({ onMigrated }),
13
12
  * maxTokens: 250_000,
14
- * migrationAgent: anotherAgent,
15
- * onMigrated: (doc, agent) => { ... },
16
13
  * }));
17
14
  * ```
18
15
  */
@@ -23,60 +20,17 @@ export class AutoMigratePlugin {
23
20
  }
24
21
  async onAfterSend(ctx) {
25
22
  const agent = ctx.agent;
26
- const { maxTokens, migrationAgent, onBeforeMigrate, onMigrated } = this.options;
23
+ const { service, maxTokens } = this.options;
27
24
  const promptTokens = agent.lastUsage?.prompt_tokens;
28
25
  if (promptTokens == null)
29
26
  return;
30
- if (!TaskMigrationService.shouldMigrate(promptTokens, maxTokens))
27
+ if (promptTokens <= maxTokens)
31
28
  return;
32
- // 迁移前钩子:由上层(CLI)处理用户提示
33
- if (onBeforeMigrate) {
34
- try {
35
- await onBeforeMigrate(promptTokens, maxTokens, agent);
36
- }
37
- catch (err) {
38
- log.error(`[autoMigrate] onBeforeMigrate 回调失败: ${err?.message ?? err}`);
39
- }
40
- }
41
29
  try {
42
- const historyText = this.serializeMessages(agent.messages);
43
- const migrationResult = await migrationAgent.send(historyText);
44
- const handoffDoc = this.getLastAssistantContent(migrationResult);
45
- if (!handoffDoc)
46
- return;
47
- // 仅替换消息列表,不重建 agent,保留所有引用和插件绑定
48
- agent.messages.length = 0;
49
- agent.messages.push(...agent.definition.messages, ...TaskMigrationService.createPostMessages(handoffDoc));
50
- if (onMigrated) {
51
- try {
52
- await onMigrated(handoffDoc, agent);
53
- }
54
- catch (err) {
55
- log.error(`[autoMigrate] onMigrated 回调失败: ${err?.message ?? err}`);
56
- }
57
- }
30
+ await service.migrate({ agent, promptTokens, maxTokens });
58
31
  }
59
32
  catch (err) {
60
33
  log.error(`[autoMigrate] 迁移失败: ${err?.message ?? err}`);
61
34
  }
62
35
  }
63
- serializeMessages(messages) {
64
- return messages
65
- .map((m) => {
66
- const role = m.role ?? "unknown";
67
- const content = typeof m.content === "string" ? m.content : JSON.stringify(m.content);
68
- return `[${role}]: ${content}`;
69
- })
70
- .join("\n\n");
71
- }
72
- getLastAssistantContent(messages) {
73
- for (let i = messages.length - 1; i >= 0; i--) {
74
- if (messages[i].role === AgentNS.Role.Assistant && messages[i].content) {
75
- return typeof messages[i].content === "string"
76
- ? messages[i].content
77
- : null;
78
- }
79
- }
80
- return null;
81
- }
82
36
  }
@@ -22,8 +22,8 @@ export interface ContextGuardOptions {
22
22
  * ```ts
23
23
  * agent.use(new ContextGuardPlugin({ maxTokens: 250_000 }));
24
24
  * agent.use(new AutoMigratePlugin({
25
+ * service: new TaskMigrationService({ onMigrated }),
25
26
  * maxTokens: 250_000,
26
- * migrationAgent,
27
27
  * }));
28
28
  * ```
29
29
  */
@@ -13,8 +13,8 @@ import { ContextOverflowError } from "../shared/errors.js";
13
13
  * ```ts
14
14
  * agent.use(new ContextGuardPlugin({ maxTokens: 250_000 }));
15
15
  * agent.use(new AutoMigratePlugin({
16
+ * service: new TaskMigrationService({ onMigrated }),
16
17
  * maxTokens: 250_000,
17
- * migrationAgent,
18
18
  * }));
19
19
  * ```
20
20
  */
@@ -1,7 +1,61 @@
1
1
  import { AgentNS } from "@ai-zen/agents-core";
2
- import type { AgentDefinition } from "../types/index.js";
2
+ import type OpenAI from "openai";
3
+ import { type Logger } from "../shared/logger.js";
4
+ import type { SdkAgent } from "./SdkAgent.js";
5
+ /**
6
+ * 迁移上下文 — 贯穿「迁移前 → 生成交接文档 → 迁移后」的结构化数据。
7
+ *
8
+ * 供 `TaskMigrationService.migrate` 与钩子(`onBeforeMigrate` / `onMigrated`)使用。
9
+ * 手动迁移与自动迁移共用同一上下文结构,保证上层(CLI)行为一致。
10
+ */
11
+ export interface MigrationContext {
12
+ /** 当前对话 Agent(引用不变,仅替换消息列表) */
13
+ agent: SdkAgent;
14
+ /** 生成交接文档所用模型名 */
15
+ model: string;
16
+ /** 当前 prompt token 用量(自动迁移时由 AutoMigratePlugin 提供;手动迁移可省略) */
17
+ promptTokens?: number;
18
+ /** 上下文阈值(自动迁移时由 AutoMigratePlugin 提供;手动迁移可省略) */
19
+ maxTokens?: number;
20
+ /** 序列化后的完整对话历史(迁移输入) */
21
+ historyText: string;
22
+ /** 迁移前消息总数 */
23
+ messageCountBefore: number;
24
+ /** 生成的交接文档(迁移完成后填充) */
25
+ handoffDoc?: string;
26
+ }
27
+ /**
28
+ * 任务迁移服务构造参数。
29
+ */
30
+ export interface TaskMigrationServiceOptions {
31
+ /** 生成交接文档所用客户端(可选;未传则 migrate 时回退到 agent.client) */
32
+ client?: OpenAI;
33
+ /** 生成交接文档所用模型名(可选;未传则 migrate 时回退到 agent.model) */
34
+ model?: string;
35
+ /** 生成交接文档所用模型参数(可选;未传则 migrate 时回退到 agent.modelConfig) */
36
+ modelConfig?: Record<string, unknown>;
37
+ /** 迁移前钩子(服务持有)。迁移开始前触发,此时 agent.messages 仍为完整旧历史 */
38
+ onBeforeMigrate?: (ctx: MigrationContext) => void | Promise<void>;
39
+ /** 迁移后钩子(服务持有)。迁移完成后触发,交接文档已注入 agent.messages */
40
+ onMigrated?: (ctx: MigrationContext) => void | Promise<void>;
41
+ /** 可注入日志(可选,默认使用全局单例) */
42
+ logger?: Logger;
43
+ }
3
44
  /**
4
45
  * 任务迁移服务。
46
+ *
47
+ * 一个服务实例对应一次迁移上下文。它**不持有**任何模型调用——生成交接文档时
48
+ * 默认复用传入 `agent`(`SdkAgent`)自身的 `client` / `model` / `modelConfig`,
49
+ * 因此无需 Provider、无需独立「迁移 Agent」对象,也无需自建 OpenAI 客户端。
50
+ * 如需用不同模型生成交接文档,可在构造时显式传入 `client` / `model` / `modelConfig`,
51
+ * 未传则 migrate 时回退到 agent 自带的配置。
52
+ *
53
+ * 职责:
54
+ * 1. 生成交接文档的提示词 / 注入格式(静态纯工具,供上层使用)
55
+ * 2. 执行完整迁移(串行化 → 模型生成 → 替换消息),并暴露迁移前后钩子(实例方法)
56
+ *
57
+ * 设计上,`AutoMigratePlugin` 只负责「何时触发」(检测 token 超限),
58
+ * 实际迁移逻辑统一收敛到本服务,从而让手动迁移与自动迁移复用同一条链路。
5
59
  */
6
60
  export declare class TaskMigrationService {
7
61
  static readonly HANDOFF_SECTIONS: {
@@ -12,13 +66,39 @@ export declare class TaskMigrationService {
12
66
  readonly files: "## 📁 文件索引";
13
67
  readonly instructions: "## 🔔 接手指令";
14
68
  };
15
- static shouldMigrate(promptTokens: number, maxTokens: number): boolean;
69
+ private onBeforeMigrate?;
70
+ private onMigrated?;
71
+ private client?;
72
+ private model?;
73
+ private modelConfig?;
74
+ private logger;
75
+ constructor(options?: TaskMigrationServiceOptions);
16
76
  static createPrompt(): string;
17
- static createAgentDefinition(options?: {
18
- modelId?: string;
19
- }): AgentDefinition;
20
77
  static createPostMessages(handoffDoc: string): AgentNS.Message[];
21
- }
22
- export interface BuildMigrationAgentOptions {
23
- modelId?: string;
78
+ /**
79
+ * 序列化消息为 `[role]: content` 文本,供模型理解完整对话历史。
80
+ */
81
+ static serializeMessages(messages: AgentNS.Message[]): string;
82
+ /**
83
+ * 生成交接文档:使用指定的 client / model / modelConfig 带交接 prompt 生成结构化文档。
84
+ */
85
+ private generateHandoff;
86
+ /**
87
+ * 执行完整迁移:序列化历史 → 模型生成交接文档 → 替换消息列表 → 触发钩子。
88
+ *
89
+ * 设计约束:
90
+ * - 不重建 Agent,保留所有引用与插件绑定(仅替换消息列表)
91
+ * - 钩子(onBeforeMigrate / onMigrated)抛错会被捕获并记录,**不中断**迁移流程
92
+ * - 生成失败 / 未取到交接文档时抛错,此时 `agent.messages` 保持原样(不会被部分修改)
93
+ *
94
+ * @param params.agent 当前对话 Agent
95
+ * @param params.promptTokens 当前 prompt token 用量(可选)
96
+ * @param params.maxTokens 上下文阈值(可选)
97
+ * @returns 完整的迁移上下文(含生成的交接文档)
98
+ */
99
+ migrate(params: {
100
+ agent: SdkAgent;
101
+ promptTokens?: number;
102
+ maxTokens?: number;
103
+ }): Promise<MigrationContext>;
24
104
  }
@@ -1,6 +1,20 @@
1
1
  import { AgentNS } from "@ai-zen/agents-core";
2
+ import { getLogger } from "../shared/logger.js";
2
3
  /**
3
4
  * 任务迁移服务。
5
+ *
6
+ * 一个服务实例对应一次迁移上下文。它**不持有**任何模型调用——生成交接文档时
7
+ * 默认复用传入 `agent`(`SdkAgent`)自身的 `client` / `model` / `modelConfig`,
8
+ * 因此无需 Provider、无需独立「迁移 Agent」对象,也无需自建 OpenAI 客户端。
9
+ * 如需用不同模型生成交接文档,可在构造时显式传入 `client` / `model` / `modelConfig`,
10
+ * 未传则 migrate 时回退到 agent 自带的配置。
11
+ *
12
+ * 职责:
13
+ * 1. 生成交接文档的提示词 / 注入格式(静态纯工具,供上层使用)
14
+ * 2. 执行完整迁移(串行化 → 模型生成 → 替换消息),并暴露迁移前后钩子(实例方法)
15
+ *
16
+ * 设计上,`AutoMigratePlugin` 只负责「何时触发」(检测 token 超限),
17
+ * 实际迁移逻辑统一收敛到本服务,从而让手动迁移与自动迁移复用同一条链路。
4
18
  */
5
19
  export class TaskMigrationService {
6
20
  static HANDOFF_SECTIONS = {
@@ -11,8 +25,20 @@ export class TaskMigrationService {
11
25
  files: "## 📁 文件索引",
12
26
  instructions: "## 🔔 接手指令",
13
27
  };
14
- static shouldMigrate(promptTokens, maxTokens) {
15
- return promptTokens > maxTokens;
28
+ onBeforeMigrate;
29
+ onMigrated;
30
+ client;
31
+ model;
32
+ modelConfig;
33
+ logger;
34
+ constructor(options = {}) {
35
+ const { onBeforeMigrate, onMigrated, logger, client, model, modelConfig } = options;
36
+ this.onBeforeMigrate = onBeforeMigrate;
37
+ this.onMigrated = onMigrated;
38
+ this.client = client;
39
+ this.model = model;
40
+ this.modelConfig = modelConfig;
41
+ this.logger = logger ?? getLogger();
16
42
  }
17
43
  static createPrompt() {
18
44
  return [
@@ -38,20 +64,6 @@ export class TaskMigrationService {
38
64
  `- 接手后建议的操作步骤`,
39
65
  ].join("\n");
40
66
  }
41
- static createAgentDefinition(options = {}) {
42
- const now = new Date().toISOString();
43
- return {
44
- id: "task-migration",
45
- name: "任务交接助手",
46
- description: "专用迁移 Agent:分析对话历史并生成结构化交接文档",
47
- messages: [
48
- { role: AgentNS.Role.System, content: TaskMigrationService.createPrompt() },
49
- ],
50
- modelId: options.modelId,
51
- createdAt: now,
52
- updatedAt: now,
53
- };
54
- }
55
67
  static createPostMessages(handoffDoc) {
56
68
  const content = [
57
69
  "这是上一轮对话的任务交接文档。请先阅读交接文档,理解上下文后再继续协助用户完成任务。",
@@ -66,4 +78,104 @@ export class TaskMigrationService {
66
78
  ].join("\n");
67
79
  return [{ role: AgentNS.Role.User, content }];
68
80
  }
81
+ /**
82
+ * 序列化消息为 `[role]: content` 文本,供模型理解完整对话历史。
83
+ */
84
+ static serializeMessages(messages) {
85
+ return messages
86
+ .map((m) => {
87
+ const role = m.role ?? "unknown";
88
+ const content = typeof m.content === "string" ? m.content : JSON.stringify(m.content);
89
+ return `[${role}]: ${content}`;
90
+ })
91
+ .join("\n\n");
92
+ }
93
+ /**
94
+ * 生成交接文档:使用指定的 client / model / modelConfig 带交接 prompt 生成结构化文档。
95
+ */
96
+ async generateHandoff(client, model, modelConfig, historyText) {
97
+ const response = await client.chat.completions.create({
98
+ model,
99
+ ...(modelConfig ?? {}),
100
+ messages: [
101
+ { role: "system", content: TaskMigrationService.createPrompt() },
102
+ { role: "user", content: historyText },
103
+ ],
104
+ });
105
+ const handoffDoc = response.choices[0]?.message?.content;
106
+ if (!handoffDoc) {
107
+ throw new Error("未能生成任务交接文档");
108
+ }
109
+ return handoffDoc;
110
+ }
111
+ /**
112
+ * 执行完整迁移:序列化历史 → 模型生成交接文档 → 替换消息列表 → 触发钩子。
113
+ *
114
+ * 设计约束:
115
+ * - 不重建 Agent,保留所有引用与插件绑定(仅替换消息列表)
116
+ * - 钩子(onBeforeMigrate / onMigrated)抛错会被捕获并记录,**不中断**迁移流程
117
+ * - 生成失败 / 未取到交接文档时抛错,此时 `agent.messages` 保持原样(不会被部分修改)
118
+ *
119
+ * @param params.agent 当前对话 Agent
120
+ * @param params.promptTokens 当前 prompt token 用量(可选)
121
+ * @param params.maxTokens 上下文阈值(可选)
122
+ * @returns 完整的迁移上下文(含生成的交接文档)
123
+ */
124
+ async migrate(params) {
125
+ const { agent, promptTokens, maxTokens } = params;
126
+ const historyText = TaskMigrationService.serializeMessages(agent.messages);
127
+ const messageCountBefore = agent.messages.length;
128
+ // 生成交接文档所用模型调用:优先使用构造时传入的 client/model/modelConfig,
129
+ // 未传则回退到 agent 自带的(无需 Provider / 独立迁移 Agent)。
130
+ const client = this.client ?? agent.client;
131
+ const model = this.model ?? agent.model;
132
+ const modelConfig = this.modelConfig ?? agent.modelConfig;
133
+ // 迁移前上下文(不含 handoffDoc,独立对象,语义固定)
134
+ const beforeCtx = {
135
+ agent,
136
+ model,
137
+ maxTokens,
138
+ promptTokens,
139
+ historyText,
140
+ messageCountBefore,
141
+ };
142
+ // 迁移前钩子(容错,不中断)
143
+ if (this.onBeforeMigrate) {
144
+ try {
145
+ await this.onBeforeMigrate(beforeCtx);
146
+ }
147
+ catch (err) {
148
+ this.logger.error(`[migrate] onBeforeMigrate 回调失败: ${err?.message ?? err}`);
149
+ }
150
+ }
151
+ // 生成交接文档
152
+ const handoffDoc = await this.generateHandoff(client, model, modelConfig, historyText);
153
+ // 替换消息列表
154
+ const nextMessages = [
155
+ ...agent.definition.messages,
156
+ ...TaskMigrationService.createPostMessages(handoffDoc),
157
+ ];
158
+ agent.messages.length = 0;
159
+ agent.messages.push(...nextMessages);
160
+ // 迁移后上下文(含 handoffDoc,与迁移前上下文独立)
161
+ const afterCtx = {
162
+ agent,
163
+ model,
164
+ maxTokens,
165
+ promptTokens,
166
+ historyText,
167
+ messageCountBefore,
168
+ handoffDoc,
169
+ };
170
+ // 迁移后钩子(容错,不中断;此时交接文档已注入)
171
+ if (this.onMigrated) {
172
+ try {
173
+ await this.onMigrated(afterCtx);
174
+ }
175
+ catch (err) {
176
+ this.logger.error(`[migrate] onMigrated 回调失败: ${err?.message ?? err}`);
177
+ }
178
+ }
179
+ return afterCtx;
180
+ }
69
181
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-zen/agents-sdk",
3
- "version": "0.7.1",
3
+ "version": "0.8.0",
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",