@zqagent/core 0.2.20 → 0.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (238) hide show
  1. package/README.md +1 -1
  2. package/README.oss.en.md +3 -3
  3. package/README.oss.md +8 -3
  4. package/README.oss.zh-TW.md +3 -3
  5. package/dist/McpClient-CEQBUPuC.d.cts +145 -0
  6. package/dist/McpClient-CEQBUPuC.d.ts +145 -0
  7. package/dist/{ToolRegistry-C3pTFiaJ.d.cts → ToolRegistry-Cy0fQxrb.d.ts} +2 -111
  8. package/dist/{ToolRegistry-C3pTFiaJ.d.ts → ToolRegistry-DouDvn9E.d.cts} +2 -111
  9. package/dist/{explore-RtZtb-V6.d.ts → TurnEngine-BPKhWuSI.d.cts} +108 -73
  10. package/dist/{explore-BgRhj6SP.d.cts → TurnEngine-DtGEuVLT.d.ts} +108 -73
  11. package/dist/conflictGuard-BBOOIxxK.d.cts +42 -0
  12. package/dist/conflictGuard-BBOOIxxK.d.ts +42 -0
  13. package/dist/index.cjs +5418 -1096
  14. package/dist/index.cjs.map +1 -1
  15. package/dist/index.d.cts +577 -195
  16. package/dist/index.d.ts +577 -195
  17. package/dist/index.js +5299 -1011
  18. package/dist/index.js.map +1 -1
  19. package/dist/internal/crypto/secrets.cjs +25 -0
  20. package/dist/internal/crypto/secrets.cjs.map +1 -0
  21. package/dist/internal/crypto/secrets.d.cts +2 -0
  22. package/dist/internal/crypto/secrets.d.ts +2 -0
  23. package/dist/internal/crypto/secrets.js +3 -0
  24. package/dist/internal/crypto/secrets.js.map +1 -0
  25. package/dist/internal/engine/FileLogHook.cjs +179 -0
  26. package/dist/internal/engine/FileLogHook.cjs.map +1 -0
  27. package/dist/internal/engine/FileLogHook.d.cts +67 -0
  28. package/dist/internal/engine/FileLogHook.d.ts +67 -0
  29. package/dist/internal/engine/FileLogHook.js +144 -0
  30. package/dist/internal/engine/FileLogHook.js.map +1 -0
  31. package/dist/internal/engine/approvalView.cjs +25 -0
  32. package/dist/internal/engine/approvalView.cjs.map +1 -0
  33. package/dist/internal/engine/approvalView.d.cts +1 -0
  34. package/dist/internal/engine/approvalView.d.ts +1 -0
  35. package/dist/internal/engine/approvalView.js +3 -0
  36. package/dist/internal/engine/approvalView.js.map +1 -0
  37. package/dist/internal/engine/limits.cjs +59 -0
  38. package/dist/internal/engine/limits.cjs.map +1 -0
  39. package/dist/internal/engine/limits.d.cts +32 -0
  40. package/dist/internal/engine/limits.d.ts +32 -0
  41. package/dist/internal/engine/limits.js +31 -0
  42. package/dist/internal/engine/limits.js.map +1 -0
  43. package/dist/internal/engine/sessionNaming.cjs +47 -0
  44. package/dist/internal/engine/sessionNaming.cjs.map +1 -0
  45. package/dist/internal/engine/sessionNaming.d.cts +25 -0
  46. package/dist/internal/engine/sessionNaming.d.ts +25 -0
  47. package/dist/internal/engine/sessionNaming.js +20 -0
  48. package/dist/internal/engine/sessionNaming.js.map +1 -0
  49. package/dist/internal/llmConfig.cjs +561 -0
  50. package/dist/internal/llmConfig.cjs.map +1 -0
  51. package/dist/internal/llmConfig.d.cts +75 -0
  52. package/dist/internal/llmConfig.d.ts +75 -0
  53. package/dist/internal/llmConfig.js +535 -0
  54. package/dist/internal/llmConfig.js.map +1 -0
  55. package/dist/internal/mcp/builtin/index.cjs +685 -0
  56. package/dist/internal/mcp/builtin/index.cjs.map +1 -0
  57. package/dist/internal/mcp/builtin/index.d.cts +21 -0
  58. package/dist/internal/mcp/builtin/index.d.ts +21 -0
  59. package/dist/internal/mcp/builtin/index.js +652 -0
  60. package/dist/internal/mcp/builtin/index.js.map +1 -0
  61. package/dist/internal/mcp/config.cjs +278 -0
  62. package/dist/internal/mcp/config.cjs.map +1 -0
  63. package/dist/internal/mcp/config.d.cts +61 -0
  64. package/dist/internal/mcp/config.d.ts +61 -0
  65. package/dist/internal/mcp/config.js +235 -0
  66. package/dist/internal/mcp/config.js.map +1 -0
  67. package/dist/internal/mcp/presets.cjs +184 -0
  68. package/dist/internal/mcp/presets.cjs.map +1 -0
  69. package/dist/internal/mcp/presets.d.cts +39 -0
  70. package/dist/internal/mcp/presets.d.ts +39 -0
  71. package/dist/internal/mcp/presets.js +158 -0
  72. package/dist/internal/mcp/presets.js.map +1 -0
  73. package/dist/internal/node-name.cjs +45 -0
  74. package/dist/internal/node-name.cjs.map +1 -0
  75. package/dist/internal/node-name.d.cts +3 -0
  76. package/dist/internal/node-name.d.ts +3 -0
  77. package/dist/internal/node-name.js +10 -0
  78. package/dist/internal/node-name.js.map +1 -0
  79. package/dist/internal/paths.cjs +63 -0
  80. package/dist/internal/paths.cjs.map +1 -0
  81. package/dist/internal/paths.d.cts +17 -0
  82. package/dist/internal/paths.d.ts +17 -0
  83. package/dist/internal/paths.js +25 -0
  84. package/dist/internal/paths.js.map +1 -0
  85. package/dist/internal/providers/diagnostics.cjs +288 -0
  86. package/dist/internal/providers/diagnostics.cjs.map +1 -0
  87. package/dist/internal/providers/diagnostics.d.cts +103 -0
  88. package/dist/internal/providers/diagnostics.d.ts +103 -0
  89. package/dist/internal/providers/diagnostics.js +250 -0
  90. package/dist/internal/providers/diagnostics.js.map +1 -0
  91. package/dist/internal/providers/models.cjs +699 -0
  92. package/dist/internal/providers/models.cjs.map +1 -0
  93. package/dist/internal/providers/models.d.cts +136 -0
  94. package/dist/internal/providers/models.d.ts +136 -0
  95. package/dist/internal/providers/models.js +654 -0
  96. package/dist/internal/providers/models.js.map +1 -0
  97. package/dist/internal/terminal.cjs +161 -0
  98. package/dist/internal/terminal.cjs.map +1 -0
  99. package/dist/internal/terminal.d.cts +58 -0
  100. package/dist/internal/terminal.d.ts +58 -0
  101. package/dist/internal/terminal.js +128 -0
  102. package/dist/internal/terminal.js.map +1 -0
  103. package/dist/internal/tools/execAsync.cjs +187 -0
  104. package/dist/internal/tools/execAsync.cjs.map +1 -0
  105. package/dist/internal/tools/execAsync.d.cts +30 -0
  106. package/dist/internal/tools/execAsync.d.ts +30 -0
  107. package/dist/internal/tools/execAsync.js +162 -0
  108. package/dist/internal/tools/execAsync.js.map +1 -0
  109. package/dist/internal/tools/git.cjs +935 -0
  110. package/dist/internal/tools/git.cjs.map +1 -0
  111. package/dist/internal/tools/git.d.cts +206 -0
  112. package/dist/internal/tools/git.d.ts +206 -0
  113. package/dist/internal/tools/git.js +883 -0
  114. package/dist/internal/tools/git.js.map +1 -0
  115. package/dist/internal/tools/pathSafety.cjs +99 -0
  116. package/dist/internal/tools/pathSafety.cjs.map +1 -0
  117. package/dist/internal/tools/pathSafety.d.cts +44 -0
  118. package/dist/internal/tools/pathSafety.d.ts +44 -0
  119. package/dist/internal/tools/pathSafety.js +61 -0
  120. package/dist/internal/tools/pathSafety.js.map +1 -0
  121. package/dist/internal/tools/plan.cjs +146 -0
  122. package/dist/internal/tools/plan.cjs.map +1 -0
  123. package/dist/internal/tools/plan.d.cts +48 -0
  124. package/dist/internal/tools/plan.d.ts +48 -0
  125. package/dist/internal/tools/plan.js +117 -0
  126. package/dist/internal/tools/plan.js.map +1 -0
  127. package/dist/internal/tools/ripgrep.cjs +118 -0
  128. package/dist/internal/tools/ripgrep.cjs.map +1 -0
  129. package/dist/internal/tools/ripgrep.d.cts +6 -0
  130. package/dist/internal/tools/ripgrep.d.ts +6 -0
  131. package/dist/internal/tools/ripgrep.js +89 -0
  132. package/dist/internal/tools/ripgrep.js.map +1 -0
  133. package/dist/internal/tools/sandbox.cjs +253 -0
  134. package/dist/internal/tools/sandbox.cjs.map +1 -0
  135. package/dist/internal/tools/sandbox.d.cts +67 -0
  136. package/dist/internal/tools/sandbox.d.ts +67 -0
  137. package/dist/internal/tools/sandbox.js +213 -0
  138. package/dist/internal/tools/sandbox.js.map +1 -0
  139. package/dist/internal/tools/shell.cjs +771 -0
  140. package/dist/internal/tools/shell.cjs.map +1 -0
  141. package/dist/internal/tools/shell.d.cts +47 -0
  142. package/dist/internal/tools/shell.d.ts +47 -0
  143. package/dist/internal/tools/shell.js +731 -0
  144. package/dist/internal/tools/shell.js.map +1 -0
  145. package/dist/internal/tools/shellRuntime.cjs +220 -0
  146. package/dist/internal/tools/shellRuntime.cjs.map +1 -0
  147. package/dist/internal/tools/shellRuntime.d.cts +67 -0
  148. package/dist/internal/tools/shellRuntime.d.ts +67 -0
  149. package/dist/internal/tools/shellRuntime.js +181 -0
  150. package/dist/internal/tools/shellRuntime.js.map +1 -0
  151. package/dist/internal/update/controller.cjs +1035 -0
  152. package/dist/internal/update/controller.cjs.map +1 -0
  153. package/dist/internal/update/controller.d.cts +150 -0
  154. package/dist/internal/update/controller.d.ts +150 -0
  155. package/dist/internal/update/controller.js +991 -0
  156. package/dist/internal/update/controller.js.map +1 -0
  157. package/dist/internal/update/platform.cjs +234 -0
  158. package/dist/internal/update/platform.cjs.map +1 -0
  159. package/dist/internal/update/platform.d.cts +77 -0
  160. package/dist/internal/update/platform.d.ts +77 -0
  161. package/dist/internal/update/platform.js +184 -0
  162. package/dist/internal/update/platform.js.map +1 -0
  163. package/dist/tools/ask.d.cts +3 -1
  164. package/dist/tools/ask.d.ts +3 -1
  165. package/dist/tools/diff.cjs +17 -6
  166. package/dist/tools/diff.cjs.map +1 -1
  167. package/dist/tools/diff.d.cts +5 -2
  168. package/dist/tools/diff.d.ts +5 -2
  169. package/dist/tools/diff.js +17 -6
  170. package/dist/tools/diff.js.map +1 -1
  171. package/dist/tools/directories.d.cts +3 -1
  172. package/dist/tools/directories.d.ts +3 -1
  173. package/dist/tools/explore.cjs +1778 -204
  174. package/dist/tools/explore.cjs.map +1 -1
  175. package/dist/tools/explore.d.cts +82 -2
  176. package/dist/tools/explore.d.ts +82 -2
  177. package/dist/tools/explore.js +1780 -204
  178. package/dist/tools/explore.js.map +1 -1
  179. package/dist/tools/files.cjs +56 -21
  180. package/dist/tools/files.cjs.map +1 -1
  181. package/dist/tools/files.d.cts +6 -3
  182. package/dist/tools/files.d.ts +6 -3
  183. package/dist/tools/files.js +56 -21
  184. package/dist/tools/files.js.map +1 -1
  185. package/dist/tools/git.cjs +34 -25
  186. package/dist/tools/git.cjs.map +1 -1
  187. package/dist/tools/git.d.cts +4 -204
  188. package/dist/tools/git.d.ts +4 -204
  189. package/dist/tools/git.js +34 -25
  190. package/dist/tools/git.js.map +1 -1
  191. package/dist/tools/media.cjs +280 -3
  192. package/dist/tools/media.cjs.map +1 -1
  193. package/dist/tools/media.d.cts +3 -1
  194. package/dist/tools/media.d.ts +3 -1
  195. package/dist/tools/media.js +278 -1
  196. package/dist/tools/media.js.map +1 -1
  197. package/dist/tools/plan.cjs +62 -0
  198. package/dist/tools/plan.cjs.map +1 -1
  199. package/dist/tools/plan.d.cts +4 -17
  200. package/dist/tools/plan.d.ts +4 -17
  201. package/dist/tools/plan.js +59 -0
  202. package/dist/tools/plan.js.map +1 -1
  203. package/dist/tools/search.cjs +3 -0
  204. package/dist/tools/search.cjs.map +1 -1
  205. package/dist/tools/search.d.cts +3 -1
  206. package/dist/tools/search.d.ts +3 -1
  207. package/dist/tools/search.js +3 -0
  208. package/dist/tools/search.js.map +1 -1
  209. package/dist/tools/shell.cjs +76 -11
  210. package/dist/tools/shell.cjs.map +1 -1
  211. package/dist/tools/shell.d.cts +5 -39
  212. package/dist/tools/shell.d.ts +5 -39
  213. package/dist/tools/shell.js +75 -11
  214. package/dist/tools/shell.js.map +1 -1
  215. package/dist/tools/todo.d.cts +3 -1
  216. package/dist/tools/todo.d.ts +3 -1
  217. package/dist/tools/web.cjs +279 -5
  218. package/dist/tools/web.cjs.map +1 -1
  219. package/dist/tools/web.d.cts +3 -1
  220. package/dist/tools/web.d.ts +3 -1
  221. package/dist/tools/web.js +275 -3
  222. package/dist/tools/web.js.map +1 -1
  223. package/dist/types-BYyXWWPW.d.cts +237 -0
  224. package/dist/types-BYyXWWPW.d.ts +237 -0
  225. package/package.json +26 -37
  226. package/bin/zqagent +0 -2
  227. package/dist/cli/index.cjs +0 -25218
  228. package/dist/cli/index.cjs.map +0 -1
  229. package/dist/cli/index.d.cts +0 -2
  230. package/dist/cli/index.d.ts +0 -2
  231. package/dist/cli/index.js +0 -25201
  232. package/dist/cli/index.js.map +0 -1
  233. package/dist/server/index.cjs +0 -20510
  234. package/dist/server/index.cjs.map +0 -1
  235. package/dist/server/index.d.cts +0 -2
  236. package/dist/server/index.d.ts +0 -2
  237. package/dist/server/index.js +0 -20493
  238. package/dist/server/index.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/providers/proxy.ts","../../../../../node_modules/.pnpm/socks-proxy-agent@9.0.0_supports-color@7.2.0/node_modules/socks-proxy-agent/src/index.ts","../../../../../node_modules/.pnpm/agent-base@8.0.0/node_modules/agent-base/src/index.ts","../../../src/providers/diagnostics.ts","../../../src/providers/models.ts"],"sourcesContent":["/**\n * 代理出口(LLM 相关 HTTP 请求统一走这里)\n *\n * 支持 `SOCKS5_PROXY`(`socks5://` / `socks5h://`),供 node-fetch 使用:\n * - `fetchModels()`(/connect 刷新模型)→ node-fetch + agent;未配置时代理为 null。\n * - OpenAI SDK 的请求默认走 Node 内置 fetch(undici),undici **不读**\n * HTTP_PROXY/HTTPS_PROXY/ALL_PROXY 环境变量;因此配置了 SOCKS5 代理时,\n * 这里同时提供 `makeSocksFetch()`,把它注入 SDK 的 `fetch` 选项,\n * 保证「对话」与「刷新模型」走同一条网络路径(避免一边通一边不通)。\n */\nimport nodeFetch from \"node-fetch\";\nimport { SocksProxyAgent } from \"socks-proxy-agent\";\n\n/** SOCKS5 代理地址(环境变量 SOCKS5_PROXY),未设置时为 null */\nexport function socksProxyUrl(): string | null {\n const raw = (process.env.SOCKS5_PROXY ?? \"\").trim();\n return raw || null;\n}\n\n/** 是否为 socks 代理地址 */\nfunction isSocks(raw: string): boolean {\n return /^socks/i.test(raw);\n}\n\n/** 全局共享的 SOCKS5 Agent;未配置或构造失败时为 null(直连) */\nlet cached: { url: string; agent: SocksProxyAgent | null } | null = null;\n\nexport function socksAgent(): SocksProxyAgent | null {\n const url = socksProxyUrl();\n if (!url) return null;\n if (cached?.url === url) return cached.agent;\n let agent: SocksProxyAgent | null = null;\n if (isSocks(url)) {\n try {\n agent = new SocksProxyAgent(url);\n } catch {\n try {\n agent = new SocksProxyAgent(url.replace(/^socks5h:\\/\\//, \"socks5://\"));\n } catch {\n agent = null;\n }\n }\n }\n cached = { url, agent };\n return agent;\n}\n\n/**\n * 检测到「不支持的环境变量代理」时给出可执行提示(否则返回 null)。\n * undici/内置 fetch 不读这些变量,本项目也不引入 http(s) 代理 agent。\n */\nexport function unsupportedProxyHint(): string | null {\n const keys = [\"HTTPS_PROXY\", \"https_proxy\", \"HTTP_PROXY\", \"http_proxy\", \"ALL_PROXY\", \"all_proxy\"];\n const hit = keys.filter((k) => {\n const v = (process.env[k] ?? \"\").trim();\n return v && !isSocks(v);\n });\n if (hit.length === 0) return null;\n return `检测到 ${hit[0]}=${process.env[hit[0]]}(HTTP 代理暂不支持):请改为 SOCKS5_PROXY=socks5://<host>:<port>(或让本地代理开放 socks5 端口)`;\n}\n\n/** 用 node-fetch + socks agent 实现 fetch(保持与全局 fetch 相同的调用签名) */\nexport function makeSocksFetch(agent: SocksProxyAgent): typeof fetch {\n return ((url: unknown, init?: unknown) =>\n nodeFetch(String(url), { ...(init as object), agent } as never)) as unknown as typeof fetch;\n}\n",null,null,"/**\n * LLM 连接诊断与配置归一化\n *\n * 背景:CLI 在部分环境(如 openEuler 等国产 Linux、经企业网关/代理的网络)会\n * 只看到 `Connection error.`(OpenAI SDK 的 APIConnectionError),无法判断是\n * DNS、TLS、代理还是配置问题。这里提供:\n * - `cleanLlmValue` / `normalizeBaseURL`:清理 CRLF/引号/BOM、补协议、\n * 去掉 `/chat/completions` 之类的端点后缀(配置从其它系统/编辑器带进来时常见)\n * - `enrichLlmError`:把「底层 errno(沿 cause 链递归,穿透 SDK 包装)\n * + 实际请求端点」补进错误信息\n * - `networkErrorHints`:按错误给出可执行的排查建议(CLI 直接打印)\n */\n\n/** 是否连接/网络类错误(用于自动重试与提示) */\nexport function isNetworkErrorText(text: string): boolean {\n const t = text.toLowerCase();\n return [\n \"connection error\",\n \"fetch failed\",\n \"econnreset\",\n \"econnrefused\",\n \"etimedout\",\n \"ehostunreach\",\n \"enetunreach\",\n \"enotfound\",\n \"eai_again\",\n \"socket hang up\",\n \"und_err\",\n \"network\",\n \"timed out\",\n \"tls\",\n \"certificate\",\n \"unable_to_verify\",\n \"self signed\",\n ].some((m) => t.includes(m));\n}\n\n/**\n * 清理从配置文件/环境变量/粘贴进来的值:\n * BOM、首尾空白、换行与制表符、以及整体包裹的引号。\n * (Windows 记事本/CRLF、PowerShell `setx`、复制粘贴常见)\n */\nexport function cleanLlmValue(v?: string | null): string | undefined {\n if (v == null) return undefined;\n let s = String(v).replace(/^\\uFEFF/, \"\");\n s = s.replace(/[\\r\\n\\t]/g, \"\").trim();\n if (\n s.length >= 2 &&\n ((s.startsWith('\"') && s.endsWith('\"')) || (s.startsWith(\"'\") && s.endsWith(\"'\")))\n ) {\n s = s.slice(1, -1).trim();\n }\n return s || undefined;\n}\n\n/**\n * 规范化 Base URL:\n * - 缺协议时补 `https://`\n * - 去掉末尾 `/`\n * - 去掉误填的端点后缀(`/chat/completions`、`/responses`、`/completions`、`/models`)\n */\nexport function normalizeBaseURL(raw?: string | null): string | undefined {\n let s = cleanLlmValue(raw);\n if (!s) return undefined;\n if (!/^[a-z][a-z0-9+.-]*:\\/\\//i.test(s)) s = `https://${s}`;\n s = s.replace(/\\/+$/, \"\");\n s = s.replace(/\\/(chat\\/completions|completions|responses|models)$/i, \"\");\n return s.replace(/\\/+$/, \"\") || undefined;\n}\n\n/**\n * 是否为推理模型(o 系列 / gpt-5 等)。\n *\n * 这类模型在 Chat Completions 上**只接受** `max_completion_tokens`,传\n * `max_tokens` 会直接 400 `Unsupported parameter: 'max_tokens' is not\n * supported with this model`。Provider 与模型自检(`probeModel`)必须共用\n * 这一判断:一旦两处各写一份,自检就会把可用的推理模型误判成不可用。\n */\nexport function isReasoningModel(model: string): boolean {\n return /^(o\\d|gpt-5)/i.test(model);\n}\n\nfunction asRecord(v: unknown): Record<string, unknown> | null {\n return v && typeof v === \"object\" ? (v as Record<string, unknown>) : null;\n}\n\n/**\n * 展开错误链:`err` 本身 → `cause` → `cause.cause`…\n * 同时展开 `AggregateError.errors`(IPv4/IPv6 双栈都连不上时 undici 会这样抛)。\n * 带已访问集合,防止 cause 循环引用死循环。\n */\nfunction causeChain(err: unknown, maxNodes = 6): Record<string, unknown>[] {\n const out: Record<string, unknown>[] = [];\n const seen = new Set<unknown>();\n const queue: unknown[] = [err];\n while (queue.length > 0 && out.length < maxNodes) {\n const cur = queue.shift();\n if (!cur || typeof cur !== \"object\" || seen.has(cur)) continue;\n seen.add(cur);\n const rec = asRecord(cur);\n if (!rec) continue;\n out.push(rec);\n if (rec[\"cause\"]) queue.push(rec[\"cause\"]);\n const errs = rec[\"errors\"];\n if (Array.isArray(errs)) queue.push(...errs);\n }\n return out;\n}\n\n/**\n * 形如错误码的 name(`ECONNRESET`、`UND_ERR_CONNECT_TIMEOUT`)。\n * 内建/包装类的 name(`TypeError`、`Error`、`APIConnectionError`)不是错误码,\n * 混进 `code=` 只会误导排查方向。\n */\nfunction isErrorCodeLike(v: string): boolean {\n return /^[A-Z][A-Z0-9_]*$/.test(v);\n}\n\n/**\n * 自顶向下穿透包装层,取第一个可用的错误码(如 undici 的 ECONNRESET /\n * UND_ERR_* / ENOTFOUND)。\n *\n * OpenAI SDK 在连接失败时抛出的是\n * `APIConnectionError(\"Connection error.\")` → `cause: TypeError(\"fetch failed\")`\n * → `cause: Error(\"getaddrinfo ENOTFOUND ...\")`,真正的 errno 在最深处。\n * 只看一层只会拿到 `TypeError`,等于没有信息。\n */\nexport function errorCode(err: unknown): string | undefined {\n for (const src of causeChain(err)) {\n for (const k of [\"code\", \"errno\"]) {\n const v = src[k];\n if (typeof v === \"string\" && v) return v;\n }\n const name = src[\"name\"];\n if (typeof name === \"string\" && name !== \"Error\" && isErrorCodeLike(name)) {\n return name;\n }\n }\n return undefined;\n}\n\n/** 从 SDK 错误对象里取供应商原始错误体(OpenAI SDK 挂在 `error` 属性上) */\nfunction errorBody(err: unknown): Record<string, unknown> | null {\n return asRecord(asRecord(err)?.[\"error\"]);\n}\n\n/** 「端点 + 底层原因」后缀(无可用信息时返回空串) */\nexport function llmErrorContext(err: unknown, baseURL?: string): string {\n const parts: string[] = [];\n const code = errorCode(err);\n if (code) parts.push(`code=${code}`);\n const msg = err instanceof Error ? err.message : String(err ?? \"\");\n // 优先取「错误码所在那一层」的 message,保证 code 与文字描述同源;\n // 该层没有 message 时再取链路最深层(如 fetch failed 之下的\n // \"getaddrinfo ENOTFOUND ...\"),而不是停在 TypeError 的 \"fetch failed\"。\n const chain = causeChain(err);\n const codeSrc = chain.find(\n (c) => typeof c[\"code\"] === \"string\" || typeof c[\"errno\"] === \"string\",\n );\n const pickMsg = (c?: Record<string, unknown>): string | undefined => {\n const m = c?.[\"message\"];\n return typeof m === \"string\" && m && m !== msg ? m : undefined;\n };\n const causeMsg =\n pickMsg(codeSrc) ??\n [...chain].reverse().map(pickMsg).find((m): m is string => !!m) ??\n \"\";\n if (causeMsg) parts.push(causeMsg.slice(0, 160));\n // 供应商错误体里的真实原因(经 normalizeErrorFetch 归一化后可读到):\n // 形状非标准的网关 SDK 只会给出一句 \"400 status code (no body)\"\n const body = errorBody(err);\n const bodyMsg = typeof body?.[\"message\"] === \"string\" ? body[\"message\"] : \"\";\n if (bodyMsg && !msg.includes(bodyMsg)) parts.push(`详情: ${bodyMsg.slice(0, 200)}`);\n const bodyCode = typeof body?.[\"code\"] === \"string\" ? body[\"code\"] : \"\";\n if (bodyCode && !parts.some((p) => p.includes(bodyCode))) {\n parts.push(`code=${bodyCode}`);\n }\n const rid =\n (typeof err === \"object\" && err !== null\n ? String((err as { request_id?: unknown }).request_id ?? \"\")\n : \"\") ||\n (typeof body?.[\"request_id\"] === \"string\" ? body[\"request_id\"] : \"\");\n if (rid) parts.push(`request_id=${rid}`);\n if (baseURL) parts.push(`端点 ${baseURL}`);\n return parts.length > 0 ? `(${parts.join(\" · \")})` : \"\";\n}\n\n/**\n * 就地把「端点 + 底层原因」补进错误信息(保留原错误对象类型/status,\n * 便于上层继续按类型分支,如 Responses 不支持时降级)。\n */\nexport function enrichLlmError(err: unknown, baseURL?: string): unknown {\n if (!(err instanceof Error)) return err;\n const ctx = llmErrorContext(err, baseURL);\n if (!ctx) return err;\n if (err.message.includes(\"端点 \") || err.message.includes(ctx)) return err;\n try {\n err.message = `${err.message}${ctx}`;\n } catch {\n // message 只读时忽略\n }\n return err;\n}\n\n/** 连接类错误 → 可执行的排查建议(非连接类返回空数组) */\nexport function networkErrorHints(message: string, errorType = \"\"): string[] {\n const text = `${message} ${errorType}`;\n if (!isNetworkErrorText(text)) return [];\n const hints: string[] = [\n \"排查:① 网络是否可达:curl -sI <BaseURL>/models(或 /connect 重测)\",\n \"② 需要代理时:SOCKS5_PROXY=socks5://127.0.0.1:7890(HTTP_PROXY 不支持)\",\n \"③ 企业网关/证书拦截:NODE_EXTRA_CA_CERTS=/path/to/ca.pem\",\n ];\n if (/enotfound|eai_again|getaddrinfo/i.test(text)) {\n hints.push(\"④ DNS 解析失败:检查 /etc/resolv.conf,或改用 IP/内网域名\");\n } else if (/etimedout|econnrefused|unreachable/i.test(text)) {\n hints.push(\"④ 连接超时/被拒:检查防火墙出网策略与端口(443)\");\n } else if (/certificate|unable_to_verify|self signed|tls/i.test(text)) {\n hints.push(\"④ 证书链问题:为 Node 指定系统 CA(NODE_EXTRA_CA_CERTS)或补齐 ca-certificates\");\n } else if (/econnreset|socket hang up/i.test(text)) {\n hints.push(\"④ 连接被重置:多为链路/网关抖动,已自动重试;可调大 NETWORK_RETRY_MAX\");\n }\n hints.push(\"⑤ IPv6 抖动可试:NODE_OPTIONS=--dns-result-order=ipv4first\");\n return hints;\n}\n\n/**\n * 「凭据无效」类错误的排查建议(非该类错误返回空数组)。\n *\n * 与 `paramErrorHints` 分开:401 不是模型问题,混进去会让「模型类提示」\n * 在鉴权失败时也冒出来,反而误导。这里只解释「Key 为什么失效、去哪儿换」。\n *\n * 内置模型(志前 API)的 Key 由平台托管:用户在控制台重置 Key 后本机这份\n * 立即失效,必须引导去点「刷新内置模型」,否则会一直看到 401 以为节点坏了。\n */\nexport function authErrorHints(message: string, code = \"\"): string[] {\n const text = `${message} ${code}`;\n if (!/401|403|unauthorized|forbidden|invalid[_\\s]*api[_\\s]*key|api[\\s_-]*key\\s*(?:无效|invalid)|管理令牌无效/i.test(text)) {\n return [];\n }\n return [\n \"凭据无效:若用的是「志前大模型(内置)」,在节点设置的模型页点「刷新内置模型」重新领取 Key\" +\n \"(在控制台重置过 Key 后需要这一步);若是自填的供应商,请检查 Key 是否正确或已过期\",\n ];\n}\n\n/**\n * 「模型/参数不被接受」类错误的排查建议(非该类错误返回空数组)。\n *\n * 典型场景(均为阿里兼容模式实测):\n * - 选到非对话/专用通道模型(`cmaas-*`、向量/语音/图像…)\n * → `InvalidParameter: Required parameter(s) missing or invalid`\n * - 模型在该账号下没开通\n * → `invalid_parameter_error: The product is not activated`\n * 这两句原文对排查毫无帮助,必须翻成可执行的下一步。\n */\nexport function paramErrorHints(message: string, code = \"\"): string[] {\n const text = `${message} ${code}`;\n const hints: string[] = [];\n\n // 一、账号/开通类\n if (\n /not[_\\s]*(activated|enabled|purchased)|arrearage|quota|subscription|insufficient|未开通|欠费|额度|余额不足/i.test(\n text,\n )\n ) {\n hints.push(\n \"该模型在此 **API Key/账号** 下未开通(或账号欠费、额度用尽):到网关控制台开通该模型,或换一个已开通的模型\",\n );\n // 内置模型(志前 API)走的是用户自己的租户额度,额度用完是最常见的失败原因,\n // 必须给出「去哪儿充值」而不是让人对着 402 猜\n hints.push(\n \"如果你用的是「志前大模型(内置)」:到 https://hub.cd-zq.com 查看余额并充值;\" +\n \"也可以先切换到你自己配置的其它模型继续(节点设置 → 模型 → 设为默认)\",\n );\n }\n\n // 二、模型名/参数类\n if (\n /invalid[_\\s]*parameter|invalidparameter|required[_\\s]+parameter|missing[_\\s]+parameter|invalid[_\\s]*model|model[_\\s]*not[_\\s]*found|not[_\\s]*support|unsupported/i.test(\n text,\n )\n ) {\n hints.push(\n \"多半是**模型名不可用于对话接口**:网关 /models 列的是全量目录,含向量(embedding)、重排(rerank)、语音(asr/tts)、实时(realtime)、图像(image/wan)、专用通道(cmaas-*) 等,这些模型不能当 Agent 后端\",\n \"换一个对话模型:/connect 重新选择(对话模型已排在列表前面),或 /models 查看\",\n \"手动验证某个模型能不能用:\\n\" +\n \" curl -s <BaseURL>/chat/completions -H 'Authorization: Bearer $KEY' -H 'content-type: application/json' \\\\\\n\" +\n \" -d '{\\\"model\\\":\\\"<模型名>\\\",\\\"messages\\\":[{\\\"role\\\":\\\"user\\\",\\\"content\\\":\\\"hi\\\"}]}'\",\n \"若确认模型无误:检查是否多传了该服务不支持的参数(如推理强度 reasoning/effort),可用 /connect 把推理强度设为「默认」\",\n );\n }\n\n return hints;\n}\n\n/** LLM 请求超时(毫秒):OPENAI_TIMEOUT_MS,默认 10 分钟 */\nexport function llmTimeoutMs(): number {\n const n = Number(process.env.OPENAI_TIMEOUT_MS);\n return Number.isFinite(n) && n > 0 ? n : 600_000;\n}\n\n/**\n * LLM 流空闲超时(毫秒):OPENAI_STREAM_IDLE_MS,默认 2 分钟。\n *\n * 上面的 `llmTimeoutMs()` 走 SDK 的 timeout,而 SDK 在**响应头返回后**就清掉了\n * 计时器 —— 响应体的流式读取阶段没有任何超时保护。网关静默断供(既不推数据\n * 也不关连接)时,引擎会永久挂在 `for await` 上,会话状态卡在 running。\n * 故对两次 chunk 之间的间隔单独设一道看门狗。\n */\nexport function llmStreamIdleMs(): number {\n const n = Number(process.env.OPENAI_STREAM_IDLE_MS);\n return Number.isFinite(n) && n > 0 ? n : 120_000;\n}\n\n// ===================== 错误体归一化(fetch 层) =====================\n\ntype FetchLike = (input: any, init?: any) => Promise<any>;\n\n/** 把 Headers 转成普通对象(不同 fetch 实现的 Headers 都能遍历) */\nfunction headerRecord(h: unknown): Record<string, string> | undefined {\n if (!h) return undefined;\n const out: Record<string, string> = {};\n try {\n const iter =\n (h as any).entries?.() ?? ((h as any)[Symbol.iterator]?.() as any);\n if (iter) for (const [k, v] of iter) out[String(k)] = String(v);\n } catch {\n // 不可遍历的 Headers:放弃(新 Response 用默认头)\n }\n return Object.keys(out).length > 0 ? out : undefined;\n}\n\n/**\n * 包装 fetch:把**非标准形状的错误响应体**归一化成 OpenAI SDK 认识的结构。\n *\n * SDK 的 `APIError.generate` 只读 `body.error`,而阿里云百炼兼容模式的\n * `/chat/completions` 错误体是顶层 `{code, message, request_id}`(没有\n * `error` 包裹),于是 SDK 抛出的 message 变成\n * `400 status code (no body)` —— 真实原因(InvalidParameter / 模型不可用)\n * 被彻底吞掉,降级判定与排查建议全部失效。这里在读到非 2xx 时补一层\n * `{error:{...}}`,让下游(分类、日志、CLI 提示)拿到原始信息。\n */\nexport function normalizeErrorFetch(inner: FetchLike): FetchLike {\n return async (input, init) => {\n const res = await inner(input, init);\n if (res?.ok) return res;\n // 204/205/304 不允许带 body(Response 构造会抛),也没有错误信息可读\n if (res?.status === 204 || res?.status === 205 || res?.status === 304) return res;\n\n let text: string;\n try {\n text = await res.text();\n } catch {\n return res; // 读不动就原样交回(SDK 会按连接错误处理)\n }\n\n let out = text;\n try {\n const body = JSON.parse(text) as Record<string, unknown> | null;\n const std = body && typeof body === \"object\" && !(\"error\" in body) &&\n (body.message != null || body.code != null);\n if (std && body) {\n out = JSON.stringify({\n error: {\n message: String(body.message ?? `HTTP ${res.status}`),\n code: body.code ?? null,\n type: body.type ?? \"invalid_request_error\",\n param: body.param ?? null,\n },\n request_id: body.request_id ?? null,\n });\n }\n } catch {\n // 非 JSON 错误体(网关 HTML 等):保持原文,让 SDK 至少带上状态码\n }\n\n return new Response(out, {\n status: res.status,\n statusText: res.statusText,\n headers: headerRecord(res.headers),\n });\n };\n}\n\n/** LLM SDK 传输层重试次数:OPENAI_MAX_RETRIES,默认 3 */\nexport function llmMaxRetries(): number {\n const n = Number(process.env.OPENAI_MAX_RETRIES);\n return Number.isFinite(n) && n >= 0 ? n : 3;\n}\n","/**\n * LLM 可用模型列表(OpenAI 兼容 /models 接口)\n *\n * 供 CLI(/connect 刷新模型列表)与 Server(GET /api/v1/models)共用。\n * 支持 SOCKS5_PROXY 走代理出口;单个端点 10s 超时。\n */\nimport { makeSocksFetch, socksAgent } from \"./proxy.js\";\nimport {\n cleanLlmValue,\n isReasoningModel,\n normalizeBaseURL,\n} from \"./diagnostics.js\";\nimport {\n capabilitiesOf,\n normalizeCapabilities,\n type ModelCapability,\n} from \"@zqagent/shared/model-capabilities\";\n\n/** 统一 HTTP 请求封装:配置了 SOCKS5 代理时用 node-fetch + agent,否则用全局 fetch。 */\nasync function doFetch(url: string, init?: Record<string, unknown>): Promise<unknown> {\n const agent = socksAgent();\n if (agent) {\n const f = makeSocksFetch(agent);\n return f(url, init as RequestInit);\n }\n return (globalThis as unknown as { fetch: typeof fetch }).fetch(url, init as RequestInit);\n}\n\n/** 单个模型的元数据(从 /models 响应中提取,部分供应商提供) */\nexport interface ModelMeta {\n /** 模型 id */\n id: string;\n /** 上下文窗口大小(token 数);仅当供应商在 /models 响应中返回时才有值 */\n contextWindow?: number;\n /**\n * 该模型可接收的输入类型(用户在「节点配置 → LLM」里勾选)。\n * 未配置时按纯文本处理,见 `modelCapabilities`。\n */\n capabilities?: ModelCapability[];\n}\n\n/**\n * 规范化模型元数据列表(按 id 去重;仅保留合法的上下文长度)。\n * 供配置持久化(`llmConfig.ts`)与 Server 视图(`server/nodeLlm.ts`)共用。\n */\nexport function normalizeModelMeta(v: unknown): ModelMeta[] | undefined {\n if (!Array.isArray(v)) return undefined;\n const out: ModelMeta[] = [];\n const seen = new Set<string>();\n for (const x of v) {\n if (!x || typeof x !== \"object\") continue;\n const rec = x as Record<string, unknown>;\n const id = String(rec.id ?? \"\").trim();\n if (!id || seen.has(id)) continue;\n seen.add(id);\n const ctx = Number(rec.contextWindow);\n const capabilities = normalizeCapabilities(rec.capabilities);\n out.push({\n id,\n ...(Number.isFinite(ctx) && ctx > 0 ? { contextWindow: ctx } : {}),\n ...(capabilities ? { capabilities } : {}),\n });\n if (out.length >= 500) break;\n }\n return out;\n}\n\n/** 从模型元数据中查输入能力(精确匹配优先,其次前缀匹配;缺省仅文本) */\nexport function modelCapabilities(\n meta: ModelMeta[] | undefined,\n model: string,\n): ModelCapability[] {\n return capabilitiesOf(meta, model);\n}\n\n/**\n * 合并两份模型元数据(按 id)。\n *\n * 用途:刷新 `/models` 拿到新的上下文长度、重新拉取内置模型凭据时,\n * 不能把用户手工勾选的 `capabilities` 覆盖掉——供应商并不返回能力信息。\n * 因此 **base 的 capabilities 始终优先**,patch 只补充其余字段(上下文长度等)。\n *\n * @param base 旧元数据(提供 capabilities)\n * @param patch 新元数据(提供 contextWindow 等)\n */\nexport function mergeModelMeta(\n base: ModelMeta[] | undefined,\n patch: ModelMeta[] | undefined,\n): ModelMeta[] | undefined {\n if (!base?.length) return patch;\n if (!patch?.length) return base;\n const byId = new Map<string, ModelMeta>();\n for (const m of base) byId.set(m.id, { ...m });\n for (const m of patch) {\n const prev = byId.get(m.id);\n byId.set(m.id, {\n ...prev,\n ...m,\n ...(prev?.capabilities?.length ? { capabilities: prev.capabilities } : {}),\n });\n }\n return [...byId.values()];\n}\n\n/** 从模型元数据中查上下文长度(精确匹配优先,其次前缀匹配),未命中返回 undefined */\nexport function modelContextWindow(\n meta: ModelMeta[] | undefined,\n model: string,\n): number | undefined {\n if (!model || !meta?.length) return undefined;\n const exact = meta.find((m) => m.id === model && m.contextWindow);\n if (exact?.contextWindow) return exact.contextWindow;\n const prefix = meta.find((m) => m.contextWindow && model.startsWith(m.id));\n return prefix?.contextWindow;\n}\n\n/** 模型列表查询结果 */\nexport interface ModelListResult {\n /** 模型 id 列表(**对话模型优先**,非对话模型排在末尾) */\n models: string[];\n /** 实际命中的端点 */\n endpoint: string;\n /** 疑似非对话模型(向量/语音/图像/专用通道等);这些模型不能用作 Agent 后端 */\n nonChat: string[];\n /** 模型元数据(上下文长度等);仅当供应商 /models 响应包含这些信息时才有值 */\n modelMeta?: ModelMeta[];\n}\n\n/**\n * 明显**不提供** OpenAI 兼容对话接口的模型特征。\n *\n * 网关的 /models 往往是\"全量目录\"而非\"可用对话模型\":阿里云百炼会把\n * 向量、重排、语音识别/合成、实时音视频、图像生成、以及专用通道\n * (cmaas-* 机密推理、内部运维模型)一并列出。把它们排在前面会让\n * 用户在 /connect 里一眼选到不可用的模型(实测 `cmaas-qwen3.8-flash`\n * 在 /chat/completions 与 /responses 上都回 400 InvalidParameter)。\n */\nconst NON_CHAT_PATTERNS: RegExp[] = [\n /embedding/i,\n /rerank/i,\n /(^|[-_/.])(asr|tts)([-_/.]|$)/i,\n /speech|voice|unisound|audio-?coding/i,\n /realtime|livetranslate|(^|[-_])s2s([-_]|$)/i,\n /image|wan\\d|z-image|paint|draw|flux|kolors|cogview/i,\n /deep-research|search-planning/i,\n /(^|[-_])cmaas[-_]|confidential/i,\n /(^|[-_])test[-_]|auto-handle|gpu-auto/i,\n];\n\n/** 是否可作为 Agent 的对话后端(启发式判断,不确定时按\"可用\"处理) */\nexport function isChatModel(modelId: string): boolean {\n return !NON_CHAT_PATTERNS.some((re) => re.test(modelId));\n}\n\n/** 对话模型在前、非对话模型在后,两组内保持服务端原顺序 */\nfunction rankModels(models: string[]): { ordered: string[]; nonChat: string[] } {\n const chat: string[] = [];\n const nonChat: string[] = [];\n for (const m of models) (isChatModel(m) ? chat : nonChat).push(m);\n return { ordered: [...chat, ...nonChat], nonChat };\n}\n\n/** 解析 OpenAI 兼容 /models 响应({ data: [{ id, ... }] }),提取模型 id 及可选的上下文长度 */\nfunction parseModelList(body: unknown): { models: string[]; meta: ModelMeta[] } {\n if (!body || typeof body !== \"object\") return { models: [], meta: [] };\n const data = (body as Record<string, unknown>).data;\n if (!Array.isArray(data)) return { models: [], meta: [] };\n const models: string[] = [];\n const meta: ModelMeta[] = [];\n for (const item of data) {\n if (!item || typeof item !== \"object\") continue;\n const rec = item as Record<string, unknown>;\n const id = rec.id;\n if (typeof id !== \"string\" || !id.trim()) continue;\n models.push(id);\n // 各供应商的字段名不一:OpenRouter=context_length、部分网关=context_window /\n // max_context_length / max_input_tokens、vLLM=max_model_len。取首个合法值。\n const ctx =\n rec.context_length ??\n rec.context_window ??\n rec.max_context_length ??\n rec.max_input_tokens ??\n rec.max_model_len;\n if (typeof ctx === \"number\" && ctx > 0) {\n meta.push({ id, contextWindow: ctx });\n }\n }\n return { models, meta };\n}\n\n/**\n * `/models` 的候选 URL 组合。\n *\n * 各家的「模型列表」路径并不统一,而 Base URL 的写法更是五花八门\n * (`https://api.deepseek.com`、`…/v1`、`…/compatible-mode/v1`,甚至把\n * `/models`、`/chat/completions` 整段粘进来)。只试 `${base}/models`\n * 与 `${base}/v1/models` 两种时,填了 `/v1` 的 DeepSeek、填了网关前缀的\n * 自建服务都会「刷新出 0 个模型」而不报错,用户无从判断哪里错了。\n *\n * 这里按「最可能命中的顺序」生成候选:先原样,再去掉版本段(`/v1`、\n * `/compatible-mode/v1`…)试根路径,最后补 `/api/v1`、`/api` 前缀。\n * 调用方按顺序尝试,命中第一个返回非空列表的端点。\n */\nexport function modelListCandidates(baseURL: string): string[] {\n const base = baseURL.replace(/\\/+$/, \"\");\n if (!base) return [];\n const out: string[] = [];\n const add = (u: string) => {\n if (u && !out.includes(u)) out.push(u);\n };\n\n // 1. 用户填的 base 原样(…/v1、…/compatible-mode/v1 都能直接命中)\n add(`${base}/models`);\n // 2. 补标准 /v1 前缀(base 只填到域名时最常见)\n if (!/\\/v\\d+[a-z0-9]*$/i.test(base)) add(`${base}/v1/models`);\n\n // 3. 去掉版本段后的根:DeepSeek 官方是 GET /models,不挂在 /v1 下\n const root = stripVersionSuffix(base);\n if (root !== base) {\n add(`${root}/models`);\n add(`${root}/v1/models`);\n }\n // 4. 部分网关把控制面 API 挂在 /api 下\n add(`${root}/api/v1/models`);\n add(`${root}/api/models`);\n return out.slice(0, 6);\n}\n\n/** 去掉结尾的版本/兼容模式段(`/v1`、`/v1beta`、`/compatible-mode/v1`、`/openai/v1`…) */\nexport function stripVersionSuffix(base: string): string {\n const s = base.replace(/\\/+$/, \"\");\n const suffixes = [\n \"/compatible-mode/v1\",\n \"/compatible-mode\",\n \"/openai/v1\",\n \"/v1beta\",\n \"/v1\",\n // DeepSeek 的 /beta 只用于前缀补全(FIM),模型列表仍在根路径\n \"/beta\",\n ];\n for (const suf of suffixes) {\n if (s.toLowerCase().endsWith(suf)) return s.slice(0, -suf.length).replace(/\\/+$/, \"\");\n }\n return s;\n}\n\n/** 一次刷新尝试的结果(用于把失败原因回传给界面) */\nexport interface ModelFetchAttempt {\n /** 尝试的完整 URL */\n url: string;\n /** HTTP 状态码;网络层失败(超时/DNS)时无 */\n status?: number;\n /** 失败原因摘要(供应商错误体 / 网络错误) */\n error?: string;\n}\n\n/** 刷新模型列表的诊断结果 */\nexport interface ModelFetchDiagnostics {\n /** 成功时的结果;全部候选都失败为 null */\n result: ModelListResult | null;\n /** 依次尝试过的端点与原因 */\n attempts: ModelFetchAttempt[];\n /** 是否因鉴权失败提前终止(此时换路径也没有意义) */\n authFailed: boolean;\n}\n\n/** 读一小段错误响应体(供提示用),失败不抛 */\nasync function errorSnippet(resp: unknown): Promise<string | undefined> {\n const r = resp as { text?: () => Promise<string> };\n if (typeof r.text !== \"function\") return undefined;\n try {\n const text = (await r.text()).trim();\n if (!text) return undefined;\n // 优先取结构化错误里的 message:原样截断会把 JSON 截成半句(很难看)\n try {\n const body = JSON.parse(text) as Record<string, unknown>;\n const err = body[\"error\"];\n const msg =\n (err && typeof err === \"object\" ? String((err as Record<string, unknown>)[\"message\"] ?? \"\") : \"\") ||\n String(body[\"message\"] ?? \"\");\n if (msg) {\n // 去掉 request_id 之类的噪声,同一原因在多个候选上重复出现时更易读\n return msg.replace(/\\s*\\(request_id:[^)]*\\)/gi, \"\").slice(0, 160);\n }\n } catch {\n // 非 JSON 错误体(网关 HTML 等):按原文截断\n }\n return text.replace(/\\s+/g, \" \").slice(0, 200);\n } catch {\n return undefined;\n }\n}\n\n/**\n * 刷新模型列表(带诊断)。\n *\n * 与 `fetchModels` 的区别:全部失败时仍返回「试过哪些 URL、各自为什么失败」,\n * 让上层能把原因写进错误提示——静默返回 null 只会让界面显示「✓ 0」。\n *\n * @param opts.budgetMs 总预算;超出后剩余候选直接标记为未尝试(避免一次点击\n * 把节点事件循环占住几十秒,FleetCommands 的 6s 上限依赖它)\n * @param opts.timeoutMs 单个端点超时(默认 8s)\n */\nexport async function fetchModelsWithDiagnostics(\n baseURL: string,\n apiKey: string,\n preferredModel?: string,\n opts: { budgetMs?: number; timeoutMs?: number } = {},\n): Promise<ModelFetchDiagnostics> {\n // 归一化:补协议、去端点后缀;Key 去掉 CRLF/引号(Windows/openEuler 粘贴常见)\n const base = normalizeBaseURL(baseURL) ?? baseURL.replace(/\\/+$/, \"\");\n const key = cleanLlmValue(apiKey) ?? \"\";\n const budgetMs = opts.budgetMs ?? 25_000;\n const timeoutMs = opts.timeoutMs ?? 8_000;\n const started = Date.now();\n const attempts: ModelFetchAttempt[] = [];\n const candidates = modelListCandidates(base);\n /** 是否见过 401/403:全部候选都失败后据此给出「Key 无效」的提示 */\n let authFailed = false;\n\n for (const url of candidates) {\n const left = budgetMs - (Date.now() - started);\n if (left < 500) {\n // 预算用尽:不再发请求,但要如实告诉用户哪些端点没试过\n attempts.push({ url, error: \"超出重试总预算,未尝试\" });\n continue;\n }\n try {\n const resp = (await doFetch(url, {\n method: \"GET\",\n headers: {\n Authorization: `Bearer ${key}`,\n Accept: \"application/json\",\n },\n signal: AbortSignal.timeout(Math.min(timeoutMs, left)),\n })) as { ok: boolean; status?: number; json: () => Promise<unknown>; text?: () => Promise<string> };\n\n if (!resp.ok) {\n const status = typeof resp.status === \"number\" ? resp.status : undefined;\n const detail = await errorSnippet(resp);\n attempts.push({ url, ...(status !== undefined ? { status } : {}), ...(detail ? { error: detail } : {}) });\n // 401/403 也继续试其它组合:少数网关对「路径不存在」也回 401,\n // 早停会漏掉真正的模型列表端点。全部失败后再按此提示 Key 问题。\n if (status === 401 || status === 403) authFailed = true;\n continue;\n }\n\n const body = (await resp.json()) as unknown;\n const { models, meta } = parseModelList(body);\n if (models.length === 0) {\n // 部分网关对不存在的路径也回 200 + 空目录:继续试下一个组合\n attempts.push({ url, error: \"返回的模型列表为空\" });\n continue;\n }\n const { ordered, nonChat } = rankModels(models);\n const filled = await fillContextWindow(base, key, ordered, meta, preferredModel, {\n budgetMs: Math.max(1_000, budgetMs - (Date.now() - started)),\n });\n return {\n result: {\n models: ordered,\n endpoint: url,\n nonChat,\n ...(filled.length > 0 ? { modelMeta: filled } : {}),\n },\n attempts,\n authFailed: false,\n };\n } catch (e) {\n // 单端点失败(超时/网络/解析),尝试下一个候选\n attempts.push({ url, error: e instanceof Error ? e.message : String(e) });\n }\n }\n return { result: null, attempts, authFailed };\n}\n\n/**\n * 把刷新失败翻译成可操作的提示。\n *\n * 典型:Base URL 填成 `https://api.deepseek.com/v1` 时,官方 `GET /models`\n * 不在 /v1 下,旧实现只会显示「刷新出 0 个」,用户完全不知道该怎么办。\n */\nexport function describeModelFetchFailure(\n baseURL: string,\n diag: ModelFetchDiagnostics,\n): string {\n const lines = diag.attempts.map((a) => {\n const why = a.status !== undefined ? `HTTP ${a.status}` : (a.error ?? \"请求失败\");\n return ` · ${a.url} → ${why}${a.status !== undefined && a.error ? `(${a.error})` : \"\"}`;\n });\n if (diag.authFailed) {\n return (\n `模型列表刷新失败:API Key 无效或无权限(Base URL:${baseURL})。` +\n `\\n已尝试:\\n${lines.join(\"\\n\")}` +\n `\\n请检查 Key 是否填写正确、是否已过期,或该 Key 是否有权访问此端点。`\n );\n }\n return (\n `模型列表刷新失败:所有候选端点都没有返回可用模型(Base URL:${baseURL})。` +\n `\\n已尝试:\\n${lines.join(\"\\n\")}` +\n `\\n请确认 Base URL 是否正确(一般填到 /v1 为止,如 https://api.deepseek.com),` +\n `以及该服务是否提供 OpenAI 兼容的 GET /models 接口。`\n );\n}\n\n/** `/models` 未给出上下文长度时,对当前(或首个)模型单体探测一次兜底 */\nasync function fillContextWindow(\n base: string,\n key: string,\n models: string[],\n meta: ModelMeta[],\n preferred?: string,\n opts: { budgetMs?: number } = {},\n): Promise<ModelMeta[]> {\n if (meta.length > 0) return meta;\n const target = preferred?.trim() || models[0];\n if (!target) return meta;\n const ctx = await fetchModelContext(base, key, target, opts.budgetMs);\n return ctx ? [{ id: target, contextWindow: ctx }] : meta;\n}\n\n/** 刷新模型列表(简单形态):只要结果,失败返回 null。需要失败原因时用 `fetchModelsWithDiagnostics`。 */\nexport async function fetchModels(\n baseURL: string,\n apiKey: string,\n /** 当前已选模型:/models 未返回上下文长度时对它单体探测一次兜底 */\n preferredModel?: string,\n): Promise<ModelListResult | null> {\n const diag = await fetchModelsWithDiagnostics(baseURL, apiKey, preferredModel);\n return diag.result;\n}\n\n/**\n * 兜底:从 `/models/{id}` 端点读取单个模型的上下文长度。\n *\n * 多数 OpenAI 兼容网关并不实现该端点(OpenAI 官方与阿里百炼都不提供),\n * 因此失败时静默返回 undefined,由引擎回退到内置的模型表。\n */\nexport async function fetchModelContext(\n baseURL: string,\n apiKey: string,\n modelId: string,\n timeoutMs = 10_000,\n): Promise<number | undefined> {\n if (!modelId) return undefined;\n const base = normalizeBaseURL(baseURL) ?? baseURL.replace(/\\/+$/, \"\");\n const key = cleanLlmValue(apiKey) ?? \"\";\n try {\n const resp = (await doFetch(`${base}/models/${encodeURIComponent(modelId)}`, {\n method: \"GET\",\n headers: { Authorization: `Bearer ${key}`, Accept: \"application/json\" },\n signal: AbortSignal.timeout(Math.max(500, timeoutMs)),\n })) as { ok: boolean; json: () => Promise<unknown> };\n if (!resp.ok) return undefined;\n const body = (await resp.json()) as unknown;\n // 兼容两种形态:`{ id, context_length }` 与 `{ data: { … } }`\n const rec = (body ?? {}) as Record<string, unknown>;\n const inner = rec.data && typeof rec.data === \"object\" && !Array.isArray(rec.data) ? rec.data : rec;\n return parseModelList({ data: [inner] }).meta[0]?.contextWindow;\n } catch {\n return undefined;\n }\n}\n\n// ===================== 模型可用性自检 =====================\n\n/** 模型自检结果 */\nexport interface ModelProbeResult {\n /** 该模型能否正常对话 */\n ok: boolean;\n /** 失败原因(provider 的 message + code / 网络错误) */\n error?: string;\n /** provider 返回的错误码(如 InvalidParameter、model_not_found) */\n code?: string;\n /** 自检请求耗时(毫秒) */\n latencyMs: number;\n}\n\n/** 从错误响应体里尽量提取可读信息(两种兼容模式风格都支持) */\nfunction extractError(body: unknown): { message: string; code?: string } {\n const b = (body ?? {}) as Record<string, unknown>;\n const e = (b.error ?? b) as Record<string, unknown>;\n const message = String(e.message ?? b.message ?? \"\").trim();\n const code = typeof e.code === \"string\" ? e.code : typeof b.code === \"string\" ? b.code : undefined;\n const requestId = typeof b.request_id === \"string\" ? b.request_id : undefined;\n return {\n message: message || \"未知错误\",\n code: code ? `${code}${requestId ? ` (request_id=${requestId})` : \"\"}` : undefined,\n };\n}\n\n/**\n * 自检:向 `/chat/completions` 发一条最小请求,确认所选模型真的可用。\n *\n * 背景:网关的 /models 是\"全量目录\",列出的模型未必能在对话接口上用\n * (阿里兼容模式对专用通道/非对话模型直接回 400 InvalidParameter)。\n * 在 /connect 里选完就跑一次,比让用户在第一次对话时才撞墙要好得多。\n *\n * 用 Chat Completions 而非 Responses:前者是全供应商的公共底线。\n */\nexport async function probeModel(\n baseURL: string,\n apiKey: string,\n model: string,\n): Promise<ModelProbeResult> {\n const base = normalizeBaseURL(baseURL) ?? baseURL.replace(/\\/+$/, \"\");\n const key = cleanLlmValue(apiKey) ?? \"\";\n const started = Date.now();\n try {\n const resp = (await doFetch(`${base}/chat/completions`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${key}`,\n },\n body: JSON.stringify({\n model,\n messages: [{ role: \"user\", content: \"ping\" }],\n // 与 Provider 共用 isReasoningModel:o 系列 / gpt-5 只接受\n // max_completion_tokens,传 max_tokens 会 400,把可用模型误判成不可用\n ...(isReasoningModel(model)\n ? { max_completion_tokens: 8 }\n : { max_tokens: 8 }),\n stream: false,\n }),\n signal: AbortSignal.timeout(20_000),\n })) as { status: number; ok: boolean; json: () => Promise<unknown> };\n\n const latencyMs = Date.now() - started;\n if (resp.ok) return { ok: true, latencyMs };\n\n let body: unknown = null;\n try {\n body = await resp.json();\n } catch {\n // 非 JSON 错误体(网关 HTML 等)\n }\n const { message, code } = extractError(body);\n return {\n ok: false,\n error: `HTTP ${resp.status} ${message}`,\n ...(code ? { code } : {}),\n latencyMs,\n };\n } catch (err) {\n return {\n ok: false,\n error: err instanceof Error ? err.message : String(err),\n latencyMs: Date.now() - started,\n };\n }\n}\n"],"mappings":";AAUA,OAAO,eAAe;;;ACVtB,SAAS,mBAAmD;;;ACA5D,YAAY,SAAS;AAErB,YAAY,UAAU;AACtB,SAAS,SAAS,kBAAkB;AAkBpC,IAAM,WAAW,uBAAO,wBAAwB;AAQ1C,IAAgBA,SAAhB,cAAmC,WAAK;EAO7C,YAAY,MAAwB;AACnC,UAAM,IAAI;AACV,SAAK,QAAQ,IAAI,CAAA;EAClB;;;;EAUA,iBAAiB,SAA0B;AAC1C,QAAI,SAAS;AAIZ,UAAI,OAAQ,QAAgB,mBAAmB,WAAW;AACzD,eAAO,QAAQ;MAChB;AAKA,UAAI,OAAO,QAAQ,aAAa,UAAU;AACzC,eAAO,QAAQ,aAAa;MAC7B;IACD;AAKA,UAAM,EAAE,MAAK,IAAK,IAAI,MAAK;AAC3B,QAAI,OAAO,UAAU;AAAU,aAAO;AACtC,WAAO,MACL,MAAM,IAAI,EACV,KACA,CAAC,MACA,EAAE,QAAQ,YAAY,MAAM,MAC5B,EAAE,QAAQ,aAAa,MAAM,EAAE;EAEnC;;;;;;;EAQQ,iBAAiB,MAAY;AAIpC,QAAI,KAAK,eAAe,YAAY,KAAK,oBAAoB,UAAU;AACtE,aAAO;IACR;AAIA,QAAI,CAAC,KAAK,QAAQ,IAAI,GAAG;AAExB,WAAK,QAAQ,IAAI,IAAI,CAAA;IACtB;AACA,UAAM,aAAa,IAAQ,WAAO,EAAE,UAAU,MAAK,CAAE;AACpD,SAAK,QAAQ,IAAI,EAAmB,KAAK,UAAU;AAEpD,SAAK;AACL,WAAO;EACR;EAEQ,iBAAiB,MAAc,QAAyB;AAC/D,QAAI,CAAC,KAAK,QAAQ,IAAI,KAAK,WAAW,MAAM;AAC3C;IACD;AACA,UAAM,UAAU,KAAK,QAAQ,IAAI;AACjC,UAAM,QAAQ,QAAQ,QAAQ,MAAM;AACpC,QAAI,UAAU,IAAI;AACjB,cAAQ,OAAO,OAAO,CAAC;AAEvB,WAAK;AACL,UAAI,QAAQ,WAAW,GAAG;AAEzB,eAAO,KAAK,QAAQ,IAAI;MACzB;IACD;EACD;;;EAIA,QAAQ,SAA0B;AACjC,UAAM,iBAAiB,KAAK,iBAAiB,OAAO;AACpD,QAAI,gBAAgB;AACnB,aAAO,WAAW,UAAU,QAAQ,KAAK,MAAM,OAAO;IACvD;AACA,WAAO,MAAM,QAAQ,OAAO;EAC7B;EAEA,aACC,KACA,SACA,IAA2C;AAE3C,UAAM,cAAc;MACnB,GAAG;MACH,gBAAgB,KAAK,iBAAiB,OAAO;;AAE9C,UAAM,OAAO,KAAK,QAAQ,WAAW;AACrC,UAAM,aAAa,KAAK,iBAAiB,IAAI;AAC7C,YAAQ,QAAO,EACb,KAAK,MAAM,KAAK,QAAQ,KAAK,WAAW,CAAC,EACzC,KACA,CAAC,WAAU;AACV,WAAK,iBAAiB,MAAM,UAAU;AACtC,UAAI,kBAAuB,YAAO;AACjC,YAAI;AAEH,iBAAO,OAAO,WAAW,KAAK,WAAW;QAC1C,SAAS,KAAc;AACtB,iBAAO,GAAG,GAAY;QACvB;MACD;AACA,WAAK,QAAQ,EAAE,gBAAgB;AAE/B,YAAM,aAAa,KAAK,SAAS,EAAE;IACpC,GACA,CAAC,QAAO;AACP,WAAK,iBAAiB,MAAM,UAAU;AACtC,SAAG,GAAG;IACP,CAAC;EAEJ;EAEA,mBAAgB;AACf,UAAM,SAAS,KAAK,QAAQ,EAAE;AAC9B,SAAK,QAAQ,EAAE,gBAAgB;AAC/B,QAAI,CAAC,QAAQ;AACZ,YAAM,IAAI,MACT,oDAAoD;IAEtD;AACA,WAAO;EACR;EAEA,IAAI,cAAW;AACd,WACC,KAAK,QAAQ,EAAE,gBACd,KAAK,aAAa,WAAW,MAAM;EAEtC;EAEA,IAAI,YAAY,GAAS;AACxB,QAAI,KAAK,QAAQ,GAAG;AACnB,WAAK,QAAQ,EAAE,cAAc;IAC9B;EACD;EAEA,IAAI,WAAQ;AACX,WACC,KAAK,QAAQ,EAAE,aACd,KAAK,iBAAgB,IAAK,WAAW;EAExC;EAEA,IAAI,SAAS,GAAS;AACrB,QAAI,KAAK,QAAQ,GAAG;AACnB,WAAK,QAAQ,EAAE,WAAW;IAC3B;EACD;;;;AD1MD,OAAO,iBAAiB;AACxB,YAAY,SAAS;AACrB,YAAYC,UAAS;AACrB,YAAY,SAAS;AAErB,SAAS,WAAW;AAEpB,IAAM,QAAQ,YAAY,mBAAmB;AAE7C,IAAM,6BAA6B,CAGlC,YACG;AACH,MACC,QAAQ,eAAe,UACvB,QAAQ,QACR,CAAK,UAAK,QAAQ,IAAI,GACrB;AACD,WAAO;MACN,GAAG;MACH,YAAY,QAAQ;;EAEtB;AACA,SAAO;AACR;AAEA,SAAS,cAAc,KAAQ;AAC9B,MAAIC,UAAS;AACb,MAAI,OAA2B;AAC/B,QAAM,OAAO,IAAI;AAIjB,QAAM,OAAO,SAAS,IAAI,MAAM,EAAE,KAAK;AAIvC,UAAQ,IAAI,SAAS,QAAQ,KAAK,EAAE,GAAG;IACtC,KAAK;AACJ,MAAAA,UAAS;AACT,aAAO;AACP;;IAED,KAAK;AACJ,aAAO;AACP;IACD,KAAK;AACJ,MAAAA,UAAS;AACT,aAAO;AACP;;IAED,KAAK;AACJ,aAAO;AACP;IACD,KAAK;AACJ,aAAO;AACP;IACD;AACC,YAAM,IAAI,UACT,8CAA8C,OAC7C,IAAI,QAAQ,CACZ,EAAE;EAEN;AAEA,QAAM,QAAoB;IACzB;IACA;IACA;;AAGD,MAAI,IAAI,UAAU;AACjB,WAAO,eAAe,OAAO,UAAU;MACtC,OAAO,mBAAmB,IAAI,QAAQ;MACtC,YAAY;KACZ;EACF;AAEA,MAAI,IAAI,YAAY,MAAM;AACzB,WAAO,eAAe,OAAO,YAAY;MACxC,OAAO,mBAAmB,IAAI,QAAQ;MACtC,YAAY;KACZ;EACF;AAEA,SAAO,EAAE,QAAAA,SAAQ,MAAK;AACvB;AAYM,IAAO,kBAAP,cAA+BC,OAAK;EAczC,YAAY,KAAmB,MAA6B;AAC3D,UAAM,IAAI;AAEV,UAAM,MAAM,OAAO,QAAQ,WAAW,IAAI,IAAI,GAAG,IAAI;AACrD,UAAM,EAAE,OAAO,QAAAD,QAAM,IAAK,cAAc,GAAG;AAE3C,SAAK,eAAeA;AACpB,SAAK,QAAQ;AACb,SAAK,UAAU,MAAM,WAAW;AAChC,SAAK,gBAAgB,MAAM,iBAAiB;EAC7C;;;;;EAMA,MAAM,QACL,KACA,MAAsB;AAEtB,UAAM,EAAE,cAAc,OAAO,QAAO,IAAK;AAEzC,QAAI,CAAC,KAAK,MAAM;AACf,YAAM,IAAI,MAAM,oBAAoB;IACrC;AAEA,QAAI,EAAE,KAAI,IAAK;AACf,UAAM,EAAE,MAAM,QAAQ,WAAe,WAAM,IAAK;AAEhD,QAAI,cAAc;AAEjB,aAAO,MAAM,IAAI,QAAgB,CAAC,SAAS,WAAU;AAEpD,iBAAS,MAAM,CAAA,GAAI,CAAC,KAAK,YAAW;AACnC,cAAI,KAAK;AACR,mBAAO,GAAG;UACX,OAAO;AACN,oBACC,OAAO,YAAY,WAChB,UACA,QAAQ,CAAC,EAAE,OAAO;UAEvB;QACD,CAAC;MACF,CAAC;IACF;AAEA,UAAM,YAAgC;MACrC;MACA,aAAa;QACZ;QACA,MAAM,OAAO,SAAS,WAAW,OAAO,SAAS,MAAM,EAAE;;MAE1D,SAAS;MACT,SAAS,WAAW;;;MAGpB,gBAAgB,KAAK,iBAAiB;;AAGvC,UAAM,UAAU,CAAC,cAA6B;AAC7C,UAAI,QAAO;AACX,aAAO,QAAO;AACd,UAAI;AAAW,kBAAU,QAAO;IACjC;AAEA,UAAM,uCAAuC,SAAS;AACtD,UAAM,EAAE,OAAM,IAAK,MAAM,YAAY,iBAAiB,SAAS;AAC/D,UAAM,6CAA6C;AAEnD,QAAI,YAAY,MAAM;AACrB,aAAO,WAAW,OAAO;AACzB,aAAO,GAAG,WAAW,MAAM,QAAO,CAAE;IACrC;AAEA,QAAI,KAAK,gBAAgB;AAGxB,YAAM,oCAAoC;AAC1C,YAAM,YAAgB,YAAQ;QAC7B,GAAG,KACF,2BAA2B,IAAI,GAC/B,QACA,QACA,MAAM;QAEP;OACA;AAED,gBAAU,KAAK,SAAS,CAAC,UAAS;AACjC,cAAM,oBAAoB,MAAM,OAAO;AACvC,gBAAQ,SAAS;MAClB,CAAC;AAED,aAAO;IACR;AAEA,WAAO;EACR;;AA/GO,gBAAA,YAAY;EAClB;EACA;EACA;EACA;EACA;;AA6GF,SAAS,KACR,QACG,MAAO;AAIV,QAAM,MAAM,CAAA;AACZ,MAAI;AACJ,OAAK,OAAO,KAAK;AAChB,QAAI,CAAC,KAAK,SAAS,GAAG,GAAG;AACxB,UAAI,GAAG,IAAI,IAAI,GAAG;IACnB;EACD;AACA,SAAO;AACR;;;ADxNO,SAAS,gBAA+B;AAC7C,QAAM,OAAO,QAAQ,IAAI,gBAAgB,IAAI,KAAK;AAClD,SAAO,OAAO;AAChB;AAGA,SAAS,QAAQ,KAAsB;AACrC,SAAO,UAAU,KAAK,GAAG;AAC3B;AAGA,IAAI,SAAgE;AAE7D,SAAS,aAAqC;AACnD,QAAM,MAAM,cAAc;AAC1B,MAAI,CAAC,IAAK,QAAO;AACjB,MAAI,QAAQ,QAAQ,IAAK,QAAO,OAAO;AACvC,MAAI,QAAgC;AACpC,MAAI,QAAQ,GAAG,GAAG;AAChB,QAAI;AACF,cAAQ,IAAI,gBAAgB,GAAG;AAAA,IACjC,QAAQ;AACN,UAAI;AACF,gBAAQ,IAAI,gBAAgB,IAAI,QAAQ,iBAAiB,WAAW,CAAC;AAAA,MACvE,QAAQ;AACN,gBAAQ;AAAA,MACV;AAAA,IACF;AAAA,EACF;AACA,WAAS,EAAE,KAAK,MAAM;AACtB,SAAO;AACT;AAiBO,SAAS,eAAe,OAAsC;AACnE,UAAQ,CAAC,KAAc,SACrB,UAAU,OAAO,GAAG,GAAG,EAAE,GAAI,MAAiB,MAAM,CAAU;AAClE;;;AGvBO,SAAS,cAAc,GAAuC;AACnE,MAAI,KAAK,KAAM,QAAO;AACtB,MAAI,IAAI,OAAO,CAAC,EAAE,QAAQ,WAAW,EAAE;AACvC,MAAI,EAAE,QAAQ,aAAa,EAAE,EAAE,KAAK;AACpC,MACE,EAAE,UAAU,MACV,EAAE,WAAW,GAAG,KAAK,EAAE,SAAS,GAAG,KAAO,EAAE,WAAW,GAAG,KAAK,EAAE,SAAS,GAAG,IAC/E;AACA,QAAI,EAAE,MAAM,GAAG,EAAE,EAAE,KAAK;AAAA,EAC1B;AACA,SAAO,KAAK;AACd;AAQO,SAAS,iBAAiB,KAAyC;AACxE,MAAI,IAAI,cAAc,GAAG;AACzB,MAAI,CAAC,EAAG,QAAO;AACf,MAAI,CAAC,2BAA2B,KAAK,CAAC,EAAG,KAAI,WAAW,CAAC;AACzD,MAAI,EAAE,QAAQ,QAAQ,EAAE;AACxB,MAAI,EAAE,QAAQ,wDAAwD,EAAE;AACxE,SAAO,EAAE,QAAQ,QAAQ,EAAE,KAAK;AAClC;AAUO,SAAS,iBAAiB,OAAwB;AACvD,SAAO,gBAAgB,KAAK,KAAK;AACnC;;;ACpEA;AAAA,EACE;AAAA,EACA;AAAA,OAEK;AAGP,eAAe,QAAQ,KAAa,MAAkD;AACpF,QAAM,QAAQ,WAAW;AACzB,MAAI,OAAO;AACT,UAAM,IAAI,eAAe,KAAK;AAC9B,WAAO,EAAE,KAAK,IAAmB;AAAA,EACnC;AACA,SAAQ,WAAkD,MAAM,KAAK,IAAmB;AAC1F;AAmBO,SAAS,mBAAmB,GAAqC;AACtE,MAAI,CAAC,MAAM,QAAQ,CAAC,EAAG,QAAO;AAC9B,QAAM,MAAmB,CAAC;AAC1B,QAAM,OAAO,oBAAI,IAAY;AAC7B,aAAW,KAAK,GAAG;AACjB,QAAI,CAAC,KAAK,OAAO,MAAM,SAAU;AACjC,UAAM,MAAM;AACZ,UAAM,KAAK,OAAO,IAAI,MAAM,EAAE,EAAE,KAAK;AACrC,QAAI,CAAC,MAAM,KAAK,IAAI,EAAE,EAAG;AACzB,SAAK,IAAI,EAAE;AACX,UAAM,MAAM,OAAO,IAAI,aAAa;AACpC,UAAM,eAAe,sBAAsB,IAAI,YAAY;AAC3D,QAAI,KAAK;AAAA,MACP;AAAA,MACA,GAAI,OAAO,SAAS,GAAG,KAAK,MAAM,IAAI,EAAE,eAAe,IAAI,IAAI,CAAC;AAAA,MAChE,GAAI,eAAe,EAAE,aAAa,IAAI,CAAC;AAAA,IACzC,CAAC;AACD,QAAI,IAAI,UAAU,IAAK;AAAA,EACzB;AACA,SAAO;AACT;AAGO,SAAS,kBACd,MACA,OACmB;AACnB,SAAO,eAAe,MAAM,KAAK;AACnC;AAYO,SAAS,eACd,MACA,OACyB;AACzB,MAAI,CAAC,MAAM,OAAQ,QAAO;AAC1B,MAAI,CAAC,OAAO,OAAQ,QAAO;AAC3B,QAAM,OAAO,oBAAI,IAAuB;AACxC,aAAW,KAAK,KAAM,MAAK,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC;AAC7C,aAAW,KAAK,OAAO;AACrB,UAAM,OAAO,KAAK,IAAI,EAAE,EAAE;AAC1B,SAAK,IAAI,EAAE,IAAI;AAAA,MACb,GAAG;AAAA,MACH,GAAG;AAAA,MACH,GAAI,MAAM,cAAc,SAAS,EAAE,cAAc,KAAK,aAAa,IAAI,CAAC;AAAA,IAC1E,CAAC;AAAA,EACH;AACA,SAAO,CAAC,GAAG,KAAK,OAAO,CAAC;AAC1B;AAGO,SAAS,mBACd,MACA,OACoB;AACpB,MAAI,CAAC,SAAS,CAAC,MAAM,OAAQ,QAAO;AACpC,QAAM,QAAQ,KAAK,KAAK,CAAC,MAAM,EAAE,OAAO,SAAS,EAAE,aAAa;AAChE,MAAI,OAAO,cAAe,QAAO,MAAM;AACvC,QAAM,SAAS,KAAK,KAAK,CAAC,MAAM,EAAE,iBAAiB,MAAM,WAAW,EAAE,EAAE,CAAC;AACzE,SAAO,QAAQ;AACjB;AAuBA,IAAM,oBAA8B;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAGO,SAAS,YAAY,SAA0B;AACpD,SAAO,CAAC,kBAAkB,KAAK,CAAC,OAAO,GAAG,KAAK,OAAO,CAAC;AACzD;AAGA,SAAS,WAAW,QAA4D;AAC9E,QAAM,OAAiB,CAAC;AACxB,QAAM,UAAoB,CAAC;AAC3B,aAAW,KAAK,OAAQ,EAAC,YAAY,CAAC,IAAI,OAAO,SAAS,KAAK,CAAC;AAChE,SAAO,EAAE,SAAS,CAAC,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ;AACnD;AAGA,SAAS,eAAe,MAAwD;AAC9E,MAAI,CAAC,QAAQ,OAAO,SAAS,SAAU,QAAO,EAAE,QAAQ,CAAC,GAAG,MAAM,CAAC,EAAE;AACrE,QAAM,OAAQ,KAAiC;AAC/C,MAAI,CAAC,MAAM,QAAQ,IAAI,EAAG,QAAO,EAAE,QAAQ,CAAC,GAAG,MAAM,CAAC,EAAE;AACxD,QAAM,SAAmB,CAAC;AAC1B,QAAM,OAAoB,CAAC;AAC3B,aAAW,QAAQ,MAAM;AACvB,QAAI,CAAC,QAAQ,OAAO,SAAS,SAAU;AACvC,UAAM,MAAM;AACZ,UAAM,KAAK,IAAI;AACf,QAAI,OAAO,OAAO,YAAY,CAAC,GAAG,KAAK,EAAG;AAC1C,WAAO,KAAK,EAAE;AAGd,UAAM,MACJ,IAAI,kBACJ,IAAI,kBACJ,IAAI,sBACJ,IAAI,oBACJ,IAAI;AACN,QAAI,OAAO,QAAQ,YAAY,MAAM,GAAG;AACtC,WAAK,KAAK,EAAE,IAAI,eAAe,IAAI,CAAC;AAAA,IACtC;AAAA,EACF;AACA,SAAO,EAAE,QAAQ,KAAK;AACxB;AAeO,SAAS,oBAAoB,SAA2B;AAC7D,QAAM,OAAO,QAAQ,QAAQ,QAAQ,EAAE;AACvC,MAAI,CAAC,KAAM,QAAO,CAAC;AACnB,QAAM,MAAgB,CAAC;AACvB,QAAM,MAAM,CAAC,MAAc;AACzB,QAAI,KAAK,CAAC,IAAI,SAAS,CAAC,EAAG,KAAI,KAAK,CAAC;AAAA,EACvC;AAGA,MAAI,GAAG,IAAI,SAAS;AAEpB,MAAI,CAAC,oBAAoB,KAAK,IAAI,EAAG,KAAI,GAAG,IAAI,YAAY;AAG5D,QAAM,OAAO,mBAAmB,IAAI;AACpC,MAAI,SAAS,MAAM;AACjB,QAAI,GAAG,IAAI,SAAS;AACpB,QAAI,GAAG,IAAI,YAAY;AAAA,EACzB;AAEA,MAAI,GAAG,IAAI,gBAAgB;AAC3B,MAAI,GAAG,IAAI,aAAa;AACxB,SAAO,IAAI,MAAM,GAAG,CAAC;AACvB;AAGO,SAAS,mBAAmB,MAAsB;AACvD,QAAM,IAAI,KAAK,QAAQ,QAAQ,EAAE;AACjC,QAAM,WAAW;AAAA,IACf;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA;AAAA,IAEA;AAAA,EACF;AACA,aAAW,OAAO,UAAU;AAC1B,QAAI,EAAE,YAAY,EAAE,SAAS,GAAG,EAAG,QAAO,EAAE,MAAM,GAAG,CAAC,IAAI,MAAM,EAAE,QAAQ,QAAQ,EAAE;AAAA,EACtF;AACA,SAAO;AACT;AAuBA,eAAe,aAAa,MAA4C;AACtE,QAAM,IAAI;AACV,MAAI,OAAO,EAAE,SAAS,WAAY,QAAO;AACzC,MAAI;AACF,UAAM,QAAQ,MAAM,EAAE,KAAK,GAAG,KAAK;AACnC,QAAI,CAAC,KAAM,QAAO;AAElB,QAAI;AACF,YAAM,OAAO,KAAK,MAAM,IAAI;AAC5B,YAAM,MAAM,KAAK,OAAO;AACxB,YAAM,OACH,OAAO,OAAO,QAAQ,WAAW,OAAQ,IAAgC,SAAS,KAAK,EAAE,IAAI,OAC9F,OAAO,KAAK,SAAS,KAAK,EAAE;AAC9B,UAAI,KAAK;AAEP,eAAO,IAAI,QAAQ,6BAA6B,EAAE,EAAE,MAAM,GAAG,GAAG;AAAA,MAClE;AAAA,IACF,QAAQ;AAAA,IAER;AACA,WAAO,KAAK,QAAQ,QAAQ,GAAG,EAAE,MAAM,GAAG,GAAG;AAAA,EAC/C,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAYA,eAAsB,2BACpB,SACA,QACA,gBACA,OAAkD,CAAC,GACnB;AAEhC,QAAM,OAAO,iBAAiB,OAAO,KAAK,QAAQ,QAAQ,QAAQ,EAAE;AACpE,QAAM,MAAM,cAAc,MAAM,KAAK;AACrC,QAAM,WAAW,KAAK,YAAY;AAClC,QAAM,YAAY,KAAK,aAAa;AACpC,QAAM,UAAU,KAAK,IAAI;AACzB,QAAM,WAAgC,CAAC;AACvC,QAAM,aAAa,oBAAoB,IAAI;AAE3C,MAAI,aAAa;AAEjB,aAAW,OAAO,YAAY;AAC5B,UAAM,OAAO,YAAY,KAAK,IAAI,IAAI;AACtC,QAAI,OAAO,KAAK;AAEd,eAAS,KAAK,EAAE,KAAK,OAAO,qEAAc,CAAC;AAC3C;AAAA,IACF;AACA,QAAI;AACF,YAAM,OAAQ,MAAM,QAAQ,KAAK;AAAA,QAC/B,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,eAAe,UAAU,GAAG;AAAA,UAC5B,QAAQ;AAAA,QACV;AAAA,QACA,QAAQ,YAAY,QAAQ,KAAK,IAAI,WAAW,IAAI,CAAC;AAAA,MACvD,CAAC;AAED,UAAI,CAAC,KAAK,IAAI;AACZ,cAAM,SAAS,OAAO,KAAK,WAAW,WAAW,KAAK,SAAS;AAC/D,cAAM,SAAS,MAAM,aAAa,IAAI;AACtC,iBAAS,KAAK,EAAE,KAAK,GAAI,WAAW,SAAY,EAAE,OAAO,IAAI,CAAC,GAAI,GAAI,SAAS,EAAE,OAAO,OAAO,IAAI,CAAC,EAAG,CAAC;AAGxG,YAAI,WAAW,OAAO,WAAW,IAAK,cAAa;AACnD;AAAA,MACF;AAEA,YAAM,OAAQ,MAAM,KAAK,KAAK;AAC9B,YAAM,EAAE,QAAQ,KAAK,IAAI,eAAe,IAAI;AAC5C,UAAI,OAAO,WAAW,GAAG;AAEvB,iBAAS,KAAK,EAAE,KAAK,OAAO,yDAAY,CAAC;AACzC;AAAA,MACF;AACA,YAAM,EAAE,SAAS,QAAQ,IAAI,WAAW,MAAM;AAC9C,YAAM,SAAS,MAAM,kBAAkB,MAAM,KAAK,SAAS,MAAM,gBAAgB;AAAA,QAC/E,UAAU,KAAK,IAAI,KAAO,YAAY,KAAK,IAAI,IAAI,QAAQ;AAAA,MAC7D,CAAC;AACD,aAAO;AAAA,QACL,QAAQ;AAAA,UACN,QAAQ;AAAA,UACR,UAAU;AAAA,UACV;AAAA,UACA,GAAI,OAAO,SAAS,IAAI,EAAE,WAAW,OAAO,IAAI,CAAC;AAAA,QACnD;AAAA,QACA;AAAA,QACA,YAAY;AAAA,MACd;AAAA,IACF,SAAS,GAAG;AAEV,eAAS,KAAK,EAAE,KAAK,OAAO,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,EAAE,CAAC;AAAA,IAC1E;AAAA,EACF;AACA,SAAO,EAAE,QAAQ,MAAM,UAAU,WAAW;AAC9C;AAQO,SAAS,0BACd,SACA,MACQ;AACR,QAAM,QAAQ,KAAK,SAAS,IAAI,CAAC,MAAM;AACrC,UAAM,MAAM,EAAE,WAAW,SAAY,QAAQ,EAAE,MAAM,KAAM,EAAE,SAAS;AACtE,WAAO,UAAO,EAAE,GAAG,WAAM,GAAG,GAAG,EAAE,WAAW,UAAa,EAAE,QAAQ,SAAI,EAAE,KAAK,WAAM,EAAE;AAAA,EACxF,CAAC;AACD,MAAI,KAAK,YAAY;AACnB,WACE,yHAAoC,OAAO;AAAA;AAAA,EAChC,MAAM,KAAK,IAAI,CAAC;AAAA;AAAA,EAG/B;AACA,SACE,uKAAqC,OAAO;AAAA;AAAA,EACjC,MAAM,KAAK,IAAI,CAAC;AAAA;AAI/B;AAGA,eAAe,kBACb,MACA,KACA,QACA,MACA,WACA,OAA8B,CAAC,GACT;AACtB,MAAI,KAAK,SAAS,EAAG,QAAO;AAC5B,QAAM,SAAS,WAAW,KAAK,KAAK,OAAO,CAAC;AAC5C,MAAI,CAAC,OAAQ,QAAO;AACpB,QAAM,MAAM,MAAM,kBAAkB,MAAM,KAAK,QAAQ,KAAK,QAAQ;AACpE,SAAO,MAAM,CAAC,EAAE,IAAI,QAAQ,eAAe,IAAI,CAAC,IAAI;AACtD;AAGA,eAAsB,YACpB,SACA,QAEA,gBACiC;AACjC,QAAM,OAAO,MAAM,2BAA2B,SAAS,QAAQ,cAAc;AAC7E,SAAO,KAAK;AACd;AAQA,eAAsB,kBACpB,SACA,QACA,SACA,YAAY,KACiB;AAC7B,MAAI,CAAC,QAAS,QAAO;AACrB,QAAM,OAAO,iBAAiB,OAAO,KAAK,QAAQ,QAAQ,QAAQ,EAAE;AACpE,QAAM,MAAM,cAAc,MAAM,KAAK;AACrC,MAAI;AACF,UAAM,OAAQ,MAAM,QAAQ,GAAG,IAAI,WAAW,mBAAmB,OAAO,CAAC,IAAI;AAAA,MAC3E,QAAQ;AAAA,MACR,SAAS,EAAE,eAAe,UAAU,GAAG,IAAI,QAAQ,mBAAmB;AAAA,MACtE,QAAQ,YAAY,QAAQ,KAAK,IAAI,KAAK,SAAS,CAAC;AAAA,IACtD,CAAC;AACD,QAAI,CAAC,KAAK,GAAI,QAAO;AACrB,UAAM,OAAQ,MAAM,KAAK,KAAK;AAE9B,UAAM,MAAO,QAAQ,CAAC;AACtB,UAAM,QAAQ,IAAI,QAAQ,OAAO,IAAI,SAAS,YAAY,CAAC,MAAM,QAAQ,IAAI,IAAI,IAAI,IAAI,OAAO;AAChG,WAAO,eAAe,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,EAAE,KAAK,CAAC,GAAG;AAAA,EACpD,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAiBA,SAAS,aAAa,MAAmD;AACvE,QAAM,IAAK,QAAQ,CAAC;AACpB,QAAM,IAAK,EAAE,SAAS;AACtB,QAAM,UAAU,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,EAAE,KAAK;AAC1D,QAAM,OAAO,OAAO,EAAE,SAAS,WAAW,EAAE,OAAO,OAAO,EAAE,SAAS,WAAW,EAAE,OAAO;AACzF,QAAM,YAAY,OAAO,EAAE,eAAe,WAAW,EAAE,aAAa;AACpE,SAAO;AAAA,IACL,SAAS,WAAW;AAAA,IACpB,MAAM,OAAO,GAAG,IAAI,GAAG,YAAY,gBAAgB,SAAS,MAAM,EAAE,KAAK;AAAA,EAC3E;AACF;AAWA,eAAsB,WACpB,SACA,QACA,OAC2B;AAC3B,QAAM,OAAO,iBAAiB,OAAO,KAAK,QAAQ,QAAQ,QAAQ,EAAE;AACpE,QAAM,MAAM,cAAc,MAAM,KAAK;AACrC,QAAM,UAAU,KAAK,IAAI;AACzB,MAAI;AACF,UAAM,OAAQ,MAAM,QAAQ,GAAG,IAAI,qBAAqB;AAAA,MACtD,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,eAAe,UAAU,GAAG;AAAA,MAC9B;AAAA,MACA,MAAM,KAAK,UAAU;AAAA,QACnB;AAAA,QACA,UAAU,CAAC,EAAE,MAAM,QAAQ,SAAS,OAAO,CAAC;AAAA;AAAA;AAAA,QAG5C,GAAI,iBAAiB,KAAK,IACtB,EAAE,uBAAuB,EAAE,IAC3B,EAAE,YAAY,EAAE;AAAA,QACpB,QAAQ;AAAA,MACV,CAAC;AAAA,MACD,QAAQ,YAAY,QAAQ,GAAM;AAAA,IACpC,CAAC;AAED,UAAM,YAAY,KAAK,IAAI,IAAI;AAC/B,QAAI,KAAK,GAAI,QAAO,EAAE,IAAI,MAAM,UAAU;AAE1C,QAAI,OAAgB;AACpB,QAAI;AACF,aAAO,MAAM,KAAK,KAAK;AAAA,IACzB,QAAQ;AAAA,IAER;AACA,UAAM,EAAE,SAAS,KAAK,IAAI,aAAa,IAAI;AAC3C,WAAO;AAAA,MACL,IAAI;AAAA,MACJ,OAAO,QAAQ,KAAK,MAAM,IAAI,OAAO;AAAA,MACrC,GAAI,OAAO,EAAE,KAAK,IAAI,CAAC;AAAA,MACvB;AAAA,IACF;AAAA,EACF,SAAS,KAAK;AACZ,WAAO;AAAA,MACL,IAAI;AAAA,MACJ,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,MACtD,WAAW,KAAK,IAAI,IAAI;AAAA,IAC1B;AAAA,EACF;AACF;","names":["Agent","net","lookup","Agent"]}
