"""引擎适配 — FC为主,prompt-JSON保底""" import json, re, uuid from .types import EngineResponse, ToolCall class EngineAdapter: def __init__(self, engine): self._engine = engine self._fc = hasattr(engine, 'generate_with_tools') def supports_fc(self): return self._fc def name(self): return getattr(self._engine, 'name', 'unknown') def generate(self, messages, tools=None, system_prompt=None): if self._fc and tools: return self._gen_fc(messages, tools, system_prompt) return self._gen_legacy(messages, system_prompt) def _gen_fc(self, messages, tools, system_prompt): try: raw = self._engine.generate_with_tools(messages, tools, system_prompt=system_prompt) text = "" thinking = "" tool_calls = None if isinstance(raw, dict): text = raw.get("content", "") or "" thinking = raw.get("reasoning_content", "") or "" tc_list = raw.get("tool_calls", []) or [] if tc_list: tool_calls = [] for tc in tc_list: func = tc.get("function", {}) cid = tc.get("id", str(uuid.uuid4())[:8]) name = func.get("name", "") args_str = func.get("arguments", "{}") arguments = None try: if isinstance(args_str, str) and args_str: arguments = json.loads(args_str) except json.JSONDecodeError: pass args = self._to_legacy(name, arguments) if arguments else (args_str or "") tool_calls.append(ToolCall(id=cid, name=name, args=args, arguments=arguments, raw=tc)) return EngineResponse(text=text, thinking=thinking, tool_calls=tool_calls, raw=raw) except Exception as e: return EngineResponse(text=f"引擎调用失败: {e}") def _gen_legacy(self, messages, system_prompt): user_input = "" for msg in reversed(messages): if msg.get("role") == "user": user_input = msg.get("content", "") break try: resp = self._engine.generate_response(user_input, system_prompt=system_prompt) except Exception as e: return EngineResponse(text=f"引擎调用失败: {e}") if not resp: return EngineResponse() text, thinking = self._strip_thinking(resp) return EngineResponse(text=text, thinking=thinking, raw=resp) def _strip_thinking(self, text): if not text: return "", "" thinking = "" if "" in text and "" in text: m = re.search(r'(.*?)(.*)', text, re.DOTALL) if m: thinking = m.group(1).strip() text = m.group(2).strip() start = getattr(self._engine, 'thinking_start_marker', None) end = getattr(self._engine, 'thinking_end_marker', None) if start and end and start in text and end in text: parts = text.split(start) if len(parts) >= 2: rest = parts[1] if end in rest: tp, rp = rest.split(end, 1) thinking = (thinking + "\n" + tp.strip()).strip() if thinking else tp.strip() text = (parts[0] + rp).strip() return text, thinking def _to_legacy(self, tool_name, args): """FC dict参数 → 旧插件字符串参数""" if not args: return "" if tool_name == "ai_search": url = args.get("url", "") q = args.get("query", args.get("search", "")) if url: return f"url {url}" if q: return f"search {q}" if tool_name == "network_tools": op = args.get("operation", args.get("op", "get")) t = args.get("target", args.get("url", args.get("host", ""))) if op and t: return f"{op} {t}" if tool_name == "code_editor": op = args.get("operation", args.get("action", "")) fp = args.get("file", args.get("path", args.get("filename", ""))) ct = args.get("content", args.get("text", args.get("code", ""))) old = args.get("old", args.get("old_code", args.get("find", ""))) new = args.get("new", args.get("new_code", args.get("replace", ""))) ln = args.get("line", args.get("line_number", "")) sl = args.get("start", args.get("start_line", "")) el = args.get("end", args.get("end_line", "")) pat = args.get("pattern", args.get("file_pattern", "")) d = args.get("directory", args.get("dir", ".")) if op == "create" and fp and ct: return f"create {fp}\n{ct}" if op == "write" and fp and ct: return f"write {fp}\n{ct}" if op == "append" and fp and ct: return f"append {fp}\n{ct}" if op in ("read_range", "read"): return f"read_range {fp} {sl or 1} {el or 50}" if op == "edit": return f"edit {fp}\n{old}\n<<<>>>\n{new}" if op == "insert": return f"insert {fp} {ln}\n{ct}" if op in ("find", "search"): return f"find {pat} {d}" if op == "syntax_check": return f"syntax_check {fp}" return json.dumps(args, ensure_ascii=False) if tool_name == "code_search": op = args.get("operation", "find") pat = args.get("pattern", args.get("query", args.get("search", ""))) d = args.get("directory", args.get("dir", ".")) return f"{op} {pat} {d}" if tool_name == "git_tools": op = args.get("operation", "status") a = args.get("args", "") return f"{op} {a}" if a else op if tool_name == "cmd_executor": return args.get("command", args.get("cmd", args.get("shell", ""))) if tool_name == "file_manager": op = args.get("operation", args.get("action", "list")) p = args.get("path", args.get("file", ".")) ct = args.get("content", args.get("text", "")) if op in ("read", "list", "info"): return f"{op} {p}" if op == "write" and ct: return f'write {p} -c "{ct}"' if op == "append" and ct: return f'append {p} -c "{ct}"' if op and p: return f"{op} {p}" if tool_name == "browser_auto": op = args.get("operation", "") url = args.get("url", "") if op == "open" and url: return f"open {url}" if tool_name == "auto_engineer": op = args.get("operation", "") t = args.get("target", args.get("path", "")) if op and t: return f"{op} {t}" return op if tool_name == "sub_agent": op = args.get("operation", "list") if op == "create": task = args.get("task", args.get("prompt", "")) return f"create {task}" return op if tool_name in ("task_manager", "scheduler"): return args.get("operation", "list") # 通用 if "operation" in args: op = args["operation"] rest = " ".join(str(v) for k, v in args.items() if k != "operation") return f"{op} {rest}".strip() return " ".join(str(v) for v in args.values()) def handle_command(self, cmd): if hasattr(self._engine, 'handle_command'): return self._engine.handle_command(cmd) return False def get_raw(self): return self._engine