@next-open-ai/openbot 0.6.8 → 0.6.66
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/LICENSE +21 -0
- package/README.md +155 -136
- package/apps/desktop/renderer/dist/assets/index-CxDZnMBH.css +10 -0
- package/apps/desktop/renderer/dist/assets/index-k47Qiokg.js +93 -0
- package/apps/desktop/renderer/dist/index.html +2 -2
- package/dist/cli/cli.js +136 -0
- package/dist/cli/extension-cmd.d.ts +15 -0
- package/dist/cli/extension-cmd.js +107 -0
- package/dist/core/agent/agent-dir.d.ts +6 -0
- package/dist/core/agent/agent-dir.js +8 -0
- package/dist/core/agent/agent-manager.d.ts +27 -6
- package/dist/core/agent/agent-manager.js +147 -26
- package/dist/core/agent/proxy/adapters/claude-code-adapter.d.ts +2 -0
- package/dist/core/agent/proxy/adapters/claude-code-adapter.js +186 -0
- package/dist/core/agent/proxy/adapters/coze-adapter.d.ts +2 -0
- package/dist/core/agent/proxy/adapters/coze-adapter.js +406 -0
- package/dist/core/agent/proxy/adapters/local-adapter.d.ts +2 -0
- package/dist/core/agent/proxy/adapters/local-adapter.js +95 -0
- package/dist/core/agent/proxy/adapters/openclawx-adapter.d.ts +2 -0
- package/dist/core/agent/proxy/adapters/openclawx-adapter.js +115 -0
- package/dist/core/agent/proxy/adapters/opencode-adapter.d.ts +11 -0
- package/dist/core/agent/proxy/adapters/opencode-adapter.js +786 -0
- package/dist/core/agent/proxy/adapters/opencode-free-models.d.ts +20 -0
- package/dist/core/agent/proxy/adapters/opencode-free-models.js +14 -0
- package/dist/core/agent/proxy/adapters/opencode-local-runner.d.ts +5 -0
- package/dist/core/agent/proxy/adapters/opencode-local-runner.js +95 -0
- package/dist/core/agent/proxy/index.d.ts +3 -0
- package/dist/core/agent/proxy/index.js +18 -0
- package/dist/core/agent/proxy/registry.d.ts +7 -0
- package/dist/core/agent/proxy/registry.js +13 -0
- package/dist/core/agent/proxy/run-for-channel.d.ts +3 -0
- package/dist/core/agent/proxy/run-for-channel.js +31 -0
- package/dist/core/agent/proxy/types.d.ts +30 -0
- package/dist/core/agent/proxy/types.js +1 -0
- package/dist/core/agent/run.js +1 -1
- package/dist/core/agent/token-usage-log-extension.d.ts +14 -0
- package/dist/core/agent/token-usage-log-extension.js +61 -0
- package/dist/core/config/agent-reload-pending.d.ts +9 -0
- package/dist/core/config/agent-reload-pending.js +67 -0
- package/dist/core/config/desktop-config.d.ts +136 -5
- package/dist/core/config/desktop-config.js +470 -46
- package/dist/core/config/provider-support-default.js +27 -0
- package/dist/core/extensions/index.d.ts +1 -0
- package/dist/core/extensions/index.js +1 -0
- package/dist/core/extensions/load.d.ts +11 -0
- package/dist/core/extensions/load.js +101 -0
- package/dist/core/inbound-message-preprocess.d.ts +27 -0
- package/dist/core/inbound-message-preprocess.js +96 -0
- package/dist/core/local-llm-server/download-model.d.ts +16 -0
- package/dist/core/local-llm-server/download-model.js +37 -0
- package/dist/core/local-llm-server/index.d.ts +32 -0
- package/dist/core/local-llm-server/index.js +152 -0
- package/dist/core/local-llm-server/llm-context.d.ts +66 -0
- package/dist/core/local-llm-server/llm-context.js +270 -0
- package/dist/core/local-llm-server/model-resolve.d.ts +27 -0
- package/dist/core/local-llm-server/model-resolve.js +90 -0
- package/dist/core/local-llm-server/server.d.ts +1 -0
- package/dist/core/local-llm-server/server.js +234 -0
- package/dist/core/local-llm-server/start-from-config.d.ts +5 -0
- package/dist/core/local-llm-server/start-from-config.js +50 -0
- package/dist/core/mcp/adapter.d.ts +4 -2
- package/dist/core/mcp/adapter.js +10 -4
- package/dist/core/mcp/client.d.ts +4 -0
- package/dist/core/mcp/client.js +2 -0
- package/dist/core/mcp/config.d.ts +14 -3
- package/dist/core/mcp/config.js +68 -3
- package/dist/core/mcp/index.d.ts +10 -6
- package/dist/core/mcp/index.js +7 -3
- package/dist/core/mcp/operator.d.ts +28 -2
- package/dist/core/mcp/operator.js +131 -30
- package/dist/core/mcp/transport/index.d.ts +4 -0
- package/dist/core/mcp/transport/index.js +6 -1
- package/dist/core/mcp/transport/stdio.d.ts +12 -0
- package/dist/core/mcp/transport/stdio.js +147 -29
- package/dist/core/mcp/types.d.ts +18 -0
- package/dist/core/memory/compaction-extension.d.ts +4 -3
- package/dist/core/memory/compaction-extension.js +6 -14
- package/dist/core/memory/embedding-types.d.ts +10 -0
- package/dist/core/memory/embedding-types.js +5 -0
- package/dist/core/memory/embedding.d.ts +2 -1
- package/dist/core/memory/embedding.js +38 -6
- package/dist/core/memory/index.js +3 -0
- package/dist/core/memory/local-embedding-llama.d.ts +13 -0
- package/dist/core/memory/local-embedding-llama.js +78 -0
- package/dist/core/memory/local-embedding.d.ts +11 -0
- package/dist/core/memory/local-embedding.js +69 -0
- package/dist/core/memory/persist-compaction-on-close.d.ts +14 -0
- package/dist/core/memory/persist-compaction-on-close.js +32 -0
- package/dist/core/session-outlet/index.d.ts +19 -0
- package/dist/core/session-outlet/index.js +33 -0
- package/dist/core/session-outlet/outlet.d.ts +15 -0
- package/dist/core/session-outlet/outlet.js +49 -0
- package/dist/core/session-outlet/types.d.ts +35 -0
- package/dist/core/session-outlet/types.js +5 -0
- package/dist/core/tools/bookmark-tool.d.ts +4 -0
- package/dist/core/tools/bookmark-tool.js +59 -3
- package/dist/core/tools/index.d.ts +3 -1
- package/dist/core/tools/index.js +3 -1
- package/dist/core/tools/memory-recall-tool.d.ts +6 -0
- package/dist/core/tools/memory-recall-tool.js +77 -0
- package/dist/core/tools/truncate-result.d.ts +14 -0
- package/dist/core/tools/truncate-result.js +27 -0
- package/dist/core/tools/web-search/create-web-search-tool.d.ts +17 -0
- package/dist/core/tools/web-search/create-web-search-tool.js +87 -0
- package/dist/core/tools/web-search/index.d.ts +4 -0
- package/dist/core/tools/web-search/index.js +2 -0
- package/dist/core/tools/web-search/providers/brave.d.ts +2 -0
- package/dist/core/tools/web-search/providers/brave.js +87 -0
- package/dist/core/tools/web-search/providers/duck-duck-scrape.d.ts +2 -0
- package/dist/core/tools/web-search/providers/duck-duck-scrape.js +47 -0
- package/dist/core/tools/web-search/providers/index.d.ts +5 -0
- package/dist/core/tools/web-search/providers/index.js +13 -0
- package/dist/core/tools/web-search/types.d.ts +35 -0
- package/dist/core/tools/web-search/types.js +4 -0
- package/dist/gateway/channel/adapters/telegram.js +13 -2
- package/dist/gateway/channel/adapters/wechat.d.ts +24 -0
- package/dist/gateway/channel/adapters/wechat.js +205 -0
- package/dist/gateway/channel/channel-core.d.ts +1 -0
- package/dist/gateway/channel/channel-core.js +101 -59
- package/dist/gateway/channel/run-agent.d.ts +2 -4
- package/dist/gateway/channel/run-agent.js +13 -125
- package/dist/gateway/methods/agent-cancel.d.ts +3 -1
- package/dist/gateway/methods/agent-cancel.js +16 -2
- package/dist/gateway/methods/agent-chat.d.ts +4 -0
- package/dist/gateway/methods/agent-chat.js +377 -118
- package/dist/gateway/methods/run-scheduled-task.js +9 -7
- package/dist/gateway/proxy-run-abort.d.ts +6 -0
- package/dist/gateway/proxy-run-abort.js +39 -0
- package/dist/gateway/server.js +123 -19
- package/dist/server/agent-config/agent-config.controller.d.ts +10 -2
- package/dist/server/agent-config/agent-config.controller.js +19 -4
- package/dist/server/agent-config/agent-config.module.js +3 -1
- package/dist/server/agent-config/agent-config.service.d.ts +91 -6
- package/dist/server/agent-config/agent-config.service.js +115 -3
- package/dist/server/agents/agents.controller.d.ts +16 -0
- package/dist/server/agents/agents.controller.js +62 -1
- package/dist/server/agents/agents.gateway.js +1 -1
- package/dist/server/agents/agents.service.js +1 -1
- package/dist/server/bootstrap.d.ts +1 -0
- package/dist/server/bootstrap.js +28 -4
- package/dist/server/config/config.controller.d.ts +134 -2
- package/dist/server/config/config.controller.js +199 -3
- package/dist/server/config/config.module.js +5 -4
- package/dist/server/config/config.service.d.ts +32 -2
- package/dist/server/config/config.service.js +69 -9
- package/dist/server/config/local-models.service.d.ts +67 -0
- package/dist/server/config/local-models.service.js +242 -0
- package/dist/server/workspace/workspace.service.d.ts +7 -0
- package/dist/server/workspace/workspace.service.js +16 -0
- package/package.json +10 -2
- package/presets/preset-agents.json +128 -0
- package/presets/preset-config.json +29 -0
- package/presets/preset-providers.json +180 -0
- package/presets/recommended-local-models.json +36 -0
- package/presets/workspaces/code-assistant/skills/code-review/SKILL.md +19 -0
- package/presets/workspaces/code-assistant/skills/code-runner/SKILL.md +21 -0
- package/presets/workspaces/code-assistant/skills/git-helper/SKILL.md +29 -0
- package/presets/workspaces/creator-assistant/skills/.gitkeep +0 -0
- package/presets/workspaces/creator-assistant/skills/creator-tools/SKILL.md +15 -0
- package/presets/workspaces/doc-assistant/skills/doc-processor/SKILL.md +21 -0
- package/presets/workspaces/download-assistant/skills/downloader/SKILL.md +20 -0
- package/presets/workspaces/file-assistant/skills/file-converter/SKILL.md +21 -0
- package/presets/workspaces/file-assistant/skills/file-organizer/SKILL.md +17 -0
- package/presets/workspaces/file-assistant/skills/file-search/SKILL.md +22 -0
- package/presets/workspaces/morning-briefing/skills/news-fetcher/SKILL.md +16 -0
- package/presets/workspaces/morning-briefing/skills/web-summarizer/SKILL.md +20 -0
- package/presets/workspaces/news-assistant/skills/news-fetcher/SKILL.md +16 -0
- package/presets/workspaces/news-assistant/skills/web-summarizer/SKILL.md +20 -0
- package/presets/workspaces/office-automation/skills/rpa-helper/SKILL.md +9 -0
- package/presets/workspaces/self-media-bot/skills/self-media-tools/SKILL.md +9 -0
- package/skills/url-bookmark/SKILL.md +12 -12
- package/apps/desktop/renderer/dist/assets/index-LCp1YPVA.css +0 -10
- package/apps/desktop/renderer/dist/assets/index-l5fpDsHs.js +0 -89
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* 供 CLI、WebSocket Gateway 等读取与写入,与 Nest Desktop Backend 使用的 config.json / agents.json 一致。
|
|
4
4
|
* provider-support.json 提供流行 provider 及模型目录,供配置时下拉备选;配置完成后可同步到 agent 目录 models.json 供 pi 使用。
|
|
5
5
|
*/
|
|
6
|
-
import { readFile, writeFile } from "fs/promises";
|
|
6
|
+
import { readFile, writeFile, cp, mkdir } from "fs/promises";
|
|
7
7
|
import { readFileSync, existsSync, mkdirSync } from "fs";
|
|
8
8
|
import { join } from "path";
|
|
9
9
|
import { homedir } from "os";
|
|
@@ -13,6 +13,10 @@ function getDesktopDir() {
|
|
|
13
13
|
const home = process.env.HOME || process.env.USERPROFILE || homedir();
|
|
14
14
|
return join(home, ".openbot", "desktop");
|
|
15
15
|
}
|
|
16
|
+
/** 获取预装资源目录(打包后在 Resources/presets,dev 时在仓库根 presets) */
|
|
17
|
+
function getPresetsDir() {
|
|
18
|
+
return process.env.OPENBOT_PRESETS_DIR || join(process.cwd(), "presets");
|
|
19
|
+
}
|
|
16
20
|
const DEFAULT_AGENT_ID = "default";
|
|
17
21
|
const DEFAULT_MAX_AGENT_SESSIONS = 5;
|
|
18
22
|
/** 同步读取桌面全局配置(Gateway 等需要同步读的场景) */
|
|
@@ -57,7 +61,7 @@ export function getChannelsConfigSync() {
|
|
|
57
61
|
return {};
|
|
58
62
|
}
|
|
59
63
|
}
|
|
60
|
-
/** 同步读取 RAG embedding
|
|
64
|
+
/** 同步读取 RAG embedding 配置;embeddingSource 为 local 或未配置在线模型时返回 null,长记忆将空转 */
|
|
61
65
|
export function getRagEmbeddingConfigSync() {
|
|
62
66
|
try {
|
|
63
67
|
const configPath = getConfigPath();
|
|
@@ -65,8 +69,27 @@ export function getRagEmbeddingConfigSync() {
|
|
|
65
69
|
return null;
|
|
66
70
|
const content = readFileSync(configPath, "utf-8");
|
|
67
71
|
const data = JSON.parse(content);
|
|
68
|
-
const
|
|
69
|
-
|
|
72
|
+
const rag = data.rag;
|
|
73
|
+
if (rag?.embeddingSource === "local")
|
|
74
|
+
return null;
|
|
75
|
+
let provider;
|
|
76
|
+
let modelId;
|
|
77
|
+
if (rag?.embeddingModelItemCode && Array.isArray(data.configuredModels)) {
|
|
78
|
+
const code = rag.embeddingModelItemCode;
|
|
79
|
+
const item = data.configuredModels.find((m) => {
|
|
80
|
+
if (m.type !== "embedding")
|
|
81
|
+
return false;
|
|
82
|
+
return m.modelItemCode === code || (m.provider && m.modelId && `${m.provider}:${m.modelId}` === code);
|
|
83
|
+
});
|
|
84
|
+
if (item && item.provider) {
|
|
85
|
+
provider = item.provider;
|
|
86
|
+
modelId = item.modelId;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (!provider || !modelId) {
|
|
90
|
+
provider = rag?.embeddingProvider?.trim();
|
|
91
|
+
modelId = rag?.embeddingModel?.trim();
|
|
92
|
+
}
|
|
70
93
|
if (!provider || !modelId)
|
|
71
94
|
return null;
|
|
72
95
|
const prov = data.providers?.[provider];
|
|
@@ -86,6 +109,43 @@ export function getRagEmbeddingConfigSync() {
|
|
|
86
109
|
return null;
|
|
87
110
|
}
|
|
88
111
|
}
|
|
112
|
+
/** 同步读取 RAG 本地 GGUF 模型路径;embeddingSource 非 local 或未填时返回 null,调用方使用默认模型(如 embeddinggemma)。 */
|
|
113
|
+
export function getRagLocalModelPathSync() {
|
|
114
|
+
try {
|
|
115
|
+
const configPath = getConfigPath();
|
|
116
|
+
if (!existsSync(configPath))
|
|
117
|
+
return null;
|
|
118
|
+
const content = readFileSync(configPath, "utf-8");
|
|
119
|
+
const data = JSON.parse(content);
|
|
120
|
+
if (data.rag?.embeddingSource !== "local")
|
|
121
|
+
return null;
|
|
122
|
+
const modelPath = data.rag?.localModelPath?.trim();
|
|
123
|
+
return modelPath || null;
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/** 同步读取 RAG 向量库配置;vectorStore 为 qdrant 且 url 有效时返回,否则为 null(使用本地向量库)。 */
|
|
130
|
+
export function getRagQdrantConfigSync() {
|
|
131
|
+
try {
|
|
132
|
+
const configPath = getConfigPath();
|
|
133
|
+
if (!existsSync(configPath))
|
|
134
|
+
return null;
|
|
135
|
+
const content = readFileSync(configPath, "utf-8");
|
|
136
|
+
const data = JSON.parse(content);
|
|
137
|
+
if (data.rag?.vectorStore !== "qdrant" || !data.rag?.qdrant?.url?.trim())
|
|
138
|
+
return null;
|
|
139
|
+
return {
|
|
140
|
+
url: data.rag.qdrant.url.trim().replace(/\/$/, ""),
|
|
141
|
+
apiKey: data.rag.qdrant.apiKey?.trim(),
|
|
142
|
+
collection: data.rag.qdrant.collection?.trim(),
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
89
149
|
const EMBEDDING_DEFAULT_BASE_URL = {
|
|
90
150
|
openai: "https://api.openai.com/v1",
|
|
91
151
|
"openai-custom": "",
|
|
@@ -149,8 +209,8 @@ export async function loadDesktopAgentConfig(agentId) {
|
|
|
149
209
|
}
|
|
150
210
|
}
|
|
151
211
|
const resolvedAgentId = agentId === "default" ? "default" : agentId;
|
|
152
|
-
let provider = config.defaultProvider ?? "
|
|
153
|
-
let model = config.defaultModel ?? "
|
|
212
|
+
let provider = config.defaultProvider ?? "ollama";
|
|
213
|
+
let model = config.defaultModel ?? "qwen3:4b";
|
|
154
214
|
if (config.defaultModelItemCode && Array.isArray(config.configuredModels)) {
|
|
155
215
|
const configured = config.configuredModels.find((m) => m.modelItemCode === config.defaultModelItemCode);
|
|
156
216
|
if (configured) {
|
|
@@ -158,9 +218,14 @@ export async function loadDesktopAgentConfig(agentId) {
|
|
|
158
218
|
model = configured.modelId;
|
|
159
219
|
}
|
|
160
220
|
}
|
|
221
|
+
/** 是否从当前智能体自己的配置得到了模型(有 modelItemCode 或 provider/model);若否,则使用的是全局默认 */
|
|
222
|
+
let agentHadOwnModel = false;
|
|
161
223
|
let workspaceName = resolvedAgentId;
|
|
162
224
|
let mcpServers;
|
|
225
|
+
let mcpMaxResultTokens;
|
|
163
226
|
let systemPrompt;
|
|
227
|
+
let useLongMemory = true;
|
|
228
|
+
let contextSize;
|
|
164
229
|
if (existsSync(agentsPath)) {
|
|
165
230
|
try {
|
|
166
231
|
const raw = await readFile(agentsPath, "utf-8");
|
|
@@ -172,30 +237,49 @@ export async function loadDesktopAgentConfig(agentId) {
|
|
|
172
237
|
workspaceName = agent.workspace;
|
|
173
238
|
else if (agent.id)
|
|
174
239
|
workspaceName = agent.id;
|
|
175
|
-
if (agent.
|
|
176
|
-
|
|
240
|
+
if (agent.mcpMaxResultTokens != null && typeof agent.mcpMaxResultTokens === "number" && agent.mcpMaxResultTokens > 0) {
|
|
241
|
+
mcpMaxResultTokens = agent.mcpMaxResultTokens;
|
|
242
|
+
}
|
|
243
|
+
if (agent.contextSize != null && typeof agent.contextSize === "number" && agent.contextSize > 0) {
|
|
244
|
+
contextSize = agent.contextSize;
|
|
245
|
+
}
|
|
246
|
+
if (agent.mcpServers != null) {
|
|
247
|
+
if (Array.isArray(agent.mcpServers) || (typeof agent.mcpServers === "object" && !Array.isArray(agent.mcpServers))) {
|
|
248
|
+
mcpServers = agent.mcpServers;
|
|
249
|
+
}
|
|
177
250
|
}
|
|
178
251
|
if (agent.systemPrompt && typeof agent.systemPrompt === "string") {
|
|
179
252
|
systemPrompt = agent.systemPrompt.trim();
|
|
180
253
|
}
|
|
254
|
+
if (agent.useLongMemory !== undefined)
|
|
255
|
+
useLongMemory = !!agent.useLongMemory;
|
|
181
256
|
if (agent.modelItemCode && Array.isArray(config.configuredModels)) {
|
|
182
257
|
const configured = config.configuredModels.find((m) => m.modelItemCode === agent.modelItemCode);
|
|
183
258
|
if (configured) {
|
|
184
259
|
provider = configured.provider;
|
|
185
260
|
model = configured.modelId;
|
|
261
|
+
agentHadOwnModel = true;
|
|
186
262
|
}
|
|
187
263
|
else {
|
|
188
|
-
if (agent.provider)
|
|
264
|
+
if (agent.provider) {
|
|
189
265
|
provider = agent.provider;
|
|
190
|
-
|
|
266
|
+
agentHadOwnModel = true;
|
|
267
|
+
}
|
|
268
|
+
if (agent.model) {
|
|
191
269
|
model = agent.model;
|
|
270
|
+
agentHadOwnModel = true;
|
|
271
|
+
}
|
|
192
272
|
}
|
|
193
273
|
}
|
|
194
274
|
else {
|
|
195
|
-
if (agent.provider)
|
|
275
|
+
if (agent.provider) {
|
|
196
276
|
provider = agent.provider;
|
|
197
|
-
|
|
277
|
+
agentHadOwnModel = true;
|
|
278
|
+
}
|
|
279
|
+
if (agent.model) {
|
|
198
280
|
model = agent.model;
|
|
281
|
+
agentHadOwnModel = true;
|
|
282
|
+
}
|
|
199
283
|
}
|
|
200
284
|
}
|
|
201
285
|
}
|
|
@@ -203,11 +287,168 @@ export async function loadDesktopAgentConfig(agentId) {
|
|
|
203
287
|
// ignore
|
|
204
288
|
}
|
|
205
289
|
}
|
|
290
|
+
// 本地 LLM 可用且当前智能体未配置自己的模型时,使用本地推理作为缺省,使所有智能体“拥有”该配置
|
|
291
|
+
if (!agentHadOwnModel && process.env.LOCAL_LLM_BASE_URL?.trim()) {
|
|
292
|
+
provider = "local";
|
|
293
|
+
model = "local-llm";
|
|
294
|
+
}
|
|
206
295
|
const provConfig = config.providers?.[provider];
|
|
207
296
|
const apiKey = provConfig?.apiKey && typeof provConfig.apiKey === "string" && provConfig.apiKey.trim()
|
|
208
297
|
? provConfig.apiKey.trim()
|
|
209
298
|
: undefined;
|
|
210
|
-
|
|
299
|
+
let runnerType = "local";
|
|
300
|
+
let coze;
|
|
301
|
+
let openclawx;
|
|
302
|
+
let opencode;
|
|
303
|
+
let claudeCode;
|
|
304
|
+
const tw = config.tools?.webSearch;
|
|
305
|
+
const timeoutSeconds = typeof tw?.timeoutSeconds === "number" && tw.timeoutSeconds > 0 ? tw.timeoutSeconds : 15;
|
|
306
|
+
const cacheTtlMinutes = typeof tw?.cacheTtlMinutes === "number" && tw.cacheTtlMinutes >= 0 ? tw.cacheTtlMinutes : 5;
|
|
307
|
+
const maxResultsRaw = typeof tw?.maxResults === "number" ? tw.maxResults : 5;
|
|
308
|
+
const maxResults = Math.min(10, Math.max(1, maxResultsRaw));
|
|
309
|
+
let webSearch = {
|
|
310
|
+
enabled: false,
|
|
311
|
+
provider: "duck-duck-scrape",
|
|
312
|
+
timeoutSeconds,
|
|
313
|
+
cacheTtlMinutes,
|
|
314
|
+
maxResults,
|
|
315
|
+
};
|
|
316
|
+
if (existsSync(agentsPath)) {
|
|
317
|
+
try {
|
|
318
|
+
const rawAgents = await readFile(agentsPath, "utf-8");
|
|
319
|
+
const dataAgents = JSON.parse(rawAgents);
|
|
320
|
+
const agentsList = Array.isArray(dataAgents.agents) ? dataAgents.agents : [];
|
|
321
|
+
const agentRow = agentsList.find((a) => a.id === resolvedAgentId);
|
|
322
|
+
if (agentRow) {
|
|
323
|
+
if (agentRow.runnerType === "coze" ||
|
|
324
|
+
agentRow.runnerType === "openclawx" ||
|
|
325
|
+
agentRow.runnerType === "opencode" ||
|
|
326
|
+
agentRow.runnerType === "claude_code") {
|
|
327
|
+
runnerType = agentRow.runnerType;
|
|
328
|
+
}
|
|
329
|
+
if (agentRow.runnerType === "claude_code") {
|
|
330
|
+
const wd = agentRow.claudeCode?.workingDirectory;
|
|
331
|
+
claudeCode = {
|
|
332
|
+
workingDirectory: typeof wd === "string" && wd.trim() ? wd.trim() : undefined,
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
if (agentRow.coze) {
|
|
336
|
+
const row = agentRow.coze;
|
|
337
|
+
const region = row.region === "cn" || row.region === "com" ? row.region : "com";
|
|
338
|
+
const credsCn = row.cn;
|
|
339
|
+
const credsCom = row.com;
|
|
340
|
+
const legacyBotId = row.botId != null ? String(row.botId).trim() : "";
|
|
341
|
+
const legacyKey = row.apiKey != null ? String(row.apiKey).trim() : "";
|
|
342
|
+
const fromRegion = region === "cn"
|
|
343
|
+
? credsCn && String(credsCn.botId || "").trim() && String(credsCn.apiKey || "").trim()
|
|
344
|
+
? { botId: String(credsCn.botId).trim(), apiKey: String(credsCn.apiKey).trim() }
|
|
345
|
+
: null
|
|
346
|
+
: credsCom && String(credsCom.botId || "").trim() && String(credsCom.apiKey || "").trim()
|
|
347
|
+
? { botId: String(credsCom.botId).trim(), apiKey: String(credsCom.apiKey).trim() }
|
|
348
|
+
: null;
|
|
349
|
+
const fromLegacy = legacyBotId && legacyKey ? { botId: legacyBotId, apiKey: legacyKey } : null;
|
|
350
|
+
const creds = fromRegion ?? fromLegacy;
|
|
351
|
+
if (creds) {
|
|
352
|
+
coze = {
|
|
353
|
+
botId: creds.botId,
|
|
354
|
+
apiKey: creds.apiKey,
|
|
355
|
+
region,
|
|
356
|
+
endpoint: row.endpoint?.trim(),
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
else if (runnerType === "coze") {
|
|
360
|
+
console.warn(`[loadDesktopAgentConfig] agentId=${resolvedAgentId} runnerType=coze but no credentials for region=${region} (configure 国内/国际 in proxy settings)`);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
if (agentRow.openclawx?.baseUrl) {
|
|
364
|
+
openclawx = {
|
|
365
|
+
baseUrl: String(agentRow.openclawx.baseUrl).replace(/\/$/, ""),
|
|
366
|
+
apiKey: agentRow.openclawx.apiKey?.trim(),
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
if (agentRow.opencode?.port != null) {
|
|
370
|
+
const raw = agentRow.opencode;
|
|
371
|
+
const port = Number(raw.port);
|
|
372
|
+
if (!Number.isNaN(port) && port > 0) {
|
|
373
|
+
const mode = raw.mode === "local" || raw.mode === "remote"
|
|
374
|
+
? raw.mode
|
|
375
|
+
: raw.address != null && String(raw.address).trim()
|
|
376
|
+
? "remote"
|
|
377
|
+
: "local";
|
|
378
|
+
const address = mode === "remote" && raw.address != null
|
|
379
|
+
? String(raw.address).trim()
|
|
380
|
+
: "127.0.0.1";
|
|
381
|
+
if (mode === "local" || address) {
|
|
382
|
+
const apiStyle = raw.apiStyle === "openai" ? "openai" : "server";
|
|
383
|
+
opencode = {
|
|
384
|
+
mode,
|
|
385
|
+
address: mode === "remote" ? address : "127.0.0.1",
|
|
386
|
+
port,
|
|
387
|
+
password: raw.password != null ? String(raw.password).trim() : undefined,
|
|
388
|
+
username: raw.username != null ? String(raw.username).trim() || undefined : undefined,
|
|
389
|
+
apiStyle,
|
|
390
|
+
path: raw.path != null ? String(raw.path).trim() || undefined : undefined,
|
|
391
|
+
streamPath: raw.streamPath != null
|
|
392
|
+
? String(raw.streamPath).trim() || undefined
|
|
393
|
+
: undefined,
|
|
394
|
+
model: raw.model != null ? String(raw.model).trim() || undefined : undefined,
|
|
395
|
+
workingDirectory: raw.workingDirectory != null ? String(raw.workingDirectory).trim() || undefined : undefined,
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
if (agentRow.webSearch?.enabled === true) {
|
|
401
|
+
let preferredProvider = agentRow.webSearch?.provider === "brave" || agentRow.webSearch?.provider === "duck-duck-scrape"
|
|
402
|
+
? agentRow.webSearch.provider
|
|
403
|
+
: tw?.defaultProvider === "brave" || tw?.defaultProvider === "duck-duck-scrape"
|
|
404
|
+
? tw.defaultProvider
|
|
405
|
+
: "duck-duck-scrape";
|
|
406
|
+
let braveKey;
|
|
407
|
+
if (preferredProvider === "brave") {
|
|
408
|
+
braveKey =
|
|
409
|
+
(typeof tw?.providers?.brave?.apiKey === "string" && tw.providers.brave.apiKey.trim()
|
|
410
|
+
? tw.providers.brave.apiKey.trim()
|
|
411
|
+
: undefined) ??
|
|
412
|
+
(process.env.BRAVE_API_KEY && process.env.BRAVE_API_KEY.trim() ? process.env.BRAVE_API_KEY.trim() : undefined);
|
|
413
|
+
if (!braveKey)
|
|
414
|
+
preferredProvider = "duck-duck-scrape";
|
|
415
|
+
}
|
|
416
|
+
const maxResultTokens = agentRow.webSearch?.maxResultTokens != null && typeof agentRow.webSearch?.maxResultTokens === "number" && agentRow.webSearch.maxResultTokens > 0
|
|
417
|
+
? agentRow.webSearch.maxResultTokens
|
|
418
|
+
: undefined;
|
|
419
|
+
webSearch = {
|
|
420
|
+
enabled: true,
|
|
421
|
+
provider: preferredProvider,
|
|
422
|
+
apiKey: preferredProvider === "brave" ? braveKey : undefined,
|
|
423
|
+
timeoutSeconds,
|
|
424
|
+
cacheTtlMinutes,
|
|
425
|
+
maxResults,
|
|
426
|
+
maxResultTokens,
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
catch {
|
|
432
|
+
// ignore
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
return {
|
|
436
|
+
provider,
|
|
437
|
+
model,
|
|
438
|
+
apiKey: apiKey ?? undefined,
|
|
439
|
+
workspace: workspaceName,
|
|
440
|
+
mcpServers,
|
|
441
|
+
mcpMaxResultTokens,
|
|
442
|
+
systemPrompt,
|
|
443
|
+
runnerType,
|
|
444
|
+
coze,
|
|
445
|
+
openclawx,
|
|
446
|
+
opencode,
|
|
447
|
+
claudeCode,
|
|
448
|
+
useLongMemory,
|
|
449
|
+
webSearch,
|
|
450
|
+
contextSize,
|
|
451
|
+
};
|
|
211
452
|
}
|
|
212
453
|
function ensureDesktopDir() {
|
|
213
454
|
const desktopDir = getDesktopDir();
|
|
@@ -357,50 +598,209 @@ export async function getDesktopConfigList() {
|
|
|
357
598
|
* 确保桌面目录下存在 provider-support.json(不存在则写入默认内容)。
|
|
358
599
|
* 供配置供应商时作备选下拉列表项。
|
|
359
600
|
*/
|
|
601
|
+
/**
|
|
602
|
+
* 确保桌面目录下存在 provider-support.json,并与预装的最新供应商列表合并。
|
|
603
|
+
*/
|
|
360
604
|
export async function ensureProviderSupportFile() {
|
|
605
|
+
const presetPath = join(getPresetsDir(), "preset-providers.json");
|
|
606
|
+
let presetData = { providers: DEFAULT_PROVIDER_SUPPORT };
|
|
607
|
+
if (existsSync(presetPath)) {
|
|
608
|
+
try {
|
|
609
|
+
presetData = JSON.parse(await readFile(presetPath, "utf-8"));
|
|
610
|
+
}
|
|
611
|
+
catch { }
|
|
612
|
+
}
|
|
613
|
+
const presetProviders = presetData.providers || DEFAULT_PROVIDER_SUPPORT;
|
|
361
614
|
const path = getProviderSupportPath();
|
|
362
|
-
if (existsSync(path))
|
|
363
|
-
return;
|
|
364
615
|
ensureDesktopDir();
|
|
365
|
-
|
|
616
|
+
if (!existsSync(path)) {
|
|
617
|
+
await writeFile(path, JSON.stringify(presetProviders, null, 2), "utf-8");
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
try {
|
|
621
|
+
const userContent = await readFile(path, "utf-8");
|
|
622
|
+
const userProviders = JSON.parse(userContent);
|
|
623
|
+
let changed = false;
|
|
624
|
+
for (const [providerId, presetEntry] of Object.entries(presetProviders)) {
|
|
625
|
+
if (!userProviders[providerId]) {
|
|
626
|
+
userProviders[providerId] = presetEntry;
|
|
627
|
+
changed = true;
|
|
628
|
+
}
|
|
629
|
+
else {
|
|
630
|
+
const userModels = userProviders[providerId].models || [];
|
|
631
|
+
for (const presetModel of presetEntry.models) {
|
|
632
|
+
if (!userModels.some((m) => m.id === presetModel.id)) {
|
|
633
|
+
userModels.push(presetModel);
|
|
634
|
+
changed = true;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
userProviders[providerId].models = userModels;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
if (changed) {
|
|
641
|
+
await writeFile(path, JSON.stringify(userProviders, null, 2), "utf-8");
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
catch {
|
|
645
|
+
await writeFile(path, JSON.stringify(presetProviders, null, 2), "utf-8");
|
|
646
|
+
}
|
|
366
647
|
}
|
|
367
|
-
/**
|
|
648
|
+
/** 预装本地推理缺省:推荐列表第一个 LLM(Qwen 3.5 4B)对应的本地文件名,与 modelUriToFilename 一致 */
|
|
649
|
+
const DEFAULT_LOCAL_LLM_MODEL_ID = "hf_unsloth_Qwen3.5-4B-GGUF_Qwen3.5-4B-Q5_K_M.gguf";
|
|
650
|
+
const DEFAULT_LOCAL_MODEL_ITEM_CODE = "local-qwen35-4b";
|
|
651
|
+
/** 代码内建默认:local provider + 本地 Qwen 3.5 4B,首次与合并时优先保证存在 */
|
|
652
|
+
const BUILTIN_DEFAULT_CONFIG = {
|
|
653
|
+
defaultProvider: "local",
|
|
654
|
+
defaultModel: DEFAULT_LOCAL_LLM_MODEL_ID,
|
|
655
|
+
defaultModelItemCode: DEFAULT_LOCAL_MODEL_ITEM_CODE,
|
|
656
|
+
defaultAgentId: DEFAULT_AGENT_ID,
|
|
657
|
+
maxAgentSessions: DEFAULT_MAX_AGENT_SESSIONS,
|
|
658
|
+
providers: {
|
|
659
|
+
local: { baseUrl: "http://127.0.0.1:11435/v1" },
|
|
660
|
+
},
|
|
661
|
+
configuredModels: [
|
|
662
|
+
{
|
|
663
|
+
provider: "local",
|
|
664
|
+
modelId: DEFAULT_LOCAL_LLM_MODEL_ID,
|
|
665
|
+
type: "llm",
|
|
666
|
+
alias: "Qwen 3.5 4B Q5_K_M",
|
|
667
|
+
modelItemCode: DEFAULT_LOCAL_MODEL_ITEM_CODE,
|
|
668
|
+
},
|
|
669
|
+
{
|
|
670
|
+
provider: "local",
|
|
671
|
+
modelId: "hf_ggml-org_embeddinggemma-300M-GGUF_embeddinggemma-300M-Q8_0.gguf",
|
|
672
|
+
type: "embedding",
|
|
673
|
+
alias: "EmbeddingGemma 300M Q8 (768维)",
|
|
674
|
+
modelItemCode: "local-embeddinggemma-300m",
|
|
675
|
+
},
|
|
676
|
+
],
|
|
677
|
+
};
|
|
678
|
+
/** 若 config.json 不存在则用 preset-config.json 初始化,若存在则浅合并补充新基础键值。预装 local provider + 本地 Qwen 3.5 4B 模型并设为缺省;preset 与代码默认合并,保证 local 一定存在。 */
|
|
368
679
|
async function ensureConfigJsonInitialized() {
|
|
680
|
+
const presetPath = join(getPresetsDir(), "preset-config.json");
|
|
681
|
+
let presetConfig = { ...BUILTIN_DEFAULT_CONFIG };
|
|
682
|
+
if (existsSync(presetPath)) {
|
|
683
|
+
try {
|
|
684
|
+
const data = JSON.parse(await readFile(presetPath, "utf-8"));
|
|
685
|
+
if (data.config && typeof data.config === "object") {
|
|
686
|
+
presetConfig = { ...BUILTIN_DEFAULT_CONFIG, ...data.config };
|
|
687
|
+
presetConfig.providers = { ...BUILTIN_DEFAULT_CONFIG.providers, ...(presetConfig.providers || {}) };
|
|
688
|
+
const hasLocalModel = (presetConfig.configuredModels || []).some((m) => m?.provider === "local" && (m?.modelId === DEFAULT_LOCAL_LLM_MODEL_ID || m?.modelItemCode === DEFAULT_LOCAL_MODEL_ITEM_CODE));
|
|
689
|
+
if (!hasLocalModel) {
|
|
690
|
+
presetConfig.configuredModels = [
|
|
691
|
+
...(BUILTIN_DEFAULT_CONFIG.configuredModels || []),
|
|
692
|
+
...(presetConfig.configuredModels || []),
|
|
693
|
+
];
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
catch { }
|
|
698
|
+
}
|
|
369
699
|
const configPath = getConfigPath();
|
|
370
|
-
if (existsSync(configPath))
|
|
371
|
-
return;
|
|
372
700
|
ensureDesktopDir();
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
701
|
+
if (!existsSync(configPath)) {
|
|
702
|
+
await writeFile(configPath, JSON.stringify(presetConfig, null, 2), "utf-8");
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
try {
|
|
706
|
+
const userConfig = JSON.parse(await readFile(configPath, "utf-8"));
|
|
707
|
+
let changed = false;
|
|
708
|
+
for (const [key, value] of Object.entries(presetConfig)) {
|
|
709
|
+
if (userConfig[key] === undefined) {
|
|
710
|
+
userConfig[key] = value;
|
|
711
|
+
changed = true;
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
if (changed) {
|
|
715
|
+
await writeFile(configPath, JSON.stringify(userConfig, null, 2), "utf-8");
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
catch { }
|
|
382
719
|
}
|
|
383
|
-
/**
|
|
720
|
+
/** 合并 preset-agents.json 中的预置智能体,并随之释放对应的工作区技能 */
|
|
384
721
|
async function ensureAgentsJsonInitialized() {
|
|
722
|
+
const presetPath = join(getPresetsDir(), "preset-agents.json");
|
|
723
|
+
let presetAgents = [];
|
|
724
|
+
if (existsSync(presetPath)) {
|
|
725
|
+
try {
|
|
726
|
+
const data = JSON.parse(await readFile(presetPath, "utf-8"));
|
|
727
|
+
if (Array.isArray(data.agents))
|
|
728
|
+
presetAgents = data.agents;
|
|
729
|
+
}
|
|
730
|
+
catch { }
|
|
731
|
+
}
|
|
385
732
|
const agentsPath = join(getDesktopDir(), "agents.json");
|
|
386
|
-
if (existsSync(agentsPath))
|
|
387
|
-
return;
|
|
388
733
|
ensureDesktopDir();
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
734
|
+
let currentData = { agents: [] };
|
|
735
|
+
if (existsSync(agentsPath)) {
|
|
736
|
+
try {
|
|
737
|
+
currentData = JSON.parse(await readFile(agentsPath, "utf-8"));
|
|
738
|
+
if (!Array.isArray(currentData.agents))
|
|
739
|
+
currentData.agents = [];
|
|
740
|
+
}
|
|
741
|
+
catch { }
|
|
742
|
+
}
|
|
743
|
+
let changed = false;
|
|
744
|
+
for (const pa of presetAgents) {
|
|
745
|
+
if (!currentData.agents.some((a) => a.id === pa.id)) {
|
|
746
|
+
currentData.agents.push(pa);
|
|
747
|
+
changed = true;
|
|
748
|
+
try {
|
|
749
|
+
const srcSkillsDir = join(getPresetsDir(), "workspaces", pa.id, "skills");
|
|
750
|
+
const homeDir = process.env.HOME || process.env.USERPROFILE || homedir();
|
|
751
|
+
const destSkillsDir = join(homeDir, ".openbot", "workspace", pa.id, "skills");
|
|
752
|
+
if (existsSync(srcSkillsDir)) {
|
|
753
|
+
if (!existsSync(destSkillsDir)) {
|
|
754
|
+
await mkdir(destSkillsDir, { recursive: true });
|
|
755
|
+
}
|
|
756
|
+
await cp(srcSkillsDir, destSkillsDir, { recursive: true, force: false, errorOnExist: false });
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
catch (err) {
|
|
760
|
+
console.error(`Failed to copy preset skills for agent ${pa.id}:`, err);
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
// 所有未单独配置模型的智能体使用 config 的缺省模型(预装为 local + Qwen 3.5 4B)
|
|
765
|
+
const configPath = join(getDesktopDir(), "config.json");
|
|
766
|
+
if (existsSync(configPath)) {
|
|
767
|
+
try {
|
|
768
|
+
const configRaw = await readFile(configPath, "utf-8");
|
|
769
|
+
const configData = JSON.parse(configRaw);
|
|
770
|
+
const defProvider = configData.defaultProvider?.trim();
|
|
771
|
+
const defModel = configData.defaultModel?.trim();
|
|
772
|
+
const defCode = configData.defaultModelItemCode?.trim();
|
|
773
|
+
if (defProvider && defModel) {
|
|
774
|
+
for (const agent of currentData.agents) {
|
|
775
|
+
const hasOwn = (agent.provider && String(agent.provider).trim()) || (agent.model && String(agent.model).trim()) || (agent.modelItemCode && String(agent.modelItemCode).trim());
|
|
776
|
+
if (!hasOwn) {
|
|
777
|
+
agent.provider = defProvider;
|
|
778
|
+
agent.model = defModel;
|
|
779
|
+
if (defCode)
|
|
780
|
+
agent.modelItemCode = defCode;
|
|
781
|
+
changed = true;
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
catch { /* ignore */ }
|
|
787
|
+
}
|
|
788
|
+
if (changed || !existsSync(agentsPath)) {
|
|
789
|
+
await writeFile(agentsPath, JSON.stringify(currentData, null, 2), "utf-8");
|
|
790
|
+
}
|
|
395
791
|
}
|
|
396
792
|
/**
|
|
397
|
-
* CLI / Gateway 运行时调用,确保 config.json、provider-support.json、agents.json
|
|
793
|
+
* CLI / Gateway 运行时调用,确保 config.json、provider-support.json、agents.json 均完成初始化,
|
|
794
|
+
* 并同步到 agent 目录 models.json,供 pi ModelRegistry 解析 local 等模型与凭证。
|
|
398
795
|
*/
|
|
399
796
|
export async function ensureDesktopConfigInitialized() {
|
|
400
797
|
ensureDesktopDir();
|
|
401
798
|
await ensureProviderSupportFile();
|
|
402
799
|
await ensureConfigJsonInitialized();
|
|
403
800
|
await ensureAgentsJsonInitialized();
|
|
801
|
+
await syncDesktopConfigToModelsJson().catch((err) => {
|
|
802
|
+
console.warn("[ensureDesktopConfigInitialized] syncDesktopConfigToModelsJson failed:", err);
|
|
803
|
+
});
|
|
404
804
|
}
|
|
405
805
|
/**
|
|
406
806
|
* 取某 provider 在 provider-support 中的第一个 llm 模型 id;若无则返回第一个模型 id。
|
|
@@ -451,6 +851,10 @@ const SYNC_DEFAULTS = {
|
|
|
451
851
|
"openai-custom": { baseUrl: "", apiKey: "OPENAI_API_KEY", api: "openai-completions" },
|
|
452
852
|
nvidia: { baseUrl: "https://integrate.api.nvidia.com/v1", apiKey: "NVIDIA_API_KEY", api: "openai-completions" },
|
|
453
853
|
kimi: { baseUrl: "https://api.moonshot.cn/v1", apiKey: "MOONSHOT_API_KEY", api: "openai-completions" },
|
|
854
|
+
/** 本地 Ollama,无需真实 API Key */
|
|
855
|
+
ollama: { baseUrl: "http://localhost:11434/v1", apiKey: "OPENAI_API_KEY", api: "openai-completions" },
|
|
856
|
+
/** 内置本地推理(node-llama-cpp),无需 API Key,baseUrl 指向本地子进程服务 */
|
|
857
|
+
local: { baseUrl: "http://127.0.0.1:11435/v1", apiKey: "OPENAI_API_KEY", api: "openai-completions" },
|
|
454
858
|
};
|
|
455
859
|
const DEFAULT_COST = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
456
860
|
const DEFAULT_CONTEXT_WINDOW = 64000;
|
|
@@ -475,25 +879,41 @@ function configuredModelToPi(item, displayName) {
|
|
|
475
879
|
}
|
|
476
880
|
/**
|
|
477
881
|
* 根据桌面 config(已配置的 providers + configuredModels)与 provider-support,生成并写入 agent 目录的 models.json。
|
|
478
|
-
*
|
|
882
|
+
* 包含:config.providers 中已配置的 provider;以及 configuredModels / defaultProvider 中引用但未在 providers 中的 provider(用 support 默认 baseUrl 补全,避免 Ollama 等仅选模型未填 baseUrl 时连不上)。
|
|
479
883
|
*/
|
|
480
884
|
export async function syncDesktopConfigToModelsJson() {
|
|
481
885
|
const config = await readDesktopConfigJson();
|
|
482
886
|
const configured = config.providers ?? {};
|
|
483
887
|
const configuredModels = Array.isArray(config.configuredModels) ? config.configuredModels : [];
|
|
484
|
-
|
|
888
|
+
const support = await getProviderSupport();
|
|
889
|
+
const providerIdsFromModels = new Set();
|
|
890
|
+
for (const m of configuredModels)
|
|
891
|
+
if (m?.provider)
|
|
892
|
+
providerIdsFromModels.add(m.provider);
|
|
893
|
+
if (config.defaultProvider)
|
|
894
|
+
providerIdsFromModels.add(config.defaultProvider);
|
|
895
|
+
const allProviderIds = new Set([...Object.keys(configured), ...providerIdsFromModels]);
|
|
896
|
+
if (allProviderIds.size === 0) {
|
|
485
897
|
return;
|
|
486
898
|
}
|
|
487
|
-
const support = await getProviderSupport();
|
|
488
899
|
const piProviders = {};
|
|
489
|
-
for (const
|
|
490
|
-
|
|
900
|
+
for (const providerId of allProviderIds) {
|
|
901
|
+
const userConfig = configured[providerId];
|
|
902
|
+
// ollama / local 不需要 API Key,其他 provider 必须有 apiKey
|
|
903
|
+
const isNoKeyProvider = providerId === "ollama" || providerId === "local";
|
|
904
|
+
if (!isNoKeyProvider && !userConfig?.apiKey?.trim())
|
|
491
905
|
continue;
|
|
492
906
|
const defaults = SYNC_DEFAULTS[providerId] ?? { baseUrl: "", apiKey: "OPENAI_API_KEY", api: "openai-completions" };
|
|
493
|
-
|
|
907
|
+
let baseUrl = userConfig?.baseUrl?.trim() || (support[providerId]?.baseUrl ?? "").trim() || defaults.baseUrl;
|
|
908
|
+
if (providerId === "ollama" && process.env.OLLAMA_BASE_URL?.trim()) {
|
|
909
|
+
const u = process.env.OLLAMA_BASE_URL.trim().replace(/\/$/, "");
|
|
910
|
+
baseUrl = u.endsWith("/v1") ? u : u + "/v1";
|
|
911
|
+
}
|
|
494
912
|
if (!baseUrl)
|
|
495
913
|
continue;
|
|
496
914
|
const def = support[providerId];
|
|
915
|
+
if (!def)
|
|
916
|
+
continue;
|
|
497
917
|
const items = configuredModels.filter((m) => m.provider === providerId);
|
|
498
918
|
let models;
|
|
499
919
|
if (items.length > 0) {
|
|
@@ -501,7 +921,11 @@ export async function syncDesktopConfigToModelsJson() {
|
|
|
501
921
|
const displayName = (item.alias && item.alias.trim()) ||
|
|
502
922
|
(def?.models?.find((m) => m.id === item.modelId)?.name) ||
|
|
503
923
|
item.modelId;
|
|
504
|
-
|
|
924
|
+
const pi = configuredModelToPi(item, displayName);
|
|
925
|
+
// 本地 node-llama-cpp 关闭思考:不向 SDK 发送 reasoning,避免启用 thinking 相关参数
|
|
926
|
+
if (providerId === "local")
|
|
927
|
+
return { ...pi, reasoning: false };
|
|
928
|
+
return pi;
|
|
505
929
|
});
|
|
506
930
|
}
|
|
507
931
|
else if (def?.models?.length) {
|
|
@@ -520,7 +944,7 @@ export async function syncDesktopConfigToModelsJson() {
|
|
|
520
944
|
continue;
|
|
521
945
|
}
|
|
522
946
|
piProviders[providerId] = {
|
|
523
|
-
name: (userConfig
|
|
947
|
+
name: (userConfig?.alias?.trim() || def?.name) || providerId,
|
|
524
948
|
apiKey: defaults.apiKey,
|
|
525
949
|
api: defaults.api,
|
|
526
950
|
baseUrl: baseUrl.replace(/\/$/, ""),
|