@@ -0,0 +1,161 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/terminal.ts
21
+ var terminal_exports = {};
22
+ __export(terminal_exports, {
23
+ EMOJI_SUPPORTED: () => EMOJI_SUPPORTED,
24
+ UNICODE_SUPPORTED: () => UNICODE_SUPPORTED,
25
+ cliErr: () => cliErr,
26
+ cliLog: () => cliLog,
27
+ cliWarn: () => cliWarn,
28
+ cliWrite: () => cliWrite,
29
+ detectEmojiSupport: () => detectEmojiSupport,
30
+ sanitizeEmoji: () => sanitizeEmoji,
31
+ sym: () => sym
32
+ });
33
+ module.exports = __toCommonJS(terminal_exports);
34
+ function detectEmojiSupport(input = {}) {
35
+ const env = input.env ?? process.env;
36
+ const platform = input.platform ?? process.platform;
37
+ const isTTY = input.isTTY ?? Boolean(process.stdout.isTTY);
38
+ const flag = (env.ZQAGENT_EMOJI ?? "").trim().toLowerCase();
39
+ if (["0", "false", "no", "off"].includes(flag)) return false;
40
+ if (["1", "true", "yes", "on"].includes(flag)) return true;
41
+ if (env.ZQAGENT_ASCII && env.ZQAGENT_ASCII !== "0") return false;
42
+ if (env.NO_EMOJI && env.NO_EMOJI !== "0") return false;
43
+ if (platform !== "win32") return true;
44
+ if (!isTTY) return true;
45
+ if (env.WT_SESSION) return true;
46
+ const termProgram = (env.TERM_PROGRAM ?? "").toLowerCase();
47
+ if (termProgram === "vscode" || termProgram === "windows-terminal") return true;
48
+ if ((env.ConEmuANSI ?? "").toUpperCase() === "ON") return true;
49
+ const term = (env.TERM ?? "").toLowerCase();
50
+ if (term.includes("xterm") || term.includes("mintty")) return true;
51
+ return false;
52
+ }
53
+ var EMOJI_SUPPORTED = detectEmojiSupport();
54
+ var UNICODE_SUPPORTED = EMOJI_SUPPORTED;
55
+ var EMOJI_PAIRS = [
56
+ ["\u2705", "[OK]"],
57
+ ["\u274C", "[x]"],
58
+ ["\u26A0\uFE0F", "[!]"],
59
+ ["\u26A0", "[!]"],
60
+ ["\u2139\uFE0F", "[i]"],
61
+ ["\u2139", "[i]"],
62
+ ["\u{1F4CB}", "[i]"],
63
+ ["\u{1F4C4}", "[file]"],
64
+ ["\u{1F4C1}", "[dir]"],
65
+ ["\u{1F4BE}", "[saved]"],
66
+ ["\u{1F4E1}", "[net]"],
67
+ ["\u{1F9F0}", "[tools]"],
68
+ ["\u{1F512}", "[lock]"],
69
+ ["\u{1F6AB}", "[denied]"],
70
+ ["\u{1F4E6}", "[files]"],
71
+ ["\u{1F3F7}\uFE0F", "[name]"],
72
+ ["\u{1F3F7}", "[name]"],
73
+ ["\u{1F4A1}", "*"],
74
+ ["\u2934", "->"],
75
+ ["\u2699", "[run]"],
76
+ ["\u23F9", "[stop]"],
77
+ ["\u{1F504}", "[..]"],
78
+ ["\u2717", "x"],
79
+ ["\u{1F680}", "[run]"],
80
+ ["\u{1F50D}", "[search]"],
81
+ ["\u{1F527}", "[tool]"],
82
+ ["\u{1F3AF}", "[goal]"],
83
+ ["\u{1F4DD}", "[note]"],
84
+ ["\u2728", "*"],
85
+ ["\u{1F41B}", "[bug]"],
86
+ ["\u{1F389}", "*"],
87
+ ["\u{1F440}", "[look]"],
88
+ ["\u{1F916}", "[bot]"],
89
+ ["\u{1F514}", "[!]"],
90
+ ["\u2B06", "^"],
91
+ ["\u2B07", "v"]
92
+ ];
93
+ var EMOJI_MAP = new Map(EMOJI_PAIRS);
94
+ function escapeRegExp(s) {
95
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
96
+ }
97
+ var EMOJI_RE = new RegExp(
98
+ [...EMOJI_PAIRS].sort((a, b) => b[0].length - a[0].length).map(([k]) => escapeRegExp(k)).join("|"),
99
+ "g"
100
+ );
101
+ function sanitizeEmoji(text, supported = EMOJI_SUPPORTED) {
102
+ if (supported || !text) return text;
103
+ return text.replace(EMOJI_RE, (m) => EMOJI_MAP.get(m) ?? m);
104
+ }
105
+ var sym = {
106
+ get ok() {
107
+ return EMOJI_SUPPORTED ? "\u2705" : "[OK]";
108
+ },
109
+ get fail() {
110
+ return EMOJI_SUPPORTED ? "\u274C" : "[x]";
111
+ },
112
+ get warn() {
113
+ return EMOJI_SUPPORTED ? "\u26A0\uFE0F" : "[!]";
114
+ },
115
+ get info() {
116
+ return EMOJI_SUPPORTED ? "\u2139\uFE0F" : "[i]";
117
+ },
118
+ get list() {
119
+ return EMOJI_SUPPORTED ? "\u{1F4CB}" : "[i]";
120
+ },
121
+ get file() {
122
+ return EMOJI_SUPPORTED ? "\u{1F4C4}" : "[file]";
123
+ },
124
+ get dir() {
125
+ return EMOJI_SUPPORTED ? "\u{1F4C1}" : "[dir]";
126
+ },
127
+ get save() {
128
+ return EMOJI_SUPPORTED ? "\u{1F4BE}" : "[saved]";
129
+ }
130
+ };
131
+ function mapArgs(args) {
132
+ return args.map((a) => typeof a === "string" ? sanitizeEmoji(a) : a);
133
+ }
134
+ function cliLog(...args) {
135
+ console.log(...mapArgs(args));
136
+ }
137
+ function cliErr(...args) {
138
+ console.error(...mapArgs(args));
139
+ }
140
+ function cliWarn(...args) {
141
+ console.warn(...mapArgs(args));
142
+ }
143
+ function cliWrite(chunk, ...rest) {
144
+ return process.stdout.write(
145
+ typeof chunk === "string" ? sanitizeEmoji(chunk) : chunk,
146
+ ...rest
147
+ );
148
+ }
149
+ // Annotate the CommonJS export names for ESM import in node:
150
+ 0 && (module.exports = {
151
+ EMOJI_SUPPORTED,
152
+ UNICODE_SUPPORTED,
153
+ cliErr,
154
+ cliLog,
155
+ cliWarn,
156
+ cliWrite,
157
+ detectEmojiSupport,
158
+ sanitizeEmoji,
159
+ sym
160
+ });
161
+ //# sourceMappingURL=terminal.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/terminal.ts"],"sourcesContent":["/**\n * 终端输出兼容层:终端能力探测 + emoji 降级\n *\n * 背景:Windows 传统控制台(conhost / cmd.exe / 旧 PowerShell 宿主)默认字体\n * 不含 astral 平面 emoji 字形,Node 通过 WriteConsoleW 写入时找不到字形,会显示\n * 成 `?`/豆腐块。这是字形覆盖问题,`chcp 65001` 之类改编码的手段解决不了。\n *\n * 处理:探测终端是否支持 emoji,不支持时把 CLI 自身与引擎钩子输出的 emoji\n * 降级为等义 ASCII 标记(✅→[OK]、❌→[x]、⚠️→[!] …),Markdown 表格也一并\n * 改用 ASCII 边框(见 MarkdownRenderer 的 `unicode` 选项)。\n *\n * 环境变量覆盖:\n * ZQAGENT_EMOJI=1/0 强制开/关\n * ZQAGENT_ASCII=1 强制降级\n * NO_EMOJI=1 强制降级\n */\n\n/** 探测入参(便于测试注入;默认取当前进程的 process / stdout) */\nexport interface TerminalDetectInput {\n env?: NodeJS.ProcessEnv;\n platform?: NodeJS.Platform;\n isTTY?: boolean;\n}\n\n/**\n * 探测当前终端是否能渲染 emoji。\n * 非 Windows 一律支持;Windows 下仅 Windows Terminal / VS Code 终端 / ConEmu\n * 等现代终端支持,传统 conhost 降级。重定向/管道(非 TTY)保持原样(UTF-8 文件安全)。\n */\nexport function detectEmojiSupport(input: TerminalDetectInput = {}): boolean {\n const env = input.env ?? process.env;\n const platform = input.platform ?? process.platform;\n const isTTY = input.isTTY ?? Boolean(process.stdout.isTTY);\n\n const flag = (env.ZQAGENT_EMOJI ?? \"\").trim().toLowerCase();\n if ([\"0\", \"false\", \"no\", \"off\"].includes(flag)) return false;\n if ([\"1\", \"true\", \"yes\", \"on\"].includes(flag)) return true;\n if (env.ZQAGENT_ASCII && env.ZQAGENT_ASCII !== \"0\") return false;\n if (env.NO_EMOJI && env.NO_EMOJI !== \"0\") return false;\n\n if (platform !== \"win32\") return true;\n // 重定向到文件 / 管道:写的是 UTF-8 字节,交给下游自行渲染\n if (!isTTY) return true;\n // Windows Terminal 会设置 WT_SESSION\n if (env.WT_SESSION) return true;\n const termProgram = (env.TERM_PROGRAM ?? \"\").toLowerCase();\n if (termProgram === \"vscode\" || termProgram === \"windows-terminal\") return true;\n if ((env.ConEmuANSI ?? \"\").toUpperCase() === \"ON\") return true;\n const term = (env.TERM ?? \"\").toLowerCase();\n if (term.includes(\"xterm\") || term.includes(\"mintty\")) return true;\n\n // 传统 conhost / cmd.exe / 旧 PowerShell 宿主\n return false;\n}\n\n/** 本进程终端是否支持 emoji(启动时求值一次) */\nexport const EMOJI_SUPPORTED: boolean = detectEmojiSupport();\n\n/** 是否支持 Unicode 绘制(表格边框等)。与 emoji 同源:传统 cmd 下一并降级 */\nexport const UNICODE_SUPPORTED: boolean = EMOJI_SUPPORTED;\n\n/** emoji → ASCII 等义标记(保持纯 ASCII,确保传统控制台可读) */\nconst EMOJI_PAIRS: ReadonlyArray<readonly [string, string]> = [\n [\"✅\", \"[OK]\"],\n [\"❌\", \"[x]\"],\n [\"⚠️\", \"[!]\"],\n [\"⚠\", \"[!]\"],\n [\"ℹ️\", \"[i]\"],\n [\"ℹ\", \"[i]\"],\n [\"📋\", \"[i]\"],\n [\"📄\", \"[file]\"],\n [\"📁\", \"[dir]\"],\n [\"💾\", \"[saved]\"],\n [\"📡\", \"[net]\"],\n [\"🧰\", \"[tools]\"],\n [\"🔒\", \"[lock]\"],\n [\"🚫\", \"[denied]\"],\n [\"📦\", \"[files]\"],\n [\"🏷️\", \"[name]\"],\n [\"🏷\", \"[name]\"],\n [\"💡\", \"*\"],\n [\"⤴\", \"->\"],\n [\"⚙\", \"[run]\"],\n [\"⏹\", \"[stop]\"],\n [\"🔄\", \"[..]\"],\n [\"✗\", \"x\"],\n [\"🚀\", \"[run]\"],\n [\"🔍\", \"[search]\"],\n [\"🔧\", \"[tool]\"],\n [\"🎯\", \"[goal]\"],\n [\"📝\", \"[note]\"],\n [\"✨\", \"*\"],\n [\"🐛\", \"[bug]\"],\n [\"🎉\", \"*\"],\n [\"👀\", \"[look]\"],\n [\"🤖\", \"[bot]\"],\n [\"🔔\", \"[!]\"],\n [\"⬆\", \"^\"],\n [\"⬇\", \"v\"],\n];\n\nconst EMOJI_MAP = new Map<string, string>(EMOJI_PAIRS);\n\nfunction escapeRegExp(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\n// 长的键排在前面,避免 \"⚠️\" 被 \"⚠\" 先匹配掉\nconst EMOJI_RE = new RegExp(\n [...EMOJI_PAIRS]\n .sort((a, b) => b[0].length - a[0].length)\n .map(([k]) => escapeRegExp(k))\n .join(\"|\"),\n \"g\",\n);\n\n/**\n * 把文本里的 emoji 降级为 ASCII 标记。`supported` 为 true 时原样返回。\n * 第二个参数便于测试注入;生产调用默认用本进程的 EMOJI_SUPPORTED。\n */\nexport function sanitizeEmoji(text: string, supported: boolean = EMOJI_SUPPORTED): string {\n if (supported || !text) return text;\n return text.replace(EMOJI_RE, (m) => EMOJI_MAP.get(m) ?? m);\n}\n\n/** emoji 或 ASCII 标记,按终端能力取值 */\nexport const sym = {\n get ok() {\n return EMOJI_SUPPORTED ? \"✅\" : \"[OK]\";\n },\n get fail() {\n return EMOJI_SUPPORTED ? \"❌\" : \"[x]\";\n },\n get warn() {\n return EMOJI_SUPPORTED ? \"⚠️\" : \"[!]\";\n },\n get info() {\n return EMOJI_SUPPORTED ? \"ℹ️\" : \"[i]\";\n },\n get list() {\n return EMOJI_SUPPORTED ? \"📋\" : \"[i]\";\n },\n get file() {\n return EMOJI_SUPPORTED ? \"📄\" : \"[file]\";\n },\n get dir() {\n return EMOJI_SUPPORTED ? \"📁\" : \"[dir]\";\n },\n get save() {\n return EMOJI_SUPPORTED ? \"💾\" : \"[saved]\";\n },\n} as const;\n\n// =====================================================================\n// 输出包装:CLI 自身与引擎钩子的输出统一经过这里,按终端能力降级\n// =====================================================================\n\nfunction mapArgs(args: unknown[]): unknown[] {\n return args.map((a) => (typeof a === \"string\" ? sanitizeEmoji(a) : a));\n}\n\n/** console.log 的降级包装 */\nexport function cliLog(...args: unknown[]): void {\n console.log(...mapArgs(args));\n}\n\n/** console.error 的降级包装 */\nexport function cliErr(...args: unknown[]): void {\n console.error(...mapArgs(args));\n}\n\n/** console.warn 的降级包装 */\nexport function cliWarn(...args: unknown[]): void {\n console.warn(...mapArgs(args));\n}\n\n/** process.stdout.write 的降级包装(仅处理字符串块,保留二进制透传) */\nexport function cliWrite(chunk: string | Uint8Array, ...rest: unknown[]): boolean {\n return (process.stdout.write as (...a: unknown[]) => boolean)(\n typeof chunk === \"string\" ? sanitizeEmoji(chunk) : chunk,\n ...rest,\n );\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * 终端输出兼容层:终端能力探测 + emoji 降级
3
+ *
4
+ * 背景:Windows 传统控制台(conhost / cmd.exe / 旧 PowerShell 宿主)默认字体
5
+ * 不含 astral 平面 emoji 字形,Node 通过 WriteConsoleW 写入时找不到字形,会显示
6
+ * 成 `?`/豆腐块。这是字形覆盖问题,`chcp 65001` 之类改编码的手段解决不了。
7
+ *
8
+ * 处理:探测终端是否支持 emoji,不支持时把 CLI 自身与引擎钩子输出的 emoji
9
+ * 降级为等义 ASCII 标记(✅→[OK]、❌→[x]、⚠️→[!] …),Markdown 表格也一并
10
+ * 改用 ASCII 边框(见 MarkdownRenderer 的 `unicode` 选项)。
11
+ *
12
+ * 环境变量覆盖:
13
+ * ZQAGENT_EMOJI=1/0 强制开/关
14
+ * ZQAGENT_ASCII=1 强制降级
15
+ * NO_EMOJI=1 强制降级
16
+ */
17
+ /** 探测入参(便于测试注入;默认取当前进程的 process / stdout) */
18
+ interface TerminalDetectInput {
19
+ env?: NodeJS.ProcessEnv;
20
+ platform?: NodeJS.Platform;
21
+ isTTY?: boolean;
22
+ }
23
+ /**
24
+ * 探测当前终端是否能渲染 emoji。
25
+ * 非 Windows 一律支持;Windows 下仅 Windows Terminal / VS Code 终端 / ConEmu
26
+ * 等现代终端支持,传统 conhost 降级。重定向/管道(非 TTY)保持原样(UTF-8 文件安全)。
27
+ */
28
+ declare function detectEmojiSupport(input?: TerminalDetectInput): boolean;
29
+ /** 本进程终端是否支持 emoji(启动时求值一次) */
30
+ declare const EMOJI_SUPPORTED: boolean;
31
+ /** 是否支持 Unicode 绘制(表格边框等)。与 emoji 同源:传统 cmd 下一并降级 */
32
+ declare const UNICODE_SUPPORTED: boolean;
33
+ /**
34
+ * 把文本里的 emoji 降级为 ASCII 标记。`supported` 为 true 时原样返回。
35
+ * 第二个参数便于测试注入;生产调用默认用本进程的 EMOJI_SUPPORTED。
36
+ */
37
+ declare function sanitizeEmoji(text: string, supported?: boolean): string;
38
+ /** emoji 或 ASCII 标记,按终端能力取值 */
39
+ declare const sym: {
40
+ readonly ok: "✅" | "[OK]";
41
+ readonly fail: "❌" | "[x]";
42
+ readonly warn: "⚠️" | "[!]";
43
+ readonly info: "ℹ️" | "[i]";
44
+ readonly list: "[i]" | "📋";
45
+ readonly file: "📄" | "[file]";
46
+ readonly dir: "📁" | "[dir]";
47
+ readonly save: "💾" | "[saved]";
48
+ };
49
+ /** console.log 的降级包装 */
50
+ declare function cliLog(...args: unknown[]): void;
51
+ /** console.error 的降级包装 */
52
+ declare function cliErr(...args: unknown[]): void;
53
+ /** console.warn 的降级包装 */
54
+ declare function cliWarn(...args: unknown[]): void;
55
+ /** process.stdout.write 的降级包装(仅处理字符串块,保留二进制透传) */
56
+ declare function cliWrite(chunk: string | Uint8Array, ...rest: unknown[]): boolean;
57
+
58
+ export { EMOJI_SUPPORTED, type TerminalDetectInput, UNICODE_SUPPORTED, cliErr, cliLog, cliWarn, cliWrite, detectEmojiSupport, sanitizeEmoji, sym };
@@ -0,0 +1,58 @@
1
+ /**
2
+ * 终端输出兼容层:终端能力探测 + emoji 降级
3
+ *
4
+ * 背景:Windows 传统控制台(conhost / cmd.exe / 旧 PowerShell 宿主)默认字体
5
+ * 不含 astral 平面 emoji 字形,Node 通过 WriteConsoleW 写入时找不到字形,会显示
6
+ * 成 `?`/豆腐块。这是字形覆盖问题,`chcp 65001` 之类改编码的手段解决不了。
7
+ *
8
+ * 处理:探测终端是否支持 emoji,不支持时把 CLI 自身与引擎钩子输出的 emoji
9
+ * 降级为等义 ASCII 标记(✅→[OK]、❌→[x]、⚠️→[!] …),Markdown 表格也一并
10
+ * 改用 ASCII 边框(见 MarkdownRenderer 的 `unicode` 选项)。
11
+ *
12
+ * 环境变量覆盖:
13
+ * ZQAGENT_EMOJI=1/0 强制开/关
14
+ * ZQAGENT_ASCII=1 强制降级
15
+ * NO_EMOJI=1 强制降级
16
+ */
17
+ /** 探测入参(便于测试注入;默认取当前进程的 process / stdout) */
18
+ interface TerminalDetectInput {
19
+ env?: NodeJS.ProcessEnv;
20
+ platform?: NodeJS.Platform;
21
+ isTTY?: boolean;
22
+ }
23
+ /**
24
+ * 探测当前终端是否能渲染 emoji。
25
+ * 非 Windows 一律支持;Windows 下仅 Windows Terminal / VS Code 终端 / ConEmu
26
+ * 等现代终端支持,传统 conhost 降级。重定向/管道(非 TTY)保持原样(UTF-8 文件安全)。
27
+ */
28
+ declare function detectEmojiSupport(input?: TerminalDetectInput): boolean;
29
+ /** 本进程终端是否支持 emoji(启动时求值一次) */
30
+ declare const EMOJI_SUPPORTED: boolean;
31
+ /** 是否支持 Unicode 绘制(表格边框等)。与 emoji 同源:传统 cmd 下一并降级 */
32
+ declare const UNICODE_SUPPORTED: boolean;
33
+ /**
34
+ * 把文本里的 emoji 降级为 ASCII 标记。`supported` 为 true 时原样返回。
35
+ * 第二个参数便于测试注入;生产调用默认用本进程的 EMOJI_SUPPORTED。
36
+ */
37
+ declare function sanitizeEmoji(text: string, supported?: boolean): string;
38
+ /** emoji 或 ASCII 标记,按终端能力取值 */
39
+ declare const sym: {
40
+ readonly ok: "✅" | "[OK]";
41
+ readonly fail: "❌" | "[x]";
42
+ readonly warn: "⚠️" | "[!]";
43
+ readonly info: "ℹ️" | "[i]";
44
+ readonly list: "[i]" | "📋";
45
+ readonly file: "📄" | "[file]";
46
+ readonly dir: "📁" | "[dir]";
47
+ readonly save: "💾" | "[saved]";
48
+ };
49
+ /** console.log 的降级包装 */
50
+ declare function cliLog(...args: unknown[]): void;
51
+ /** console.error 的降级包装 */
52
+ declare function cliErr(...args: unknown[]): void;
53
+ /** console.warn 的降级包装 */
54
+ declare function cliWarn(...args: unknown[]): void;
55
+ /** process.stdout.write 的降级包装(仅处理字符串块,保留二进制透传) */
56
+ declare function cliWrite(chunk: string | Uint8Array, ...rest: unknown[]): boolean;
57
+
58
+ export { EMOJI_SUPPORTED, type TerminalDetectInput, UNICODE_SUPPORTED, cliErr, cliLog, cliWarn, cliWrite, detectEmojiSupport, sanitizeEmoji, sym };
@@ -0,0 +1,128 @@
1
+ // src/terminal.ts
2
+ function detectEmojiSupport(input = {}) {
3
+ const env = input.env ?? process.env;
4
+ const platform = input.platform ?? process.platform;
5
+ const isTTY = input.isTTY ?? Boolean(process.stdout.isTTY);
6
+ const flag = (env.ZQAGENT_EMOJI ?? "").trim().toLowerCase();
7
+ if (["0", "false", "no", "off"].includes(flag)) return false;
8
+ if (["1", "true", "yes", "on"].includes(flag)) return true;
9
+ if (env.ZQAGENT_ASCII && env.ZQAGENT_ASCII !== "0") return false;
10
+ if (env.NO_EMOJI && env.NO_EMOJI !== "0") return false;
11
+ if (platform !== "win32") return true;
12
+ if (!isTTY) return true;
13
+ if (env.WT_SESSION) return true;
14
+ const termProgram = (env.TERM_PROGRAM ?? "").toLowerCase();
15
+ if (termProgram === "vscode" || termProgram === "windows-terminal") return true;
16
+ if ((env.ConEmuANSI ?? "").toUpperCase() === "ON") return true;
17
+ const term = (env.TERM ?? "").toLowerCase();
18
+ if (term.includes("xterm") || term.includes("mintty")) return true;
19
+ return false;
20
+ }
21
+ var EMOJI_SUPPORTED = detectEmojiSupport();
22
+ var UNICODE_SUPPORTED = EMOJI_SUPPORTED;
23
+ var EMOJI_PAIRS = [
24
+ ["\u2705", "[OK]"],
25
+ ["\u274C", "[x]"],
26
+ ["\u26A0\uFE0F", "[!]"],
27
+ ["\u26A0", "[!]"],
28
+ ["\u2139\uFE0F", "[i]"],
29
+ ["\u2139", "[i]"],
30
+ ["\u{1F4CB}", "[i]"],
31
+ ["\u{1F4C4}", "[file]"],
32
+ ["\u{1F4C1}", "[dir]"],
33
+ ["\u{1F4BE}", "[saved]"],
34
+ ["\u{1F4E1}", "[net]"],
35
+ ["\u{1F9F0}", "[tools]"],
36
+ ["\u{1F512}", "[lock]"],
37
+ ["\u{1F6AB}", "[denied]"],
38
+ ["\u{1F4E6}", "[files]"],
39
+ ["\u{1F3F7}\uFE0F", "[name]"],
40
+ ["\u{1F3F7}", "[name]"],
41
+ ["\u{1F4A1}", "*"],
42
+ ["\u2934", "->"],
43
+ ["\u2699", "[run]"],
44
+ ["\u23F9", "[stop]"],
45
+ ["\u{1F504}", "[..]"],
46
+ ["\u2717", "x"],
47
+ ["\u{1F680}", "[run]"],
48
+ ["\u{1F50D}", "[search]"],
49
+ ["\u{1F527}", "[tool]"],
50
+ ["\u{1F3AF}", "[goal]"],
51
+ ["\u{1F4DD}", "[note]"],
52
+ ["\u2728", "*"],
53
+ ["\u{1F41B}", "[bug]"],
54
+ ["\u{1F389}", "*"],
55
+ ["\u{1F440}", "[look]"],
56
+ ["\u{1F916}", "[bot]"],
57
+ ["\u{1F514}", "[!]"],
58
+ ["\u2B06", "^"],
59
+ ["\u2B07", "v"]
60
+ ];
61
+ var EMOJI_MAP = new Map(EMOJI_PAIRS);
62
+ function escapeRegExp(s) {
63
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
64
+ }
65
+ var EMOJI_RE = new RegExp(
66
+ [...EMOJI_PAIRS].sort((a, b) => b[0].length - a[0].length).map(([k]) => escapeRegExp(k)).join("|"),
67
+ "g"
68
+ );
69
+ function sanitizeEmoji(text, supported = EMOJI_SUPPORTED) {
70
+ if (supported || !text) return text;
71
+ return text.replace(EMOJI_RE, (m) => EMOJI_MAP.get(m) ?? m);
72
+ }
73
+ var sym = {
74
+ get ok() {
75
+ return EMOJI_SUPPORTED ? "\u2705" : "[OK]";
76
+ },
77
+ get fail() {
78
+ return EMOJI_SUPPORTED ? "\u274C" : "[x]";
79
+ },
80
+ get warn() {
81
+ return EMOJI_SUPPORTED ? "\u26A0\uFE0F" : "[!]";
82
+ },
83
+ get info() {
84
+ return EMOJI_SUPPORTED ? "\u2139\uFE0F" : "[i]";
85
+ },
86
+ get list() {
87
+ return EMOJI_SUPPORTED ? "\u{1F4CB}" : "[i]";
88
+ },
89
+ get file() {
90
+ return EMOJI_SUPPORTED ? "\u{1F4C4}" : "[file]";
91
+ },
92
+ get dir() {
93
+ return EMOJI_SUPPORTED ? "\u{1F4C1}" : "[dir]";
94
+ },
95
+ get save() {
96
+ return EMOJI_SUPPORTED ? "\u{1F4BE}" : "[saved]";
97
+ }
98
+ };
99
+ function mapArgs(args) {
100
+ return args.map((a) => typeof a === "string" ? sanitizeEmoji(a) : a);
101
+ }
102
+ function cliLog(...args) {
103
+ console.log(...mapArgs(args));
104
+ }
105
+ function cliErr(...args) {
106
+ console.error(...mapArgs(args));
107
+ }
108
+ function cliWarn(...args) {
109
+ console.warn(...mapArgs(args));
110
+ }
111
+ function cliWrite(chunk, ...rest) {
112
+ return process.stdout.write(
113
+ typeof chunk === "string" ? sanitizeEmoji(chunk) : chunk,
114
+ ...rest
115
+ );
116
+ }
117
+ export {
118
+ EMOJI_SUPPORTED,
119
+ UNICODE_SUPPORTED,
120
+ cliErr,
121
+ cliLog,
122
+ cliWarn,
123
+ cliWrite,
124
+ detectEmojiSupport,
125
+ sanitizeEmoji,
126
+ sym
127
+ };
128
+ //# sourceMappingURL=terminal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/terminal.ts"],"sourcesContent":["/**\n * 终端输出兼容层:终端能力探测 + emoji 降级\n *\n * 背景:Windows 传统控制台(conhost / cmd.exe / 旧 PowerShell 宿主)默认字体\n * 不含 astral 平面 emoji 字形,Node 通过 WriteConsoleW 写入时找不到字形,会显示\n * 成 `?`/豆腐块。这是字形覆盖问题,`chcp 65001` 之类改编码的手段解决不了。\n *\n * 处理:探测终端是否支持 emoji,不支持时把 CLI 自身与引擎钩子输出的 emoji\n * 降级为等义 ASCII 标记(✅→[OK]、❌→[x]、⚠️→[!] …),Markdown 表格也一并\n * 改用 ASCII 边框(见 MarkdownRenderer 的 `unicode` 选项)。\n *\n * 环境变量覆盖:\n * ZQAGENT_EMOJI=1/0 强制开/关\n * ZQAGENT_ASCII=1 强制降级\n * NO_EMOJI=1 强制降级\n */\n\n/** 探测入参(便于测试注入;默认取当前进程的 process / stdout) */\nexport interface TerminalDetectInput {\n env?: NodeJS.ProcessEnv;\n platform?: NodeJS.Platform;\n isTTY?: boolean;\n}\n\n/**\n * 探测当前终端是否能渲染 emoji。\n * 非 Windows 一律支持;Windows 下仅 Windows Terminal / VS Code 终端 / ConEmu\n * 等现代终端支持,传统 conhost 降级。重定向/管道(非 TTY)保持原样(UTF-8 文件安全)。\n */\nexport function detectEmojiSupport(input: TerminalDetectInput = {}): boolean {\n const env = input.env ?? process.env;\n const platform = input.platform ?? process.platform;\n const isTTY = input.isTTY ?? Boolean(process.stdout.isTTY);\n\n const flag = (env.ZQAGENT_EMOJI ?? \"\").trim().toLowerCase();\n if ([\"0\", \"false\", \"no\", \"off\"].includes(flag)) return false;\n if ([\"1\", \"true\", \"yes\", \"on\"].includes(flag)) return true;\n if (env.ZQAGENT_ASCII && env.ZQAGENT_ASCII !== \"0\") return false;\n if (env.NO_EMOJI && env.NO_EMOJI !== \"0\") return false;\n\n if (platform !== \"win32\") return true;\n // 重定向到文件 / 管道:写的是 UTF-8 字节,交给下游自行渲染\n if (!isTTY) return true;\n // Windows Terminal 会设置 WT_SESSION\n if (env.WT_SESSION) return true;\n const termProgram = (env.TERM_PROGRAM ?? \"\").toLowerCase();\n if (termProgram === \"vscode\" || termProgram === \"windows-terminal\") return true;\n if ((env.ConEmuANSI ?? \"\").toUpperCase() === \"ON\") return true;\n const term = (env.TERM ?? \"\").toLowerCase();\n if (term.includes(\"xterm\") || term.includes(\"mintty\")) return true;\n\n // 传统 conhost / cmd.exe / 旧 PowerShell 宿主\n return false;\n}\n\n/** 本进程终端是否支持 emoji(启动时求值一次) */\nexport const EMOJI_SUPPORTED: boolean = detectEmojiSupport();\n\n/** 是否支持 Unicode 绘制(表格边框等)。与 emoji 同源:传统 cmd 下一并降级 */\nexport const UNICODE_SUPPORTED: boolean = EMOJI_SUPPORTED;\n\n/** emoji → ASCII 等义标记(保持纯 ASCII,确保传统控制台可读) */\nconst EMOJI_PAIRS: ReadonlyArray<readonly [string, string]> = [\n [\"✅\", \"[OK]\"],\n [\"❌\", \"[x]\"],\n [\"⚠️\", \"[!]\"],\n [\"⚠\", \"[!]\"],\n [\"ℹ️\", \"[i]\"],\n [\"ℹ\", \"[i]\"],\n [\"📋\", \"[i]\"],\n [\"📄\", \"[file]\"],\n [\"📁\", \"[dir]\"],\n [\"💾\", \"[saved]\"],\n [\"📡\", \"[net]\"],\n [\"🧰\", \"[tools]\"],\n [\"🔒\", \"[lock]\"],\n [\"🚫\", \"[denied]\"],\n [\"📦\", \"[files]\"],\n [\"🏷️\", \"[name]\"],\n [\"🏷\", \"[name]\"],\n [\"💡\", \"*\"],\n [\"⤴\", \"->\"],\n [\"⚙\", \"[run]\"],\n [\"⏹\", \"[stop]\"],\n [\"🔄\", \"[..]\"],\n [\"✗\", \"x\"],\n [\"🚀\", \"[run]\"],\n [\"🔍\", \"[search]\"],\n [\"🔧\", \"[tool]\"],\n [\"🎯\", \"[goal]\"],\n [\"📝\", \"[note]\"],\n [\"✨\", \"*\"],\n [\"🐛\", \"[bug]\"],\n [\"🎉\", \"*\"],\n [\"👀\", \"[look]\"],\n [\"🤖\", \"[bot]\"],\n [\"🔔\", \"[!]\"],\n [\"⬆\", \"^\"],\n [\"⬇\", \"v\"],\n];\n\nconst EMOJI_MAP = new Map<string, string>(EMOJI_PAIRS);\n\nfunction escapeRegExp(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\n// 长的键排在前面,避免 \"⚠️\" 被 \"⚠\" 先匹配掉\nconst EMOJI_RE = new RegExp(\n [...EMOJI_PAIRS]\n .sort((a, b) => b[0].length - a[0].length)\n .map(([k]) => escapeRegExp(k))\n .join(\"|\"),\n \"g\",\n);\n\n/**\n * 把文本里的 emoji 降级为 ASCII 标记。`supported` 为 true 时原样返回。\n * 第二个参数便于测试注入;生产调用默认用本进程的 EMOJI_SUPPORTED。\n */\nexport function sanitizeEmoji(text: string, supported: boolean = EMOJI_SUPPORTED): string {\n if (supported || !text) return text;\n return text.replace(EMOJI_RE, (m) => EMOJI_MAP.get(m) ?? m);\n}\n\n/** emoji 或 ASCII 标记,按终端能力取值 */\nexport const sym = {\n get ok() {\n return EMOJI_SUPPORTED ? \"✅\" : \"[OK]\";\n },\n get fail() {\n return EMOJI_SUPPORTED ? \"❌\" : \"[x]\";\n },\n get warn() {\n return EMOJI_SUPPORTED ? \"⚠️\" : \"[!]\";\n },\n get info() {\n return EMOJI_SUPPORTED ? \"ℹ️\" : \"[i]\";\n },\n get list() {\n return EMOJI_SUPPORTED ? \"📋\" : \"[i]\";\n },\n get file() {\n return EMOJI_SUPPORTED ? \"📄\" : \"[file]\";\n },\n get dir() {\n return EMOJI_SUPPORTED ? \"📁\" : \"[dir]\";\n },\n get save() {\n return EMOJI_SUPPORTED ? \"💾\" : \"[saved]\";\n },\n} as const;\n\n// =====================================================================\n// 输出包装:CLI 自身与引擎钩子的输出统一经过这里,按终端能力降级\n// =====================================================================\n\nfunction mapArgs(args: unknown[]): unknown[] {\n return args.map((a) => (typeof a === \"string\" ? sanitizeEmoji(a) : a));\n}\n\n/** console.log 的降级包装 */\nexport function cliLog(...args: unknown[]): void {\n console.log(...mapArgs(args));\n}\n\n/** console.error 的降级包装 */\nexport function cliErr(...args: unknown[]): void {\n console.error(...mapArgs(args));\n}\n\n/** console.warn 的降级包装 */\nexport function cliWarn(...args: unknown[]): void {\n console.warn(...mapArgs(args));\n}\n\n/** process.stdout.write 的降级包装(仅处理字符串块,保留二进制透传) */\nexport function cliWrite(chunk: string | Uint8Array, ...rest: unknown[]): boolean {\n return (process.stdout.write as (...a: unknown[]) => boolean)(\n typeof chunk === \"string\" ? sanitizeEmoji(chunk) : chunk,\n ...rest,\n );\n}\n"],"mappings":"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","names":[]}