# 第二章：核心类型系统 -- AgentMessage、AgentTool、AgentEvent

> 对应源文件：`packages/agent/src/types.ts`

## 1. AgentMessage -- 可扩展的消息类型

### 基础定义

```typescript
type AgentMessage = Message | CustomAgentMessages[keyof CustomAgentMessages];
```

翻译成人话：`AgentMessage` 是 "LLM 标准消息" 加上 "你自定义的消息"。

### 如何添加自定义消息

TypeScript 有一个叫 **declaration merging（声明合并）** 的特性。你可以在自己的代码中"扩展"别人定义的接口：

```typescript
// 在你的应用代码中写：
declare module "@mariozechner/agent" {
  interface CustomAgentMessages {
    // 你的自定义消息类型
    notification: { role: "notification"; text: string; timestamp: number };
    bashExecution: { role: "bashExecution"; command: string; output: string; timestamp: number };
  }
}
```

这样 `AgentMessage` 就自动变成了：
```
AgentMessage = UserMessage | AssistantMessage | ToolResultMessage
             | NotificationMessage | BashExecutionMessage
```

pi 的 `coding-agent` 就是这样添加了 `bashExecution`、`custom`、`branchSummary`、`compactionSummary` 等消息类型。

### 消息转换

LLM 不认识你的自定义消息，所以发给 LLM 前需要转换。这就是 `convertToLlm` 的作用：

```typescript
convertToLlm: (messages: AgentMessage[]) => Message[]
```

例如，把 `bashExecution` 消息转成 LLM 能理解的 `UserMessage`：

```typescript
convertToLlm: (messages) => messages.flatMap(m => {
  if (m.role === "bashExecution") {
    // 转换为用户消息
    return [{ role: "user", content: `命令: ${m.command}\n输出: ${m.output}`, timestamp: m.timestamp }];
  }
  if (m.role === "notification") {
    // UI 专用消息，不发给 LLM
    return [];
  }
  // 标准消息直接通过
  return [m];
})
```

---

## 2. AgentTool -- 可执行的工具

`AgentTool` 在 `ai` 包的 `Tool` 基础上增加了**执行能力**：

```
Tool（ai 包）                     AgentTool（agent 包）
├── name: "read"                  ├── name: "read"
├── description: "读取文件"        ├── description: "读取文件"
├── parameters: Schema            ├── parameters: Schema
                                  ├── label: "read"            ← 新增：UI 显示名称
                                  ├── execute(id, args)        ← 新增：实际执行函数
                                  ├── prepareArguments(args)   ← 新增：参数预处理
                                  └── executionMode: "parallel" ← 新增：执行策略
```

### Tool 和 AgentTool 的区别

`Tool` 只是一个"描述"：告诉 LLM "这个工具叫什么，参数是什么"。它没有执行逻辑。

`AgentTool` 有完整的执行能力。当 AI 说"我要调用 read 工具"时，agent 包调用 `tool.execute()` 实际执行。

### execute 函数

```typescript
execute: (
  toolCallId: string,           // 这次调用的唯一 ID
  params: { path: string },     // 经过验证的参数
  signal?: AbortSignal,         // 用于取消
  onUpdate?: (partial) => void, // 流式进度回调
) => Promise<AgentToolResult>
```

返回值 `AgentToolResult` 包含：
- `content`：返回给 LLM 的文本/图片内容
- `details`：任意结构化数据（给 UI 用，不发给 LLM）
- `terminate`：是否建议提前终止循环

### executionMode -- 并行 vs 串行

当 AI 一次返回多个工具调用（比如同时 read 三个文件）时：

| 模式 | 行为 |
|------|------|
| `"parallel"` | 三个 read 同时执行（默认） |
| `"sequential"` | 一个接一个执行 |

全局默认是 parallel，但单个工具可以覆盖。比如 `bash` 工具通常设为 sequential（避免并发命令冲突）。

### prepareArguments -- 参数兼容性

LLM 有时候会生成不太标准的参数。`prepareArguments` 让你在验证前"修正"参数：

```typescript
prepareArguments: (args) => {
  // LLM 有时用 "file_path" 而不是 "path"
  if (args.file_path && !args.path) {
    return { ...args, path: args.file_path };
  }
  return args;
}
```

---

## 3. AgentEvent -- 循环过程中的事件

agent 包在循环的每个关键节点发出事件。这些事件构成了 UI 更新的基础。

### 事件类型

