@lark-apaas/nestjs-capability 0.0.1-alpha.1 → 0.0.1-alpha.11
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 +123 -5
- package/dist/index.cjs +841 -145
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +339 -77
- package/dist/index.d.ts +339 -77
- package/dist/index.js +839 -154
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,11 +1,21 @@
|
|
|
1
1
|
var __defProp = Object.defineProperty;
|
|
2
2
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
|
|
4
|
+
// src/interfaces/error-codes.ts
|
|
5
|
+
var ErrorCodes = {
|
|
6
|
+
/** 成功 */
|
|
7
|
+
SUCCESS: "0",
|
|
8
|
+
/** 能力不存在 */
|
|
9
|
+
CAPABILITY_NOT_FOUND: "k_ec_cap_001",
|
|
10
|
+
/** 插件不存在 */
|
|
11
|
+
PLUGIN_NOT_FOUND: "k_ec_cap_002",
|
|
12
|
+
/** Action 不存在 */
|
|
13
|
+
ACTION_NOT_FOUND: "k_ec_cap_003",
|
|
14
|
+
/** 参数验证失败 */
|
|
15
|
+
PARAMS_VALIDATION_ERROR: "k_ec_cap_004",
|
|
16
|
+
/** 执行失败 */
|
|
17
|
+
EXECUTION_ERROR: "k_ec_cap_005"
|
|
18
|
+
};
|
|
9
19
|
|
|
10
20
|
// src/services/template-engine.service.ts
|
|
11
21
|
import { Injectable } from "@nestjs/common";
|
|
@@ -20,7 +30,10 @@ var TemplateEngineService = class {
|
|
|
20
30
|
static {
|
|
21
31
|
__name(this, "TemplateEngineService");
|
|
22
32
|
}
|
|
23
|
-
|
|
33
|
+
// 匹配 {{input.xxx}} 或 {{input.xxx.yyy}} 表达式
|
|
34
|
+
EXPR_REGEX = /\{\{input\.([a-zA-Z_][a-zA-Z_0-9]*(?:\.[a-zA-Z_][a-zA-Z_0-9]*)*)\}\}/g;
|
|
35
|
+
// 匹配整串单个表达式
|
|
36
|
+
WHOLE_STRING_EXPR_REGEX = /^\{\{input\.([a-zA-Z_][a-zA-Z_0-9]*(?:\.[a-zA-Z_][a-zA-Z_0-9]*)*)\}\}$/;
|
|
24
37
|
resolve(template, input) {
|
|
25
38
|
if (typeof template === "string") {
|
|
26
39
|
return this.resolveString(template, input);
|
|
@@ -34,12 +47,25 @@ var TemplateEngineService = class {
|
|
|
34
47
|
return template;
|
|
35
48
|
}
|
|
36
49
|
resolveString(template, input) {
|
|
37
|
-
const
|
|
38
|
-
if (
|
|
50
|
+
const wholeMatch = template.match(this.WHOLE_STRING_EXPR_REGEX);
|
|
51
|
+
if (wholeMatch) {
|
|
52
|
+
const path2 = wholeMatch[1];
|
|
53
|
+
const value = this.getValueByPath(input, path2);
|
|
54
|
+
return value !== void 0 ? value : template;
|
|
55
|
+
}
|
|
56
|
+
this.EXPR_REGEX.lastIndex = 0;
|
|
57
|
+
if (!this.EXPR_REGEX.test(template)) {
|
|
39
58
|
return template;
|
|
40
59
|
}
|
|
41
|
-
|
|
42
|
-
|
|
60
|
+
this.EXPR_REGEX.lastIndex = 0;
|
|
61
|
+
const result = template.replace(this.EXPR_REGEX, (match, path2) => {
|
|
62
|
+
const value = this.getValueByPath(input, path2);
|
|
63
|
+
if (value === void 0) {
|
|
64
|
+
return match;
|
|
65
|
+
}
|
|
66
|
+
return String(value);
|
|
67
|
+
});
|
|
68
|
+
return result;
|
|
43
69
|
}
|
|
44
70
|
resolveObject(template, input) {
|
|
45
71
|
const result = {};
|
|
@@ -66,6 +92,8 @@ TemplateEngineService = _ts_decorate([
|
|
|
66
92
|
|
|
67
93
|
// src/services/plugin-loader.service.ts
|
|
68
94
|
import { Injectable as Injectable2, Logger } from "@nestjs/common";
|
|
95
|
+
import { createRequire } from "module";
|
|
96
|
+
import { pathToFileURL } from "url";
|
|
69
97
|
function _ts_decorate2(decorators, target, key, desc) {
|
|
70
98
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
71
99
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
@@ -73,12 +101,13 @@ function _ts_decorate2(decorators, target, key, desc) {
|
|
|
73
101
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
74
102
|
}
|
|
75
103
|
__name(_ts_decorate2, "_ts_decorate");
|
|
104
|
+
var require2 = createRequire(import.meta.url);
|
|
76
105
|
var PluginNotFoundError = class extends Error {
|
|
77
106
|
static {
|
|
78
107
|
__name(this, "PluginNotFoundError");
|
|
79
108
|
}
|
|
80
|
-
constructor(
|
|
81
|
-
super(`Plugin not found: ${
|
|
109
|
+
constructor(pluginKey) {
|
|
110
|
+
super(`Plugin not found: ${pluginKey}`);
|
|
82
111
|
this.name = "PluginNotFoundError";
|
|
83
112
|
}
|
|
84
113
|
};
|
|
@@ -86,8 +115,8 @@ var PluginLoadError = class extends Error {
|
|
|
86
115
|
static {
|
|
87
116
|
__name(this, "PluginLoadError");
|
|
88
117
|
}
|
|
89
|
-
constructor(
|
|
90
|
-
super(`Failed to load plugin ${
|
|
118
|
+
constructor(pluginKey, reason) {
|
|
119
|
+
super(`Failed to load plugin ${pluginKey}: ${reason}`);
|
|
91
120
|
this.name = "PluginLoadError";
|
|
92
121
|
}
|
|
93
122
|
};
|
|
@@ -97,46 +126,100 @@ var PluginLoaderService = class _PluginLoaderService {
|
|
|
97
126
|
}
|
|
98
127
|
logger = new Logger(_PluginLoaderService.name);
|
|
99
128
|
pluginInstances = /* @__PURE__ */ new Map();
|
|
100
|
-
|
|
101
|
-
|
|
129
|
+
/** 记录每个插件的加载版本(时间戳),用于 ESM 缓存绕过 */
|
|
130
|
+
pluginVersions = /* @__PURE__ */ new Map();
|
|
131
|
+
async loadPlugin(pluginKey) {
|
|
132
|
+
const cached = this.pluginInstances.get(pluginKey);
|
|
102
133
|
if (cached) {
|
|
103
|
-
this.logger.debug(`Using cached plugin instance: ${
|
|
134
|
+
this.logger.debug(`Using cached plugin instance: ${pluginKey}`);
|
|
104
135
|
return cached;
|
|
105
136
|
}
|
|
106
|
-
this.logger.log(`Loading plugin: ${
|
|
137
|
+
this.logger.log(`Loading plugin: ${pluginKey}`);
|
|
107
138
|
try {
|
|
108
|
-
const
|
|
139
|
+
const resolvedPath = require2.resolve(pluginKey);
|
|
140
|
+
const version = this.pluginVersions.get(pluginKey) ?? 0;
|
|
141
|
+
const fileUrl = pathToFileURL(resolvedPath).href;
|
|
142
|
+
const importPath = version > 0 ? `${fileUrl}?v=${version}` : fileUrl;
|
|
143
|
+
const mod = await import(importPath);
|
|
144
|
+
const pluginPackage = mod.default ?? mod;
|
|
109
145
|
if (typeof pluginPackage.create !== "function") {
|
|
110
|
-
throw new PluginLoadError(
|
|
146
|
+
throw new PluginLoadError(pluginKey, "Plugin does not export create() function");
|
|
111
147
|
}
|
|
112
148
|
const instance = pluginPackage.create();
|
|
113
|
-
this.pluginInstances.set(
|
|
114
|
-
this.logger.log(`Plugin loaded successfully: ${
|
|
149
|
+
this.pluginInstances.set(pluginKey, instance);
|
|
150
|
+
this.logger.log(`Plugin loaded successfully: ${pluginKey}`);
|
|
115
151
|
return instance;
|
|
116
152
|
} catch (error) {
|
|
117
153
|
if (error.code === "MODULE_NOT_FOUND") {
|
|
118
|
-
throw new PluginNotFoundError(
|
|
154
|
+
throw new PluginNotFoundError(pluginKey);
|
|
119
155
|
}
|
|
120
|
-
throw new PluginLoadError(
|
|
156
|
+
throw new PluginLoadError(pluginKey, error instanceof Error ? error.message : String(error));
|
|
121
157
|
}
|
|
122
158
|
}
|
|
123
|
-
isPluginInstalled(
|
|
159
|
+
isPluginInstalled(pluginKey) {
|
|
124
160
|
try {
|
|
125
|
-
|
|
161
|
+
require2.resolve(pluginKey);
|
|
126
162
|
return true;
|
|
127
163
|
} catch {
|
|
128
164
|
return false;
|
|
129
165
|
}
|
|
130
166
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
167
|
+
/**
|
|
168
|
+
* 清除插件缓存
|
|
169
|
+
* - 清除应用层 pluginInstances 缓存
|
|
170
|
+
* - 清除 Node.js CJS 模块缓存(require.cache)
|
|
171
|
+
* - 更新版本号,下次 import 时绕过 ESM 缓存
|
|
172
|
+
* @param pluginKey - 插件标识,不传则清除所有
|
|
173
|
+
*/
|
|
174
|
+
clearCache(pluginKey) {
|
|
175
|
+
if (pluginKey) {
|
|
176
|
+
this.pluginInstances.delete(pluginKey);
|
|
177
|
+
this.clearNodeModuleCache(pluginKey);
|
|
178
|
+
this.pluginVersions.set(pluginKey, Date.now());
|
|
179
|
+
this.logger.log(`Cleared cache for plugin: ${pluginKey}`);
|
|
135
180
|
} else {
|
|
181
|
+
for (const key of this.pluginInstances.keys()) {
|
|
182
|
+
this.clearNodeModuleCache(key);
|
|
183
|
+
this.pluginVersions.set(key, Date.now());
|
|
184
|
+
}
|
|
136
185
|
this.pluginInstances.clear();
|
|
137
186
|
this.logger.log("Cleared all plugin caches");
|
|
138
187
|
}
|
|
139
188
|
}
|
|
189
|
+
/**
|
|
190
|
+
* 清除 CJS 模块缓存
|
|
191
|
+
*/
|
|
192
|
+
clearNodeModuleCache(pluginKey) {
|
|
193
|
+
try {
|
|
194
|
+
const resolvedPath = require2.resolve(pluginKey);
|
|
195
|
+
const mod = require2.cache[resolvedPath];
|
|
196
|
+
if (mod) {
|
|
197
|
+
this.clearModuleAndChildren(mod, pluginKey);
|
|
198
|
+
}
|
|
199
|
+
delete require2.cache[resolvedPath];
|
|
200
|
+
} catch {
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* 递归清除子模块缓存
|
|
205
|
+
*/
|
|
206
|
+
clearModuleAndChildren(mod, pluginKey) {
|
|
207
|
+
const pluginScope = pluginKey.startsWith("@") ? pluginKey.split("/").slice(0, 2).join("/") : pluginKey;
|
|
208
|
+
mod.children.forEach((child) => {
|
|
209
|
+
if (child.filename.includes(`node_modules/${pluginScope}`)) {
|
|
210
|
+
this.clearModuleAndChildren(child, pluginKey);
|
|
211
|
+
delete require2.cache[child.filename];
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* 强制重新加载插件
|
|
217
|
+
* @param pluginKey - 插件标识
|
|
218
|
+
*/
|
|
219
|
+
async reloadPlugin(pluginKey) {
|
|
220
|
+
this.clearCache(pluginKey);
|
|
221
|
+
return this.loadPlugin(pluginKey);
|
|
222
|
+
}
|
|
140
223
|
};
|
|
141
224
|
PluginLoaderService = _ts_decorate2([
|
|
142
225
|
Injectable2()
|
|
@@ -147,6 +230,29 @@ import { Injectable as Injectable3, Logger as Logger2, Inject } from "@nestjs/co
|
|
|
147
230
|
import { RequestContextService, PLATFORM_HTTP_CLIENT } from "@lark-apaas/nestjs-common";
|
|
148
231
|
import * as fs from "fs";
|
|
149
232
|
import * as path from "path";
|
|
233
|
+
import * as chokidar from "chokidar";
|
|
234
|
+
|
|
235
|
+
// src/utils/log-utils.ts
|
|
236
|
+
var DEFAULT_MAX_LENGTH = 1e3;
|
|
237
|
+
function truncateString(str, maxLength) {
|
|
238
|
+
if (str.length <= maxLength) {
|
|
239
|
+
return str;
|
|
240
|
+
}
|
|
241
|
+
return str.slice(0, maxLength) + `... [truncated, total ${str.length} chars]`;
|
|
242
|
+
}
|
|
243
|
+
__name(truncateString, "truncateString");
|
|
244
|
+
function stringifyForLog(value, maxLength = DEFAULT_MAX_LENGTH) {
|
|
245
|
+
try {
|
|
246
|
+
const str = JSON.stringify(value, null, 2);
|
|
247
|
+
return truncateString(str, maxLength);
|
|
248
|
+
} catch {
|
|
249
|
+
const str = String(value);
|
|
250
|
+
return truncateString(str, maxLength);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
__name(stringifyForLog, "stringifyForLog");
|
|
254
|
+
|
|
255
|
+
// src/services/capability.service.ts
|
|
150
256
|
function _ts_decorate3(decorators, target, key, desc) {
|
|
151
257
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
152
258
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
@@ -177,8 +283,8 @@ var ActionNotFoundError = class extends Error {
|
|
|
177
283
|
static {
|
|
178
284
|
__name(this, "ActionNotFoundError");
|
|
179
285
|
}
|
|
180
|
-
constructor(
|
|
181
|
-
super(`Action '${actionName}' not found in plugin ${
|
|
286
|
+
constructor(pluginKey, actionName) {
|
|
287
|
+
super(`Action '${actionName}' not found in plugin ${pluginKey}`);
|
|
182
288
|
this.name = "ActionNotFoundError";
|
|
183
289
|
}
|
|
184
290
|
};
|
|
@@ -187,25 +293,150 @@ var CapabilityService = class _CapabilityService {
|
|
|
187
293
|
__name(this, "CapabilityService");
|
|
188
294
|
}
|
|
189
295
|
requestContextService;
|
|
190
|
-
|
|
296
|
+
platformHttpClient;
|
|
191
297
|
pluginLoaderService;
|
|
192
298
|
templateEngineService;
|
|
193
299
|
logger = new Logger2(_CapabilityService.name);
|
|
194
300
|
capabilities = /* @__PURE__ */ new Map();
|
|
301
|
+
/** 文件路径到 capability id 的映射,用于文件删除时查找 */
|
|
302
|
+
filePathToId = /* @__PURE__ */ new Map();
|
|
195
303
|
capabilitiesDir;
|
|
196
|
-
|
|
304
|
+
fileWatcher = null;
|
|
305
|
+
options = {};
|
|
306
|
+
constructor(requestContextService, platformHttpClient, pluginLoaderService, templateEngineService) {
|
|
197
307
|
this.requestContextService = requestContextService;
|
|
198
|
-
this.
|
|
308
|
+
this.platformHttpClient = platformHttpClient;
|
|
199
309
|
this.pluginLoaderService = pluginLoaderService;
|
|
200
310
|
this.templateEngineService = templateEngineService;
|
|
201
|
-
|
|
311
|
+
const isDev = process.env.NODE_ENV === "development";
|
|
312
|
+
this.capabilitiesDir = path.join(process.cwd(), isDev ? "server/capabilities" : "capabilities");
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* 设置模块配置
|
|
316
|
+
*/
|
|
317
|
+
setOptions(options) {
|
|
318
|
+
this.options = options;
|
|
319
|
+
if (options.capabilitiesDir) {
|
|
320
|
+
this.capabilitiesDir = options.capabilitiesDir;
|
|
321
|
+
}
|
|
202
322
|
}
|
|
203
323
|
setCapabilitiesDir(dir) {
|
|
204
324
|
this.capabilitiesDir = dir;
|
|
205
325
|
}
|
|
206
326
|
async onModuleInit() {
|
|
207
327
|
await this.loadCapabilities();
|
|
328
|
+
if (this.options.enableWatching) {
|
|
329
|
+
this.startWatching();
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
async onModuleDestroy() {
|
|
333
|
+
this.stopWatching();
|
|
334
|
+
}
|
|
335
|
+
// ==================== 文件监听相关方法 ====================
|
|
336
|
+
/**
|
|
337
|
+
* 启动文件监听(沙箱环境自动调用)
|
|
338
|
+
*/
|
|
339
|
+
startWatching() {
|
|
340
|
+
if (this.fileWatcher) {
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
if (!fs.existsSync(this.capabilitiesDir)) {
|
|
344
|
+
this.logger.warn(`Cannot start watching: directory not found: ${this.capabilitiesDir}`);
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
const pattern = path.join(this.capabilitiesDir, "*.json");
|
|
348
|
+
const debounce = this.options.watchDebounce ?? 300;
|
|
349
|
+
this.logger.log(`Starting file watcher: ${pattern}`);
|
|
350
|
+
this.fileWatcher = chokidar.watch(pattern, {
|
|
351
|
+
persistent: true,
|
|
352
|
+
ignoreInitial: true,
|
|
353
|
+
awaitWriteFinish: {
|
|
354
|
+
// 等待写入完成
|
|
355
|
+
stabilityThreshold: debounce,
|
|
356
|
+
pollInterval: 100
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
this.fileWatcher.on("add", (filePath) => this.handleFileAdd(filePath)).on("change", (filePath) => this.handleFileChange(filePath)).on("unlink", (filePath) => this.handleFileUnlink(filePath)).on("error", (error) => this.logger.error("File watcher error:", error));
|
|
360
|
+
this.logger.log("File watcher started");
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* 停止文件监听
|
|
364
|
+
*/
|
|
365
|
+
stopWatching() {
|
|
366
|
+
if (this.fileWatcher) {
|
|
367
|
+
this.fileWatcher.close();
|
|
368
|
+
this.fileWatcher = null;
|
|
369
|
+
this.logger.log("File watcher stopped");
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* 重新加载所有能力配置
|
|
374
|
+
*/
|
|
375
|
+
async reloadAllCapabilities() {
|
|
376
|
+
this.capabilities.clear();
|
|
377
|
+
this.filePathToId.clear();
|
|
378
|
+
await this.loadCapabilities();
|
|
379
|
+
}
|
|
380
|
+
handleFileAdd(filePath) {
|
|
381
|
+
this.logger.log(`Capability file added: ${filePath}`);
|
|
382
|
+
try {
|
|
383
|
+
this.loadCapabilityFromFile(filePath);
|
|
384
|
+
} catch (error) {
|
|
385
|
+
this.logger.error(`Failed to load new capability: ${filePath}`, error);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
handleFileChange(filePath) {
|
|
389
|
+
this.logger.log(`Capability file changed: ${filePath}`);
|
|
390
|
+
try {
|
|
391
|
+
this.reloadCapabilityFromFile(filePath);
|
|
392
|
+
} catch (error) {
|
|
393
|
+
this.logger.error(`Failed to reload capability: ${filePath}`, error);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
handleFileUnlink(filePath) {
|
|
397
|
+
this.logger.log(`Capability file removed: ${filePath}`);
|
|
398
|
+
this.removeCapabilityByFile(filePath);
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* 从文件加载单个能力配置
|
|
402
|
+
*/
|
|
403
|
+
loadCapabilityFromFile(filePath) {
|
|
404
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
405
|
+
const config = JSON.parse(content);
|
|
406
|
+
if (!config.id) {
|
|
407
|
+
this.logger.warn(`Skipping capability without id: ${filePath}`);
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
this.capabilities.set(config.id, config);
|
|
411
|
+
this.filePathToId.set(filePath, config.id);
|
|
412
|
+
this.logger.log(`Loaded capability: ${config.id} (${config.name})`);
|
|
208
413
|
}
|
|
414
|
+
/**
|
|
415
|
+
* 重新加载单个能力配置
|
|
416
|
+
*/
|
|
417
|
+
reloadCapabilityFromFile(filePath) {
|
|
418
|
+
const oldId = this.filePathToId.get(filePath);
|
|
419
|
+
if (oldId) {
|
|
420
|
+
const oldConfig = this.capabilities.get(oldId);
|
|
421
|
+
if (oldConfig) {
|
|
422
|
+
this.pluginLoaderService.clearCache(oldConfig.pluginKey);
|
|
423
|
+
}
|
|
424
|
+
this.capabilities.delete(oldId);
|
|
425
|
+
}
|
|
426
|
+
this.loadCapabilityFromFile(filePath);
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* 根据文件路径移除能力配置
|
|
430
|
+
*/
|
|
431
|
+
removeCapabilityByFile(filePath) {
|
|
432
|
+
const capabilityId = this.filePathToId.get(filePath);
|
|
433
|
+
if (capabilityId) {
|
|
434
|
+
this.capabilities.delete(capabilityId);
|
|
435
|
+
this.filePathToId.delete(filePath);
|
|
436
|
+
this.logger.log(`Removed capability: ${capabilityId}`);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
// ==================== 原有方法 ====================
|
|
209
440
|
async loadCapabilities() {
|
|
210
441
|
this.logger.log(`Loading capabilities from ${this.capabilitiesDir}`);
|
|
211
442
|
if (!fs.existsSync(this.capabilitiesDir)) {
|
|
@@ -216,14 +447,7 @@ var CapabilityService = class _CapabilityService {
|
|
|
216
447
|
for (const file of files) {
|
|
217
448
|
try {
|
|
218
449
|
const filePath = path.join(this.capabilitiesDir, file);
|
|
219
|
-
|
|
220
|
-
const config = JSON.parse(content);
|
|
221
|
-
if (!config.id) {
|
|
222
|
-
this.logger.warn(`Skipping capability without id: ${file}`);
|
|
223
|
-
continue;
|
|
224
|
-
}
|
|
225
|
-
this.capabilities.set(config.id, config);
|
|
226
|
-
this.logger.log(`Loaded capability: ${config.id} (${config.name})`);
|
|
450
|
+
this.loadCapabilityFromFile(filePath);
|
|
227
451
|
} catch (error) {
|
|
228
452
|
this.logger.error(`Failed to load capability from ${file}:`, error);
|
|
229
453
|
}
|
|
@@ -241,56 +465,246 @@ var CapabilityService = class _CapabilityService {
|
|
|
241
465
|
if (!config) {
|
|
242
466
|
throw new CapabilityNotFoundError(capabilityId);
|
|
243
467
|
}
|
|
468
|
+
return this.createExecutor(config);
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* 使用传入的配置加载能力执行器
|
|
472
|
+
* 用于 debug 场景,支持用户传入自定义配置
|
|
473
|
+
*/
|
|
474
|
+
loadWithConfig(config) {
|
|
475
|
+
return this.createExecutor(config);
|
|
476
|
+
}
|
|
477
|
+
createExecutor(config) {
|
|
244
478
|
return {
|
|
245
479
|
call: /* @__PURE__ */ __name(async (actionName, input, contextOverride) => {
|
|
246
|
-
return this.
|
|
247
|
-
}, "call")
|
|
480
|
+
return this.executeCall(config, actionName, input, contextOverride);
|
|
481
|
+
}, "call"),
|
|
482
|
+
callStream: /* @__PURE__ */ __name((actionName, input, contextOverride) => {
|
|
483
|
+
return this.executeCallStream(config, actionName, input, contextOverride);
|
|
484
|
+
}, "callStream"),
|
|
485
|
+
callStreamWithEvents: /* @__PURE__ */ __name((actionName, input, contextOverride) => {
|
|
486
|
+
return this.executeCallStreamWithEvents(config, actionName, input, contextOverride);
|
|
487
|
+
}, "callStreamWithEvents"),
|
|
488
|
+
isStream: /* @__PURE__ */ __name(async (actionName) => {
|
|
489
|
+
return this.checkIsStream(config, actionName);
|
|
490
|
+
}, "isStream")
|
|
248
491
|
};
|
|
249
492
|
}
|
|
250
|
-
|
|
493
|
+
/**
|
|
494
|
+
* 检查 action 是否为流式
|
|
495
|
+
*/
|
|
496
|
+
async checkIsStream(config, actionName) {
|
|
497
|
+
const pluginInstance = await this.pluginLoaderService.loadPlugin(config.pluginKey);
|
|
498
|
+
if (!pluginInstance.hasAction(actionName)) {
|
|
499
|
+
throw new ActionNotFoundError(config.pluginKey, actionName);
|
|
500
|
+
}
|
|
501
|
+
return pluginInstance.isStreamAction?.(actionName) ?? false;
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* 执行 capability(始终返回 Promise)
|
|
505
|
+
* - unary action: 直接返回结果
|
|
506
|
+
* - stream action: 内部聚合所有 chunk 后返回
|
|
507
|
+
*/
|
|
508
|
+
async executeCall(config, actionName, input, contextOverride) {
|
|
251
509
|
const startTime = Date.now();
|
|
510
|
+
const loggerContext = {
|
|
511
|
+
capability_id: config.id,
|
|
512
|
+
plugin_key: config.pluginKey,
|
|
513
|
+
action: actionName
|
|
514
|
+
};
|
|
252
515
|
try {
|
|
253
|
-
const pluginInstance = await this.pluginLoaderService.loadPlugin(config.
|
|
516
|
+
const pluginInstance = await this.pluginLoaderService.loadPlugin(config.pluginKey);
|
|
254
517
|
if (!pluginInstance.hasAction(actionName)) {
|
|
255
|
-
throw new ActionNotFoundError(config.
|
|
518
|
+
throw new ActionNotFoundError(config.pluginKey, actionName);
|
|
256
519
|
}
|
|
257
|
-
const resolvedParams = this.templateEngineService.resolve(config.formValue, input);
|
|
520
|
+
const resolvedParams = config.formValue ? this.templateEngineService.resolve(config.formValue, input) : input;
|
|
258
521
|
const context = this.buildActionContext(contextOverride);
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
522
|
+
const isStream = pluginInstance.isStreamAction?.(actionName) ?? false;
|
|
523
|
+
this.logger.log("Executing capability (call)", {
|
|
524
|
+
...loggerContext,
|
|
525
|
+
is_stream: isStream,
|
|
526
|
+
input: stringifyForLog(input)
|
|
264
527
|
});
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
528
|
+
let result;
|
|
529
|
+
if (isStream && pluginInstance.runStream) {
|
|
530
|
+
const chunks = [];
|
|
531
|
+
for await (const chunk of pluginInstance.runStream(actionName, context, resolvedParams)) {
|
|
532
|
+
chunks.push(chunk);
|
|
533
|
+
}
|
|
534
|
+
result = pluginInstance.aggregate ? pluginInstance.aggregate(actionName, chunks) : chunks;
|
|
535
|
+
} else {
|
|
536
|
+
result = await pluginInstance.run(actionName, context, resolvedParams);
|
|
537
|
+
}
|
|
538
|
+
this.logger.log("Capability (call) executed successfully", {
|
|
539
|
+
...loggerContext,
|
|
540
|
+
duration_ms: Date.now() - startTime,
|
|
541
|
+
output: stringifyForLog(result)
|
|
271
542
|
});
|
|
272
543
|
return result;
|
|
273
544
|
} catch (error) {
|
|
274
|
-
this.logger.error({
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
545
|
+
this.logger.error("Capability (call) execution failed", {
|
|
546
|
+
...loggerContext,
|
|
547
|
+
duration_ms: Date.now() - startTime,
|
|
548
|
+
error: error instanceof Error ? error.message : String(error)
|
|
549
|
+
});
|
|
550
|
+
throw error;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* 流式执行 capability
|
|
555
|
+
* - stream action: 返回原始 AsyncIterable
|
|
556
|
+
* - unary action: 包装为单次 yield
|
|
557
|
+
*/
|
|
558
|
+
async *executeCallStream(config, actionName, input, contextOverride) {
|
|
559
|
+
const startTime = Date.now();
|
|
560
|
+
const loggerContext = {
|
|
561
|
+
capability_id: config.id,
|
|
562
|
+
plugin_key: config.pluginKey,
|
|
563
|
+
action: actionName
|
|
564
|
+
};
|
|
565
|
+
const chunks = [];
|
|
566
|
+
try {
|
|
567
|
+
const pluginInstance = await this.pluginLoaderService.loadPlugin(config.pluginKey);
|
|
568
|
+
if (!pluginInstance.hasAction(actionName)) {
|
|
569
|
+
throw new ActionNotFoundError(config.pluginKey, actionName);
|
|
570
|
+
}
|
|
571
|
+
const resolvedParams = config.formValue ? this.templateEngineService.resolve(config.formValue, input) : input;
|
|
572
|
+
const context = this.buildActionContext(contextOverride);
|
|
573
|
+
const isStream = pluginInstance.isStreamAction?.(actionName) ?? false;
|
|
574
|
+
this.logger.log("Executing capability (stream)", {
|
|
575
|
+
...loggerContext,
|
|
576
|
+
is_stream: isStream,
|
|
577
|
+
input: stringifyForLog(input)
|
|
578
|
+
});
|
|
579
|
+
if (isStream && pluginInstance.runStream) {
|
|
580
|
+
for await (const chunk of pluginInstance.runStream(actionName, context, resolvedParams)) {
|
|
581
|
+
chunks.push(chunk);
|
|
582
|
+
yield chunk;
|
|
583
|
+
}
|
|
584
|
+
} else {
|
|
585
|
+
const result = await pluginInstance.run(actionName, context, resolvedParams);
|
|
586
|
+
chunks.push(result);
|
|
587
|
+
yield result;
|
|
588
|
+
}
|
|
589
|
+
const aggregatedResult = pluginInstance.aggregate ? pluginInstance.aggregate(actionName, chunks) : chunks;
|
|
590
|
+
this.logger.log("Capability (stream) executed successfully", {
|
|
591
|
+
...loggerContext,
|
|
592
|
+
duration_ms: Date.now() - startTime,
|
|
593
|
+
output: stringifyForLog(aggregatedResult)
|
|
594
|
+
});
|
|
595
|
+
} catch (error) {
|
|
596
|
+
this.logger.error("Capability (stream) execution failed", {
|
|
597
|
+
...loggerContext,
|
|
598
|
+
duration_ms: Date.now() - startTime,
|
|
599
|
+
error: error instanceof Error ? error.message : String(error)
|
|
280
600
|
});
|
|
281
601
|
throw error;
|
|
282
602
|
}
|
|
283
603
|
}
|
|
604
|
+
/**
|
|
605
|
+
* 流式执行 capability,返回带事件协议的流
|
|
606
|
+
* - 优先使用 pluginInstance.runStreamWithEvents
|
|
607
|
+
* - 如果插件不支持,则包装 runStream/run 为 StreamEvent
|
|
608
|
+
*/
|
|
609
|
+
async *executeCallStreamWithEvents(config, actionName, input, contextOverride) {
|
|
610
|
+
const startTime = Date.now();
|
|
611
|
+
const loggerContext = {
|
|
612
|
+
capability_id: config.id,
|
|
613
|
+
plugin_key: config.pluginKey,
|
|
614
|
+
action: actionName
|
|
615
|
+
};
|
|
616
|
+
const chunks = [];
|
|
617
|
+
try {
|
|
618
|
+
const pluginInstance = await this.pluginLoaderService.loadPlugin(config.pluginKey);
|
|
619
|
+
if (!pluginInstance.hasAction(actionName)) {
|
|
620
|
+
throw new ActionNotFoundError(config.pluginKey, actionName);
|
|
621
|
+
}
|
|
622
|
+
const resolvedParams = config.formValue ? this.templateEngineService.resolve(config.formValue, input) : input;
|
|
623
|
+
const context = this.buildActionContext(contextOverride);
|
|
624
|
+
const isStream = pluginInstance.isStreamAction?.(actionName) ?? false;
|
|
625
|
+
this.logger.log("Executing capability (streamWithEvents)", {
|
|
626
|
+
...loggerContext,
|
|
627
|
+
is_stream: isStream,
|
|
628
|
+
input: stringifyForLog(input)
|
|
629
|
+
});
|
|
630
|
+
if (pluginInstance.runStreamWithEvents) {
|
|
631
|
+
for await (const event of pluginInstance.runStreamWithEvents(actionName, context, resolvedParams)) {
|
|
632
|
+
if (event.type === "data") {
|
|
633
|
+
chunks.push(event.data);
|
|
634
|
+
yield event;
|
|
635
|
+
} else if (event.type === "done") {
|
|
636
|
+
const aggregatedResult2 = pluginInstance.aggregate ? pluginInstance.aggregate(actionName, chunks) : chunks;
|
|
637
|
+
yield {
|
|
638
|
+
type: "done",
|
|
639
|
+
metadata: {
|
|
640
|
+
...event.metadata,
|
|
641
|
+
aggregated: aggregatedResult2
|
|
642
|
+
}
|
|
643
|
+
};
|
|
644
|
+
} else {
|
|
645
|
+
yield event;
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
} else {
|
|
649
|
+
if (isStream && pluginInstance.runStream) {
|
|
650
|
+
for await (const chunk of pluginInstance.runStream(actionName, context, resolvedParams)) {
|
|
651
|
+
chunks.push(chunk);
|
|
652
|
+
yield {
|
|
653
|
+
type: "data",
|
|
654
|
+
data: chunk
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
} else {
|
|
658
|
+
const result = await pluginInstance.run(actionName, context, resolvedParams);
|
|
659
|
+
chunks.push(result);
|
|
660
|
+
yield {
|
|
661
|
+
type: "data",
|
|
662
|
+
data: result
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
const aggregatedResult2 = pluginInstance.aggregate ? pluginInstance.aggregate(actionName, chunks) : chunks;
|
|
666
|
+
yield {
|
|
667
|
+
type: "done",
|
|
668
|
+
metadata: {
|
|
669
|
+
chunks: chunks.length,
|
|
670
|
+
duration: Date.now() - startTime,
|
|
671
|
+
aggregated: aggregatedResult2
|
|
672
|
+
}
|
|
673
|
+
};
|
|
674
|
+
}
|
|
675
|
+
const aggregatedResult = pluginInstance.aggregate ? pluginInstance.aggregate(actionName, chunks) : chunks;
|
|
676
|
+
this.logger.log("Capability (streamWithEvents) executed successfully", {
|
|
677
|
+
...loggerContext,
|
|
678
|
+
duration_ms: Date.now() - startTime,
|
|
679
|
+
output: stringifyForLog(aggregatedResult)
|
|
680
|
+
});
|
|
681
|
+
} catch (error) {
|
|
682
|
+
this.logger.error("Capability (streamWithEvents) execution failed", {
|
|
683
|
+
...loggerContext,
|
|
684
|
+
duration_ms: Date.now() - startTime,
|
|
685
|
+
error: error instanceof Error ? error.message : String(error)
|
|
686
|
+
});
|
|
687
|
+
yield {
|
|
688
|
+
type: "error",
|
|
689
|
+
error: {
|
|
690
|
+
code: "EXECUTION_ERROR",
|
|
691
|
+
message: error instanceof Error ? error.message : String(error)
|
|
692
|
+
}
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
}
|
|
284
696
|
buildActionContext(override) {
|
|
285
697
|
return {
|
|
286
698
|
logger: this.logger,
|
|
287
|
-
|
|
288
|
-
userContext: override?.userContext ?? this.getUserContext()
|
|
699
|
+
platformHttpClient: this.platformHttpClient,
|
|
700
|
+
userContext: override?.userContext ?? this.getUserContext(),
|
|
701
|
+
isDebug: override?.isDebug ?? false
|
|
289
702
|
};
|
|
290
703
|
}
|
|
291
704
|
getUserContext() {
|
|
292
705
|
const ctx = this.requestContextService.getContext();
|
|
293
706
|
return {
|
|
707
|
+
appId: ctx?.appId ?? "",
|
|
294
708
|
userId: ctx?.userId ?? "",
|
|
295
709
|
tenantId: ctx?.tenantId ?? ""
|
|
296
710
|
};
|
|
@@ -309,7 +723,7 @@ CapabilityService = _ts_decorate3([
|
|
|
309
723
|
], CapabilityService);
|
|
310
724
|
|
|
311
725
|
// src/controllers/debug.controller.ts
|
|
312
|
-
import { Controller, Post, Get, Param, Body, HttpException, HttpStatus } from "@nestjs/common";
|
|
726
|
+
import { Controller, Post, Get, Param, Body, Res, HttpException, HttpStatus, Logger as Logger3 } from "@nestjs/common";
|
|
313
727
|
function _ts_decorate4(decorators, target, key, desc) {
|
|
314
728
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
315
729
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
@@ -327,31 +741,41 @@ function _ts_param2(paramIndex, decorator) {
|
|
|
327
741
|
};
|
|
328
742
|
}
|
|
329
743
|
__name(_ts_param2, "_ts_param");
|
|
330
|
-
var DebugController = class {
|
|
744
|
+
var DebugController = class _DebugController {
|
|
331
745
|
static {
|
|
332
746
|
__name(this, "DebugController");
|
|
333
747
|
}
|
|
334
748
|
capabilityService;
|
|
749
|
+
pluginLoaderService;
|
|
335
750
|
templateEngineService;
|
|
336
|
-
|
|
751
|
+
logger = new Logger3(_DebugController.name);
|
|
752
|
+
constructor(capabilityService, pluginLoaderService, templateEngineService) {
|
|
337
753
|
this.capabilityService = capabilityService;
|
|
754
|
+
this.pluginLoaderService = pluginLoaderService;
|
|
338
755
|
this.templateEngineService = templateEngineService;
|
|
339
756
|
}
|
|
340
757
|
list() {
|
|
341
758
|
const capabilities = this.capabilityService.listCapabilities();
|
|
342
759
|
return {
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
760
|
+
status_code: ErrorCodes.SUCCESS,
|
|
761
|
+
data: {
|
|
762
|
+
capabilities: capabilities.map((c) => ({
|
|
763
|
+
id: c.id,
|
|
764
|
+
name: c.name,
|
|
765
|
+
pluginID: c.pluginKey,
|
|
766
|
+
pluginVersion: c.pluginVersion
|
|
767
|
+
}))
|
|
768
|
+
}
|
|
351
769
|
};
|
|
352
770
|
}
|
|
353
|
-
|
|
354
|
-
|
|
771
|
+
/**
|
|
772
|
+
* 获取 capability 配置
|
|
773
|
+
* 优先使用 body.capability,否则从服务获取
|
|
774
|
+
*/
|
|
775
|
+
getCapabilityConfig(capabilityId, bodyCapability) {
|
|
776
|
+
if (bodyCapability) {
|
|
777
|
+
return bodyCapability;
|
|
778
|
+
}
|
|
355
779
|
const config = this.capabilityService.getCapability(capabilityId);
|
|
356
780
|
if (!config) {
|
|
357
781
|
throw new HttpException({
|
|
@@ -360,72 +784,194 @@ var DebugController = class {
|
|
|
360
784
|
error: "CAPABILITY_NOT_FOUND"
|
|
361
785
|
}, HttpStatus.NOT_FOUND);
|
|
362
786
|
}
|
|
363
|
-
|
|
787
|
+
return config;
|
|
788
|
+
}
|
|
789
|
+
/**
|
|
790
|
+
* 获取 action 名称
|
|
791
|
+
* 优先使用传入的 action,否则使用插件第一个 action
|
|
792
|
+
*/
|
|
793
|
+
async getActionName(pluginKey, bodyAction) {
|
|
794
|
+
if (bodyAction) {
|
|
795
|
+
return bodyAction;
|
|
796
|
+
}
|
|
797
|
+
const pluginInstance = await this.pluginLoaderService.loadPlugin(pluginKey);
|
|
798
|
+
const actions = pluginInstance.listActions();
|
|
799
|
+
if (actions.length === 0) {
|
|
800
|
+
throw new HttpException({
|
|
801
|
+
code: 1,
|
|
802
|
+
message: `Plugin ${pluginKey} has no actions`,
|
|
803
|
+
error: "NO_ACTIONS"
|
|
804
|
+
}, HttpStatus.BAD_REQUEST);
|
|
805
|
+
}
|
|
806
|
+
return actions[0];
|
|
807
|
+
}
|
|
808
|
+
async debug(capabilityId, body) {
|
|
809
|
+
const startTime = Date.now();
|
|
810
|
+
const params = body.params ?? {};
|
|
811
|
+
const config = this.getCapabilityConfig(capabilityId, body.capability);
|
|
812
|
+
const action = await this.getActionName(config.pluginKey, body.action);
|
|
813
|
+
const resolvedParams = this.templateEngineService.resolve(config.formValue, params);
|
|
364
814
|
try {
|
|
365
|
-
const result = await this.capabilityService.
|
|
815
|
+
const result = await this.capabilityService.loadWithConfig(config).call(action, params, {
|
|
816
|
+
isDebug: true
|
|
817
|
+
});
|
|
366
818
|
return {
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
819
|
+
status_code: ErrorCodes.SUCCESS,
|
|
820
|
+
data: {
|
|
821
|
+
output: result,
|
|
822
|
+
debug: {
|
|
823
|
+
capabilityConfig: config,
|
|
824
|
+
resolvedParams,
|
|
825
|
+
duration: Date.now() - startTime,
|
|
826
|
+
pluginID: config.pluginKey,
|
|
827
|
+
action
|
|
828
|
+
}
|
|
375
829
|
}
|
|
376
830
|
};
|
|
377
831
|
} catch (error) {
|
|
378
|
-
const duration = Date.now() - startTime;
|
|
379
832
|
if (error instanceof CapabilityNotFoundError) {
|
|
380
833
|
throw new HttpException({
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
error: "CAPABILITY_NOT_FOUND",
|
|
384
|
-
debug: {
|
|
385
|
-
duration
|
|
386
|
-
}
|
|
834
|
+
status_code: ErrorCodes.CAPABILITY_NOT_FOUND,
|
|
835
|
+
error_msg: `Capability not found: ${capabilityId}`
|
|
387
836
|
}, HttpStatus.NOT_FOUND);
|
|
388
837
|
}
|
|
389
838
|
if (error instanceof PluginNotFoundError) {
|
|
390
839
|
throw new HttpException({
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
error: "PLUGIN_NOT_FOUND",
|
|
394
|
-
debug: {
|
|
395
|
-
duration,
|
|
396
|
-
pluginID: config.pluginID
|
|
397
|
-
}
|
|
840
|
+
status_code: ErrorCodes.PLUGIN_NOT_FOUND,
|
|
841
|
+
error_msg: `Plugin not found: ${config.pluginKey}`
|
|
398
842
|
}, HttpStatus.INTERNAL_SERVER_ERROR);
|
|
399
843
|
}
|
|
400
844
|
if (error instanceof ActionNotFoundError) {
|
|
401
845
|
throw new HttpException({
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
error: "ACTION_NOT_FOUND",
|
|
405
|
-
debug: {
|
|
406
|
-
duration,
|
|
407
|
-
pluginID: config.pluginID
|
|
408
|
-
}
|
|
846
|
+
status_code: ErrorCodes.ACTION_NOT_FOUND,
|
|
847
|
+
error_msg: `Action '${action}' not found in plugin ${config.pluginKey}`
|
|
409
848
|
}, HttpStatus.BAD_REQUEST);
|
|
410
849
|
}
|
|
411
850
|
throw new HttpException({
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
error: "EXECUTION_ERROR",
|
|
415
|
-
debug: {
|
|
416
|
-
duration,
|
|
417
|
-
pluginID: config.pluginID,
|
|
418
|
-
resolvedParams
|
|
419
|
-
}
|
|
851
|
+
status_code: ErrorCodes.EXECUTION_ERROR,
|
|
852
|
+
error_msg: `Execution failed: ${error instanceof Error ? error.message : String(error)}`
|
|
420
853
|
}, HttpStatus.INTERNAL_SERVER_ERROR);
|
|
421
854
|
}
|
|
422
855
|
}
|
|
856
|
+
async debugStream(capabilityId, body, res) {
|
|
857
|
+
const params = body.params ?? {};
|
|
858
|
+
res.setHeader("Content-Type", "text/event-stream");
|
|
859
|
+
res.setHeader("Cache-Control", "no-cache");
|
|
860
|
+
res.setHeader("Connection", "keep-alive");
|
|
861
|
+
try {
|
|
862
|
+
const config = this.getCapabilityConfig(capabilityId, body.capability);
|
|
863
|
+
const action = await this.getActionName(config.pluginKey, body.action);
|
|
864
|
+
const loggerContext = {
|
|
865
|
+
capability_id: config.id,
|
|
866
|
+
plugin_key: config.pluginKey,
|
|
867
|
+
action
|
|
868
|
+
};
|
|
869
|
+
this.logger.log(`Executing capability (stream)`, {
|
|
870
|
+
...loggerContext,
|
|
871
|
+
input: stringifyForLog(params)
|
|
872
|
+
});
|
|
873
|
+
const capability = this.capabilityService.loadWithConfig(config);
|
|
874
|
+
const eventStream = capability.callStreamWithEvents(action, params, {
|
|
875
|
+
isDebug: true
|
|
876
|
+
});
|
|
877
|
+
let pendingChunk = null;
|
|
878
|
+
for await (const event of eventStream) {
|
|
879
|
+
switch (event.type) {
|
|
880
|
+
case "data": {
|
|
881
|
+
if (pendingChunk !== null) {
|
|
882
|
+
const response = {
|
|
883
|
+
status_code: ErrorCodes.SUCCESS,
|
|
884
|
+
data: {
|
|
885
|
+
type: "content",
|
|
886
|
+
delta: pendingChunk
|
|
887
|
+
}
|
|
888
|
+
};
|
|
889
|
+
res.write(`data: ${JSON.stringify(response)}
|
|
890
|
+
|
|
891
|
+
`);
|
|
892
|
+
}
|
|
893
|
+
pendingChunk = event.data;
|
|
894
|
+
break;
|
|
895
|
+
}
|
|
896
|
+
case "done": {
|
|
897
|
+
if (pendingChunk !== null) {
|
|
898
|
+
const response = {
|
|
899
|
+
status_code: ErrorCodes.SUCCESS,
|
|
900
|
+
data: {
|
|
901
|
+
type: "content",
|
|
902
|
+
delta: pendingChunk,
|
|
903
|
+
finished: true
|
|
904
|
+
}
|
|
905
|
+
};
|
|
906
|
+
res.write(`data: ${JSON.stringify(response)}
|
|
907
|
+
|
|
908
|
+
`);
|
|
909
|
+
}
|
|
910
|
+
this.logger.log(`Capability (stream) executed successfully`, {
|
|
911
|
+
...loggerContext,
|
|
912
|
+
duration_ms: event.metadata.duration,
|
|
913
|
+
output: stringifyForLog(event.metadata.aggregated)
|
|
914
|
+
});
|
|
915
|
+
res.end();
|
|
916
|
+
return;
|
|
917
|
+
}
|
|
918
|
+
case "error": {
|
|
919
|
+
const response = {
|
|
920
|
+
status_code: ErrorCodes.SUCCESS,
|
|
921
|
+
data: {
|
|
922
|
+
type: "error",
|
|
923
|
+
error: {
|
|
924
|
+
code: 0,
|
|
925
|
+
message: event.error.message
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
};
|
|
929
|
+
res.write(`data: ${JSON.stringify(response)}
|
|
930
|
+
|
|
931
|
+
`);
|
|
932
|
+
res.end();
|
|
933
|
+
return;
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
if (pendingChunk !== null) {
|
|
938
|
+
const response = {
|
|
939
|
+
status_code: ErrorCodes.SUCCESS,
|
|
940
|
+
data: {
|
|
941
|
+
type: "content",
|
|
942
|
+
delta: pendingChunk,
|
|
943
|
+
finished: true
|
|
944
|
+
}
|
|
945
|
+
};
|
|
946
|
+
res.write(`data: ${JSON.stringify(response)}
|
|
947
|
+
|
|
948
|
+
`);
|
|
949
|
+
}
|
|
950
|
+
res.end();
|
|
951
|
+
} catch (error) {
|
|
952
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
953
|
+
const response = {
|
|
954
|
+
status_code: ErrorCodes.SUCCESS,
|
|
955
|
+
data: {
|
|
956
|
+
type: "error",
|
|
957
|
+
error: {
|
|
958
|
+
code: 0,
|
|
959
|
+
message: errorMsg
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
};
|
|
963
|
+
res.write(`data: ${JSON.stringify(response)}
|
|
964
|
+
|
|
965
|
+
`);
|
|
966
|
+
res.end();
|
|
967
|
+
}
|
|
968
|
+
}
|
|
423
969
|
};
|
|
424
970
|
_ts_decorate4([
|
|
425
971
|
Get("list"),
|
|
426
972
|
_ts_metadata2("design:type", Function),
|
|
427
973
|
_ts_metadata2("design:paramtypes", []),
|
|
428
|
-
_ts_metadata2("design:returntype", typeof
|
|
974
|
+
_ts_metadata2("design:returntype", typeof SuccessResponse === "undefined" ? Object : SuccessResponse)
|
|
429
975
|
], DebugController.prototype, "list", null);
|
|
430
976
|
_ts_decorate4([
|
|
431
977
|
Post("debug/:capability_id"),
|
|
@@ -434,21 +980,35 @@ _ts_decorate4([
|
|
|
434
980
|
_ts_metadata2("design:type", Function),
|
|
435
981
|
_ts_metadata2("design:paramtypes", [
|
|
436
982
|
String,
|
|
437
|
-
typeof
|
|
983
|
+
typeof DebugRequestBody === "undefined" ? Object : DebugRequestBody
|
|
438
984
|
]),
|
|
439
985
|
_ts_metadata2("design:returntype", Promise)
|
|
440
986
|
], DebugController.prototype, "debug", null);
|
|
987
|
+
_ts_decorate4([
|
|
988
|
+
Post("debug/:capability_id/stream"),
|
|
989
|
+
_ts_param2(0, Param("capability_id")),
|
|
990
|
+
_ts_param2(1, Body()),
|
|
991
|
+
_ts_param2(2, Res()),
|
|
992
|
+
_ts_metadata2("design:type", Function),
|
|
993
|
+
_ts_metadata2("design:paramtypes", [
|
|
994
|
+
String,
|
|
995
|
+
typeof DebugRequestBody === "undefined" ? Object : DebugRequestBody,
|
|
996
|
+
typeof Response === "undefined" ? Object : Response
|
|
997
|
+
]),
|
|
998
|
+
_ts_metadata2("design:returntype", Promise)
|
|
999
|
+
], DebugController.prototype, "debugStream", null);
|
|
441
1000
|
DebugController = _ts_decorate4([
|
|
442
1001
|
Controller("__innerapi__/capability"),
|
|
443
1002
|
_ts_metadata2("design:type", Function),
|
|
444
1003
|
_ts_metadata2("design:paramtypes", [
|
|
445
1004
|
typeof CapabilityService === "undefined" ? Object : CapabilityService,
|
|
1005
|
+
typeof PluginLoaderService === "undefined" ? Object : PluginLoaderService,
|
|
446
1006
|
typeof TemplateEngineService === "undefined" ? Object : TemplateEngineService
|
|
447
1007
|
])
|
|
448
1008
|
], DebugController);
|
|
449
1009
|
|
|
450
1010
|
// src/controllers/webhook.controller.ts
|
|
451
|
-
import { Controller as Controller2, Get as Get2, Post as Post2, Param as Param2, Body as Body2, HttpException as HttpException2, HttpStatus as HttpStatus2 } from "@nestjs/common";
|
|
1011
|
+
import { Controller as Controller2, Get as Get2, Post as Post2, Param as Param2, Body as Body2, Res as Res2, HttpException as HttpException2, HttpStatus as HttpStatus2, Logger as Logger4 } from "@nestjs/common";
|
|
452
1012
|
function _ts_decorate5(decorators, target, key, desc) {
|
|
453
1013
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
454
1014
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
@@ -466,71 +1026,176 @@ function _ts_param3(paramIndex, decorator) {
|
|
|
466
1026
|
};
|
|
467
1027
|
}
|
|
468
1028
|
__name(_ts_param3, "_ts_param");
|
|
469
|
-
var WebhookController = class {
|
|
1029
|
+
var WebhookController = class _WebhookController {
|
|
470
1030
|
static {
|
|
471
1031
|
__name(this, "WebhookController");
|
|
472
1032
|
}
|
|
473
1033
|
capabilityService;
|
|
1034
|
+
logger = new Logger4(_WebhookController.name);
|
|
474
1035
|
constructor(capabilityService) {
|
|
475
1036
|
this.capabilityService = capabilityService;
|
|
476
1037
|
}
|
|
477
1038
|
list() {
|
|
478
1039
|
const capabilities = this.capabilityService.listCapabilities();
|
|
479
1040
|
return {
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
}
|
|
1041
|
+
status_code: ErrorCodes.SUCCESS,
|
|
1042
|
+
data: {
|
|
1043
|
+
capabilities: capabilities.map((c) => ({
|
|
1044
|
+
id: c.id,
|
|
1045
|
+
name: c.name,
|
|
1046
|
+
pluginID: c.pluginKey,
|
|
1047
|
+
pluginVersion: c.pluginVersion
|
|
1048
|
+
}))
|
|
1049
|
+
}
|
|
489
1050
|
};
|
|
490
1051
|
}
|
|
491
1052
|
async execute(capabilityId, body) {
|
|
492
1053
|
try {
|
|
493
1054
|
const result = await this.capabilityService.load(capabilityId).call(body.action, body.params);
|
|
494
1055
|
return {
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
1056
|
+
status_code: ErrorCodes.SUCCESS,
|
|
1057
|
+
data: {
|
|
1058
|
+
output: result
|
|
1059
|
+
}
|
|
498
1060
|
};
|
|
499
1061
|
} catch (error) {
|
|
500
1062
|
if (error instanceof CapabilityNotFoundError) {
|
|
501
1063
|
throw new HttpException2({
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
error: "CAPABILITY_NOT_FOUND"
|
|
1064
|
+
status_code: ErrorCodes.CAPABILITY_NOT_FOUND,
|
|
1065
|
+
error_msg: `Capability not found: ${capabilityId}`
|
|
505
1066
|
}, HttpStatus2.NOT_FOUND);
|
|
506
1067
|
}
|
|
507
1068
|
if (error instanceof PluginNotFoundError) {
|
|
508
1069
|
throw new HttpException2({
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
error: "PLUGIN_NOT_FOUND"
|
|
1070
|
+
status_code: ErrorCodes.PLUGIN_NOT_FOUND,
|
|
1071
|
+
error_msg: `Plugin not found`
|
|
512
1072
|
}, HttpStatus2.INTERNAL_SERVER_ERROR);
|
|
513
1073
|
}
|
|
514
1074
|
if (error instanceof ActionNotFoundError) {
|
|
515
1075
|
throw new HttpException2({
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
error: "ACTION_NOT_FOUND"
|
|
1076
|
+
status_code: ErrorCodes.ACTION_NOT_FOUND,
|
|
1077
|
+
error_msg: `Action '${body.action}' not found`
|
|
519
1078
|
}, HttpStatus2.BAD_REQUEST);
|
|
520
1079
|
}
|
|
521
1080
|
throw new HttpException2({
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
error: "EXECUTION_ERROR"
|
|
1081
|
+
status_code: ErrorCodes.EXECUTION_ERROR,
|
|
1082
|
+
error_msg: `Execution failed: ${error instanceof Error ? error.message : String(error)}`
|
|
525
1083
|
}, HttpStatus2.INTERNAL_SERVER_ERROR);
|
|
526
1084
|
}
|
|
527
1085
|
}
|
|
1086
|
+
async executeStream(capabilityId, body, res) {
|
|
1087
|
+
const loggerContext = {
|
|
1088
|
+
capability_id: capabilityId,
|
|
1089
|
+
action: body.action
|
|
1090
|
+
};
|
|
1091
|
+
res.setHeader("Content-Type", "text/event-stream");
|
|
1092
|
+
res.setHeader("Cache-Control", "no-cache");
|
|
1093
|
+
res.setHeader("Connection", "keep-alive");
|
|
1094
|
+
try {
|
|
1095
|
+
this.logger.log(`Executing capability (stream)`, {
|
|
1096
|
+
...loggerContext,
|
|
1097
|
+
input: stringifyForLog(body.params)
|
|
1098
|
+
});
|
|
1099
|
+
const capability = this.capabilityService.load(capabilityId);
|
|
1100
|
+
const eventStream = capability.callStreamWithEvents(body.action, body.params);
|
|
1101
|
+
let pendingChunk = null;
|
|
1102
|
+
for await (const event of eventStream) {
|
|
1103
|
+
switch (event.type) {
|
|
1104
|
+
case "data": {
|
|
1105
|
+
if (pendingChunk !== null) {
|
|
1106
|
+
const response = {
|
|
1107
|
+
status_code: ErrorCodes.SUCCESS,
|
|
1108
|
+
data: {
|
|
1109
|
+
type: "content",
|
|
1110
|
+
delta: pendingChunk
|
|
1111
|
+
}
|
|
1112
|
+
};
|
|
1113
|
+
res.write(`data: ${JSON.stringify(response)}
|
|
1114
|
+
|
|
1115
|
+
`);
|
|
1116
|
+
}
|
|
1117
|
+
pendingChunk = event.data;
|
|
1118
|
+
break;
|
|
1119
|
+
}
|
|
1120
|
+
case "done": {
|
|
1121
|
+
if (pendingChunk !== null) {
|
|
1122
|
+
const response = {
|
|
1123
|
+
status_code: ErrorCodes.SUCCESS,
|
|
1124
|
+
data: {
|
|
1125
|
+
type: "content",
|
|
1126
|
+
delta: pendingChunk,
|
|
1127
|
+
finished: true
|
|
1128
|
+
}
|
|
1129
|
+
};
|
|
1130
|
+
res.write(`data: ${JSON.stringify(response)}
|
|
1131
|
+
|
|
1132
|
+
`);
|
|
1133
|
+
}
|
|
1134
|
+
this.logger.log(`Capability (stream) executed successfully`, {
|
|
1135
|
+
...loggerContext,
|
|
1136
|
+
duration_ms: event.metadata.duration,
|
|
1137
|
+
output: stringifyForLog(event.metadata.aggregated)
|
|
1138
|
+
});
|
|
1139
|
+
res.end();
|
|
1140
|
+
return;
|
|
1141
|
+
}
|
|
1142
|
+
case "error": {
|
|
1143
|
+
const response = {
|
|
1144
|
+
status_code: ErrorCodes.SUCCESS,
|
|
1145
|
+
data: {
|
|
1146
|
+
type: "error",
|
|
1147
|
+
error: {
|
|
1148
|
+
code: 0,
|
|
1149
|
+
message: event.error.message
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
};
|
|
1153
|
+
res.write(`data: ${JSON.stringify(response)}
|
|
1154
|
+
|
|
1155
|
+
`);
|
|
1156
|
+
res.end();
|
|
1157
|
+
return;
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
if (pendingChunk !== null) {
|
|
1162
|
+
const response = {
|
|
1163
|
+
status_code: ErrorCodes.SUCCESS,
|
|
1164
|
+
data: {
|
|
1165
|
+
type: "content",
|
|
1166
|
+
delta: pendingChunk,
|
|
1167
|
+
finished: true
|
|
1168
|
+
}
|
|
1169
|
+
};
|
|
1170
|
+
res.write(`data: ${JSON.stringify(response)}
|
|
1171
|
+
|
|
1172
|
+
`);
|
|
1173
|
+
}
|
|
1174
|
+
res.end();
|
|
1175
|
+
} catch (error) {
|
|
1176
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1177
|
+
const response = {
|
|
1178
|
+
status_code: ErrorCodes.SUCCESS,
|
|
1179
|
+
data: {
|
|
1180
|
+
type: "error",
|
|
1181
|
+
error: {
|
|
1182
|
+
code: 0,
|
|
1183
|
+
message: errorMsg
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
};
|
|
1187
|
+
res.write(`data: ${JSON.stringify(response)}
|
|
1188
|
+
|
|
1189
|
+
`);
|
|
1190
|
+
res.end();
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
528
1193
|
};
|
|
529
1194
|
_ts_decorate5([
|
|
530
1195
|
Get2("list"),
|
|
531
1196
|
_ts_metadata3("design:type", Function),
|
|
532
1197
|
_ts_metadata3("design:paramtypes", []),
|
|
533
|
-
_ts_metadata3("design:returntype", typeof
|
|
1198
|
+
_ts_metadata3("design:returntype", typeof SuccessResponse === "undefined" ? Object : SuccessResponse)
|
|
534
1199
|
], WebhookController.prototype, "list", null);
|
|
535
1200
|
_ts_decorate5([
|
|
536
1201
|
Post2(":capability_id"),
|
|
@@ -543,6 +1208,19 @@ _ts_decorate5([
|
|
|
543
1208
|
]),
|
|
544
1209
|
_ts_metadata3("design:returntype", Promise)
|
|
545
1210
|
], WebhookController.prototype, "execute", null);
|
|
1211
|
+
_ts_decorate5([
|
|
1212
|
+
Post2(":capability_id/stream"),
|
|
1213
|
+
_ts_param3(0, Param2("capability_id")),
|
|
1214
|
+
_ts_param3(1, Body2()),
|
|
1215
|
+
_ts_param3(2, Res2()),
|
|
1216
|
+
_ts_metadata3("design:type", Function),
|
|
1217
|
+
_ts_metadata3("design:paramtypes", [
|
|
1218
|
+
String,
|
|
1219
|
+
typeof ExecuteRequestBody === "undefined" ? Object : ExecuteRequestBody,
|
|
1220
|
+
typeof Response === "undefined" ? Object : Response
|
|
1221
|
+
]),
|
|
1222
|
+
_ts_metadata3("design:returntype", Promise)
|
|
1223
|
+
], WebhookController.prototype, "executeStream", null);
|
|
546
1224
|
WebhookController = _ts_decorate5([
|
|
547
1225
|
Controller2("api/capability"),
|
|
548
1226
|
_ts_metadata3("design:type", Function),
|
|
@@ -553,7 +1231,7 @@ WebhookController = _ts_decorate5([
|
|
|
553
1231
|
|
|
554
1232
|
// src/capability.module.ts
|
|
555
1233
|
import { Module } from "@nestjs/common";
|
|
556
|
-
import { RequestContextService as RequestContextService2, PLATFORM_HTTP_CLIENT as PLATFORM_HTTP_CLIENT2 } from "@lark-apaas/nestjs-common";
|
|
1234
|
+
import { CommonModule, RequestContextService as RequestContextService2, PLATFORM_HTTP_CLIENT as PLATFORM_HTTP_CLIENT2 } from "@lark-apaas/nestjs-common";
|
|
557
1235
|
function _ts_decorate6(decorators, target, key, desc) {
|
|
558
1236
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
559
1237
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
@@ -580,6 +1258,9 @@ var CapabilityModule = class _CapabilityModule {
|
|
|
580
1258
|
static forRoot(options) {
|
|
581
1259
|
return {
|
|
582
1260
|
module: _CapabilityModule,
|
|
1261
|
+
imports: [
|
|
1262
|
+
CommonModule
|
|
1263
|
+
],
|
|
583
1264
|
controllers: getControllers(),
|
|
584
1265
|
providers: [
|
|
585
1266
|
{
|
|
@@ -590,8 +1271,8 @@ var CapabilityModule = class _CapabilityModule {
|
|
|
590
1271
|
provide: CapabilityService,
|
|
591
1272
|
useFactory: /* @__PURE__ */ __name((requestContextService, httpClient, pluginLoader, templateEngine, moduleOptions) => {
|
|
592
1273
|
const service = new CapabilityService(requestContextService, httpClient, pluginLoader, templateEngine);
|
|
593
|
-
if (moduleOptions
|
|
594
|
-
service.
|
|
1274
|
+
if (moduleOptions) {
|
|
1275
|
+
service.setOptions(moduleOptions);
|
|
595
1276
|
}
|
|
596
1277
|
return service;
|
|
597
1278
|
}, "useFactory"),
|
|
@@ -614,6 +1295,9 @@ var CapabilityModule = class _CapabilityModule {
|
|
|
614
1295
|
};
|
|
615
1296
|
CapabilityModule = _ts_decorate6([
|
|
616
1297
|
Module({
|
|
1298
|
+
imports: [
|
|
1299
|
+
CommonModule
|
|
1300
|
+
],
|
|
617
1301
|
controllers: getControllers(),
|
|
618
1302
|
providers: [
|
|
619
1303
|
CapabilityService,
|
|
@@ -631,6 +1315,7 @@ export {
|
|
|
631
1315
|
CapabilityNotFoundError,
|
|
632
1316
|
CapabilityService,
|
|
633
1317
|
DebugController,
|
|
1318
|
+
ErrorCodes,
|
|
634
1319
|
PluginLoadError,
|
|
635
1320
|
PluginLoaderService,
|
|
636
1321
|
PluginNotFoundError,
|