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