```
Agent 生命周期
├── agent_start        // agent 开始处理
└── agent_end          // agent 结束，返回所有新消息

Turn 生命周期（一个 turn = 一次 LLM 调用 + 可能的工具执行）
├── turn_start         // 新一轮开始
└── turn_end           // 本轮结束

Message 生命周期
├── message_start      // 消息开始（用户、AI 或工具结果）
├── message_update     // AI 消息流式更新（token by token）
└── message_end        // 消息结束

Tool 执行生命周期
├── tool_execution_start   // 工具开始执行
├── tool_execution_update  // 工具执行中的进度更新
└── tool_execution_end     // 工具执行完成
```

### 一次典型的事件序列

用户说"读取 main.ts"：

```
1. agent_start
2. turn_start
3. message_start   (user: "读取 main.ts")
4. message_end     (user: "读取 main.ts")
5. message_start   (assistant: 开始流式输出)
6. message_update  (assistant: thinking "用户想读取...")
7. message_update  (assistant: toolCall read({path: "main.ts"}))
8. message_end     (assistant: 完整回复)
9. tool_execution_start  (read, {path: "main.ts"})
10. tool_execution_end   (read, 文件内容)
11. message_start  (toolResult: 文件内容)
12. message_end    (toolResult: 文件内容)
13. turn_end
14. turn_start     // 第二轮开始
15. message_start  (assistant: "这个文件是...")
16. message_update (assistant: 流式输出文本)
17. message_end    (assistant: 完整的分析回复)
18. turn_end
19. agent_end      // 返回所有新消息
```

---

## 4. AgentLoopConfig -- 循环配置

这是告诉 agent 循环"如何运行"的配置对象：

| 配置项 | 类型 | 说明 |
|--------|------|------|
| `model` | Model | 使用哪个模型 |
| `convertToLlm` | 函数 | AgentMessage[] → Message[]，必须提供 |
| `transformContext` | 函数 | 可选，在 convertToLlm 前转换消息（如裁剪上下文） |
| `getApiKey` | 函数 | 可选，动态获取 API key |
| `getSteeringMessages` | 函数 | 可选，返回插队消息 |
| `getFollowUpMessages` | 函数 | 可选，返回后续消息 |
| `beforeToolCall` | 函数 | 可选，工具执行前的钩子 |
| `afterToolCall` | 函数 | 可选，工具执行后的钩子 |
| `toolExecution` | `"sequential" \| "parallel"` | 工具执行策略，默认 parallel |
| `reasoning` | string | 思考级别 |

### beforeToolCall 和 afterToolCall

这两个钩子让你在工具执行前后介入：

**beforeToolCall** -- 可以阻止工具执行：
```typescript
beforeToolCall: async ({ toolCall, args }) => {
  // 用户确认机制
  if (toolCall.name === "bash" && args.command.includes("rm")) {
    const confirmed = await askUser("确定要执行删除命令吗？");
    if (!confirmed) return { block: true, reason: "用户拒绝了删除操作" };
  }
  return undefined; // 允许执行
}
```

**afterToolCall** -- 可以修改工具结果：
```typescript
afterToolCall: async ({ toolCall, result }) => {
  // 截断过长的输出
  const text = result.content[0]?.text;
  if (text && text.length > 100000) {
    return { content: [{ type: "text", text: text.slice(0, 100000) + "\n[已截断]" }] };
  }
  return undefined; // 保持原样
}
```

---

## 5. AgentState -- 公开的状态

`Agent` 类对外暴露的状态快照：

| 属性 | 说明 |
|------|------|
| `systemPrompt` | 当前系统提示 |
| `model` | 当前使用的模型 |
| `thinkingLevel` | 思考级别 |
| `tools` | 可用工具列表 |
| `messages` | 对话历史 |
| `isStreaming` | 是否正在处理中 |
| `streamingMessage` | 当前正在流式生成的消息 |
| `pendingToolCalls` | 正在执行的工具调用 ID 集合 |
| `errorMessage` | 最近一次错误信息 |

---

## 小结

```
AgentMessage ──── 可扩展的消息类型
     │              通过 declaration merging 添加自定义消息
     │              发送前通过 convertToLlm 转换
     │
AgentTool ─────── 可执行的工具
     │              name + description + parameters + execute()
     │              支持 parallel/sequential 执行模式
     │
AgentEvent ────── 生命周期事件
     │              agent > turn > message > tool 四层嵌套
     │
AgentLoopConfig ─ 循环配置
                    model + convertToLlm + 钩子们
```

下一章我们会深入 `agent-loop.ts`，看核心循环是如何工作的。
