Skip to content

Commit e12fd9b

Browse files
LegnaOSclaude
andcommitted
feat: v3.4.0 — Agent 工具系统进化
参考 Claude Code Tool 类型系统架构,将代理插件从 API 转发+工具拦截器 进化为完整的 Agent 执行引擎。 - Tool 泛型接口 + buildTool() 工厂 + ToolRegistry 注册表 - 5 个新增工具: bash/glob/grep/file_read/list_directory - 7 个现有工具从 1427 行 if/else 链提取为独立模块 - 三 Provider 统一 ToolRegistry 异步拦截 + 并发分区执行 - 新工具 JSON Schema 自动注入 Anthropic/OpenAI/Gemini 三格式 - 20 个新文件,1625 行代码 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 7e50cd8 commit e12fd9b

32 files changed

Lines changed: 2234 additions & 57 deletions

RELEASE_NOTES_v3.3.10.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Augment Proxy Manager v3.3.10 - Responses 二轮 assistant history 编码修复
2+
3+
## 核心结论
4+
5+
这次修的不是 retry,也不是 parser。
6+
7+
真正的问题是:在 OpenAI `responses` 模式下,手工回放历史消息时,`assistant` 角色内容被错误编码成了 `input_text`
8+
9+
第一轮没有 assistant history,所以能过;第二轮一旦带上上一轮 assistant 回复,上游就可能直接返回 `502 upstream_error`
10+
11+
## 本次修复
12+
13+
### 1. Assistant history 改为 `output_text`
14+
- `role: "assistant"` 的历史 message 内容不再使用 `input_text`
15+
- 改为符合 Responses 语义的 `output_text`
16+
17+
### 2. 覆盖普通 assistant 与 assistant+tool_calls 两条路径
18+
- 普通 assistant 历史消息会正确编码为 `output_text`
19+
-`tool_calls` 的 assistant 历史消息,其文本部分也会正确编码为 `output_text`
20+
21+
### 3. 清理脏 tool call
22+
- 若历史 `assistant.tool_calls` 缺失 `function.name`
23+
- 直接丢弃,不再构造坏的 `function_call` item 去污染上游请求
24+
25+
## 验证情况
26+
27+
- TypeScript diagnostics:通过
28+
- 本地 compile:`npm run compile` 已通过
29+
30+
## 兼容性
31+
32+
- 仅影响 OpenAI `responses` 历史消息转换逻辑
33+
- 不影响 `chat.completions`
34+
- 不影响 Anthropic / Google provider

RELEASE_NOTES_v3.3.8.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# Augment Proxy Manager v3.3.8 - Responses Tool Call 别名合并修复
2+
3+
## 核心结论
4+
5+
v3.3.8 修的是 `responses` 工具调用聚合里的脏数据问题:
6+
7+
- 同一个 function call 在 SSE partial 事件里常见是 `call_...`
8+
- 到 completed / output item 又可能变成 `fc_...`
9+
10+
旧实现直接拿不同 id `set``Map`,于是同一调用会裂成两条记录。其中那条没名字的脏记录被回落成了字面量 `tool`,最后执行层自然找不到 tool definition。
11+
12+
这版做的不是花活,就是把 **同一 tool call 重新并回同一条数据**
13+
14+
## 本次修复
15+
16+
### 1. Responses tool call alias merge
17+
- 合并 `call_id`
18+
- 合并 `item.id / item_id`
19+
- 合并 `output_index`
20+
- 同一 function call 不再因为不同事件使用不同 id 被拆成两条
21+
22+
### 2. 禁止脏默认值污染
23+
- partial 事件拿不到 `name` 时先留空
24+
-`output_item` / `response.completed` 再回填真实工具名
25+
- 不再伪造默认工具名 `tool`
26+
27+
### 3. Defensive finalize
28+
- 最终仍然没有真实 `name` 的 tool call 会被丢弃
29+
- 同时输出 warning log,便于继续定位上游异常 payload
30+
- 避免执行层再报:`Cannot find tool definition for tool 'tool'`
31+
32+
## 验证情况
33+
34+
- TypeScript diagnostics:通过
35+
- 本地 compile:`npm run compile` 已通过
36+
- 适合继续打包 `.vsix` 供实际对话流测试
37+
38+
## 兼容性
39+
40+
- 不影响 `chat.completions`
41+
- 不影响 Anthropic / Google provider
42+
- 只收敛 OpenAI `responses` 下的 tool-call 聚合逻辑

