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