@zqagent/core 0.2.16 → 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.
- package/README.md +1 -1
- package/README.oss.en.md +15 -2
- package/README.oss.md +18 -2
- package/README.oss.zh-TW.md +13 -2
- package/dist/McpClient-CEQBUPuC.d.cts +145 -0
- package/dist/McpClient-CEQBUPuC.d.ts +145 -0
- package/dist/{ToolRegistry-C3pTFiaJ.d.cts → ToolRegistry-Cy0fQxrb.d.ts} +2 -111
- package/dist/{ToolRegistry-C3pTFiaJ.d.ts → ToolRegistry-DouDvn9E.d.cts} +2 -111
- package/dist/{explore-RtZtb-V6.d.ts → TurnEngine-BPKhWuSI.d.cts} +108 -73
- package/dist/{explore-BgRhj6SP.d.cts → TurnEngine-DtGEuVLT.d.ts} +108 -73
- package/dist/conflictGuard-BBOOIxxK.d.cts +42 -0
- package/dist/conflictGuard-BBOOIxxK.d.ts +42 -0
- package/dist/index.cjs +5477 -1140
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +577 -195
- package/dist/index.d.ts +577 -195
- package/dist/index.js +5305 -1002
- package/dist/index.js.map +1 -1
- package/dist/internal/crypto/secrets.cjs +25 -0
- package/dist/internal/crypto/secrets.cjs.map +1 -0
- package/dist/internal/crypto/secrets.d.cts +2 -0
- package/dist/internal/crypto/secrets.d.ts +2 -0
- package/dist/internal/crypto/secrets.js +3 -0
- package/dist/internal/crypto/secrets.js.map +1 -0
- package/dist/internal/engine/FileLogHook.cjs +179 -0
- package/dist/internal/engine/FileLogHook.cjs.map +1 -0
- package/dist/internal/engine/FileLogHook.d.cts +67 -0
- package/dist/internal/engine/FileLogHook.d.ts +67 -0
- package/dist/internal/engine/FileLogHook.js +144 -0
- package/dist/internal/engine/FileLogHook.js.map +1 -0
- package/dist/internal/engine/approvalView.cjs +25 -0
- package/dist/internal/engine/approvalView.cjs.map +1 -0
- package/dist/internal/engine/approvalView.d.cts +1 -0
- package/dist/internal/engine/approvalView.d.ts +1 -0
- package/dist/internal/engine/approvalView.js +3 -0
- package/dist/internal/engine/approvalView.js.map +1 -0
- package/dist/internal/engine/limits.cjs +59 -0
- package/dist/internal/engine/limits.cjs.map +1 -0
- package/dist/internal/engine/limits.d.cts +32 -0
- package/dist/internal/engine/limits.d.ts +32 -0
- package/dist/internal/engine/limits.js +31 -0
- package/dist/internal/engine/limits.js.map +1 -0
- package/dist/internal/engine/sessionNaming.cjs +47 -0
- package/dist/internal/engine/sessionNaming.cjs.map +1 -0
- package/dist/internal/engine/sessionNaming.d.cts +25 -0
- package/dist/internal/engine/sessionNaming.d.ts +25 -0
- package/dist/internal/engine/sessionNaming.js +20 -0
- package/dist/internal/engine/sessionNaming.js.map +1 -0
- package/dist/internal/llmConfig.cjs +561 -0
- package/dist/internal/llmConfig.cjs.map +1 -0
- package/dist/internal/llmConfig.d.cts +75 -0
- package/dist/internal/llmConfig.d.ts +75 -0
- package/dist/internal/llmConfig.js +535 -0
- package/dist/internal/llmConfig.js.map +1 -0
- package/dist/internal/mcp/builtin/index.cjs +685 -0
- package/dist/internal/mcp/builtin/index.cjs.map +1 -0
- package/dist/internal/mcp/builtin/index.d.cts +21 -0
- package/dist/internal/mcp/builtin/index.d.ts +21 -0
- package/dist/internal/mcp/builtin/index.js +652 -0
- package/dist/internal/mcp/builtin/index.js.map +1 -0
- package/dist/internal/mcp/config.cjs +278 -0
- package/dist/internal/mcp/config.cjs.map +1 -0
- package/dist/internal/mcp/config.d.cts +61 -0
- package/dist/internal/mcp/config.d.ts +61 -0
- package/dist/internal/mcp/config.js +235 -0
- package/dist/internal/mcp/config.js.map +1 -0
- package/dist/internal/mcp/presets.cjs +184 -0
- package/dist/internal/mcp/presets.cjs.map +1 -0
- package/dist/internal/mcp/presets.d.cts +39 -0
- package/dist/internal/mcp/presets.d.ts +39 -0
- package/dist/internal/mcp/presets.js +158 -0
- package/dist/internal/mcp/presets.js.map +1 -0
- package/dist/internal/node-name.cjs +45 -0
- package/dist/internal/node-name.cjs.map +1 -0
- package/dist/internal/node-name.d.cts +3 -0
- package/dist/internal/node-name.d.ts +3 -0
- package/dist/internal/node-name.js +10 -0
- package/dist/internal/node-name.js.map +1 -0
- package/dist/internal/paths.cjs +63 -0
- package/dist/internal/paths.cjs.map +1 -0
- package/dist/internal/paths.d.cts +17 -0
- package/dist/internal/paths.d.ts +17 -0
- package/dist/internal/paths.js +25 -0
- package/dist/internal/paths.js.map +1 -0
- package/dist/internal/providers/diagnostics.cjs +288 -0
- package/dist/internal/providers/diagnostics.cjs.map +1 -0
- package/dist/internal/providers/diagnostics.d.cts +103 -0
- package/dist/internal/providers/diagnostics.d.ts +103 -0
- package/dist/internal/providers/diagnostics.js +250 -0
- package/dist/internal/providers/diagnostics.js.map +1 -0
- package/dist/internal/providers/models.cjs +699 -0
- package/dist/internal/providers/models.cjs.map +1 -0
- package/dist/internal/providers/models.d.cts +136 -0
- package/dist/internal/providers/models.d.ts +136 -0
- package/dist/internal/providers/models.js +654 -0
- package/dist/internal/providers/models.js.map +1 -0
- package/dist/internal/terminal.cjs +161 -0
- package/dist/internal/terminal.cjs.map +1 -0
- package/dist/internal/terminal.d.cts +58 -0
- package/dist/internal/terminal.d.ts +58 -0
- package/dist/internal/terminal.js +128 -0
- package/dist/internal/terminal.js.map +1 -0
- package/dist/internal/tools/execAsync.cjs +187 -0
- package/dist/internal/tools/execAsync.cjs.map +1 -0
- package/dist/internal/tools/execAsync.d.cts +30 -0
- package/dist/internal/tools/execAsync.d.ts +30 -0
- package/dist/internal/tools/execAsync.js +162 -0
- package/dist/internal/tools/execAsync.js.map +1 -0
- package/dist/internal/tools/git.cjs +935 -0
- package/dist/internal/tools/git.cjs.map +1 -0
- package/dist/internal/tools/git.d.cts +206 -0
- package/dist/internal/tools/git.d.ts +206 -0
- package/dist/internal/tools/git.js +883 -0
- package/dist/internal/tools/git.js.map +1 -0
- package/dist/internal/tools/pathSafety.cjs +99 -0
- package/dist/internal/tools/pathSafety.cjs.map +1 -0
- package/dist/internal/tools/pathSafety.d.cts +44 -0
- package/dist/internal/tools/pathSafety.d.ts +44 -0
- package/dist/internal/tools/pathSafety.js +61 -0
- package/dist/internal/tools/pathSafety.js.map +1 -0
- package/dist/internal/tools/plan.cjs +146 -0
- package/dist/internal/tools/plan.cjs.map +1 -0
- package/dist/internal/tools/plan.d.cts +48 -0
- package/dist/internal/tools/plan.d.ts +48 -0
- package/dist/internal/tools/plan.js +117 -0
- package/dist/internal/tools/plan.js.map +1 -0
- package/dist/internal/tools/ripgrep.cjs +118 -0
- package/dist/internal/tools/ripgrep.cjs.map +1 -0
- package/dist/internal/tools/ripgrep.d.cts +6 -0
- package/dist/internal/tools/ripgrep.d.ts +6 -0
- package/dist/internal/tools/ripgrep.js +89 -0
- package/dist/internal/tools/ripgrep.js.map +1 -0
- package/dist/internal/tools/sandbox.cjs +253 -0
- package/dist/internal/tools/sandbox.cjs.map +1 -0
- package/dist/internal/tools/sandbox.d.cts +67 -0
- package/dist/internal/tools/sandbox.d.ts +67 -0
- package/dist/internal/tools/sandbox.js +213 -0
- package/dist/internal/tools/sandbox.js.map +1 -0
- package/dist/internal/tools/shell.cjs +771 -0
- package/dist/internal/tools/shell.cjs.map +1 -0
- package/dist/internal/tools/shell.d.cts +47 -0
- package/dist/internal/tools/shell.d.ts +47 -0
- package/dist/internal/tools/shell.js +731 -0
- package/dist/internal/tools/shell.js.map +1 -0
- package/dist/internal/tools/shellRuntime.cjs +220 -0
- package/dist/internal/tools/shellRuntime.cjs.map +1 -0
- package/dist/internal/tools/shellRuntime.d.cts +67 -0
- package/dist/internal/tools/shellRuntime.d.ts +67 -0
- package/dist/internal/tools/shellRuntime.js +181 -0
- package/dist/internal/tools/shellRuntime.js.map +1 -0
- package/dist/internal/update/controller.cjs +1035 -0
- package/dist/internal/update/controller.cjs.map +1 -0
- package/dist/internal/update/controller.d.cts +150 -0
- package/dist/internal/update/controller.d.ts +150 -0
- package/dist/internal/update/controller.js +991 -0
- package/dist/internal/update/controller.js.map +1 -0
- package/dist/internal/update/platform.cjs +234 -0
- package/dist/internal/update/platform.cjs.map +1 -0
- package/dist/internal/update/platform.d.cts +77 -0
- package/dist/internal/update/platform.d.ts +77 -0
- package/dist/internal/update/platform.js +184 -0
- package/dist/internal/update/platform.js.map +1 -0
- package/dist/tools/ask.d.cts +3 -1
- package/dist/tools/ask.d.ts +3 -1
- package/dist/tools/diff.cjs +17 -6
- package/dist/tools/diff.cjs.map +1 -1
- package/dist/tools/diff.d.cts +5 -2
- package/dist/tools/diff.d.ts +5 -2
- package/dist/tools/diff.js +17 -6
- package/dist/tools/diff.js.map +1 -1
- package/dist/tools/directories.d.cts +3 -1
- package/dist/tools/directories.d.ts +3 -1
- package/dist/tools/explore.cjs +1778 -204
- package/dist/tools/explore.cjs.map +1 -1
- package/dist/tools/explore.d.cts +82 -2
- package/dist/tools/explore.d.ts +82 -2
- package/dist/tools/explore.js +1780 -204
- package/dist/tools/explore.js.map +1 -1
- package/dist/tools/files.cjs +56 -21
- package/dist/tools/files.cjs.map +1 -1
- package/dist/tools/files.d.cts +6 -3
- package/dist/tools/files.d.ts +6 -3
- package/dist/tools/files.js +56 -21
- package/dist/tools/files.js.map +1 -1
- package/dist/tools/git.cjs +34 -25
- package/dist/tools/git.cjs.map +1 -1
- package/dist/tools/git.d.cts +4 -204
- package/dist/tools/git.d.ts +4 -204
- package/dist/tools/git.js +34 -25
- package/dist/tools/git.js.map +1 -1
- package/dist/tools/media.cjs +280 -3
- package/dist/tools/media.cjs.map +1 -1
- package/dist/tools/media.d.cts +3 -1
- package/dist/tools/media.d.ts +3 -1
- package/dist/tools/media.js +278 -1
- package/dist/tools/media.js.map +1 -1
- package/dist/tools/plan.cjs +62 -0
- package/dist/tools/plan.cjs.map +1 -1
- package/dist/tools/plan.d.cts +4 -17
- package/dist/tools/plan.d.ts +4 -17
- package/dist/tools/plan.js +59 -0
- package/dist/tools/plan.js.map +1 -1
- package/dist/tools/search.cjs +3 -0
- package/dist/tools/search.cjs.map +1 -1
- package/dist/tools/search.d.cts +3 -1
- package/dist/tools/search.d.ts +3 -1
- package/dist/tools/search.js +3 -0
- package/dist/tools/search.js.map +1 -1
- package/dist/tools/shell.cjs +76 -11
- package/dist/tools/shell.cjs.map +1 -1
- package/dist/tools/shell.d.cts +5 -39
- package/dist/tools/shell.d.ts +5 -39
- package/dist/tools/shell.js +75 -11
- package/dist/tools/shell.js.map +1 -1
- package/dist/tools/todo.d.cts +3 -1
- package/dist/tools/todo.d.ts +3 -1
- package/dist/tools/web.cjs +279 -5
- package/dist/tools/web.cjs.map +1 -1
- package/dist/tools/web.d.cts +3 -1
- package/dist/tools/web.d.ts +3 -1
- package/dist/tools/web.js +275 -3
- package/dist/tools/web.js.map +1 -1
- package/dist/types-BYyXWWPW.d.cts +237 -0
- package/dist/types-BYyXWWPW.d.ts +237 -0
- package/package.json +26 -37
- package/bin/zqagent +0 -2
- package/dist/cli/index.cjs +0 -24507
- package/dist/cli/index.cjs.map +0 -1
- package/dist/cli/index.d.cts +0 -2
- package/dist/cli/index.d.ts +0 -2
- package/dist/cli/index.js +0 -24490
- package/dist/cli/index.js.map +0 -1
- package/dist/server/index.cjs +0 -19824
- package/dist/server/index.cjs.map +0 -1
- package/dist/server/index.d.cts +0 -2
- package/dist/server/index.d.ts +0 -2
- package/dist/server/index.js +0 -19807
- package/dist/server/index.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/llmConfig.ts","../../src/crypto/secrets.ts","../../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/models.ts","../../src/paths.ts"],"sourcesContent":["/**\n * LLM 配置统一存储(CLI `/connect` 与 Server「节点配置」共用同一份文件)\n *\n * 位置:`~/.config/zqagent/connections.json`(可用 `XDG_CONFIG_HOME` 覆盖;\n * 或用 `ZQAGENT_LLM_CONFIG_FILE` 直接指定文件,便于测试与自定义部署)。\n * 同一台机器上,CLI、以本机用户身份运行的 server、桌面端内嵌 sidecar 读写的都是这一份,\n * 因此任一端的增删改都会立即表现在其它端。\n *\n * 格式 v3:\n * { version: 3, activeId, providers: [{ id, name, type, baseURL, model,\n * reasoningEffort, apiKeyEnc, models, nonChat, modelMeta }], migratedFrom }\n *\n * - `apiKeyEnc`:AES-256-GCM 密文(主密钥与配置同目录,0600)。\n * - `type` 为预设 id(合并了 CLI 的 `provider` 与 Web 的 `type` 两个历史字段名)。\n * - 兼容读取旧格式并自动迁移:CLI 的 v2 / 更早的单连接格式,以及\n * Server 的 `{DATA_DIR}/node-llm.json`(明文 key → 加密)。\n */\nimport * as fs from \"fs\";\nimport * as path from \"path\";\nimport * as crypto from \"crypto\";\nimport type { ReasoningEffort } from \"./engine/types.js\";\nimport { loadOrCreateMasterKey, encryptWithKey, decryptWithKey } from \"./crypto/secrets.js\";\nimport { normalizeModelMeta, type ModelMeta } from \"./providers/models.js\";\nimport { configDir, stateDir } from \"./paths.js\";\nimport { llmPreset } from \"@zqagent/shared/llm-presets\";\n\n/**\n * 统一配置文件路径(惰性解析:读取时才计算,便于测试注入环境变量)。\n * 优先级:`ZQAGENT_LLM_CONFIG_FILE` → `configDir()/connections.json`。\n */\nexport function llmConfigFile(): string {\n const override = process.env.ZQAGENT_LLM_CONFIG_FILE?.trim();\n if (override) return override;\n return path.join(configDir(), \"connections.json\");\n}\n\n/** 单个供应商条目(含加密后的 key) */\nexport interface LlmProviderEntry {\n id: string;\n /** 展示名 */\n name: string;\n /** 预设 id(`llmPresets` 的 id;历史 provider/type 合并到此) */\n type: string;\n baseURL: string;\n model: string;\n /** 推理强度;不支持或未设置时为 null */\n reasoningEffort: ReasoningEffort | null;\n /** API Key 密文(AES-256-GCM,`v1:iv:tag:ciphertext`) */\n apiKeyEnc: string;\n /** 上次刷新得到的候选模型(可选) */\n models?: string[];\n /** 候选中非对话模型的子集(可选,仅用于提示) */\n nonChat?: string[];\n /**\n * 刷新模型列表时从供应商 `/models` 拿到的元数据(含真实上下文长度)。\n * 落盘后供引擎按实际模型精确计算压缩阈值(见 `providers/models.ts`)。\n */\n modelMeta?: ModelMeta[];\n}\n\nexport interface LlmConfigFile {\n version: 3;\n activeId: string | null;\n providers: LlmProviderEntry[];\n /** 已导入过的旧配置来源(绝对路径),避免重复合并 */\n migratedFrom?: string[];\n}\n\n// ===================== 加解密 =====================\n\n/** 主密钥按「配置目录」缓存(不同配置目录用不同密钥;测试隔离后互不影响) */\nconst keyCache = new Map<string, Buffer>();\n\nfunction masterKeyFileFor(file: string): string {\n return path.join(path.dirname(file), \"secret.key\");\n}\n\nfunction getMasterKey(file: string): Buffer {\n const keyFile = masterKeyFileFor(file);\n let key = keyCache.get(keyFile);\n if (!key) {\n key = loadOrCreateMasterKey(keyFile);\n keyCache.set(keyFile, key);\n }\n return key;\n}\n\n/** 加密 API Key;`file` 为配置文件路径(缺省用统一路径,决定主密钥位置) */\nexport function encryptKey(plain: string, file = llmConfigFile()): string {\n return encryptWithKey(getMasterKey(file), plain);\n}\n\n/** 解密 API Key;格式非法 / 主密钥变化返回 null */\nexport function decryptKey(payload: string, file = llmConfigFile()): string | null {\n return decryptWithKey(getMasterKey(file), payload);\n}\n\n/** 生成新的供应商 id */\nexport function newProviderId(): string {\n return crypto.randomBytes(8).toString(\"hex\");\n}\n\n// ===================== 规范化 =====================\n\nfunction normEffort(v: unknown): ReasoningEffort | null {\n return v === \"low\" || v === \"medium\" || v === \"high\" ? v : null;\n}\n\n/** 规范化模型列表(去重、限长) */\nfunction normModelList(v: unknown): string[] | undefined {\n if (!Array.isArray(v)) return undefined;\n const out: string[] = [];\n const seen = new Set<string>();\n for (const x of v) {\n const s = String(x ?? \"\").trim();\n if (!s || seen.has(s)) continue;\n seen.add(s);\n out.push(s);\n if (out.length >= 500) break;\n }\n return out.length ? out : undefined;\n}\n\n/** 由端点反查预设 id(迁移无 type 的旧数据) */\nfunction guessTypeByBaseURL(baseURL: string): string {\n const u = baseURL.toLowerCase().replace(/\\/+$/, \"\");\n if (!u) return \"custom\";\n for (const p of KNOWN_PRESETS) {\n const b = p.baseURL.toLowerCase().replace(/\\/+$/, \"\");\n if (b && (u === b || u.startsWith(b))) return p.id;\n }\n return \"custom\";\n}\n\nconst KNOWN_PRESETS = [\"openai\", \"deepseek\", \"qwen\", \"zhipu\", \"moonshot\", \"xiaomi\"].map(\n (id) => ({ id, baseURL: llmPreset(id)?.baseURL ?? \"\" }),\n);\n\n/** 解析单个条目(兼容 v3 / v2 / server 明文 key 三种形态) */\nfunction parseEntry(o: unknown, file: string): LlmProviderEntry | null {\n if (!o || typeof o !== \"object\") return null;\n const r = o as Record<string, unknown>;\n const baseURL = typeof r.baseURL === \"string\" ? r.baseURL : \"\";\n const model = typeof r.model === \"string\" ? r.model : \"\";\n\n // type:v3 的 type → v2 的 provider → 由 baseURL 反查预设 → custom\n let type = String(r.type ?? r.provider ?? \"\").trim();\n if (!type) type = guessTypeByBaseURL(baseURL);\n\n // key:v3/v2 用密文;server 旧格式是明文 apiKey,迁移时加密\n let apiKeyEnc = typeof r.apiKeyEnc === \"string\" ? r.apiKeyEnc : \"\";\n const plain = typeof r.apiKey === \"string\" ? r.apiKey.trim() : \"\";\n if (!apiKeyEnc && plain) apiKeyEnc = encryptKey(plain, file);\n\n // 迁移时可能既无 key 也无模型(如未填完的条目):仍保留,交由 UI 补全\n if (!baseURL && !model && !apiKeyEnc && !type) return null;\n\n const models = normModelList(r.models);\n const nonChat = normModelList(r.nonChat);\n const modelMeta = normalizeModelMeta(r.modelMeta);\n const name = String(r.name ?? \"\").trim() || autoProviderName(type, model);\n return {\n id: typeof r.id === \"string\" && r.id ? r.id : newProviderId(),\n name,\n type,\n baseURL,\n model,\n reasoningEffort: normEffort(r.reasoningEffort),\n apiKeyEnc,\n ...(models ? { models } : {}),\n ...(nonChat ? { nonChat } : {}),\n ...(modelMeta ? { modelMeta } : {}),\n };\n}\n\n/** 自动展示名:预设名 · 模型 */\nexport function autoProviderName(type: string, model: string): string {\n const name = llmPreset(type)?.name ?? type ?? \"\";\n return model ? `${name} · ${model}` : name;\n}\n\nconst PRESET_NAMES = new Set(\n [\"openai\", \"deepseek\", \"qwen\", \"zhipu\", \"moonshot\", \"xiaomi\", \"custom\"].map(\n (id) => llmPreset(id)?.name ?? \"\",\n ),\n);\n\n/**\n * 修正历史遗留的陈旧自动名(供应商标签变了、或模型换了但名字没跟上)。\n * 只动「看起来是自动名 / 顶着别家预设标签」的名字,用户自起的名字保留。\n * @returns 是否发生修改\n */\nexport function fixStaleName(p: LlmProviderEntry): boolean {\n const label = llmPreset(p.type)?.name ?? p.type;\n const fresh = autoProviderName(p.type, p.model);\n if (p.name === fresh) return false;\n const looksAuto =\n p.name === label || p.name.startsWith(`${label} · `) || PRESET_NAMES.has(p.name);\n if (!looksAuto) return false;\n p.name = fresh;\n return true;\n}\n\n// ===================== 读 / 写 =====================\n\nfunction emptyConfig(): LlmConfigFile {\n return { version: 3, activeId: null, providers: [] };\n}\n\n/** 规范化列表:按 id 去重(后者覆盖前者)、activeId 回退到首项 */\nfunction normalize(f: LlmConfigFile): LlmConfigFile {\n const byId = new Map<string, LlmProviderEntry>();\n for (const p of f.providers) byId.set(p.id, p);\n const providers = [...byId.values()];\n const activeId = providers.some((p) => p.id === f.activeId)\n ? f.activeId\n : (providers[0]?.id ?? null);\n return { ...f, providers, activeId };\n}\n\n/**\n * 读取并规范化一个配置文件。\n * @param file 要读取的文件\n * @param keyFile 主密钥所在配置(迁移时应传**目标**配置:导入的明文 key 要用目标密钥加密)\n */\nfunction readRaw(file: string, keyFile = file): LlmConfigFile | null {\n if (!fs.existsSync(file)) return null;\n let raw: unknown;\n try {\n raw = JSON.parse(fs.readFileSync(file, \"utf8\"));\n } catch {\n return null;\n }\n if (!raw || typeof raw !== \"object\") return null;\n const o = raw as Record<string, unknown>;\n\n // v3 / v2:providers 列表\n if (Array.isArray(o.providers)) {\n const providers = o.providers\n .map((x) => parseEntry(x, keyFile))\n .filter((p): p is LlmProviderEntry => p !== null);\n const activeId =\n typeof o.activeId === \"string\"\n ? o.activeId\n : typeof o.defaultProviderId === \"string\"\n ? o.defaultProviderId\n : null;\n const migratedFrom = Array.isArray(o.migratedFrom)\n ? o.migratedFrom.filter((x): x is string => typeof x === \"string\")\n : undefined;\n return normalize({\n version: 3,\n activeId,\n providers,\n ...(migratedFrom ? { migratedFrom } : {}),\n });\n }\n\n // 更早的「单连接对象」格式\n const single = parseEntry(o, keyFile);\n if (single) return { version: 3, activeId: single.id, providers: [single] };\n return null;\n}\n\n/** 把外部配置(如 server 的 node-llm.json)合并进主配置 */\nfunction mergeExternal(\n base: LlmConfigFile,\n file: string,\n targetFile: string,\n): { config: LlmConfigFile; changed: boolean } {\n const migrated = base.migratedFrom ?? [];\n if (migrated.includes(file)) return { config: base, changed: false };\n // 用目标配置的主密钥加密导入的明文 key,保证之后能被同一密钥解开\n const ext = readRaw(file, targetFile);\n if (!ext || ext.providers.length === 0) {\n return { config: base, changed: false };\n }\n // 同名 id 保留主配置中的那份;其余追加\n const have = new Set(base.providers.map((p) => p.id));\n const added = ext.providers.filter((p) => !have.has(p.id));\n const config = normalize({\n ...base,\n providers: [...base.providers, ...added],\n activeId: base.activeId ?? ext.activeId,\n migratedFrom: [...migrated, file],\n });\n return { config, changed: true };\n}\n\n/**\n * 读取统一配置(含自动迁移)。\n *\n * @param opts.file 配置文件路径(缺省 `llmConfigFile()`)\n * @param opts.legacyFiles 需要合并的旧配置文件(如 `{DATA_DIR}/node-llm.json`),\n * 仅在主配置尚未记录该来源时导入一次\n */\nexport function readLlmConfig(opts: { file?: string; legacyFiles?: string[] } = {}): LlmConfigFile {\n const file = opts.file ?? llmConfigFile();\n let f = readRaw(file) ?? emptyConfig();\n let changed = false;\n\n // 顺手修正陈旧自动名\n // 必须遍历完整个列表:fixStaleName 带副作用,some 命中首个 true 会短路\n if (f.providers.map(fixStaleName).some(Boolean)) changed = true;\n\n for (const legacy of opts.legacyFiles ?? []) {\n const r = mergeExternal(f, legacy, file);\n f = r.config;\n changed = changed || r.changed;\n }\n\n // v2 / 单连接 / 合并过旧文件:规范化后落盘固化\n if (changed || !fs.existsSync(file)) writeLlmConfig(f, file);\n return f;\n}\n\n/** 写入统一配置(0600) */\nexport function writeLlmConfig(f: LlmConfigFile, file = llmConfigFile()): void {\n const next = normalize(f);\n fs.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 });\n fs.writeFileSync(file, JSON.stringify(next, null, 2) + \"\\n\", {\n mode: 0o600,\n encoding: \"utf8\",\n });\n}\n\n/**\n * 常见的历史配置来源(供迁移合并):\n * 旧版桌面端内嵌 server 的默认数据目录 `{stateDir}/data/node-llm.json`\n * (现默认目录为 `{stateDir}/.zqagent_data`,此处保留旧路径以便老用户配置被自动接管)。\n * 桌面端与 CLI 同属一个登录用户,这样先配桌面端也能被 CLI 立刻看到。\n */\nexport function commonLegacyFiles(): string[] {\n return [path.join(stateDir(), \"data\", \"node-llm.json\")];\n}\n","// 已迁移至 @zqagent/shared(core 与 hub 共用);此文件保留为兼容再导出。\nexport * from \"@zqagent/shared/secrets\";\n","/**\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 可用模型列表(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","/**\n * 本机配置 / 状态目录的跨平台解析\n *\n * POSIX 沿用 XDG 约定;Windows 上 `HOME` 在 cmd / PowerShell 中通常不存在,\n * 若直接回退 `/tmp` 会把配置写到 `\\tmp\\...`(当前盘符根),且与\n * `%APPDATA%` 的常规位置不一致。统一顺序:显式 XDG 变量(用户显式指定,\n * 优先级最高)→ 平台原生目录 → XDG 默认位置(家目录)→ 临时目录。\n */\nimport * as os from \"os\";\nimport * as path from \"path\";\n\n/** 应用目录名(`~/.config/zqagent` 的最后一段) */\nconst APP_DIR_NAME = \"zqagent\";\n\n/**\n * 节点默认数据目录名(相对启动目录,即 `./.zqagent_data`)。\n * server / CLI 在既无 `--data-dir` 也无 `DATA_DIR` 时用它兜底,\n * 集中在此避免各处字面量走散。\n */\nexport const DEFAULT_DATA_DIR = \".zqagent_data\";\n\n/**\n * 官方 Hub 基址(不带结尾斜杠):CLI 在既无 `--hub` 也无 `HUB_URL` 时用它兜底\n * (首个交互式接入提示的默认值)。自建 Hub 请用 `--hub` / `HUB_URL` 覆盖。\n */\nexport const DEFAULT_HUB_URL = \"https://hub.cd-zq.com\";\n\n/** 是否为 Windows(仅此处需要区分平台;APPDATA 在别的系统上可能是无关变量) */\nconst IS_WINDOWS = process.platform === \"win32\";\n\n/** 用户主目录:`HOME` → Windows `USERPROFILE` → `os.homedir()` → 临时目录 */\nfunction homeDir(): string {\n return (\n process.env.HOME || process.env.USERPROFILE || os.homedir() || os.tmpdir()\n );\n}\n\n/** 配置目录:`$XDG_CONFIG_HOME/zqagent`、Windows `%APPDATA%\\zqagent`、其余 `~/.config/zqagent` */\nexport function configDir(): string {\n const base =\n process.env.XDG_CONFIG_HOME ||\n (IS_WINDOWS ? process.env.APPDATA : undefined) ||\n path.join(homeDir(), \".config\");\n return path.join(base, APP_DIR_NAME);\n}\n\n/** 状态目录:`$XDG_STATE_HOME/zqagent`、Windows `%LOCALAPPDATA%\\zqagent`、其余 `~/.local/state/zqagent` */\nexport function stateDir(): string {\n const base =\n process.env.XDG_STATE_HOME ||\n (IS_WINDOWS ? process.env.LOCALAPPDATA : undefined) ||\n path.join(homeDir(), \".local\", \"state\");\n return path.join(base, APP_DIR_NAME);\n}\n"],"mappings":"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","names":["path","Agent","HttpsAgent","net","createDebug","lookup","Agent"]}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { R as ReasoningEffort } from '../types-BYyXWWPW.cjs';
|
|
2
|
+
import { ModelMeta } from './providers/models.cjs';
|
|
3
|
+
import '@zqagent/shared/model-capabilities';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 统一配置文件路径(惰性解析:读取时才计算,便于测试注入环境变量)。
|
|
7
|
+
* 优先级:`ZQAGENT_LLM_CONFIG_FILE` → `configDir()/connections.json`。
|
|
8
|
+
*/
|
|
9
|
+
declare function llmConfigFile(): string;
|
|
10
|
+
/** 单个供应商条目(含加密后的 key) */
|
|
11
|
+
interface LlmProviderEntry {
|
|
12
|
+
id: string;
|
|
13
|
+
/** 展示名 */
|
|
14
|
+
name: string;
|
|
15
|
+
/** 预设 id(`llmPresets` 的 id;历史 provider/type 合并到此) */
|
|
16
|
+
type: string;
|
|
17
|
+
baseURL: string;
|
|
18
|
+
model: string;
|
|
19
|
+
/** 推理强度;不支持或未设置时为 null */
|
|
20
|
+
reasoningEffort: ReasoningEffort | null;
|
|
21
|
+
/** API Key 密文(AES-256-GCM,`v1:iv:tag:ciphertext`) */
|
|
22
|
+
apiKeyEnc: string;
|
|
23
|
+
/** 上次刷新得到的候选模型(可选) */
|
|
24
|
+
models?: string[];
|
|
25
|
+
/** 候选中非对话模型的子集(可选,仅用于提示) */
|
|
26
|
+
nonChat?: string[];
|
|
27
|
+
/**
|
|
28
|
+
* 刷新模型列表时从供应商 `/models` 拿到的元数据(含真实上下文长度)。
|
|
29
|
+
* 落盘后供引擎按实际模型精确计算压缩阈值(见 `providers/models.ts`)。
|
|
30
|
+
*/
|
|
31
|
+
modelMeta?: ModelMeta[];
|
|
32
|
+
}
|
|
33
|
+
interface LlmConfigFile {
|
|
34
|
+
version: 3;
|
|
35
|
+
activeId: string | null;
|
|
36
|
+
providers: LlmProviderEntry[];
|
|
37
|
+
/** 已导入过的旧配置来源(绝对路径),避免重复合并 */
|
|
38
|
+
migratedFrom?: string[];
|
|
39
|
+
}
|
|
40
|
+
/** 加密 API Key;`file` 为配置文件路径(缺省用统一路径,决定主密钥位置) */
|
|
41
|
+
declare function encryptKey(plain: string, file?: string): string;
|
|
42
|
+
/** 解密 API Key;格式非法 / 主密钥变化返回 null */
|
|
43
|
+
declare function decryptKey(payload: string, file?: string): string | null;
|
|
44
|
+
/** 生成新的供应商 id */
|
|
45
|
+
declare function newProviderId(): string;
|
|
46
|
+
/** 自动展示名:预设名 · 模型 */
|
|
47
|
+
declare function autoProviderName(type: string, model: string): string;
|
|
48
|
+
/**
|
|
49
|
+
* 修正历史遗留的陈旧自动名(供应商标签变了、或模型换了但名字没跟上)。
|
|
50
|
+
* 只动「看起来是自动名 / 顶着别家预设标签」的名字,用户自起的名字保留。
|
|
51
|
+
* @returns 是否发生修改
|
|
52
|
+
*/
|
|
53
|
+
declare function fixStaleName(p: LlmProviderEntry): boolean;
|
|
54
|
+
/**
|
|
55
|
+
* 读取统一配置(含自动迁移)。
|
|
56
|
+
*
|
|
57
|
+
* @param opts.file 配置文件路径(缺省 `llmConfigFile()`)
|
|
58
|
+
* @param opts.legacyFiles 需要合并的旧配置文件(如 `{DATA_DIR}/node-llm.json`),
|
|
59
|
+
* 仅在主配置尚未记录该来源时导入一次
|
|
60
|
+
*/
|
|
61
|
+
declare function readLlmConfig(opts?: {
|
|
62
|
+
file?: string;
|
|
63
|
+
legacyFiles?: string[];
|
|
64
|
+
}): LlmConfigFile;
|
|
65
|
+
/** 写入统一配置(0600) */
|
|
66
|
+
declare function writeLlmConfig(f: LlmConfigFile, file?: string): void;
|
|
67
|
+
/**
|
|
68
|
+
* 常见的历史配置来源(供迁移合并):
|
|
69
|
+
* 旧版桌面端内嵌 server 的默认数据目录 `{stateDir}/data/node-llm.json`
|
|
70
|
+
* (现默认目录为 `{stateDir}/.zqagent_data`,此处保留旧路径以便老用户配置被自动接管)。
|
|
71
|
+
* 桌面端与 CLI 同属一个登录用户,这样先配桌面端也能被 CLI 立刻看到。
|
|
72
|
+
*/
|
|
73
|
+
declare function commonLegacyFiles(): string[];
|
|
74
|
+
|
|
75
|
+
export { type LlmConfigFile, type LlmProviderEntry, autoProviderName, commonLegacyFiles, decryptKey, encryptKey, fixStaleName, llmConfigFile, newProviderId, readLlmConfig, writeLlmConfig };
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { R as ReasoningEffort } from '../types-BYyXWWPW.js';
|
|
2
|
+
import { ModelMeta } from './providers/models.js';
|
|
3
|
+
import '@zqagent/shared/model-capabilities';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 统一配置文件路径(惰性解析:读取时才计算,便于测试注入环境变量)。
|
|
7
|
+
* 优先级:`ZQAGENT_LLM_CONFIG_FILE` → `configDir()/connections.json`。
|
|
8
|
+
*/
|
|
9
|
+
declare function llmConfigFile(): string;
|
|
10
|
+
/** 单个供应商条目(含加密后的 key) */
|
|
11
|
+
interface LlmProviderEntry {
|
|
12
|
+
id: string;
|
|
13
|
+
/** 展示名 */
|
|
14
|
+
name: string;
|
|
15
|
+
/** 预设 id(`llmPresets` 的 id;历史 provider/type 合并到此) */
|
|
16
|
+
type: string;
|
|
17
|
+
baseURL: string;
|
|
18
|
+
model: string;
|
|
19
|
+
/** 推理强度;不支持或未设置时为 null */
|
|
20
|
+
reasoningEffort: ReasoningEffort | null;
|
|
21
|
+
/** API Key 密文(AES-256-GCM,`v1:iv:tag:ciphertext`) */
|
|
22
|
+
apiKeyEnc: string;
|
|
23
|
+
/** 上次刷新得到的候选模型(可选) */
|
|
24
|
+
models?: string[];
|
|
25
|
+
/** 候选中非对话模型的子集(可选,仅用于提示) */
|
|
26
|
+
nonChat?: string[];
|
|
27
|
+
/**
|
|
28
|
+
* 刷新模型列表时从供应商 `/models` 拿到的元数据(含真实上下文长度)。
|
|
29
|
+
* 落盘后供引擎按实际模型精确计算压缩阈值(见 `providers/models.ts`)。
|
|
30
|
+
*/
|
|
31
|
+
modelMeta?: ModelMeta[];
|
|
32
|
+
}
|
|
33
|
+
interface LlmConfigFile {
|
|
34
|
+
version: 3;
|
|
35
|
+
activeId: string | null;
|
|
36
|
+
providers: LlmProviderEntry[];
|
|
37
|
+
/** 已导入过的旧配置来源(绝对路径),避免重复合并 */
|
|
38
|
+
migratedFrom?: string[];
|
|
39
|
+
}
|
|
40
|
+
/** 加密 API Key;`file` 为配置文件路径(缺省用统一路径,决定主密钥位置) */
|
|
41
|
+
declare function encryptKey(plain: string, file?: string): string;
|
|
42
|
+
/** 解密 API Key;格式非法 / 主密钥变化返回 null */
|
|
43
|
+
declare function decryptKey(payload: string, file?: string): string | null;
|
|
44
|
+
/** 生成新的供应商 id */
|
|
45
|
+
declare function newProviderId(): string;
|
|
46
|
+
/** 自动展示名:预设名 · 模型 */
|
|
47
|
+
declare function autoProviderName(type: string, model: string): string;
|
|
48
|
+
/**
|
|
49
|
+
* 修正历史遗留的陈旧自动名(供应商标签变了、或模型换了但名字没跟上)。
|
|
50
|
+
* 只动「看起来是自动名 / 顶着别家预设标签」的名字,用户自起的名字保留。
|
|
51
|
+
* @returns 是否发生修改
|
|
52
|
+
*/
|
|
53
|
+
declare function fixStaleName(p: LlmProviderEntry): boolean;
|
|
54
|
+
/**
|
|
55
|
+
* 读取统一配置(含自动迁移)。
|
|
56
|
+
*
|
|
57
|
+
* @param opts.file 配置文件路径(缺省 `llmConfigFile()`)
|
|
58
|
+
* @param opts.legacyFiles 需要合并的旧配置文件(如 `{DATA_DIR}/node-llm.json`),
|
|
59
|
+
* 仅在主配置尚未记录该来源时导入一次
|
|
60
|
+
*/
|
|
61
|
+
declare function readLlmConfig(opts?: {
|
|
62
|
+
file?: string;
|
|
63
|
+
legacyFiles?: string[];
|
|
64
|
+
}): LlmConfigFile;
|
|
65
|
+
/** 写入统一配置(0600) */
|
|
66
|
+
declare function writeLlmConfig(f: LlmConfigFile, file?: string): void;
|
|
67
|
+
/**
|
|
68
|
+
* 常见的历史配置来源(供迁移合并):
|
|
69
|
+
* 旧版桌面端内嵌 server 的默认数据目录 `{stateDir}/data/node-llm.json`
|
|
70
|
+
* (现默认目录为 `{stateDir}/.zqagent_data`,此处保留旧路径以便老用户配置被自动接管)。
|
|
71
|
+
* 桌面端与 CLI 同属一个登录用户,这样先配桌面端也能被 CLI 立刻看到。
|
|
72
|
+
*/
|
|
73
|
+
declare function commonLegacyFiles(): string[];
|
|
74
|
+
|
|
75
|
+
export { type LlmConfigFile, type LlmProviderEntry, autoProviderName, commonLegacyFiles, decryptKey, encryptKey, fixStaleName, llmConfigFile, newProviderId, readLlmConfig, writeLlmConfig };
|