RELEASE_NOTES_v3.3.9.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Augment Proxy Manager v3.3.9 - Upstream 502/503/504 保守重试
2+
3+
## 核心结论
4+
5+
这版不是再修 parser,而是给 OpenAI-compatible 上游加一个最小兜底:
6+
7+
-`/v1/responses` 返回 `502 / 503 / 504`
8+
- 或者请求过程中出现 timeout / `ECONNRESET` / `socket hang up`
9+
10+
代理会做 **一次** 保守自动重试。
11+
12+
目的很简单:别让一次中转抖动把整轮对话直接打死。
13+
14+
## 本次修复
15+
16+
### 1. Transient upstream retry
17+
- 识别 `502 / 503 / 504`
18+
- 识别 `upstream request failed` / `upstream_error` / `bad gateway` / `gateway timeout`
19+
- 小延迟 backoff 后自动重试 1 次
20+
21+
### 2. Transport-level retry
22+
- 对以下瞬时传输错误新增同样的单次重试:
23+
- timeout
24+
- `ECONNRESET`
25+
- `ECONNREFUSED`
26+
- `ETIMEDOUT`
27+
- `socket hang up`
28+
29+
### 3. Better logs
30+
- 请求日志新增:
31+
- `tools` 数量
32+
- `bodyBytes`
33+
- `continuation`
34+
- `retry` 次数
35+
- 继续出问题时能更快判断到底是 payload 触发,还是上游随机抽风
36+
37+
## 验证情况
38+
39+
- TypeScript diagnostics:通过
40+
- 本地 compile:`npm run compile` 已通过
41+
- 适合继续打包 `.vsix` 供真实上游回归测试
42+
43+
## 兼容性
44+
45+
- 不改变正常成功请求的行为
46+
- 不影响 `400` 类真实请求错误的显式暴露
47+
- 不影响 Anthropic / Google provider

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "augment-proxy-manager",
33
"displayName": "Augment Proxy Manager",
44
"description": "管理 Augment API 代理服务器,支持自定义 API 端点和多种 AI 供应商",
5-
"version": "3.3.7",
5+
"version": "3.4.0",
66
"publisher": "legna",
77
"repository": {
88
"type": "git",

readme.md

Lines changed: 122 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
零注入 · 零登录 · 零配置
1010

11-
[![Version](https://img.shields.io/badge/version-3.3.7-blue.svg)](https://github.com/LegnaOS/VSC-Augment-Proxy-Manager)
11+
[![Version](https://img.shields.io/badge/version-3.4.0-blue.svg)](https://github.com/LegnaOS/VSC-Augment-Proxy-Manager)
1212
[![Platform](https://img.shields.io/badge/platform-macOS%20%7C%20Windows%20%7C%20Linux-lightgrey.svg)]()
1313

1414
</div>
@@ -52,6 +52,22 @@ Augment 扩展 → 本地代理 (:8765) → 你的 AI 供应商 API
5252

5353
## 功能特性
5454

55+
### 🛠️ v3.4 — Agent 工具系统进化
56+
57+
- **Tool 类型系统** — 参考 Claude Code 的 `Tool<Input,Output>` 泛型架构,`buildTool()` 工厂模式,fail-closed 安全默认值(isReadOnly=false, isConcurrencySafe=false)
58+
- **ToolRegistry 注册表** — 统一的工具注册、查找(含 alias)、分发和格式转换,单例模式贯穿全局
59+
- **并发分区执行** — 只读工具(Glob/Grep/FileRead/ListDirectory/CodebaseSearch)自动并行执行,写入工具严格串行,参考 Claude Code 的 `partitionToolCalls` 策略
60+
- **5 个新增 Agent 工具**
61+
- `bash` — Shell 命令执行(120s 超时,10MB 输出缓冲)
62+
- `glob` — 文件模式搜索(`**/*.ts``src/**/*.js`
63+
- `grep` — 内容搜索(优先 ripgrep,fallback grep)
64+
- `file_read` — 增强文件读取(行号标注、行范围选择、2MB 限制)
65+
- `list_directory` — 目录列表(文件类型 + 大小标注)
66+
- **工具 Schema 自动注入** — 新工具的 JSON Schema 自动注入到 Anthropic/OpenAI/Gemini 三种格式的 tool_definitions
67+
- **三 Provider 统一拦截** — Anthropic/OpenAI/Google 三路转发均集成 ToolRegistry 异步拦截,新工具在代理层直接执行
68+
- **现有工具模块化** — str-replace-editor、save-file、apply_patch、任务列表、codebase_search 等 7 个现有工具从 1427 行的 if/else 链提取为独立模块
69+
- **共享工具函数** — Patch 解析器(Augment V4A + Unified Diff)、路径修正、通用参数修正提取为 `shared/` 可复用模块
70+
5571
### 🧠 v3.0 — 智能上下文引擎
5672

5773
- **Viking 分层上下文** — 借鉴 [OpenViking](https://github.com/volcengine/OpenViking) 的文件系统范式,L0 摘要 / L1 结构 / L2 全文三级按需加载,精准控制注入 token 量
@@ -134,16 +150,38 @@ src/
134150
├── messages.ts # Augment 协议解析 + System Prompt 注入
135151
├── sidebar.ts # 侧边栏 Webview UI
136152
├── config.ts # 供应商配置
137-
├── globals.ts # 全局状态 (Viking/SessionMemory/RAG/Embedding)
153+
├── globals.ts # 全局状态 (Viking/SessionMemory/RAG/Embedding/ToolRegistry)
138154
├── context-manager.ts # 上下文管理
139155
├── context-compression.ts # 智能压缩
140156
├── injection.ts # Augment 扩展自动配置
141157
├── omc.ts # OMC 编排增强
142-
├── tools.ts # 工具调用处理
158+
├── tools.ts # 工具调用处理(薄层委托到 tools/)
159+
├── tools/ # v3.4.0 工具系统
160+
│ ├── Tool.ts # 核心接口 + buildTool() 工厂
161+
│ ├── ToolRegistry.ts # 工具注册表(查找/分发/并发分区执行)
162+
│ ├── ToolResultFormatter.ts # Diff 渲染 + 结果格式化
163+
│ ├── extra-tool-schemas.ts # 新工具 JSON Schema(三格式注入)
164+
│ ├── index.ts # 注册入口
165+
│ ├── StrReplaceEditorTool.ts # 精确编辑(insert/str_replace/三级匹配)
166+
│ ├── SaveFileTool.ts # 新建文件(已有文件拒绝覆盖)
167+
│ ├── ApplyPatchTool.ts # Diff/Patch 应用
168+
│ ├── TaskListTool.ts # 任务列表管理
169+
│ ├── EditFileTool.ts # 错误桩(提示用 str-replace-editor)
170+
│ ├── WebSearchTool.ts # Kimi $web_search 透传
171+
│ ├── CodebaseSearchTool.ts # RAG 本地搜索
172+
│ ├── BashTool.ts # Shell 命令执行
173+
│ ├── GlobTool.ts # 文件模式搜索
174+
│ ├── GrepTool.ts # 内容搜索(ripgrep/grep)
175+
│ ├── FileReadTool.ts # 增强文件读取
176+
│ ├── ListDirectoryTool.ts # 目录列表
177+
│ └── shared/
178+
│ ├── patch-parser.ts # Augment V4A + Unified Diff 解析器
179+
│ ├── path-utils.ts # 路径前缀修正
180+
│ └── input-fixer.ts # 通用参数修正
143181
├── providers/
144-
│ ├── anthropic.ts # Anthropic 流式转发
145-
│ ├── openai.ts # OpenAI 流式转发
146-
│ └── google.ts # Google Gemini 流式转发
182+
│ ├── anthropic.ts # Anthropic 流式转发 + ToolRegistry 拦截
183+
│ ├── openai.ts # OpenAI 流式转发 + ToolRegistry 拦截
184+
│ └── google.ts # Google Gemini 流式转发 + ToolRegistry 拦截
147185
└── rag/
148186
├── index.ts # RAG 索引 + Viking 增强搜索
149187
├── embeddings.ts # Embedding 引擎 (本地 5 模型 + 远程 API)
@@ -163,8 +201,86 @@ src/
163201
| Cursor | `~/.cursor/extensions` | `%USERPROFILE%\.cursor\extensions` |
164202
| Windsurf | `~/.windsurf/extensions` | `%USERPROFILE%\.windsurf\extensions` |
165203

204+
## 进化链
205+
206+
```
207+
v1.9.0 零注入代理 + RAG 语义搜索
208+
209+
v2.1.x Kimi/GLM 多供应商 + OMC 编排
210+
211+
v3.0.0 Viking 分层上下文 + Session Memory + 本地 Embedding
212+
213+
v3.1.0 文件编辑引擎重构 + 三 Provider 循环架构 + Diff 渲染
214+
215+
v3.3.x OpenAI Responses 协议 + Kimi 工具链闭环 + 状态端点
216+
217+
v3.4.0 Agent 工具系统进化 — Tool 类型系统 + ToolRegistry
218+
+ 5 个新工具 (Bash/Glob/Grep/FileRead/ListDir)
219+
+ 并发分区执行 + 三 Provider 统一拦截
220+
```
221+
166222
## 更新日志
167223

224+
### v3.4.0 — Agent 工具系统进化
225+
226+
**🏗️ 架构重构 — 参考 Claude Code Tool 类型系统**
227+
- 新增 `Tool` 泛型接口 + `buildTool()` 工厂模式,fail-closed 安全默认值
228+
- 新增 `ToolRegistry` 注册表,统一工具查找(含 alias)、分发、并发分区执行
229+
-`tools.ts` 中 1427 行的 `convertOrInterceptFileEdit()` if/else 链拆分为 7 个独立工具模块
230+
- Patch 解析器、路径修正、参数修正提取为 `shared/` 可复用模块
231+
- `processToolCallForAugment` 改为 async,支持 ToolRegistry 异步拦截
232+
233+
**🔧 5 个新增 Agent 工具**
234+
- `bash` — Shell 命令执行(child_process.spawn,120s 超时,10MB 输出缓冲)
235+
- `glob` — 文件模式搜索(find + 排除 node_modules/.git/dist,200 结果上限)
236+
- `grep` — 内容搜索(优先 ripgrep,fallback grep,支持正则/大小写/上下文行)
237+
- `file_read` — 增强文件读取(cat -n 格式行号,offset/limit 行范围,2MB 限制)
238+
- `list_directory` — 目录列表(目录优先排序,文件大小标注)
239+
240+
**⚡ 三 Provider 统一拦截**
241+
- Anthropic / OpenAI / Google 三路转发均集成 ToolRegistry 异步拦截
242+
- 新工具的 JSON Schema 通过 `extra-tool-schemas.ts` 自动注入三种格式的 tool_definitions
243+
- 只读工具自动并行执行,写入工具严格串行
244+
- 工具结果大小截断(50KB 上限)
245+
246+
**📁 新增文件(20 个,1625 行)**
247+
```
248+
src/tools/Tool.ts, ToolRegistry.ts, ToolResultFormatter.ts, extra-tool-schemas.ts, index.ts
249+
src/tools/StrReplaceEditorTool.ts, SaveFileTool.ts, ApplyPatchTool.ts, TaskListTool.ts
250+
src/tools/EditFileTool.ts, WebSearchTool.ts, CodebaseSearchTool.ts
251+
src/tools/BashTool.ts, GlobTool.ts, GrepTool.ts, FileReadTool.ts, ListDirectoryTool.ts
252+
src/tools/shared/patch-parser.ts, path-utils.ts, input-fixer.ts
253+
```
254+
255+
**🔄 修改文件(6 个)**
256+
- `globals.ts` — 添加 toolRegistry 字段
257+
- `proxy.ts` — 初始化 ToolRegistry
258+
- `tools.ts` — 三个 convertToolDefinitions 注入新工具 schema + processToolCallForAugment async 化
259+
- `providers/anthropic.ts` — 工具循环集成 ToolRegistry 异步拦截
260+
- `providers/openai.ts` — 通过 processToolCallForAugment 自动集成
261+
- `providers/google.ts` — 工具循环集成 ToolRegistry 异步拦截
262+
263+
### v3.3.10 — Responses 二轮 assistant history 编码修复
264+
265+
- **assistant history fix**`responses` 手工重放历史消息时,assistant 角色内容不再错误编码为 `input_text`,改为符合协议语义的 `output_text`
266+
- **second-turn stability** — 修复“第一轮正常、第二轮稳定 502”这类由 assistant history payload 非法触发的兼容问题
267+
- **tool-call hygiene** — 历史 `assistant.tool_calls` 中缺失 `function.name` 的脏记录会被直接丢弃,避免继续向上游发送坏的 `function_call` item
268+
- **compatibility** — 只影响 OpenAI `responses` 历史消息转换逻辑,不改 `chat.completions`、Anthropic、Google 路径
269+
270+
### v3.3.9 — Upstream 502/503/504 保守重试
271+
272+
- **transient upstream retry** — 对 `502 / 503 / 504` 这类瞬时上游错误新增一次保守自动重试,降低 `responses` 请求被中转偶发打死的概率
273+
- **transport retry** — 对 timeout、`ECONNRESET``socket hang up` 等瞬时传输错误也会走同一套单次 retry
274+
- **better execution logs** — 请求日志现在会带上 `tools` 数量、body 大小、continuation 状态和 retry 次数,方便继续定位上游兼容性问题
275+
- **safety** — 只重试明显的 transient failure,不会把 `400` 这类真实请求错误伪装成可恢复问题
276+
277+
### v3.3.8 — Responses Tool Call 别名合并修复
278+
279+
- **responses tool alias merge** — 合并 `call_id``item.id / item_id``output_index` 三套标识,避免同一个 function call 在流式事件和 completed payload 中被拆成两条记录
280+
- **no fake tool name** — Responses parser 不再把缺失名称的半成品工具调用污染成字面量 `tool`,而是等待最终 payload 回填真实名字
281+
- **defensive finalize** — 最终仍拿不到真实名字的脏 tool call 会被直接丢弃并记录 warning,避免执行层再报 `Cannot find tool definition for tool 'tool'`
282+
- **compatibility** — 修复只影响 OpenAI `responses` tool-call 聚合逻辑,不改 `chat.completions`、Anthropic、Google 路径
283+
168284
### v3.3.7 — OpenAI Provider Wire API 修正 + Responses 自动回退
169285

170286
- **openai provider wireApi**`wireApi` 不再只绑在 `custom` 上,`openai` provider 现在也能明确选择 `chat.completions``responses`

src/globals.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import * as http from 'http';
33
import { CurrentConfig } from './types';
44
import type { VikingContextStore } from './rag/viking-context';
55
import type { SessionMemory } from './rag/session-memory';
6+
import type { ToolRegistry } from './tools/ToolRegistry';
67

78
export interface RecordedEvent {
89
id: string;
@@ -52,6 +53,9 @@ export const state = {
5253
ragIndex: null as any,
5354
semanticEngine: null as any,
5455

56+
// v3.4.0: 工具系统
57+
toolRegistry: null as ToolRegistry | null,
58+
5559
// v2.0.0: Viking 子系统
5660
vikingStore: null as VikingContextStore | null,
5761
sessionMemory: null as SessionMemory | null,

src/providers/anthropic.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { state, log } from '../globals';
77
import { augmentToAnthropicMessages, buildSystemPrompt, extractWorkspaceInfo } from '../messages';
88
import { convertToolDefinitions, fixToolCallInput, convertOrInterceptFileEdit, renderDiffText } from '../tools';
99
import { applyContextCompression } from '../context-compression';
10+
import { renderDiffTextCompat } from '../tools/ToolResultFormatter';
1011

1112
interface AnthropicToolCall { id: string; name: string; input: any; }
1213
interface AnthropicResult { text: string; toolCalls: AnthropicToolCall[]; stopReason: string; }
@@ -375,6 +376,35 @@ export async function forwardToAnthropicStream(augmentReq: any, res: any) {
375376
}
376377
interceptedTools.push({ tc, interceptResult: interceptResult.result });
377378
log(`[LOOP] Tool ${tc.name} intercepted locally`);
379+
} else if (!interceptResult && state.toolRegistry?.isIntercepted(tc.name)) {
380+
// v3.4.0: 新工具通过 ToolRegistry 异步拦截
381+
try {
382+
const context = {
383+
workspacePath: workspaceInfo?.workspacePath || '',
384+
repositoryRoot: workspaceInfo?.repositoryRoot || workspaceInfo?.workspacePath || '',
385+
cwd: workspaceInfo?.workspacePath || process.cwd(),
386+
conversationId: workspaceInfo?.conversationId || 'default',
387+
};
388+
const toolResult = await state.toolRegistry.execute(tc.name, input, context);
389+
if (toolResult) {
390+
let resultContent = JSON.stringify(toolResult);
391+
if (resultContent.length > TOOL_RESULT_SIZE_LIMIT) {
392+
resultContent = resultContent.slice(0, TOOL_RESULT_SIZE_LIMIT) + '\n[...truncated]';
393+
}
394+
interceptedTools.push({ tc, interceptResult: toolResult });
395+
log(`[LOOP] Tool ${tc.name} intercepted via ToolRegistry`);
396+
} else {
397+
nonInterceptedTools.push({ tc, toolNode: {
398+
type: 5, tool_use: {
399+
tool_use_id: tc.id, tool_name: tc.name,
400+
input_json: JSON.stringify(input)
401+
}
402+
}});
403+
}
404+
} catch (e: any) {
405+
log(`[LOOP] ToolRegistry error for ${tc.name}: ${e.message}`);
406+
interceptedTools.push({ tc, interceptResult: { success: false, error: e.message } });
407+
}
378408
} else {
379409
nonInterceptedTools.push({ tc, toolNode: {
380410
type: 5, tool_use: {

0 commit comments

Comments
 (0)