feat: add file ref expansion syntax for file_patch and file_write

This commit is contained in:
Jiaqing Liang
2026-03-23 13:50:11 +08:00
parent 66dff812fb
commit 47e9a55feb
2 changed files with 23 additions and 6 deletions

View File

@@ -23,11 +23,11 @@
"parameters": {"type": "object", "properties": {
"path": {"type": "string", "description": "文件路径。"},
"old_content": {"type": "string", "description": "文件中需要被替换的原始文本块(需确保唯一性)。"},
"new_content": {"type": "string", "description": "替换后的新文本内容。"}}, "required": ["path", "old_content", "new_content"]}
"new_content": {"type": "string", "description": "替换后的新文本内容。支持 {{file:路径:起始行:结束行}} 语法引用文件内容,写入前自动展开。"}}, "required": ["path", "old_content", "new_content"]}
}},
{"type": "function", "function": {
"name": "file_write",
"description": "用于文件的新建、全量覆盖或追加写入。对于精细的代码修改,应优先使用 file_patch。注意要写入的内容必须放在回复正文的 <file_content> 标签或代码块中。",
"description": "用于文件的新建、全量覆盖或追加写入。对于精细的代码修改,应优先使用 file_patch。注意要写入的内容必须放在回复正文的 <file_content> 标签或代码块中。写入内容支持 {{file:路径:起始行:结束行}} 语法引用文件片段,写入前自动展开。",
"parameters": {"type": "object", "properties": {
"path": {"type": "string", "description": "文件路径。"},
"mode": {"type": "string", "enum": ["overwrite", "append", "prepend"], "description": "写入模式覆盖、追加或在开头追加。", "default": "overwrite"}}, "required": ["path"]}
@@ -44,7 +44,7 @@
"name": "web_execute_js",
"description": "万能网页操控工具。通过执行 JavaScript 脚本实现对浏览器的完全控制如点击、滚动、提取特定数据。鼓励在有把握情况下记忆中有selector/做法等精准使用以减少web_scan调用。执行结果可选择保存到本地文件进行后续分析。",
"parameters": {"type": "object", "properties": {
"script": {"type": "string", "description": "要执行的 JavaScript 代码或JS文件路径。"},
"script": {"type": "string", "description": "要执行的代码或JS文件路径。"},
"save_to_file": {"type": "string", "description": "结果存文件适合返回值较长时。不支持await。", "default": ""},
"no_monitor": {"type": "boolean", "description": "跳过页面变更监控省2-3秒。仅在纯读取信息时设置页面操作时不要设置。", "default": false}}, "required": ["script"]}
}},

23
ga.py
View File

@@ -179,13 +179,26 @@ def web_execute_js(script, switch_tab_id=None, no_monitor=False):
global driver
try:
if driver is None: first_init_driver()
if len(driver.get_all_sessions()) == 0:
return {"status": "error", "msg": "没有可用的浏览器标签页查L3记忆分析原因。"}
if len(driver.get_all_sessions()) == 0: return {"status": "error", "msg": "没有可用的浏览器标签页查L3记忆分析原因。"}
if switch_tab_id: driver.default_session_id = switch_tab_id
result = execute_js_rich(script, driver, no_monitor=no_monitor)
return result
except Exception as e:
return {"status": "error", "msg": format_error(e)}
def expand_file_refs(text, base_dir=None):
"""展开文本中的 {{file:路径:起始行:结束行}} 引用为实际文件内容。
可与普通文本混排。展开失败抛 ValueError。
base_dir: 相对路径的基准目录,默认为进程 cwd。"""
pattern = r'\{\{file:(.+?):(\d+):(\d+)\}\}'
def replacer(match):
path, start, end = match.group(1), int(match.group(2)), int(match.group(3))
path = os.path.abspath(os.path.join(base_dir or '.', path))
if not os.path.isfile(path): raise ValueError(f"引用文件不存在: {path}")
with open(path, 'r', encoding='utf-8') as f: lines = f.readlines()
if start < 1 or end > len(lines) or start > end: raise ValueError(f"行号越界: {path}{len(lines)}行, 请求{start}-{end}")
return ''.join(lines[start-1:end])
return re.sub(pattern, replacer, text)
def file_patch(path: str, old_content: str, new_content: str):
"""在文件中寻找唯一的 old_content 块并替换为 new_content。
@@ -343,6 +356,10 @@ class GenericAgentHandler(BaseHandler):
yield f"[Action] Patching file: {path}\n"
old_content = args.get("old_content", "")
new_content = args.get("new_content", "")
try: new_content = expand_file_refs(new_content, base_dir=self.cwd)
except ValueError as e:
yield f"[Status] ❌ 引用展开失败: {e}\n"
return StepOutcome({"status": "error", "msg": str(e)}, next_prompt="\n")
result = file_patch(path, old_content, new_content)
yield f"\n{smart_format(result)}\n"
next_prompt = self._get_anchor_prompt()
@@ -368,8 +385,8 @@ class GenericAgentHandler(BaseHandler):
if not blocks:
yield f"[Status] ❌ 失败: 未在回复中找到代码块内容\n"
return StepOutcome({"status": "error", "msg": "No content found, if you want a blank, you should use code_run"}, next_prompt="\n")
new_content = blocks
try:
new_content = expand_file_refs(blocks, base_dir=self.cwd)
if mode == "prepend":
old = open(path, 'r', encoding="utf-8").read() if os.path.exists(path) else ""
open(path, 'w', encoding="utf-8").write(new_content + old)