@next-open-ai/openclawx 0.8.36 → 0.8.48
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 +60 -42
- package/apps/desktop/renderer/dist/assets/index-BHY1xIZQ.css +10 -0
- package/apps/desktop/renderer/dist/assets/index-DQxlVuBe.js +93 -0
- package/apps/desktop/renderer/dist/index.html +2 -2
- package/dist/cli/cli.js +29 -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 +13 -0
- package/dist/core/agent/agent-manager.js +77 -7
- 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/local-adapter.js +3 -1
- package/dist/core/agent/proxy/adapters/opencode-adapter.js +65 -29
- package/dist/core/agent/proxy/adapters/opencode-local-runner.js +9 -0
- package/dist/core/agent/proxy/index.js +2 -0
- 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/desktop-config.d.ts +24 -2
- package/dist/core/config/desktop-config.js +87 -10
- package/dist/core/config/provider-support-default.js +26 -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/local-llm-server/index.d.ts +32 -0
- package/dist/core/local-llm-server/index.js +126 -0
- package/dist/core/local-llm-server/llm-context.d.ts +60 -0
- package/dist/core/local-llm-server/llm-context.js +221 -0
- package/dist/core/local-llm-server/model-resolve.d.ts +20 -0
- package/dist/core/local-llm-server/model-resolve.js +58 -0
- package/dist/core/local-llm-server/server.d.ts +1 -0
- package/dist/core/local-llm-server/server.js +235 -0
- package/dist/core/mcp/adapter.d.ts +4 -2
- package/dist/core/mcp/adapter.js +10 -4
- package/dist/core/mcp/index.d.ts +2 -0
- package/dist/core/mcp/index.js +1 -0
- package/dist/core/mcp/operator.d.ts +2 -0
- package/dist/core/mcp/operator.js +1 -1
- package/dist/core/memory/local-embedding.d.ts +4 -3
- package/dist/core/memory/local-embedding.js +43 -3
- package/dist/core/tools/index.d.ts +1 -0
- package/dist/core/tools/index.js +1 -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/methods/agent-chat.js +74 -42
- package/dist/gateway/methods/run-scheduled-task.js +2 -0
- package/dist/gateway/server.js +54 -1
- package/dist/server/agent-config/agent-config.controller.d.ts +1 -1
- package/dist/server/agent-config/agent-config.service.d.ts +17 -3
- package/dist/server/agent-config/agent-config.service.js +23 -0
- package/dist/server/config/config.controller.d.ts +84 -4
- package/dist/server/config/config.controller.js +135 -3
- package/dist/server/config/config.module.js +3 -2
- package/dist/server/config/config.service.d.ts +14 -0
- package/dist/server/config/local-models.service.d.ts +52 -0
- package/dist/server/config/local-models.service.js +211 -0
- package/package.json +3 -1
- package/presets/preset-agents.json +121 -91
- package/presets/recommended-local-models.json +42 -0
- package/presets/workspaces/finance-expert/skills/akshare-helper/SKILL.md +9 -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/apps/desktop/renderer/dist/assets/index-BGHtXhm3.js +0 -89
- package/apps/desktop/renderer/dist/assets/index-CB2-m4ae.css +0 -10
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { ConfigService, AppConfig } from './config.service.js';
|
|
2
|
+
import { LocalModelsService } from './local-models.service.js';
|
|
2
3
|
export declare class ConfigController {
|
|
3
4
|
private readonly configService;
|
|
4
|
-
|
|
5
|
+
private readonly localModelsService;
|
|
6
|
+
constructor(configService: ConfigService, localModelsService: LocalModelsService);
|
|
5
7
|
getConfig(): Promise<{
|
|
6
8
|
success: boolean;
|
|
7
9
|
data: {
|
|
@@ -16,7 +18,7 @@ export declare class ConfigController {
|
|
|
16
18
|
loginUsername?: string;
|
|
17
19
|
providers: {
|
|
18
20
|
[key: string]: {
|
|
19
|
-
apiKey
|
|
21
|
+
apiKey?: string;
|
|
20
22
|
baseUrl?: string;
|
|
21
23
|
alias?: string;
|
|
22
24
|
};
|
|
@@ -39,6 +41,19 @@ export declare class ConfigController {
|
|
|
39
41
|
embeddingModelItemCode?: string;
|
|
40
42
|
};
|
|
41
43
|
channels?: import("../../core/config/desktop-config.js").ChannelsConfig;
|
|
44
|
+
tools?: {
|
|
45
|
+
webSearch?: {
|
|
46
|
+
defaultProvider?: "brave" | "duck-duck-scrape";
|
|
47
|
+
providers?: {
|
|
48
|
+
brave?: {
|
|
49
|
+
apiKey?: string;
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
timeoutSeconds?: number;
|
|
53
|
+
cacheTtlMinutes?: number;
|
|
54
|
+
maxResults?: number;
|
|
55
|
+
};
|
|
56
|
+
};
|
|
42
57
|
};
|
|
43
58
|
}>;
|
|
44
59
|
updateConfig(updates: Partial<AppConfig>): Promise<{
|
|
@@ -55,7 +70,7 @@ export declare class ConfigController {
|
|
|
55
70
|
loginUsername?: string;
|
|
56
71
|
providers: {
|
|
57
72
|
[key: string]: {
|
|
58
|
-
apiKey
|
|
73
|
+
apiKey?: string;
|
|
59
74
|
baseUrl?: string;
|
|
60
75
|
alias?: string;
|
|
61
76
|
};
|
|
@@ -78,6 +93,19 @@ export declare class ConfigController {
|
|
|
78
93
|
embeddingModelItemCode?: string;
|
|
79
94
|
};
|
|
80
95
|
channels?: import("../../core/config/desktop-config.js").ChannelsConfig;
|
|
96
|
+
tools?: {
|
|
97
|
+
webSearch?: {
|
|
98
|
+
defaultProvider?: "brave" | "duck-duck-scrape";
|
|
99
|
+
providers?: {
|
|
100
|
+
brave?: {
|
|
101
|
+
apiKey?: string;
|
|
102
|
+
};
|
|
103
|
+
};
|
|
104
|
+
timeoutSeconds?: number;
|
|
105
|
+
cacheTtlMinutes?: number;
|
|
106
|
+
maxResults?: number;
|
|
107
|
+
};
|
|
108
|
+
};
|
|
81
109
|
};
|
|
82
110
|
}>;
|
|
83
111
|
getProviders(): Promise<{
|
|
@@ -86,7 +114,7 @@ export declare class ConfigController {
|
|
|
86
114
|
}>;
|
|
87
115
|
getProviderSupport(): Promise<{
|
|
88
116
|
success: boolean;
|
|
89
|
-
data: import("../../
|
|
117
|
+
data: import("../../core/config/provider-support-default.js").ProviderSupport;
|
|
90
118
|
}>;
|
|
91
119
|
getModels(provider: string, type?: string): Promise<{
|
|
92
120
|
success: boolean;
|
|
@@ -101,4 +129,56 @@ export declare class ConfigController {
|
|
|
101
129
|
success: boolean;
|
|
102
130
|
data: import("../../core/agent/proxy/adapters/opencode-free-models.js").OpenCodeFreeModelOption[];
|
|
103
131
|
};
|
|
132
|
+
/** 列出本地已缓存的 GGUF 模型文件 */
|
|
133
|
+
listLocalModels(): Promise<{
|
|
134
|
+
success: boolean;
|
|
135
|
+
data: import("./local-models.service.js").LocalModelInfo[];
|
|
136
|
+
}>;
|
|
137
|
+
/** 推荐的 GGUF 模型列表(来自 preset,与已安装展示名称一致) */
|
|
138
|
+
getRecommendedModels(): Promise<{
|
|
139
|
+
success: boolean;
|
|
140
|
+
data: import("./local-models.service.js").RecommendedModel[];
|
|
141
|
+
}>;
|
|
142
|
+
/** 仅返回尚未安装的推荐模型(已安装的不显示在下载区) */
|
|
143
|
+
getRecommendedToDownload(): Promise<{
|
|
144
|
+
success: boolean;
|
|
145
|
+
data: import("./local-models.service.js").RecommendedModel[];
|
|
146
|
+
}>;
|
|
147
|
+
/** 本地模型服务状态:是否可用、不可用原因、当前 baseUrl */
|
|
148
|
+
getLocalLlmStatus(): {
|
|
149
|
+
success: boolean;
|
|
150
|
+
data: {
|
|
151
|
+
available: boolean;
|
|
152
|
+
error: string | undefined;
|
|
153
|
+
baseUrl: string | undefined;
|
|
154
|
+
};
|
|
155
|
+
};
|
|
156
|
+
/** 启动本地模型服务:可选指定 LLM 与 Embedding 模型(文件名或 hf: URI),先停后启 */
|
|
157
|
+
startLocalLlm(body: {
|
|
158
|
+
llmModelUri?: string;
|
|
159
|
+
embeddingModelUri?: string;
|
|
160
|
+
}): Promise<{
|
|
161
|
+
success: boolean;
|
|
162
|
+
data: {
|
|
163
|
+
baseUrl: string;
|
|
164
|
+
};
|
|
165
|
+
}>;
|
|
166
|
+
/** 开始后台下载模型(立即返回,进度通过 GET local-models/progress/:uri 轮询) */
|
|
167
|
+
startDownload(body: {
|
|
168
|
+
modelUri: string;
|
|
169
|
+
}): Promise<{
|
|
170
|
+
success: boolean;
|
|
171
|
+
data: {
|
|
172
|
+
filename: string;
|
|
173
|
+
};
|
|
174
|
+
}>;
|
|
175
|
+
/** 查询下载进度 */
|
|
176
|
+
getDownloadProgress(uri: string): {
|
|
177
|
+
success: boolean;
|
|
178
|
+
data: import("./local-models.service.js").DownloadProgress | null;
|
|
179
|
+
};
|
|
180
|
+
/** 删除本地缓存的 GGUF 模型文件 */
|
|
181
|
+
deleteLocalModel(filename: string): Promise<{
|
|
182
|
+
success: boolean;
|
|
183
|
+
}>;
|
|
104
184
|
}
|
|
@@ -10,13 +10,19 @@ var __metadata = (this && this.__metadata) || function (k, v) {
|
|
|
10
10
|
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
11
11
|
return function (target, key) { decorator(target, key, paramIndex); }
|
|
12
12
|
};
|
|
13
|
-
import { Controller, Get, Put, Body, Param, Query } from '@nestjs/common';
|
|
13
|
+
import { Controller, Get, Put, Body, Param, Query, Delete, Post } from '@nestjs/common';
|
|
14
14
|
import { OPENCODE_FREE_MODELS } from '../../core/agent/proxy/adapters/opencode-free-models.js';
|
|
15
|
+
import { loadDesktopAgentConfig } from '../../core/config/desktop-config.js';
|
|
16
|
+
import { startLocalLlmServer, stopLocalLlmServer } from '../../core/local-llm-server/index.js';
|
|
17
|
+
import { toModelPathForStart, LOCAL_LLM_CACHE_DIR } from '../../core/local-llm-server/model-resolve.js';
|
|
15
18
|
import { ConfigService } from './config.service.js';
|
|
19
|
+
import { LocalModelsService } from './local-models.service.js';
|
|
16
20
|
let ConfigController = class ConfigController {
|
|
17
21
|
configService;
|
|
18
|
-
|
|
22
|
+
localModelsService;
|
|
23
|
+
constructor(configService, localModelsService) {
|
|
19
24
|
this.configService = configService;
|
|
25
|
+
this.localModelsService = localModelsService;
|
|
20
26
|
}
|
|
21
27
|
async getConfig() {
|
|
22
28
|
const config = await this.configService.getConfig();
|
|
@@ -62,6 +68,79 @@ let ConfigController = class ConfigController {
|
|
|
62
68
|
data: OPENCODE_FREE_MODELS,
|
|
63
69
|
};
|
|
64
70
|
}
|
|
71
|
+
// ─── 本地模型管理(node-llama-cpp GGUF)────────────────────────────────────
|
|
72
|
+
/** 列出本地已缓存的 GGUF 模型文件 */
|
|
73
|
+
async listLocalModels() {
|
|
74
|
+
return { success: true, data: await this.localModelsService.listModels() };
|
|
75
|
+
}
|
|
76
|
+
/** 推荐的 GGUF 模型列表(来自 preset,与已安装展示名称一致) */
|
|
77
|
+
async getRecommendedModels() {
|
|
78
|
+
const data = await this.localModelsService.getRecommendedModels();
|
|
79
|
+
return { success: true, data };
|
|
80
|
+
}
|
|
81
|
+
/** 仅返回尚未安装的推荐模型(已安装的不显示在下载区) */
|
|
82
|
+
async getRecommendedToDownload() {
|
|
83
|
+
const data = await this.localModelsService.getRecommendedToDownload();
|
|
84
|
+
return { success: true, data };
|
|
85
|
+
}
|
|
86
|
+
/** 本地模型服务状态:是否可用、不可用原因、当前 baseUrl */
|
|
87
|
+
getLocalLlmStatus() {
|
|
88
|
+
const baseUrl = process.env.LOCAL_LLM_BASE_URL;
|
|
89
|
+
const error = process.env.LOCAL_LLM_START_FAILED;
|
|
90
|
+
const available = !!baseUrl;
|
|
91
|
+
return {
|
|
92
|
+
success: true,
|
|
93
|
+
data: { available, error: error || undefined, baseUrl: baseUrl || undefined },
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
/** 启动本地模型服务:可选指定 LLM 与 Embedding 模型(文件名或 hf: URI),先停后启 */
|
|
97
|
+
async startLocalLlm(body) {
|
|
98
|
+
stopLocalLlmServer();
|
|
99
|
+
delete process.env.LOCAL_LLM_BASE_URL;
|
|
100
|
+
delete process.env.LOCAL_LLM_START_FAILED;
|
|
101
|
+
let contextSize = 32768;
|
|
102
|
+
try {
|
|
103
|
+
const defaultAgent = await loadDesktopAgentConfig('default');
|
|
104
|
+
if (defaultAgent?.contextSize != null && defaultAgent.contextSize > 0) {
|
|
105
|
+
contextSize = defaultAgent.contextSize;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
// ignore
|
|
110
|
+
}
|
|
111
|
+
const llmPath = body.llmModelUri?.trim();
|
|
112
|
+
const embPath = body.embeddingModelUri?.trim();
|
|
113
|
+
const opts = {
|
|
114
|
+
...(llmPath ? { llmModelPath: toModelPathForStart(llmPath, LOCAL_LLM_CACHE_DIR) } : {}),
|
|
115
|
+
...(embPath ? { embeddingModelPath: toModelPathForStart(embPath, LOCAL_LLM_CACHE_DIR) } : {}),
|
|
116
|
+
contextSize,
|
|
117
|
+
};
|
|
118
|
+
try {
|
|
119
|
+
const handle = await startLocalLlmServer(opts);
|
|
120
|
+
process.env.LOCAL_LLM_BASE_URL = handle.baseUrl;
|
|
121
|
+
return { success: true, data: { baseUrl: handle.baseUrl } };
|
|
122
|
+
}
|
|
123
|
+
catch (e) {
|
|
124
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
125
|
+
process.env.LOCAL_LLM_START_FAILED = msg;
|
|
126
|
+
throw new Error(msg);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/** 开始后台下载模型(立即返回,进度通过 GET local-models/progress/:uri 轮询) */
|
|
130
|
+
async startDownload(body) {
|
|
131
|
+
const result = await this.localModelsService.startDownload(body.modelUri);
|
|
132
|
+
return { success: true, data: result };
|
|
133
|
+
}
|
|
134
|
+
/** 查询下载进度 */
|
|
135
|
+
getDownloadProgress(uri) {
|
|
136
|
+
const progress = this.localModelsService.getDownloadProgress(uri);
|
|
137
|
+
return { success: true, data: progress };
|
|
138
|
+
}
|
|
139
|
+
/** 删除本地缓存的 GGUF 模型文件 */
|
|
140
|
+
async deleteLocalModel(filename) {
|
|
141
|
+
await this.localModelsService.deleteModel(filename);
|
|
142
|
+
return { success: true };
|
|
143
|
+
}
|
|
65
144
|
};
|
|
66
145
|
__decorate([
|
|
67
146
|
Get(),
|
|
@@ -102,8 +181,61 @@ __decorate([
|
|
|
102
181
|
__metadata("design:paramtypes", []),
|
|
103
182
|
__metadata("design:returntype", void 0)
|
|
104
183
|
], ConfigController.prototype, "getOpencodeFreeModels", null);
|
|
184
|
+
__decorate([
|
|
185
|
+
Get('local-models'),
|
|
186
|
+
__metadata("design:type", Function),
|
|
187
|
+
__metadata("design:paramtypes", []),
|
|
188
|
+
__metadata("design:returntype", Promise)
|
|
189
|
+
], ConfigController.prototype, "listLocalModels", null);
|
|
190
|
+
__decorate([
|
|
191
|
+
Get('local-models/recommended'),
|
|
192
|
+
__metadata("design:type", Function),
|
|
193
|
+
__metadata("design:paramtypes", []),
|
|
194
|
+
__metadata("design:returntype", Promise)
|
|
195
|
+
], ConfigController.prototype, "getRecommendedModels", null);
|
|
196
|
+
__decorate([
|
|
197
|
+
Get('local-models/recommended-to-download'),
|
|
198
|
+
__metadata("design:type", Function),
|
|
199
|
+
__metadata("design:paramtypes", []),
|
|
200
|
+
__metadata("design:returntype", Promise)
|
|
201
|
+
], ConfigController.prototype, "getRecommendedToDownload", null);
|
|
202
|
+
__decorate([
|
|
203
|
+
Get('local-models/status'),
|
|
204
|
+
__metadata("design:type", Function),
|
|
205
|
+
__metadata("design:paramtypes", []),
|
|
206
|
+
__metadata("design:returntype", void 0)
|
|
207
|
+
], ConfigController.prototype, "getLocalLlmStatus", null);
|
|
208
|
+
__decorate([
|
|
209
|
+
Post('local-models/start'),
|
|
210
|
+
__param(0, Body()),
|
|
211
|
+
__metadata("design:type", Function),
|
|
212
|
+
__metadata("design:paramtypes", [Object]),
|
|
213
|
+
__metadata("design:returntype", Promise)
|
|
214
|
+
], ConfigController.prototype, "startLocalLlm", null);
|
|
215
|
+
__decorate([
|
|
216
|
+
Post('local-models/download'),
|
|
217
|
+
__param(0, Body()),
|
|
218
|
+
__metadata("design:type", Function),
|
|
219
|
+
__metadata("design:paramtypes", [Object]),
|
|
220
|
+
__metadata("design:returntype", Promise)
|
|
221
|
+
], ConfigController.prototype, "startDownload", null);
|
|
222
|
+
__decorate([
|
|
223
|
+
Get('local-models/progress'),
|
|
224
|
+
__param(0, Query('uri')),
|
|
225
|
+
__metadata("design:type", Function),
|
|
226
|
+
__metadata("design:paramtypes", [String]),
|
|
227
|
+
__metadata("design:returntype", void 0)
|
|
228
|
+
], ConfigController.prototype, "getDownloadProgress", null);
|
|
229
|
+
__decorate([
|
|
230
|
+
Delete('local-models/:filename'),
|
|
231
|
+
__param(0, Param('filename')),
|
|
232
|
+
__metadata("design:type", Function),
|
|
233
|
+
__metadata("design:paramtypes", [String]),
|
|
234
|
+
__metadata("design:returntype", Promise)
|
|
235
|
+
], ConfigController.prototype, "deleteLocalModel", null);
|
|
105
236
|
ConfigController = __decorate([
|
|
106
237
|
Controller('config'),
|
|
107
|
-
__metadata("design:paramtypes", [ConfigService
|
|
238
|
+
__metadata("design:paramtypes", [ConfigService,
|
|
239
|
+
LocalModelsService])
|
|
108
240
|
], ConfigController);
|
|
109
241
|
export { ConfigController };
|
|
@@ -7,6 +7,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
7
7
|
import { Module, forwardRef } from '@nestjs/common';
|
|
8
8
|
import { ConfigController } from './config.controller.js';
|
|
9
9
|
import { ConfigService } from './config.service.js';
|
|
10
|
+
import { LocalModelsService } from './local-models.service.js';
|
|
10
11
|
import { AgentConfigModule } from '../agent-config/agent-config.module.js';
|
|
11
12
|
let ConfigModule = class ConfigModule {
|
|
12
13
|
};
|
|
@@ -14,8 +15,8 @@ ConfigModule = __decorate([
|
|
|
14
15
|
Module({
|
|
15
16
|
imports: [forwardRef(() => AgentConfigModule)],
|
|
16
17
|
controllers: [ConfigController],
|
|
17
|
-
providers: [ConfigService],
|
|
18
|
-
exports: [ConfigService],
|
|
18
|
+
providers: [ConfigService, LocalModelsService],
|
|
19
|
+
exports: [ConfigService, LocalModelsService],
|
|
19
20
|
})
|
|
20
21
|
], ConfigModule);
|
|
21
22
|
export { ConfigModule };
|
|
@@ -71,6 +71,20 @@ export interface AppConfig {
|
|
|
71
71
|
};
|
|
72
72
|
/** 通道配置:飞书、Telegram 等 token/key */
|
|
73
73
|
channels?: ChannelsConfig;
|
|
74
|
+
/** 工具相关:在线搜索等,Brave API Key 存于 providers.brave.apiKey */
|
|
75
|
+
tools?: {
|
|
76
|
+
webSearch?: {
|
|
77
|
+
defaultProvider?: 'brave' | 'duck-duck-scrape';
|
|
78
|
+
providers?: {
|
|
79
|
+
brave?: {
|
|
80
|
+
apiKey?: string;
|
|
81
|
+
};
|
|
82
|
+
};
|
|
83
|
+
timeoutSeconds?: number;
|
|
84
|
+
cacheTtlMinutes?: number;
|
|
85
|
+
maxResults?: number;
|
|
86
|
+
};
|
|
87
|
+
};
|
|
74
88
|
}
|
|
75
89
|
export declare class ConfigService {
|
|
76
90
|
private readonly agentConfigService;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export interface RecommendedModel {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
type: 'llm' | 'embedding';
|
|
5
|
+
sizeHint: string;
|
|
6
|
+
}
|
|
7
|
+
export interface LocalModelInfo {
|
|
8
|
+
filename: string;
|
|
9
|
+
/** 文件大小(字节) */
|
|
10
|
+
size: number;
|
|
11
|
+
/** 最后修改时间 ISO 字符串 */
|
|
12
|
+
updatedAt: string;
|
|
13
|
+
/** 推断的模型类型:llm / embedding(根据文件名关键词) */
|
|
14
|
+
inferredType: 'llm' | 'embedding' | 'unknown';
|
|
15
|
+
/** 与推荐列表一致的展示名称(能匹配到预设时),否则为 filename */
|
|
16
|
+
displayName: string;
|
|
17
|
+
}
|
|
18
|
+
export interface DownloadProgress {
|
|
19
|
+
status: string;
|
|
20
|
+
completed?: number;
|
|
21
|
+
total?: number;
|
|
22
|
+
percent?: number;
|
|
23
|
+
}
|
|
24
|
+
export declare class LocalModelsService {
|
|
25
|
+
private readonly cacheDir;
|
|
26
|
+
private recommendedCache;
|
|
27
|
+
/** 正在下载的任务:modelUri → 进度 */
|
|
28
|
+
private downloadingMap;
|
|
29
|
+
constructor();
|
|
30
|
+
/** 从 presets/recommended-local-models.json 加载推荐列表,失败则用内置默认 */
|
|
31
|
+
private getRecommendedModelsFromPreset;
|
|
32
|
+
/** 列出本地已缓存的 GGUF 模型文件,displayName 与推荐列表一致(来自 preset 匹配) */
|
|
33
|
+
listModels(): Promise<LocalModelInfo[]>;
|
|
34
|
+
/** 删除本地缓存的 GGUF 模型文件 */
|
|
35
|
+
deleteModel(filename: string): Promise<void>;
|
|
36
|
+
/** 获取推荐模型列表(来自 preset,与已安装展示名称一致) */
|
|
37
|
+
getRecommendedModels(): Promise<RecommendedModel[]>;
|
|
38
|
+
/** 仅返回尚未安装的推荐模型;已安装的视为「推荐列表中的一员」不再出现在备下载区 */
|
|
39
|
+
getRecommendedToDownload(): Promise<RecommendedModel[]>;
|
|
40
|
+
/** 检查指定模型(uri 或文件名)是否已在缓存目录存在 */
|
|
41
|
+
isModelFilePresent(modelIdOrUri: string): boolean;
|
|
42
|
+
/** 获取某个 modelUri 的下载进度(不存在则表示未在下载) */
|
|
43
|
+
getDownloadProgress(modelUri: string): DownloadProgress | null;
|
|
44
|
+
/**
|
|
45
|
+
* 后台下载模型(通过 node-llama-cpp resolveModelFile)。
|
|
46
|
+
* 返回后立即响应,进度通过 getDownloadProgress 轮询获取。
|
|
47
|
+
*/
|
|
48
|
+
startDownload(modelUri: string): Promise<{
|
|
49
|
+
filename: string;
|
|
50
|
+
}>;
|
|
51
|
+
private runDownload;
|
|
52
|
+
}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* 本地 GGUF 模型管理服务。
|
|
12
|
+
* 负责列出、下载(通过 node-llama-cpp resolveModelFile)、删除本地缓存的 GGUF 模型文件。
|
|
13
|
+
* 推荐列表从 presets/recommended-local-models.json 加载,已安装的与推荐使用同一套展示名称且已安装的不再出现在「备下载」列表。
|
|
14
|
+
* 模型缓存目录:~/.cache/llama
|
|
15
|
+
*/
|
|
16
|
+
import { Injectable } from '@nestjs/common';
|
|
17
|
+
import { readdir, stat, unlink, readFile } from 'node:fs/promises';
|
|
18
|
+
import { join, basename } from 'node:path';
|
|
19
|
+
import { homedir } from 'node:os';
|
|
20
|
+
import { existsSync, mkdirSync } from 'node:fs';
|
|
21
|
+
import { modelUriToFilename, modelUriBasename } from '../../core/local-llm-server/model-resolve.js';
|
|
22
|
+
/** 根据文件名推断模型类型 */
|
|
23
|
+
function inferModelType(filename) {
|
|
24
|
+
const lower = filename.toLowerCase();
|
|
25
|
+
if (lower.includes('embed') || lower.includes('bge') || lower.includes('e5-'))
|
|
26
|
+
return 'embedding';
|
|
27
|
+
return 'llm';
|
|
28
|
+
}
|
|
29
|
+
const PRESET_FILENAME = 'recommended-local-models.json';
|
|
30
|
+
const DEFAULT_RECOMMENDED = [
|
|
31
|
+
{ id: 'hf:Qwen/Qwen3-4B-GGUF/Qwen3-4B-Q4_K_M.gguf', name: 'Qwen3 4B Q4_K_M', type: 'llm', sizeHint: '~2.5GB' },
|
|
32
|
+
{ id: 'hf:Qwen/Qwen3-7B-GGUF/Qwen3-7B-Q4_K_M.gguf', name: 'Qwen3 7B Q4_K_M', type: 'llm', sizeHint: '~4.5GB' },
|
|
33
|
+
{ id: 'hf:Qwen/Qwen3-14B-GGUF/Qwen3-14B-Q4_K_M.gguf', name: 'Qwen3 14B Q4_K_M', type: 'llm', sizeHint: '~8.5GB' },
|
|
34
|
+
{ id: 'hf:ggml-org/embeddinggemma-300M-GGUF/embeddinggemma-300M-Q8_0.gguf', name: 'EmbeddingGemma 300M Q8 (768维)', type: 'embedding', sizeHint: '~300MB' },
|
|
35
|
+
{ id: 'hf:gpustack/bge-m3-GGUF/bge-m3-Q8_0.gguf', name: 'BGE-M3 Q8 多语言 (1024维)', type: 'embedding', sizeHint: '~1.2GB' },
|
|
36
|
+
{ id: 'hf:mixedbread-ai/mxbai-embed-large-v1-GGUF/mxbai-embed-large-v1-f16.gguf', name: 'MxBai Embed Large v1 (1024维)', type: 'embedding', sizeHint: '~670MB' },
|
|
37
|
+
];
|
|
38
|
+
function getPresetsDir() {
|
|
39
|
+
return process.env.OPENBOT_PRESETS_DIR || join(process.cwd(), 'presets');
|
|
40
|
+
}
|
|
41
|
+
/** 推荐项是否对应已安装文件(精确预测名 或 安装文件名以 uri 末尾文件名结尾,兼容不同 node-llama-cpp 命名) */
|
|
42
|
+
function recommendedMatchesInstalled(rec, installedFilenames) {
|
|
43
|
+
const predicted = modelUriToFilename(rec.id);
|
|
44
|
+
if (installedFilenames.has(predicted))
|
|
45
|
+
return true;
|
|
46
|
+
const suffix = modelUriBasename(rec.id);
|
|
47
|
+
return Array.from(installedFilenames).some((f) => f.endsWith(suffix) || f === suffix);
|
|
48
|
+
}
|
|
49
|
+
/** 已安装文件名在预设中对应的展示名,若无匹配则返回 filename */
|
|
50
|
+
function displayNameForFilename(filename, recommended) {
|
|
51
|
+
for (const rec of recommended) {
|
|
52
|
+
const predicted = modelUriToFilename(rec.id);
|
|
53
|
+
if (filename === predicted)
|
|
54
|
+
return rec.name;
|
|
55
|
+
const uriBase = modelUriBasename(rec.id);
|
|
56
|
+
if (uriBase && filename.endsWith(uriBase))
|
|
57
|
+
return rec.name;
|
|
58
|
+
}
|
|
59
|
+
return filename;
|
|
60
|
+
}
|
|
61
|
+
let LocalModelsService = class LocalModelsService {
|
|
62
|
+
cacheDir;
|
|
63
|
+
recommendedCache = null;
|
|
64
|
+
/** 正在下载的任务:modelUri → 进度 */
|
|
65
|
+
downloadingMap = new Map();
|
|
66
|
+
constructor() {
|
|
67
|
+
this.cacheDir = join(homedir(), '.cache', 'llama');
|
|
68
|
+
if (!existsSync(this.cacheDir)) {
|
|
69
|
+
mkdirSync(this.cacheDir, { recursive: true });
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/** 从 presets/recommended-local-models.json 加载推荐列表,失败则用内置默认 */
|
|
73
|
+
async getRecommendedModelsFromPreset() {
|
|
74
|
+
if (this.recommendedCache)
|
|
75
|
+
return this.recommendedCache;
|
|
76
|
+
const presetPath = join(getPresetsDir(), PRESET_FILENAME);
|
|
77
|
+
try {
|
|
78
|
+
if (existsSync(presetPath)) {
|
|
79
|
+
const raw = await readFile(presetPath, 'utf-8');
|
|
80
|
+
const data = JSON.parse(raw);
|
|
81
|
+
if (Array.isArray(data.models) && data.models.length > 0) {
|
|
82
|
+
this.recommendedCache = data.models;
|
|
83
|
+
return this.recommendedCache;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
// ignore, fallback to default
|
|
89
|
+
}
|
|
90
|
+
this.recommendedCache = DEFAULT_RECOMMENDED;
|
|
91
|
+
return this.recommendedCache;
|
|
92
|
+
}
|
|
93
|
+
/** 列出本地已缓存的 GGUF 模型文件,displayName 与推荐列表一致(来自 preset 匹配) */
|
|
94
|
+
async listModels() {
|
|
95
|
+
try {
|
|
96
|
+
const recommended = await this.getRecommendedModelsFromPreset();
|
|
97
|
+
const files = await readdir(this.cacheDir);
|
|
98
|
+
const ggufFiles = files.filter((f) => f.endsWith('.gguf'));
|
|
99
|
+
const infos = await Promise.all(ggufFiles.map(async (f) => {
|
|
100
|
+
const filePath = join(this.cacheDir, f);
|
|
101
|
+
const s = await stat(filePath);
|
|
102
|
+
return {
|
|
103
|
+
filename: f,
|
|
104
|
+
size: s.size,
|
|
105
|
+
updatedAt: s.mtime.toISOString(),
|
|
106
|
+
inferredType: inferModelType(f),
|
|
107
|
+
displayName: displayNameForFilename(f, recommended),
|
|
108
|
+
};
|
|
109
|
+
}));
|
|
110
|
+
return infos.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
return [];
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/** 删除本地缓存的 GGUF 模型文件 */
|
|
117
|
+
async deleteModel(filename) {
|
|
118
|
+
// 安全检查:只允许删除 .gguf 文件,且不含路径分隔符
|
|
119
|
+
if (!filename.endsWith('.gguf') || filename.includes('/') || filename.includes('\\')) {
|
|
120
|
+
throw new Error('非法文件名');
|
|
121
|
+
}
|
|
122
|
+
const filePath = join(this.cacheDir, filename);
|
|
123
|
+
if (!existsSync(filePath))
|
|
124
|
+
throw new Error(`文件不存在: ${filename}`);
|
|
125
|
+
await unlink(filePath);
|
|
126
|
+
}
|
|
127
|
+
/** 获取推荐模型列表(来自 preset,与已安装展示名称一致) */
|
|
128
|
+
async getRecommendedModels() {
|
|
129
|
+
return this.getRecommendedModelsFromPreset();
|
|
130
|
+
}
|
|
131
|
+
/** 仅返回尚未安装的推荐模型;已安装的视为「推荐列表中的一员」不再出现在备下载区 */
|
|
132
|
+
async getRecommendedToDownload() {
|
|
133
|
+
const [recommended, installed] = await Promise.all([
|
|
134
|
+
this.getRecommendedModelsFromPreset(),
|
|
135
|
+
this.listModels(),
|
|
136
|
+
]);
|
|
137
|
+
const installedFilenames = new Set(installed.map((m) => m.filename));
|
|
138
|
+
return recommended.filter((rec) => !recommendedMatchesInstalled(rec, installedFilenames));
|
|
139
|
+
}
|
|
140
|
+
/** 检查指定模型(uri 或文件名)是否已在缓存目录存在 */
|
|
141
|
+
isModelFilePresent(modelIdOrUri) {
|
|
142
|
+
const filename = modelUriToFilename(modelIdOrUri);
|
|
143
|
+
if (!filename || !filename.endsWith('.gguf'))
|
|
144
|
+
return false;
|
|
145
|
+
return existsSync(join(this.cacheDir, filename));
|
|
146
|
+
}
|
|
147
|
+
/** 获取某个 modelUri 的下载进度(不存在则表示未在下载) */
|
|
148
|
+
getDownloadProgress(modelUri) {
|
|
149
|
+
return this.downloadingMap.get(modelUri) ?? null;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* 后台下载模型(通过 node-llama-cpp resolveModelFile)。
|
|
153
|
+
* 返回后立即响应,进度通过 getDownloadProgress 轮询获取。
|
|
154
|
+
*/
|
|
155
|
+
async startDownload(modelUri) {
|
|
156
|
+
if (this.downloadingMap.has(modelUri)) {
|
|
157
|
+
// 已在下载中,返回预期文件名
|
|
158
|
+
return { filename: modelUriToFilename(modelUri) };
|
|
159
|
+
}
|
|
160
|
+
this.downloadingMap.set(modelUri, { status: '准备下载...' });
|
|
161
|
+
// 后台异步执行,不 await
|
|
162
|
+
this.runDownload(modelUri).catch((e) => {
|
|
163
|
+
this.downloadingMap.set(modelUri, { status: `下载失败: ${e?.message ?? e}` });
|
|
164
|
+
});
|
|
165
|
+
return { filename: modelUriToFilename(modelUri) };
|
|
166
|
+
}
|
|
167
|
+
async runDownload(modelUri) {
|
|
168
|
+
try {
|
|
169
|
+
const { resolveModelFile } = await import('node-llama-cpp');
|
|
170
|
+
this.downloadingMap.set(modelUri, { status: '解析模型地址...' });
|
|
171
|
+
// 从环境变量读取 Hugging Face token(可选)
|
|
172
|
+
const hfToken = process.env.HF_TOKEN || process.env.HUGGING_FACE_TOKEN;
|
|
173
|
+
const options = {
|
|
174
|
+
directory: this.cacheDir,
|
|
175
|
+
onProgress: ({ downloadedSize, totalSize }) => {
|
|
176
|
+
const percent = totalSize ? Math.round((downloadedSize / totalSize) * 100) : 0;
|
|
177
|
+
this.downloadingMap.set(modelUri, {
|
|
178
|
+
status: '下载中',
|
|
179
|
+
completed: downloadedSize,
|
|
180
|
+
total: totalSize,
|
|
181
|
+
percent,
|
|
182
|
+
});
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
// 如果有 token,添加到请求头
|
|
186
|
+
if (hfToken) {
|
|
187
|
+
options.headers = { Authorization: `Bearer ${hfToken}` };
|
|
188
|
+
}
|
|
189
|
+
const resolved = await resolveModelFile(modelUri, options);
|
|
190
|
+
const filename = basename(resolved);
|
|
191
|
+
this.downloadingMap.set(modelUri, { status: `完成: ${filename}`, percent: 100 });
|
|
192
|
+
// 5 秒后清除进度记录
|
|
193
|
+
setTimeout(() => this.downloadingMap.delete(modelUri), 5000);
|
|
194
|
+
}
|
|
195
|
+
catch (e) {
|
|
196
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
197
|
+
let errorMsg = `失败: ${msg}`;
|
|
198
|
+
// 如果是 401 错误,提供更友好的提示
|
|
199
|
+
if (msg.includes('401') || msg.includes('Unauthorized')) {
|
|
200
|
+
errorMsg = '下载失败: 需要 Hugging Face Token。请设置环境变量 HF_TOKEN 或 HUGGING_FACE_TOKEN';
|
|
201
|
+
}
|
|
202
|
+
this.downloadingMap.set(modelUri, { status: errorMsg });
|
|
203
|
+
throw e;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
LocalModelsService = __decorate([
|
|
208
|
+
Injectable(),
|
|
209
|
+
__metadata("design:paramtypes", [])
|
|
210
|
+
], LocalModelsService);
|
|
211
|
+
export { LocalModelsService };
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "0.8.
|
|
6
|
+
"version": "0.8.48",
|
|
7
7
|
"description": "OpenClawX - A professional desktop application for managing and executing AI agents with real-time chat, session management, and skills browsing.",
|
|
8
8
|
"type": "module",
|
|
9
9
|
"main": "dist/index.js",
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"openbot": "./dist/cli/cli.js"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
+
"@instantlyeasy/claude-code-sdk-ts": "^0.3.3",
|
|
29
30
|
"@larksuiteoapi/node-sdk": "^1.59.0",
|
|
30
31
|
"@mariozechner/pi-ai": "^0.51.2",
|
|
31
32
|
"@mariozechner/pi-coding-agent": "^0.51.2",
|
|
@@ -41,6 +42,7 @@
|
|
|
41
42
|
"commander": "^14.0.2",
|
|
42
43
|
"croner": "^9.1.0",
|
|
43
44
|
"dingtalk-stream": "^2.1.4",
|
|
45
|
+
"duck-duck-scrape": "^2.2.7",
|
|
44
46
|
"multer": "^1.4.5-lts.1",
|
|
45
47
|
"node-llama-cpp": "^3.15.0",
|
|
46
48
|
"qrcode": "^1.5.4",
|