feat: add file ref expansion syntax for file_patch and file_write
This commit is contained in:
@@ -23,11 +23,11 @@
|
|||||||
"parameters": {"type": "object", "properties": {
|
"parameters": {"type": "object", "properties": {
|
||||||
"path": {"type": "string", "description": "文件路径。"},
|
"path": {"type": "string", "description": "文件路径。"},
|
||||||
"old_content": {"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": {
|
{"type": "function", "function": {
|
||||||
"name": "file_write",
|
"name": "file_write",
|
||||||
"description": "用于文件的新建、全量覆盖或追加写入。对于精细的代码修改,应优先使用 file_patch。注意:要写入的内容必须放在回复正文的 <file_content> 标签或代码块中。",
|
"description": "用于文件的新建、全量覆盖或追加写入。对于精细的代码修改,应优先使用 file_patch。注意:要写入的内容必须放在回复正文的 <file_content> 标签或代码块中。写入内容支持 {{file:路径:起始行:结束行}} 语法引用文件片段,写入前自动展开。",
|
||||||
"parameters": {"type": "object", "properties": {
|
"parameters": {"type": "object", "properties": {
|
||||||
"path": {"type": "string", "description": "文件路径。"},
|
"path": {"type": "string", "description": "文件路径。"},
|
||||||
"mode": {"type": "string", "enum": ["overwrite", "append", "prepend"], "description": "写入模式覆盖、追加或在开头追加。", "default": "overwrite"}}, "required": ["path"]}
|
"mode": {"type": "string", "enum": ["overwrite", "append", "prepend"], "description": "写入模式覆盖、追加或在开头追加。", "default": "overwrite"}}, "required": ["path"]}
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
"name": "web_execute_js",
|
"name": "web_execute_js",
|
||||||
"description": "万能网页操控工具。通过执行 JavaScript 脚本实现对浏览器的完全控制(如点击、滚动、提取特定数据)。鼓励在有把握情况下(记忆中有selector/做法等)精准使用以减少web_scan调用。执行结果可选择保存到本地文件进行后续分析。",
|
"description": "万能网页操控工具。通过执行 JavaScript 脚本实现对浏览器的完全控制(如点击、滚动、提取特定数据)。鼓励在有把握情况下(记忆中有selector/做法等)精准使用以减少web_scan调用。执行结果可选择保存到本地文件进行后续分析。",
|
||||||
"parameters": {"type": "object", "properties": {
|
"parameters": {"type": "object", "properties": {
|
||||||
"script": {"type": "string", "description": "要执行的 JavaScript 代码或JS文件路径。"},
|
"script": {"type": "string", "description": "要执行的代码或JS文件路径。"},
|
||||||
"save_to_file": {"type": "string", "description": "结果存文件,适合返回值较长时。不支持await。", "default": ""},
|
"save_to_file": {"type": "string", "description": "结果存文件,适合返回值较长时。不支持await。", "default": ""},
|
||||||
"no_monitor": {"type": "boolean", "description": "跳过页面变更监控,省2-3秒。仅在纯读取信息时设置,页面操作时不要设置。", "default": false}}, "required": ["script"]}
|
"no_monitor": {"type": "boolean", "description": "跳过页面变更监控,省2-3秒。仅在纯读取信息时设置,页面操作时不要设置。", "default": false}}, "required": ["script"]}
|
||||||
}},
|
}},
|
||||||
|
|||||||
23
ga.py
23
ga.py
@@ -179,13 +179,26 @@ def web_execute_js(script, switch_tab_id=None, no_monitor=False):
|
|||||||
global driver
|
global driver
|
||||||
try:
|
try:
|
||||||
if driver is None: first_init_driver()
|
if driver is None: first_init_driver()
|
||||||
if len(driver.get_all_sessions()) == 0:
|
if len(driver.get_all_sessions()) == 0: return {"status": "error", "msg": "没有可用的浏览器标签页,查L3记忆分析原因。"}
|
||||||
return {"status": "error", "msg": "没有可用的浏览器标签页,查L3记忆分析原因。"}
|
|
||||||
if switch_tab_id: driver.default_session_id = switch_tab_id
|
if switch_tab_id: driver.default_session_id = switch_tab_id
|
||||||
result = execute_js_rich(script, driver, no_monitor=no_monitor)
|
result = execute_js_rich(script, driver, no_monitor=no_monitor)
|
||||||
return result
|
return result
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return {"status": "error", "msg": format_error(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):
|
def file_patch(path: str, old_content: str, new_content: str):
|
||||||
"""在文件中寻找唯一的 old_content 块并替换为 new_content。
|
"""在文件中寻找唯一的 old_content 块并替换为 new_content。
|
||||||
@@ -343,6 +356,10 @@ class GenericAgentHandler(BaseHandler):
|
|||||||
yield f"[Action] Patching file: {path}\n"
|
yield f"[Action] Patching file: {path}\n"
|
||||||
old_content = args.get("old_content", "")
|
old_content = args.get("old_content", "")
|
||||||
new_content = args.get("new_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)
|
result = file_patch(path, old_content, new_content)
|
||||||
yield f"\n{smart_format(result)}\n"
|
yield f"\n{smart_format(result)}\n"
|
||||||
next_prompt = self._get_anchor_prompt()
|
next_prompt = self._get_anchor_prompt()
|
||||||
@@ -368,8 +385,8 @@ class GenericAgentHandler(BaseHandler):
|
|||||||
if not blocks:
|
if not blocks:
|
||||||
yield f"[Status] ❌ 失败: 未在回复中找到代码块内容\n"
|
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")
|
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:
|
try:
|
||||||
|
new_content = expand_file_refs(blocks, base_dir=self.cwd)
|
||||||
if mode == "prepend":
|
if mode == "prepend":
|
||||||
old = open(path, 'r', encoding="utf-8").read() if os.path.exists(path) else ""
|
old = open(path, 'r', encoding="utf-8").read() if os.path.exists(path) else ""
|
||||||
open(path, 'w', encoding="utf-8").write(new_content + old)
|
open(path, 'w', encoding="utf-8").write(new_content + old)
|
||||||
|
|||||||
Reference in New Issue
Block a user