|
|
@@ -73,6 +73,70 @@ TOOL_REGISTRY = _build_tool_registry()
|
|
|
# 解析 & 执行引擎 (v2)
|
|
|
# ════════════════════════════════════════════════════════════
|
|
|
|
|
|
+def _extract_json_blocks(text: str) -> list[str]:
|
|
|
+ """Extract JSON code blocks, handling nested ``` in content.
|
|
|
+
|
|
|
+ Strategy: find ```json markers, then find the matching ``` closer
|
|
|
+ by looking for a ``` that is followed by a newline or end-of-string
|
|
|
+ (not inside a JSON string value).
|
|
|
+ """
|
|
|
+ blocks = []
|
|
|
+ i = 0
|
|
|
+ marker = "```json"
|
|
|
+ while i < len(text):
|
|
|
+ start = text.find(marker, i)
|
|
|
+ if start == -1:
|
|
|
+ break
|
|
|
+ # Skip past the marker and optional newline
|
|
|
+ content_start = start + len(marker)
|
|
|
+ if content_start < len(text) and text[content_start] == '\n':
|
|
|
+ content_start += 1
|
|
|
+
|
|
|
+ # Find the closing ``` — look for ``` at start of line or after newline
|
|
|
+ # that's NOT inside a JSON string
|
|
|
+ best_end = -1
|
|
|
+ j = content_start
|
|
|
+ while j < len(text):
|
|
|
+ pos = text.find("```", j)
|
|
|
+ if pos == -1:
|
|
|
+ break
|
|
|
+ # Check if this ``` is a real closer (not inside JSON string content)
|
|
|
+ # Heuristic: if the text between content_start and pos contains
|
|
|
+ # a valid-looking JSON with "action", it's the closer
|
|
|
+ candidate = text[content_start:pos].strip()
|
|
|
+ if candidate and ('"action"' in candidate or '"tool"' in candidate):
|
|
|
+ try:
|
|
|
+ # Try to parse — if it works, this is the right closer
|
|
|
+ _try_parse_json(candidate)
|
|
|
+ best_end = pos
|
|
|
+ break
|
|
|
+ except Exception:
|
|
|
+ pass
|
|
|
+ # Even if parse fails, if it looks like JSON, take it
|
|
|
+ if candidate.startswith("{") and candidate.rstrip().endswith("}"):
|
|
|
+ best_end = pos
|
|
|
+ break
|
|
|
+ j = pos + 3
|
|
|
+
|
|
|
+ if best_end != -1:
|
|
|
+ block = text[content_start:best_end].strip()
|
|
|
+ if block:
|
|
|
+ blocks.append(block)
|
|
|
+ i = best_end + 3
|
|
|
+ else:
|
|
|
+ # Fallback: take everything until the next ```
|
|
|
+ end = text.find("```", content_start)
|
|
|
+ if end != -1:
|
|
|
+ block = text[content_start:end].strip()
|
|
|
+ if block:
|
|
|
+ blocks.append(block)
|
|
|
+ i = end + 3
|
|
|
+ else:
|
|
|
+ break
|
|
|
+
|
|
|
+ return blocks
|
|
|
+
|
|
|
+
|
|
|
def _try_parse_json(json_str: str) -> dict | None:
|
|
|
"""Try multiple strategies to parse potentially malformed JSON from LLM output."""
|
|
|
# Strategy 1: direct parse
|
|
|
@@ -158,8 +222,10 @@ def parse_and_execute(llm_output: str, hooks: HookRegistry = None) -> tuple[str,
|
|
|
v2: 统一 action/tool 格式,支持内置工具 + macOS 工具
|
|
|
策略:尝试所有 ```json 块(从第一个开始),找到第一个可执行的
|
|
|
"""
|
|
|
- # 尝试从 ```json ... ``` 代码块提取(可能有多个)
|
|
|
- matches = re.findall(r'```json\s*\n?(.*?)\n?```', llm_output, re.DOTALL)
|
|
|
+ # 尝试从 ```json ... ``` 代码块提取
|
|
|
+ # 使用贪婪匹配找到最外层的 ```json...``` 对
|
|
|
+ # 注意:内容中可能含有 ``` 标记(如 markdown 代码块),需要特殊处理
|
|
|
+ matches = _extract_json_blocks(llm_output)
|
|
|
if not matches:
|
|
|
# fallback: 找内联 JSON
|
|
|
matches = re.findall(r'\{[^{}]*(?:"action"|"tool")\s*:.*?\}', llm_output, re.DOTALL)
|