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