@swifty.js/swifty 0.0.1 → 0.0.2
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/README.md +183 -0
- package/dist/agent-MICFDGUF.js +4 -0
- package/dist/anthropic-JDAGNAPR.js +4 -0
- package/dist/checker-PF3FEOF2.js +4 -0
- package/dist/chunk-4KVSJNS6.js +90 -0
- package/dist/chunk-6ARDOHBL.js +4 -0
- package/dist/chunk-7MHXMDYC.js +35 -0
- package/dist/chunk-F6HLYUZ4.js +355 -0
- package/dist/chunk-FZPTNGTU.js +4 -0
- package/dist/chunk-LPLPMGWW.js +336 -0
- package/dist/chunk-MQ5XOYLD.js +4 -0
- package/dist/chunk-RD3MICOU.js +4 -0
- package/dist/{cleanup-4R3534Z3.js → cleanup-BQJDUOKA.js} +1 -1
- package/dist/glob_addon.node +0 -0
- package/dist/main.js +240 -240
- package/dist/openai-I4WTRNNT.js +26 -0
- package/dist/{server-XPKSROM2.js → server-OGPKL2U2.js} +22 -21
- package/package.json +17 -24
- package/dist/anthropic-737END4X.js +0 -4
- package/dist/chunk-NCPOPA4A.js +0 -4
- package/dist/chunk-SC34YHMX.js +0 -521
- package/dist/chunk-Z3E5YV3P.js +0 -121
- package/dist/openai-4KX74QBZ.js +0 -27
- package/docs/ch1.md +0 -25
- package/docs/ch10.md +0 -122
- package/docs/ch11.md +0 -163
- package/docs/ch12.md +0 -289
- package/docs/ch13.md +0 -320
- package/docs/ch14.md +0 -152
- package/docs/ch15.md +0 -547
- package/docs/ch2.md +0 -273
- package/docs/ch3.md +0 -206
- package/docs/ch4.md +0 -125
- package/docs/ch5.md +0 -165
- package/docs/ch6.md +0 -201
- package/docs/ch7.md +0 -448
- package/docs/ch8.md +0 -217
- package/docs/ch9.md +0 -351
- package/docs/index.css +0 -23
- package/docs/index.md +0 -21
- package/docs/swifty.mdx +0 -7
package/docs/ch7.md
DELETED
|
@@ -1,448 +0,0 @@
|
|
|
1
|
-
# MCP
|
|
2
|
-
|
|
3
|
-
## 背景
|
|
4
|
-
|
|
5
|
-
Tool 的接入和 Agent CLI 是耦合的
|
|
6
|
-
|
|
7
|
-
MCP: 动态接入 Tools
|
|
8
|
-
|
|
9
|
-
## MCP
|
|
10
|
-
|
|
11
|
-
MCP (Model Context Protocol) 是一个开放协议, 定义 AI 应用 (MCP 客户端) 和外部 Tools Server (MCP 服务器) 的标准化通信方式
|
|
12
|
-
|
|
13
|
-
### 角色
|
|
14
|
-
|
|
15
|
-
- Host: AI 应用, 例如 Claude Code、Claude Desktop、Codex、Swifty
|
|
16
|
-
- Client: Host 的 MCP 连接组件, 负责和 MCP Server 建立连接, 发送请求, 接收响应
|
|
17
|
-
- Server: 外部 Tools 提供方
|
|
18
|
-
|
|
19
|
-
Swifty 这个 Host 会创建一个或多个 MCP Client, 每个 Client 对应一个 MCP Server
|
|
20
|
-
|
|
21
|
-
### 协议分层
|
|
22
|
-
|
|
23
|
-
- Data Layer 数据层: 定义消息格式、初始化握手、工具发现/工具调用, 核心是 JSON-RPC 2.0, lifecycle, tools, resources, prompts 等原语 (primitives)
|
|
24
|
-
- Transport Layer 传输层: 使用 stdio 还是 streamable http
|
|
25
|
-
|
|
26
|
-
```ts
|
|
27
|
-
type MCPTransport =
|
|
28
|
-
| StdioClientTransport
|
|
29
|
-
| StreamableHTTPClientTransport
|
|
30
|
-
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
31
|
-
| SSEClientTransport;
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
<!-- 源码: src/mcp/client.ts -->
|
|
35
|
-
|
|
36
|
-
- tools: 一个 MCP Server 可以暴露一组工具, 每个工具有 name, description, input_schema, required
|
|
37
|
-
- resources: 可读的数据源, 例如数据库 MCP Server 可以暴露表结构最为 resources
|
|
38
|
-
- prompts: MCP Server 提供的预定义提示词模版函数 (prompt 字符串插值)
|
|
39
|
-
|
|
40
|
-
tools
|
|
41
|
-
|
|
42
|
-
```json
|
|
43
|
-
{
|
|
44
|
-
"name": "search_issues",
|
|
45
|
-
"description": "Search github issues",
|
|
46
|
-
"inputSchema": {
|
|
47
|
-
"type": "object",
|
|
48
|
-
"properties": {
|
|
49
|
-
"repo": {
|
|
50
|
-
"type": "string",
|
|
51
|
-
"description": "Repository name, format: owner/repo"
|
|
52
|
-
},
|
|
53
|
-
"query": {
|
|
54
|
-
"type": "string",
|
|
55
|
-
"description": "Search keyword"
|
|
56
|
-
},
|
|
57
|
-
"state": {
|
|
58
|
-
"type": "string",
|
|
59
|
-
"enum": ["all", "open", "closed"]
|
|
60
|
-
}
|
|
61
|
-
},
|
|
62
|
-
"required": ["repo"]
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
resources
|
|
68
|
-
|
|
69
|
-
```json
|
|
70
|
-
{
|
|
71
|
-
"uri": "db://root:pass@127.0.0.1:5432/fe26/schema",
|
|
72
|
-
"name": "database table schema",
|
|
73
|
-
"mimeType": "application/json"
|
|
74
|
-
}
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
prompts
|
|
78
|
-
|
|
79
|
-
```json
|
|
80
|
-
{
|
|
81
|
-
"name": "generate_query",
|
|
82
|
-
"description": "Generate sql query from natural language",
|
|
83
|
-
"arguments": [
|
|
84
|
-
{
|
|
85
|
-
"name": "table",
|
|
86
|
-
"description": "Table name"
|
|
87
|
-
},
|
|
88
|
-
{
|
|
89
|
-
"name": "intent",
|
|
90
|
-
"description": "Natural language description of query intent"
|
|
91
|
-
}
|
|
92
|
-
]
|
|
93
|
-
}
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
MCP Client 可以声明
|
|
97
|
-
|
|
98
|
-
- roots: 项目根目录, 或者工作区边界
|
|
99
|
-
- sampling: 允许 MCP Server 反过来请求 Host 调用 LLM
|
|
100
|
-
- elicitation: 允许 MCP Server 请求 Host, 向用户提问
|
|
101
|
-
|
|
102
|
-
## stdio / streamable http
|
|
103
|
-
|
|
104
|
-
- stdio: Host 中的 MCP Client 将 MCP Server 作为子进程启动, Host 中的 MCP Client 通过 stdin/stdout 管道和 MCP Server 进行通信, MCP Server 在本机, 可以访问远程服务
|
|
105
|
-
- 不需要: 监听端口、网络连接、服务发现、身份认证
|
|
106
|
-
- stdio 的消息格式是 UTF-8 编码的 JSON-RPC 消息, 以换行符分隔
|
|
107
|
-
- stderr 不参与通信, 用于输出调试日志
|
|
108
|
-
- streamable http: MCP Server 是一个独立的 HTTP 服务器, Host 中的 MCP Client 通过 HTTP post/get 和 MCP Server 进行通信, 必要时使用 SSE 流式传输; MCP Server 可以在本机, 也可以在远程
|
|
109
|
-
- Host 中的 MCP Client 使用 HTTP post 将 JSON-RPC 消息发送到 MCP Server 的 endpoint
|
|
110
|
-
- MCP Server 有两种响应方式
|
|
111
|
-
1. 如果结果已 ready, 则直接返回 application/json 响应
|
|
112
|
-
2. 如果结果未 ready, 需要流式传输, 则返回 text/event-stream 响应, 使用 SSE 推送结果
|
|
113
|
-
- MCP Client 发送请求时, Accept 头必须同时声明 `Accept: application/json, text/event-stream`
|
|
114
|
-
|
|
115
|
-
```yaml
|
|
116
|
-
mcp_servers:
|
|
117
|
-
# stdio: command 字段 -> 启动子进程
|
|
118
|
-
github:
|
|
119
|
-
command: "npx"
|
|
120
|
-
args: ["-y", "@modelcontextprotocol/server-github"],
|
|
121
|
-
env:
|
|
122
|
-
GITHUB_TOKEN: "${GITHUB_TOKEN}"
|
|
123
|
-
|
|
124
|
-
# streamable http: url 字段 -> 发送 http 请求
|
|
125
|
-
remote-tool:
|
|
126
|
-
url: "https://api.example.com/mcp",
|
|
127
|
-
headers:
|
|
128
|
-
Authorization: "Bearer ${API_TOKEN}"
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
## JSON-RPC 2.0
|
|
132
|
-
|
|
133
|
-
不管是使用 stdio 还是 streamable http, MCP 的消息格式统一使用 JSON-RPC 2.0
|
|
134
|
-
|
|
135
|
-
JSON-RPC 2.0 只有 3 种消息类型
|
|
136
|
-
|
|
137
|
-
### 请求 (Request)
|
|
138
|
-
|
|
139
|
-
字段: id, method, params
|
|
140
|
-
|
|
141
|
-
```json
|
|
142
|
-
{
|
|
143
|
-
"jsonrpc": "2.0",
|
|
144
|
-
"id": 1,
|
|
145
|
-
"method": "tools/call",
|
|
146
|
-
"params": {
|
|
147
|
-
"name": "search_issues",
|
|
148
|
-
"arguments": {
|
|
149
|
-
"repo": "golang/go",
|
|
150
|
-
"query": "error handling"
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
### 响应 (Response)
|
|
157
|
-
|
|
158
|
-
字段: id (响应 id 和请求 id 对应), result 或 error
|
|
159
|
-
|
|
160
|
-
```json
|
|
161
|
-
{
|
|
162
|
-
"jsonrpc": "2.0",
|
|
163
|
-
"id": 1,
|
|
164
|
-
"result": {
|
|
165
|
-
"content": [
|
|
166
|
-
{
|
|
167
|
-
"type": "text",
|
|
168
|
-
"text": "Found 233 issues matching 'error handling'..."
|
|
169
|
-
}
|
|
170
|
-
]
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
```
|
|
174
|
-
|
|
175
|
-
### 通知 (Notification)
|
|
176
|
-
|
|
177
|
-
字段: method, params
|
|
178
|
-
|
|
179
|
-
通知和请求的区别是: 通知没有 id, 不期望响应; 请求有 id, 期望响应
|
|
180
|
-
|
|
181
|
-
```json
|
|
182
|
-
{
|
|
183
|
-
"jsonrpc": "2.0",
|
|
184
|
-
"method": "notifications/progress",
|
|
185
|
-
"params": {
|
|
186
|
-
"progressToken": "abc",
|
|
187
|
-
"progress": 0.5,
|
|
188
|
-
"total": 1.0
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
```
|
|
192
|
-
|
|
193
|
-
## 完整的 MCP 会话
|
|
194
|
-
|
|
195
|
-
以 stdio 为例
|
|
196
|
-
|
|
197
|
-
### 1. 初始化握手
|
|
198
|
-
|
|
199
|
-
Host 中的 MCP Client 将 MCP Server 作为子进程启动后, 发送 `initialize` 请求, 声明自己的协议版本、能力和身份信息; MCP Server 响应自己的协议版本、能力和身份信息
|
|
200
|
-
|
|
201
|
-
握手成功后, MCP Client 发送 `notifications/initialized` 通知
|
|
202
|
-
|
|
203
|
-
如果底层 transport 不是 stdio 而是 streamable http, 则后续 http 请求还需要携带 `MCP-Protocol-Version` http 头部字段
|
|
204
|
-
|
|
205
|
-
MCP Client
|
|
206
|
-
|
|
207
|
-
```json
|
|
208
|
-
{
|
|
209
|
-
"jsonrpc": "2.0",
|
|
210
|
-
"id": 1,
|
|
211
|
-
"method": "initialize",
|
|
212
|
-
"params": {
|
|
213
|
-
// MCP Client 声明自己的 protocolVersion 协议版本、capabilities 能力和 clientInfo 身份信息
|
|
214
|
-
"protocolVersion": "2025-11-25",
|
|
215
|
-
"capabilities": { "roots": {} },
|
|
216
|
-
"clientInfo": { "name": "swifty", "version": "0.0.1" }
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
```
|
|
220
|
-
|
|
221
|
-
MCP Server
|
|
222
|
-
|
|
223
|
-
```json
|
|
224
|
-
{
|
|
225
|
-
"jsonrpc": "2.0",
|
|
226
|
-
"id": 1,
|
|
227
|
-
"result": {
|
|
228
|
-
// MCP Server 响应自己的 protocolVersion 协议版本、capabilities 能力和 serverInfo 身份信息
|
|
229
|
-
"protocolVersion": "2025-11-25",
|
|
230
|
-
"capabilities": {
|
|
231
|
-
"tools": {},
|
|
232
|
-
"resources": {}
|
|
233
|
-
},
|
|
234
|
-
"serverInfo": { "name": "github-mcp", "version": "0.0.1" }
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
```
|
|
238
|
-
|
|
239
|
-
### 2. 工具发现
|
|
240
|
-
|
|
241
|
-
- MCP Client 发送 `tools/list` 请求, 获取 MCP Server 提供的所有工具定义
|
|
242
|
-
- MCP Server 响应自己提供的所有工具定义
|
|
243
|
-
- MCP Client 将 MCP Server 响应的工具定义包装为 Swifty 内部的 Tool 接口 (MCPToolWrapper, 适配器模式), 注册到 ToolRegistry; 下一轮对话, LLM 在工具列表中就能看到这些工具, 决定是否调用 <!-- 源码: src/mcp/tool-wrapper.ts -->
|
|
244
|
-
|
|
245
|
-
```json
|
|
246
|
-
{
|
|
247
|
-
"jsonrpc": "2.0",
|
|
248
|
-
"id": 2,
|
|
249
|
-
"method": "tools/list"
|
|
250
|
-
}
|
|
251
|
-
```
|
|
252
|
-
|
|
253
|
-
```json
|
|
254
|
-
{
|
|
255
|
-
"jsonrpc": "2.0",
|
|
256
|
-
"id": 2,
|
|
257
|
-
"result": {
|
|
258
|
-
"tools": [
|
|
259
|
-
{
|
|
260
|
-
"name": "search_issues",
|
|
261
|
-
"description": "Search github issues",
|
|
262
|
-
"inputSchema": {}
|
|
263
|
-
},
|
|
264
|
-
{
|
|
265
|
-
"name": "create_issue",
|
|
266
|
-
"description": "Create github issue",
|
|
267
|
-
"inputSchema": {}
|
|
268
|
-
}
|
|
269
|
-
]
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
```
|
|
273
|
-
|
|
274
|
-
### 3. 工具调用
|
|
275
|
-
|
|
276
|
-
当 LLM 决定调用某个 MCP 工具时, MCP Client 发送 `tools/call` 请求
|
|
277
|
-
|
|
278
|
-
```json
|
|
279
|
-
{
|
|
280
|
-
"jsonrpc": "2.0",
|
|
281
|
-
"id": 3,
|
|
282
|
-
"method": "tools/call",
|
|
283
|
-
"params": {
|
|
284
|
-
"name": "search_issues",
|
|
285
|
-
"arguments": {
|
|
286
|
-
"repo": "golang/go",
|
|
287
|
-
"query": "error handling"
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
```
|
|
292
|
-
|
|
293
|
-
MCP Server 调用工具后响应工具调用结果, 返回的 content 是一个数组, 每个元素是一个内容块 (content block), 可以是文本、图片等
|
|
294
|
-
|
|
295
|
-
```json
|
|
296
|
-
{
|
|
297
|
-
"jsonrpc": "2.0",
|
|
298
|
-
"id": 3,
|
|
299
|
-
"result": {
|
|
300
|
-
"content": [
|
|
301
|
-
{
|
|
302
|
-
"type": "text",
|
|
303
|
-
"text": "Found 233 issues matching 'error handling'..."
|
|
304
|
-
}
|
|
305
|
-
]
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
```
|
|
309
|
-
|
|
310
|
-
> 整个流程是: initialize (client request, server response) -> notifications/initialized (client notification) -> tools/list (client request, server response) -> tools/call \* N
|
|
311
|
-
|
|
312
|
-
## MCP 配置
|
|
313
|
-
|
|
314
|
-
- 项目级配置 .swifty.yaml, 当前项目生效
|
|
315
|
-
- 用户级配置 ~/.swifty/config.yaml, 所有项目生效
|
|
316
|
-
|
|
317
|
-
```yaml
|
|
318
|
-
# 项目级配置 .swifty.yaml, 当前项目生效
|
|
319
|
-
# 用户级配置 ~/.swifty/config.yaml, 所有项目生效
|
|
320
|
-
mcp_servers:
|
|
321
|
-
# stdio: command 字段 -> 启动子进程
|
|
322
|
-
github:
|
|
323
|
-
command: "npx"
|
|
324
|
-
args: ["-y", "@modelcontextprotocol/server-github"],
|
|
325
|
-
env:
|
|
326
|
-
GITHUB_TOKEN: "${GITHUB_TOKEN}"
|
|
327
|
-
|
|
328
|
-
database:
|
|
329
|
-
command: "python"
|
|
330
|
-
args: ["-m", "mcp_server_sqlite", "--db", "./data.db"]
|
|
331
|
-
```
|
|
332
|
-
|
|
333
|
-
## 完整流程: 从配置到调用
|
|
334
|
-
|
|
335
|
-
1. 启动: 读取配置文件, 获取 MCP Server 列表
|
|
336
|
-
2. 选择 transport: 根据 MCP Server 配置选择 stdio 或 streamable http
|
|
337
|
-
3. 后台 (goroutine) 异步连接: 启动时, 后台异步连接所有配置的 MCP Server
|
|
338
|
-
4. 初始化 (JSON-RPC 2.0)
|
|
339
|
-
|
|
340
|
-
- MCP Client 发送 `initialize` 请求, MCP Server 响应
|
|
341
|
-
- MCP Client 发送 `notifications/initialized` 通知
|
|
342
|
-
|
|
343
|
-
5. 工具发现: MCP Client 发送 `tools/list` 请求, MCP Server 响应, 获取工具定义
|
|
344
|
-
6. 工具注册: MCP Client 将 MCP Server 响应的工具定义包装为 CLI 内部的 Tool 接口: MCPToolWrapper (适配器模式), 注册到 ToolRegistry; 工具 name 使用 `mcp__serverName__toolName` 格式, 默认 `deferred = true`, `category = "command"` <!-- 源码: src/mcp/tool-wrapper.ts -->
|
|
345
|
-
7. LLM 调用: 下一轮对话, LLM 在工具列表中看到这些工具, 决定是否调用
|
|
346
|
-
8. 工具调用: LLM 调用工具, MCP Client 发送 `tools/call` 请求
|
|
347
|
-
9. 返回工具调用结果: MCP Server 返回工具调用结果
|
|
348
|
-
|
|
349
|
-
## 工具延迟加载: 80 个工具塞不进上下文
|
|
350
|
-
|
|
351
|
-
如果用户配置了 4 个 MCP Server, 每个 MCP Server 提供 15-20 个工具, 加上 Swifty 的 6 个内置工具, 工具数量膨胀到 80 个; 每个工具定义包含 name, description, input_schema, 大约 100-300 个 token, 80 个工具定义就是 8000-24000 个 token, 每轮对话都需要携带, 占用大量的上下文窗口, 也影响 LLM 的工具选择准确率
|
|
352
|
-
|
|
353
|
-
开启工具延迟加载后, 可以降低 token 消耗、提高 LLM 的工具选择准确率
|
|
354
|
-
|
|
355
|
-
### 延迟加载
|
|
356
|
-
|
|
357
|
-
1. 注册 MCP Server 提供的工具时, 标记延迟加载 `deferred: true`
|
|
358
|
-
2. 每个 agent loop (注意: 不是每轮 agent loop turn) 构建工具列表时, 跳过延迟加载的工具, 即工具列表中不包括延迟加载的工具定义, 仅在 `<system-reminder />` 中列出延迟加载的工具名称
|
|
359
|
-
3. LLM 在 `<system-reminder />` 中看到延迟加载的工具名称列表, 判断需要调用某个工具时, 先调用 ToolSearch 工具拉取该工具的完整定义
|
|
360
|
-
4. ToolSearch 工具在 CLI 客户端的 ToolRegistry 中找到该工具, 返回该工具的完整定义, 标记为 discovered, 从下一个 agent loop 开始, 构建的工具列表中就会包括该工具的完整定义 (tools 字段)
|
|
361
|
-
|
|
362
|
-
```js
|
|
363
|
-
// @/tools/registry.ts
|
|
364
|
-
// ToolRegistry.prototype.getAllSchemas
|
|
365
|
-
// ToolRegistry.prototype.getDeferredToolNames
|
|
366
|
-
|
|
367
|
-
function buildToolList(registry, addSystemReminder) {
|
|
368
|
-
// getAllSchemas 过滤 deferred && !discovered 的工具
|
|
369
|
-
const toolList = registry.getAllSchemas();
|
|
370
|
-
|
|
371
|
-
// getDeferredToolNames 返回所有延迟加载的工具 names
|
|
372
|
-
const deferredNames = registry.getDeferredToolNames();
|
|
373
|
-
|
|
374
|
-
if (deferredNames.length > 0) {
|
|
375
|
-
addSystemReminder(
|
|
376
|
-
`The following tools can be loaded by calling the ToolSearch tool:\n${deferredNames.join("\n")}`,
|
|
377
|
-
);
|
|
378
|
-
}
|
|
379
|
-
return toolList;
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
// @/tools/tool-search.ts
|
|
383
|
-
// ToolSearchTool.prototype.execute
|
|
384
|
-
// 支持 2 种 Tool 搜索方式:
|
|
385
|
-
// 1. "select:name1,name2" 按工具 name 精确搜索
|
|
386
|
-
// 2. 关键词模糊匹配
|
|
387
|
-
function toolSearchExecute(query, registry, maxResults = 5) {
|
|
388
|
-
let tools = [];
|
|
389
|
-
|
|
390
|
-
if (query.startsWith("select:")) {
|
|
391
|
-
// 按工具 name 精确搜索
|
|
392
|
-
const names = query
|
|
393
|
-
.slice("select:".length)
|
|
394
|
-
.split(",")
|
|
395
|
-
.map((n) => n.trim());
|
|
396
|
-
tools = registry.findDeferredByNames(names);
|
|
397
|
-
} else {
|
|
398
|
-
// 关键词模糊匹配
|
|
399
|
-
tools = registry.searchDeferred(query, maxResults);
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
for (const tool of tools) {
|
|
403
|
-
registry.markDiscovered(tool.name);
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
const schemas = tools.map((t) => JSON.stringify(t.schema(), null, 2));
|
|
407
|
-
return schemas.join("\n\n");
|
|
408
|
-
}
|
|
409
|
-
```
|
|
410
|
-
|
|
411
|
-
### 延迟加载策略
|
|
412
|
-
|
|
413
|
-
- 6 个内置工具: `deferred: false`
|
|
414
|
-
- MCP Server 提供的工具: `deferred: true`, 延迟加载; MCPToolWrapper 默认 `category = "command"`, `deferred = true` <!-- 源码: src/mcp/tool-wrapper.ts -->
|
|
415
|
-
|
|
416
|
-
## 工具权限
|
|
417
|
-
|
|
418
|
-
按工具名称匹配权限规则
|
|
419
|
-
|
|
420
|
-
MCP 工具命名规范: `mcp__serverName__toolName`
|
|
421
|
-
|
|
422
|
-
<!-- 源码: src/mcp/tool-wrapper.ts -->
|
|
423
|
-
|
|
424
|
-
sanitizeName 函数将 serverName 和 toolName 中的非字母数字字符替换为下划线, 并使用 `mcp__` 前缀和双下划线连接
|
|
425
|
-
|
|
426
|
-
```js
|
|
427
|
-
function sanitizeName(serverName: string, toolName: string): string {
|
|
428
|
-
const clean = (s: string) => s.replace(/[^a-zA-Z0-9_]/g, "_");
|
|
429
|
-
return `mcp__${clean(serverName)}__${clean(toolName)}`;
|
|
430
|
-
}
|
|
431
|
-
```
|
|
432
|
-
|
|
433
|
-
<!-- 源码: src/mcp/tool-wrapper.ts -->
|
|
434
|
-
|
|
435
|
-
MCPToolWrapper 默认配置:
|
|
436
|
-
|
|
437
|
-
- category: "command"
|
|
438
|
-
- deferred: true
|
|
439
|
-
|
|
440
|
-
```yaml
|
|
441
|
-
# 允许 GitHub MCP 工具
|
|
442
|
-
- rule: mcp__github__*(*)
|
|
443
|
-
effect: allow
|
|
444
|
-
|
|
445
|
-
# 禁止所有 MCP 工具的删除操作
|
|
446
|
-
- rule: mcp__*__delete__*(*)
|
|
447
|
-
effect: deny
|
|
448
|
-
```
|
package/docs/ch8.md
DELETED
|
@@ -1,217 +0,0 @@
|
|
|
1
|
-
# 上下文压缩
|
|
2
|
-
|
|
3
|
-
> 开启新对话, 调用 LLM API 压缩上下文
|
|
4
|
-
|
|
5
|
-
LLM API 是无状态的, 每个 LLM API 请求, 都需要发送完整的对话历史, 包括调用 ReadFile 工具读的每个文件、调用 Bash 工具执行的每条命令和输出, token 数量随对话轮次线形增长
|
|
6
|
-
|
|
7
|
-
### Agent Loop
|
|
8
|
-
|
|
9
|
-
工具调用结果 token 消耗占比最高, 也最容易过时: 例如第 3 个 turn 调用 ReadFile 工具读 main.js, 第 5 个 turn 调用 WriteFile 工具写 main.js; 第 10 个 turn 时, 第 3 个 turn 读的旧版本的 main.js 内容已过时, 需要上下文压缩
|
|
10
|
-
|
|
11
|
-
## 两层压缩
|
|
12
|
-
|
|
13
|
-
## 第 1 层: 大结果存磁盘
|
|
14
|
-
|
|
15
|
-
### 单个工具调用结果的阈值
|
|
16
|
-
|
|
17
|
-
<!-- 源码: src/tool-result/budget.ts SINGLE_RESULT_LIMIT = 50000 -->
|
|
18
|
-
|
|
19
|
-
单个工具调用结果超过 50k 字符时 (约 12.5k tokens), CLI 不会将大结果 push 到对话历史, 而是将大结果写入磁盘文件, 向对话历史中 push 一个预览, 使用 `<persisted-output />` 标签包裹, 包含文件大小、文件路径和前 2k 字符
|
|
20
|
-
|
|
21
|
-
```xml
|
|
22
|
-
<persisted-output>
|
|
23
|
-
Output too large (80KB). Full content saved to:
|
|
24
|
-
.swifty/tool_results/{toolUseId}
|
|
25
|
-
|
|
26
|
-
Preview (first 2KB):
|
|
27
|
-
...
|
|
28
|
-
</persisted-output>
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
大结果保存到磁盘文件, LLM 通常看预览就足够理解上下文; 真正需要完整内容时, 调用 ReadFile 工具读磁盘文件即可 (prompt cache 友好)
|
|
32
|
-
|
|
33
|
-
### 单条消息中多个工具调用结果的聚合限制
|
|
34
|
-
|
|
35
|
-
<!-- 源码: src/tool-result/budget.ts MESSAGE_AGGREGATE_LIMIT = 200000 -->
|
|
36
|
-
|
|
37
|
-
一轮对话中, 并发调用 10 个工具, 每个工具调用结果是 40k 字符, 单个工具调用结果都 < 50k 字符, 但是聚合总量是 400k
|
|
38
|
-
|
|
39
|
-
所以有单条消息中多个工具调用结果的聚合限制 200k, 超过 200k 字符时, 按工具调用结果的大小降序排序, 将最大的工具调用结果溢出到磁盘, 直到聚合大小降低到 200k 以内
|
|
40
|
-
|
|
41
|
-
## 第 2 层: 生成对话摘要, 保留近期消息原文, 恢复关键上下文 (Auto-Compact)
|
|
42
|
-
|
|
43
|
-
第 1 层不够用时
|
|
44
|
-
|
|
45
|
-
- LLM 侧: 生成对话摘要
|
|
46
|
-
- CLI 侧: 保留近期消息原文, 恢复关键上下文: 工具列表、skills、最近访问的文件
|
|
47
|
-
|
|
48
|
-
### LLM 侧: 生成对话摘要
|
|
49
|
-
|
|
50
|
-
#### 自动压缩阈值
|
|
51
|
-
|
|
52
|
-
以 200k tokens 上下文窗口为例
|
|
53
|
-
|
|
54
|
-
- 200_000 (上下文窗口) - 20_000 (预留给对话摘要) = 180_000 (effectiveWindow 有效上下文窗口)
|
|
55
|
-
- 180_000 (有效上下文窗口) - 13_000 (安全余量, 经验值) = 167_000 (自动压缩阈值)
|
|
56
|
-
- buffer 大小: 20k + 13k = 33k
|
|
57
|
-
|
|
58
|
-
自动压缩的检查点是每轮 agent loop turn 开始
|
|
59
|
-
|
|
60
|
-
- 第 n 次自动压缩检查: 178_000 tokens, 不会触发上下文压缩
|
|
61
|
-
- 一轮 agent loop turn, 消耗 10_000 tokens
|
|
62
|
-
- 第 n+1 次自动压缩检查: 188_000 tokens, 预留给对话摘要只有 12_000 tokens, 低于 20k 预算, 所以需要 13k 的安全余量
|
|
63
|
-
|
|
64
|
-
#### 为什么不设置为「上下文窗口用量到达 x% 时触发」?
|
|
65
|
-
|
|
66
|
-
20k + 13k = 33k 的 buffer 预防的是一轮 agent loop turn 的 token 消耗, 和上下文窗口总大小 (200k, 1M ...) 无关, 不存在一个百分比同时适配所有的上下文窗口大小
|
|
67
|
-
|
|
68
|
-
#### 为什么预留 20k 给对话摘要
|
|
69
|
-
|
|
70
|
-
<!-- 源码: src/compact/compact.ts SUMMARY_OUTPUT_RESERVE = 20000 -->
|
|
71
|
-
|
|
72
|
-
预留给对话摘要 20k tokens: 对话摘要有 9 个结构化部分 (见下文: 摘要 prompt 的设计) 和 `<analysis />` 草稿块, 一个复杂会话的摘要输出大约 15k 到 18k 的 tokens, 设置为 15k 有被截断的风险
|
|
73
|
-
|
|
74
|
-
## 摘要 prompt 的设计
|
|
75
|
-
|
|
76
|
-
<!-- 源码: src/compact/compact.ts -->
|
|
77
|
-
|
|
78
|
-
prompt 开头和结尾, 重复禁止 LLM 生成摘要时调用任何工具, 仅输出纯文本: Swifty 请求 LLM API 生成摘要时, 也会携带 tools 参数, 目的是命中 prompt cache (prompt cache 按前缀匹配, 顺序是 tools -> system -> messages)
|
|
79
|
-
|
|
80
|
-
摘要的质量直接决定上下文压缩后 Agent 的表现, 一个好的摘要 prompt 要求 LLM 生成一份结构化摘要, 明确 9 个部分:
|
|
81
|
-
|
|
82
|
-
1. 用户的请求和意图
|
|
83
|
-
2. 相关技术栈
|
|
84
|
-
3. 相关文件和代码片段
|
|
85
|
-
4. 技术方案
|
|
86
|
-
5. bugfix
|
|
87
|
-
6. 所有用户的、非工具调用结果的消息, 原文保留
|
|
88
|
-
7. TODO List
|
|
89
|
-
8. 当前工作: 最详细
|
|
90
|
-
9. 可能的下一步
|
|
91
|
-
|
|
92
|
-
#### 两阶段生成: 先打草稿再写正文
|
|
93
|
-
|
|
94
|
-
先打草稿再写正文, 可以显著提升摘要质量; prompt 要求 LLM 先输出 `<analysis />` 草稿块, 再输出正式的 `<summary />` 摘要块, 最终只保留 `<summary />`, `<analysis />` 被丢弃
|
|
95
|
-
|
|
96
|
-
```
|
|
97
|
-
Summarize the following conversation.
|
|
98
|
-
Wrap your analysis in <analysis> tags, then provide the summary in <summary> tags.
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
### CLI 侧: 压缩恢复 (保留近期消息原文, 恢复关键上下文)
|
|
102
|
-
|
|
103
|
-
近期 10k tokens 或者至少 5 条消息保留原文, 并且附加以下内容
|
|
104
|
-
|
|
105
|
-
- 工具列表
|
|
106
|
-
- skills, 预算 25k tokens
|
|
107
|
-
- 会话记录日志路径
|
|
108
|
-
- 最近访问的文件, 最多附加 5 个, 每个最多 5k tokens
|
|
109
|
-
|
|
110
|
-
压缩后的对话历史
|
|
111
|
-
|
|
112
|
-
```md
|
|
113
|
-
[system] You are Swifty, a terminal AI programming assistant...
|
|
114
|
-
|
|
115
|
-
<!-- 对话摘要 -->
|
|
116
|
-
|
|
117
|
-
[user] This session continues from a previous conversation, which has been compressed due to context limitations. Here is a summary of the earlier messages:
|
|
118
|
-
|
|
119
|
-
(摘要内容)
|
|
120
|
-
|
|
121
|
-
Recent messages have been preserved verbatim.
|
|
122
|
-
|
|
123
|
-
<!-- 会话记录日志路径 -->
|
|
124
|
-
|
|
125
|
-
If you need specific details from before compaction (code snippets, error messages, etc.), use ReadFile to read the full session transcript: $HOME/path/to/.swifty/sessions/YYYY-MM-DD-abc123.jsonl
|
|
126
|
-
|
|
127
|
-
---
|
|
128
|
-
|
|
129
|
-
<!-- 最近访问的文件 -->
|
|
130
|
-
|
|
131
|
-
## Recently read files
|
|
132
|
-
|
|
133
|
-
These snapshots are what the file-reading tool last returned. Re-open with the tool if you need the current bytes.
|
|
134
|
-
|
|
135
|
-
### handler.ts (read 2026-07-08T12:34:56Z)
|
|
136
|
-
|
|
137
|
-
(文件内容, 最多 5k tokens)
|
|
138
|
-
|
|
139
|
-
### middleware.ts (read 2026-07-08T12:35:12Z)
|
|
140
|
-
|
|
141
|
-
(文件内容, 最多 5k tokens)
|
|
142
|
-
|
|
143
|
-
<!-- 最近加载的 skills -->
|
|
144
|
-
|
|
145
|
-
## Active skills
|
|
146
|
-
|
|
147
|
-
These skills were invoked earlier in the session. Continue to follow each SOP when its triggering condition applies.
|
|
148
|
-
(skill body, 最多 5k tokens, 预算 25k tokens)
|
|
149
|
-
|
|
150
|
-
<!-- 可用工具列表 -->
|
|
151
|
-
|
|
152
|
-
## Available tools
|
|
153
|
-
|
|
154
|
-
You still have access to the following tools — call them directly when the task needs one:
|
|
155
|
-
|
|
156
|
-
- ReadFile
|
|
157
|
-
- WriteFile
|
|
158
|
-
- Bash
|
|
159
|
-
|
|
160
|
-
## Note
|
|
161
|
-
|
|
162
|
-
Everything above the divider is reconstructed context. For exact code, error strings, or user-typed text, re-read the source rather than guess from the summary.
|
|
163
|
-
|
|
164
|
-
(以下是保留的近期消息原文)
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
<!-- 源码: src/compact/compact.ts, src/compact/recovery.ts -->
|
|
168
|
-
|
|
169
|
-
摘要内容、会话记录日志路径、恢复的关键上下文 (最近访问的文件、最近加载的 skills、可用工具列表) 拼接在一条 user 消息中, 使用 --- 分隔, 后面是保留的近期消息原文; 两条连续的 user 消息会被自动合并为一条
|
|
170
|
-
|
|
171
|
-
## 熔断机制
|
|
172
|
-
|
|
173
|
-
<!-- 源码: src/compact/compact.ts MAX_CONSECUTIVE_FAILURES = 3 -->
|
|
174
|
-
|
|
175
|
-
- 如果摘要请求因为网络错误、LLM API 错误等连续失败 3 次, 则熔断器触发, 停止重试
|
|
176
|
-
- token 用量继续涨到强制压缩阈值时, 即使熔断器已触发, 仍会强制压缩上下文
|
|
177
|
-
- 如果摘要请求报错 prompt too long, 可以丢弃最旧的几轮消息, 使用剩余消息重试 (TODO)
|
|
178
|
-
|
|
179
|
-
## 强制压缩
|
|
180
|
-
|
|
181
|
-
以 200k tokens 上下文窗口为例
|
|
182
|
-
|
|
183
|
-
- 200_000 (上下文窗口) - 20_000 (预留给对话摘要) = 180_000 (effectiveWindow 有效上下文窗口)
|
|
184
|
-
- 180_000 (有效上下文窗口) - 13_000 (安全余量, 经验值) = 167_000 (自动压缩阈值)
|
|
185
|
-
- buffer 大小: 20k + 13k = 33k
|
|
186
|
-
|
|
187
|
-
1. token 用量涨到强制压缩阈值 167k 时, 即使熔断器已触发, 仍会强制压缩上下文
|
|
188
|
-
2. 自动压缩连续失败 3 次, 熔断停止重试
|
|
189
|
-
3. token 用量继续涨到 (effectiveWindow - 3000 = 177k) 时强制压缩
|
|
190
|
-
|
|
191
|
-
## 手动 /compact
|
|
192
|
-
|
|
193
|
-
输入 /compact 手动触发上下文压缩, 2 个典型场景:
|
|
194
|
-
|
|
195
|
-
1. 预防性压缩: 接下来会读大量文件, 提前压缩上下文
|
|
196
|
-
2. 话题切换
|
|
197
|
-
|
|
198
|
-
## @/tool-result/budget.ts 的 `applyBudget`
|
|
199
|
-
|
|
200
|
-
<!-- 源码: src/tool-result/budget.ts -->
|
|
201
|
-
|
|
202
|
-
```txt
|
|
203
|
-
为什么统计实际 agent loop turn 数需要统计 role 是 assistant 并且没有 tool_use 的消息数量?
|
|
204
|
-
|
|
205
|
-
- user: "帮我修复 bug" <- role=user, turn 开始
|
|
206
|
-
- assistant: [请求调用工具 ReadFile] <- role=assistant, tool_use
|
|
207
|
-
- user: [ReadFIle 工具调用结果] <- role=user, tool_result
|
|
208
|
-
- assistant: [请求调用工具 EditFile] <- role=assistant, tool_use
|
|
209
|
-
- user: [EditFile 工具调用结果] <- role=user, tool_result
|
|
210
|
-
- assistant "Bug 已修复" <- role=assistant, turn 结束, 没有 tool_use
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
- 阶段 1: 单个工具调用结果超过 50k 字符时 (SINGLE_RESULT_LIMIT), 溢出到磁盘, 替换为预览, 使用 `<persisted-output />` 标签包裹, 包含文件大小、文件路径和前 2k 字符
|
|
214
|
-
- 阶段 2: 单条消息中多个工具调用结果的聚合限制: 200k (MESSAGE_AGGREGATE_LIMIT), 超过 200k 字符时, 按工具调用结果的大小降序排序, 将最大的工具调用结果溢出到磁盘, 直到聚合大小降低到 200k 以内
|
|
215
|
-
- 阶段 3 (Snip stale): 裁剪历史消息中旧的工具调用结果, 统计实际 agent loop turn 数 (统计 role 是 assistant 并且没有 tool_use 的消息数量), 保留最近 10 个 turn 的工具调用结果, 将更早的 turn 中超过 2000 字符的工具调用结果替换为 `[Stale output snipped: N chars]`; 被替换过的工具调用结果 (以 `<persisted-output>` 或 `[Stale output snipped:` 开头) 不会被重复处理
|
|
216
|
-
|
|
217
|
-
最大的工具调用结果溢出到磁盘, 保存到 `.swifty/tool_results/{toolUseId}`
|