@lark-apaas/nestjs-capability 0.0.1-alpha.0 → 0.0.1-alpha.2

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 CHANGED
@@ -15,6 +15,7 @@ npm install @lark-apaas/nestjs-capability
15
15
  - 从 `server/capabilities/` 目录加载能力配置
16
16
  - 加载并实例化关联的插件
17
17
  - 多种调用方式(Node 调用、Debug 调用、前端调用)
18
+ - 流式调用支持(SSE)
18
19
  - 模板参数解析
19
20
 
20
21
  ## 快速开始
@@ -59,6 +60,36 @@ export class TaskService {
59
60
  }
60
61
  ```
61
62
 
63
+ ### 流式调用
64
+
65
+ 用于 LLM 对话等流式输出场景:
66
+
67
+ ```typescript
68
+ @Injectable()
69
+ export class ChatService {
70
+ constructor(private readonly capabilityService: CapabilityService) {}
71
+
72
+ async *chat(message: string): AsyncIterable<{ content: string }> {
73
+ const stream = this.capabilityService
74
+ .load('ai_chat')
75
+ .callStream('chat', { message });
76
+
77
+ for await (const chunk of stream) {
78
+ yield chunk as { content: string };
79
+ }
80
+ }
81
+ }
82
+ ```
83
+
84
+ 普通调用会自动聚合流式结果:
85
+
86
+ ```typescript
87
+ // 即使是流式 action,call() 也会返回聚合后的完整结果
88
+ const result = await this.capabilityService
89
+ .load('ai_chat')
90
+ .call('chat', { message: 'hello' });
91
+ ```
92
+
62
93
  ### 上下文覆盖
63
94
 
64
95
  ```typescript
@@ -128,16 +159,42 @@ POST /api/capability/:capability_id
128
159
  }
129
160
  ```
130
161
 
131
- ### Debug 调用(仅开发环境)
162
+ ### 前端流式调用
132
163
 
133
164
  ```
134
- POST /__innerapi__/capability/debug/:capability_id
165
+ POST /api/capability/:capability_id/stream
135
166
 
136
167
  请求体:
137
168
  {
138
- "action": "run",
169
+ "action": "chat",
139
170
  "params": {
171
+ "message": "hello"
172
+ }
173
+ }
174
+
175
+ 响应(SSE 格式):
176
+ data: {"content":"Hello"}
177
+ data: {"content":" World"}
178
+ data: [DONE]
179
+ ```
180
+
181
+ ### Debug 调用(仅开发环境)
182
+
183
+ ```
184
+ POST /__innerapi__/capability/debug/:capability_id
185
+
186
+ 请求体(所有字段可选):
187
+ {
188
+ "action": "run", // 可选,默认使用插件第一个 action
189
+ "params": { // 可选,用户输入参数
140
190
  "group_name": "测试群"
191
+ },
192
+ "capability": { // 可选,完整的 capability 配置(优先使用)
193
+ "id": "test",
194
+ "pluginID": "@test/plugin",
195
+ "pluginVersion": "1.0.0",
196
+ "name": "测试",
197
+ "formValue": {}
141
198
  }
142
199
  }
143
200
 
@@ -150,11 +207,30 @@ POST /__innerapi__/capability/debug/:capability_id
150
207
  "capabilityConfig": { ... },
151
208
  "resolvedParams": { ... },
152
209
  "duration": 123,
153
- "pluginID": "@xxx/plugin"
210
+ "pluginID": "@xxx/plugin",
211
+ "action": "run"
154
212
  }
155
213
  }
156
214
  ```
157
215
 
216
+ ### Debug 流式调用(仅开发环境)
217
+
218
+ ```
219
+ POST /__innerapi__/capability/debug/:capability_id/stream
220
+
221
+ 请求体(所有字段可选):
222
+ {
223
+ "action": "chat",
224
+ "params": { "message": "hello" },
225
+ "capability": { ... }
226
+ }
227
+
228
+ 响应(SSE 格式):
229
+ data: {"content":"Hello"}
230
+ data: {"content":" World"}
231
+ data: [DONE]
232
+ ```
233
+
158
234
  ### 列出所有能力(仅开发环境)
159
235
 
160
236
  ```
@@ -192,16 +268,30 @@ interface CapabilityService {
192
268
  // 加载能力并返回执行器
193
269
  load(capabilityId: string): CapabilityExecutor;
194
270
 
271
+ // 使用传入的配置加载能力执行器(用于 debug 场景)
272
+ loadWithConfig(config: CapabilityConfig): CapabilityExecutor;
273
+
195
274
  // 设置自定义能力目录
196
275
  setCapabilitiesDir(dir: string): void;
197
276
  }
198
277
 
199
278
  interface CapabilityExecutor {
279
+ // 调用能力(流式 action 会自动聚合结果)
200
280
  call(
201
281
  actionName: string,
202
282
  input: unknown,
203
283
  context?: Partial<PluginActionContext>
204
284
  ): Promise<unknown>;
285
+
286
+ // 流式调用能力(返回 AsyncIterable)
287
+ callStream(
288
+ actionName: string,
289
+ input: unknown,
290
+ context?: Partial<PluginActionContext>
291
+ ): AsyncIterable<unknown>;
292
+
293
+ // 检查 action 是否为流式
294
+ isStream(actionName: string): Promise<boolean>;
205
295
  }
206
296
  ```
207
297
 
@@ -257,10 +347,22 @@ interface CapabilityConfig {
257
347
 
258
348
  ```typescript
259
349
  interface PluginInstance {
350
+ // 执行 action(必需)
260
351
  run(actionName: string, context: PluginActionContext, input: unknown): Promise<unknown>;
352
+
353
+ // 流式执行 action(可选)
354
+ runStream?(actionName: string, context: PluginActionContext, input: unknown): AsyncIterable<unknown>;
355
+
356
+ // 检查是否为流式 action(可选)
357
+ isStreamAction?(actionName: string): boolean;
358
+
359
+ // 聚合流式结果(可选,默认返回 chunks 数组)
360
+ aggregate?(actionName: string, chunks: unknown[]): unknown;
361
+
362
+ // Action 相关
261
363
  hasAction(actionName: string): boolean;
262
- getActionSchema(actionName: string): ActionSchema | null;
263
364
  listActions(): string[];
365
+ getActionSchema(actionName: string): ActionSchema | null;
264
366
  getInputSchema(actionName: string, config?: unknown): ZodSchema | undefined;
265
367
  getOutputSchema(actionName: string, input: unknown, config?: unknown): ZodSchema | undefined;
266
368
  }
package/dist/index.cjs CHANGED
@@ -134,7 +134,7 @@ var PluginLoaderService = class _PluginLoaderService {
134
134
  }
135
135
  logger = new import_common2.Logger(_PluginLoaderService.name);
136
136
  pluginInstances = /* @__PURE__ */ new Map();
137
- loadPlugin(pluginID) {
137
+ async loadPlugin(pluginID) {
138
138
  const cached = this.pluginInstances.get(pluginID);
139
139
  if (cached) {
140
140
  this.logger.debug(`Using cached plugin instance: ${pluginID}`);
@@ -142,7 +142,7 @@ var PluginLoaderService = class _PluginLoaderService {
142
142
  }
143
143
  this.logger.log(`Loading plugin: ${pluginID}`);
144
144
  try {
145
- const pluginPackage = require(pluginID);
145
+ const pluginPackage = (await import(pluginID)).default;
146
146
  if (typeof pluginPackage.create !== "function") {
147
147
  throw new PluginLoadError(pluginID, "Plugin does not export create() function");
148
148
  }
@@ -278,28 +278,70 @@ var CapabilityService = class _CapabilityService {
278
278
  if (!config) {
279
279
  throw new CapabilityNotFoundError(capabilityId);
280
280
  }
281
+ return this.createExecutor(config);
282
+ }
283
+ /**
284
+ * 使用传入的配置加载能力执行器
285
+ * 用于 debug 场景,支持用户传入自定义配置
286
+ */
287
+ loadWithConfig(config) {
288
+ return this.createExecutor(config);
289
+ }
290
+ createExecutor(config) {
281
291
  return {
282
292
  call: /* @__PURE__ */ __name(async (actionName, input, contextOverride) => {
283
- return this.execute(config, actionName, input, contextOverride);
284
- }, "call")
293
+ return this.executeCall(config, actionName, input, contextOverride);
294
+ }, "call"),
295
+ callStream: /* @__PURE__ */ __name((actionName, input, contextOverride) => {
296
+ return this.executeCallStream(config, actionName, input, contextOverride);
297
+ }, "callStream"),
298
+ isStream: /* @__PURE__ */ __name(async (actionName) => {
299
+ return this.checkIsStream(config, actionName);
300
+ }, "isStream")
285
301
  };
286
302
  }
287
- async execute(config, actionName, input, contextOverride) {
303
+ /**
304
+ * 检查 action 是否为流式
305
+ */
306
+ async checkIsStream(config, actionName) {
307
+ const pluginInstance = await this.pluginLoaderService.loadPlugin(config.pluginID);
308
+ if (!pluginInstance.hasAction(actionName)) {
309
+ throw new ActionNotFoundError(config.pluginID, actionName);
310
+ }
311
+ return pluginInstance.isStreamAction?.(actionName) ?? false;
312
+ }
313
+ /**
314
+ * 执行 capability(始终返回 Promise)
315
+ * - unary action: 直接返回结果
316
+ * - stream action: 内部聚合所有 chunk 后返回
317
+ */
318
+ async executeCall(config, actionName, input, contextOverride) {
288
319
  const startTime = Date.now();
289
320
  try {
290
- const pluginInstance = this.pluginLoaderService.loadPlugin(config.pluginID);
321
+ const pluginInstance = await this.pluginLoaderService.loadPlugin(config.pluginID);
291
322
  if (!pluginInstance.hasAction(actionName)) {
292
323
  throw new ActionNotFoundError(config.pluginID, actionName);
293
324
  }
294
325
  const resolvedParams = this.templateEngineService.resolve(config.formValue, input);
295
326
  const context = this.buildActionContext(contextOverride);
327
+ const isStream = pluginInstance.isStreamAction?.(actionName) ?? false;
296
328
  this.logger.log({
297
329
  message: "Executing capability",
298
330
  capabilityId: config.id,
299
331
  action: actionName,
300
- pluginID: config.pluginID
332
+ pluginID: config.pluginID,
333
+ isStream
301
334
  });
302
- const result = await pluginInstance.run(actionName, context, resolvedParams);
335
+ let result;
336
+ if (isStream && pluginInstance.runStream) {
337
+ const chunks = [];
338
+ for await (const chunk of pluginInstance.runStream(actionName, context, resolvedParams)) {
339
+ chunks.push(chunk);
340
+ }
341
+ result = pluginInstance.aggregate ? pluginInstance.aggregate(actionName, chunks) : chunks;
342
+ } else {
343
+ result = await pluginInstance.run(actionName, context, resolvedParams);
344
+ }
303
345
  this.logger.log({
304
346
  message: "Capability executed successfully",
305
347
  capabilityId: config.id,
@@ -318,6 +360,51 @@ var CapabilityService = class _CapabilityService {
318
360
  throw error;
319
361
  }
320
362
  }
363
+ /**
364
+ * 流式执行 capability
365
+ * - stream action: 返回原始 AsyncIterable
366
+ * - unary action: 包装为单次 yield
367
+ */
368
+ async *executeCallStream(config, actionName, input, contextOverride) {
369
+ const startTime = Date.now();
370
+ try {
371
+ const pluginInstance = await this.pluginLoaderService.loadPlugin(config.pluginID);
372
+ if (!pluginInstance.hasAction(actionName)) {
373
+ throw new ActionNotFoundError(config.pluginID, actionName);
374
+ }
375
+ const resolvedParams = this.templateEngineService.resolve(config.formValue, input);
376
+ const context = this.buildActionContext(contextOverride);
377
+ const isStream = pluginInstance.isStreamAction?.(actionName) ?? false;
378
+ this.logger.log({
379
+ message: "Executing capability (stream)",
380
+ capabilityId: config.id,
381
+ action: actionName,
382
+ pluginID: config.pluginID,
383
+ isStream
384
+ });
385
+ if (isStream && pluginInstance.runStream) {
386
+ yield* pluginInstance.runStream(actionName, context, resolvedParams);
387
+ } else {
388
+ const result = await pluginInstance.run(actionName, context, resolvedParams);
389
+ yield result;
390
+ }
391
+ this.logger.log({
392
+ message: "Capability stream completed",
393
+ capabilityId: config.id,
394
+ action: actionName,
395
+ duration: Date.now() - startTime
396
+ });
397
+ } catch (error) {
398
+ this.logger.error({
399
+ message: "Capability stream execution failed",
400
+ capabilityId: config.id,
401
+ action: actionName,
402
+ error: error instanceof Error ? error.message : String(error),
403
+ duration: Date.now() - startTime
404
+ });
405
+ throw error;
406
+ }
407
+ }
321
408
  buildActionContext(override) {
322
409
  return {
323
410
  logger: this.logger,
@@ -369,9 +456,11 @@ var DebugController = class {
369
456
  __name(this, "DebugController");
370
457
  }
371
458
  capabilityService;
459
+ pluginLoaderService;
372
460
  templateEngineService;
373
- constructor(capabilityService, templateEngineService) {
461
+ constructor(capabilityService, pluginLoaderService, templateEngineService) {
374
462
  this.capabilityService = capabilityService;
463
+ this.pluginLoaderService = pluginLoaderService;
375
464
  this.templateEngineService = templateEngineService;
376
465
  }
377
466
  list() {
@@ -387,8 +476,14 @@ var DebugController = class {
387
476
  }))
388
477
  };
389
478
  }
390
- async debug(capabilityId, body) {
391
- const startTime = Date.now();
479
+ /**
480
+ * 获取 capability 配置
481
+ * 优先使用 body.capability,否则从服务获取
482
+ */
483
+ getCapabilityConfig(capabilityId, bodyCapability) {
484
+ if (bodyCapability) {
485
+ return bodyCapability;
486
+ }
392
487
  const config = this.capabilityService.getCapability(capabilityId);
393
488
  if (!config) {
394
489
  throw new import_common4.HttpException({
@@ -397,9 +492,35 @@ var DebugController = class {
397
492
  error: "CAPABILITY_NOT_FOUND"
398
493
  }, import_common4.HttpStatus.NOT_FOUND);
399
494
  }
400
- const resolvedParams = this.templateEngineService.resolve(config.formValue, body.params);
495
+ return config;
496
+ }
497
+ /**
498
+ * 获取 action 名称
499
+ * 优先使用传入的 action,否则使用插件第一个 action
500
+ */
501
+ async getActionName(pluginID, bodyAction) {
502
+ if (bodyAction) {
503
+ return bodyAction;
504
+ }
505
+ const pluginInstance = await this.pluginLoaderService.loadPlugin(pluginID);
506
+ const actions = pluginInstance.listActions();
507
+ if (actions.length === 0) {
508
+ throw new import_common4.HttpException({
509
+ code: 1,
510
+ message: `Plugin ${pluginID} has no actions`,
511
+ error: "NO_ACTIONS"
512
+ }, import_common4.HttpStatus.BAD_REQUEST);
513
+ }
514
+ return actions[0];
515
+ }
516
+ async debug(capabilityId, body) {
517
+ const startTime = Date.now();
518
+ const params = body.params ?? {};
519
+ const config = this.getCapabilityConfig(capabilityId, body.capability);
520
+ const action = await this.getActionName(config.pluginID, body.action);
521
+ const resolvedParams = this.templateEngineService.resolve(config.formValue, params);
401
522
  try {
402
- const result = await this.capabilityService.load(capabilityId).call(body.action, body.params);
523
+ const result = await this.capabilityService.loadWithConfig(config).call(action, params);
403
524
  return {
404
525
  code: 0,
405
526
  message: "success",
@@ -408,7 +529,8 @@ var DebugController = class {
408
529
  capabilityConfig: config,
409
530
  resolvedParams,
410
531
  duration: Date.now() - startTime,
411
- pluginID: config.pluginID
532
+ pluginID: config.pluginID,
533
+ action
412
534
  }
413
535
  };
414
536
  } catch (error) {
@@ -430,7 +552,8 @@ var DebugController = class {
430
552
  error: "PLUGIN_NOT_FOUND",
431
553
  debug: {
432
554
  duration,
433
- pluginID: config.pluginID
555
+ pluginID: config.pluginID,
556
+ action
434
557
  }
435
558
  }, import_common4.HttpStatus.INTERNAL_SERVER_ERROR);
436
559
  }
@@ -441,7 +564,8 @@ var DebugController = class {
441
564
  error: "ACTION_NOT_FOUND",
442
565
  debug: {
443
566
  duration,
444
- pluginID: config.pluginID
567
+ pluginID: config.pluginID,
568
+ action
445
569
  }
446
570
  }, import_common4.HttpStatus.BAD_REQUEST);
447
571
  }
@@ -452,11 +576,51 @@ var DebugController = class {
452
576
  debug: {
453
577
  duration,
454
578
  pluginID: config.pluginID,
579
+ action,
455
580
  resolvedParams
456
581
  }
457
582
  }, import_common4.HttpStatus.INTERNAL_SERVER_ERROR);
458
583
  }
459
584
  }
585
+ async debugStream(capabilityId, body, res) {
586
+ const params = body.params ?? {};
587
+ res.setHeader("Content-Type", "text/event-stream");
588
+ res.setHeader("Cache-Control", "no-cache");
589
+ res.setHeader("Connection", "keep-alive");
590
+ try {
591
+ const config = this.getCapabilityConfig(capabilityId, body.capability);
592
+ const action = await this.getActionName(config.pluginID, body.action);
593
+ const capability = this.capabilityService.loadWithConfig(config);
594
+ const stream = capability.callStream(action, params);
595
+ for await (const chunk of stream) {
596
+ res.write(`data: ${JSON.stringify(chunk)}
597
+
598
+ `);
599
+ }
600
+ res.write("data: [DONE]\n\n");
601
+ } catch (error) {
602
+ const message = error instanceof Error ? error.message : String(error);
603
+ let errorCode = "EXECUTION_ERROR";
604
+ if (error instanceof CapabilityNotFoundError) {
605
+ errorCode = "CAPABILITY_NOT_FOUND";
606
+ } else if (error instanceof PluginNotFoundError) {
607
+ errorCode = "PLUGIN_NOT_FOUND";
608
+ } else if (error instanceof ActionNotFoundError) {
609
+ errorCode = "ACTION_NOT_FOUND";
610
+ } else if (error instanceof import_common4.HttpException) {
611
+ const response = error.getResponse();
612
+ errorCode = response.error ?? "EXECUTION_ERROR";
613
+ }
614
+ res.write(`data: ${JSON.stringify({
615
+ error: message,
616
+ code: errorCode
617
+ })}
618
+
619
+ `);
620
+ } finally {
621
+ res.end();
622
+ }
623
+ }
460
624
  };
461
625
  _ts_decorate4([
462
626
  (0, import_common4.Get)("list"),
@@ -471,15 +635,29 @@ _ts_decorate4([
471
635
  _ts_metadata2("design:type", Function),
472
636
  _ts_metadata2("design:paramtypes", [
473
637
  String,
474
- typeof ExecuteRequestBody === "undefined" ? Object : ExecuteRequestBody
638
+ typeof DebugRequestBody === "undefined" ? Object : DebugRequestBody
475
639
  ]),
476
640
  _ts_metadata2("design:returntype", Promise)
477
641
  ], DebugController.prototype, "debug", null);
642
+ _ts_decorate4([
643
+ (0, import_common4.Post)("debug/:capability_id/stream"),
644
+ _ts_param2(0, (0, import_common4.Param)("capability_id")),
645
+ _ts_param2(1, (0, import_common4.Body)()),
646
+ _ts_param2(2, (0, import_common4.Res)()),
647
+ _ts_metadata2("design:type", Function),
648
+ _ts_metadata2("design:paramtypes", [
649
+ String,
650
+ typeof DebugRequestBody === "undefined" ? Object : DebugRequestBody,
651
+ typeof Response === "undefined" ? Object : Response
652
+ ]),
653
+ _ts_metadata2("design:returntype", Promise)
654
+ ], DebugController.prototype, "debugStream", null);
478
655
  DebugController = _ts_decorate4([
479
656
  (0, import_common4.Controller)("__innerapi__/capability"),
480
657
  _ts_metadata2("design:type", Function),
481
658
  _ts_metadata2("design:paramtypes", [
482
659
  typeof CapabilityService === "undefined" ? Object : CapabilityService,
660
+ typeof PluginLoaderService === "undefined" ? Object : PluginLoaderService,
483
661
  typeof TemplateEngineService === "undefined" ? Object : TemplateEngineService
484
662
  ])
485
663
  ], DebugController);
@@ -511,6 +689,20 @@ var WebhookController = class {
511
689
  constructor(capabilityService) {
512
690
  this.capabilityService = capabilityService;
513
691
  }
692
+ list() {
693
+ const capabilities = this.capabilityService.listCapabilities();
694
+ return {
695
+ code: 0,
696
+ message: "success",
697
+ data: capabilities.map((c) => ({
698
+ id: c.id,
699
+ name: c.name,
700
+ description: c.description,
701
+ pluginID: c.pluginID,
702
+ pluginVersion: c.pluginVersion
703
+ }))
704
+ };
705
+ }
514
706
  async execute(capabilityId, body) {
515
707
  try {
516
708
  const result = await this.capabilityService.load(capabilityId).call(body.action, body.params);
@@ -548,7 +740,46 @@ var WebhookController = class {
548
740
  }, import_common5.HttpStatus.INTERNAL_SERVER_ERROR);
549
741
  }
550
742
  }
743
+ async executeStream(capabilityId, body, res) {
744
+ res.setHeader("Content-Type", "text/event-stream");
745
+ res.setHeader("Cache-Control", "no-cache");
746
+ res.setHeader("Connection", "keep-alive");
747
+ try {
748
+ const capability = this.capabilityService.load(capabilityId);
749
+ const stream = capability.callStream(body.action, body.params);
750
+ for await (const chunk of stream) {
751
+ res.write(`data: ${JSON.stringify(chunk)}
752
+
753
+ `);
754
+ }
755
+ res.write("data: [DONE]\n\n");
756
+ } catch (error) {
757
+ const message = error instanceof Error ? error.message : String(error);
758
+ let errorCode = "EXECUTION_ERROR";
759
+ if (error instanceof CapabilityNotFoundError) {
760
+ errorCode = "CAPABILITY_NOT_FOUND";
761
+ } else if (error instanceof PluginNotFoundError) {
762
+ errorCode = "PLUGIN_NOT_FOUND";
763
+ } else if (error instanceof ActionNotFoundError) {
764
+ errorCode = "ACTION_NOT_FOUND";
765
+ }
766
+ res.write(`data: ${JSON.stringify({
767
+ error: message,
768
+ code: errorCode
769
+ })}
770
+
771
+ `);
772
+ } finally {
773
+ res.end();
774
+ }
775
+ }
551
776
  };
777
+ _ts_decorate5([
778
+ (0, import_common5.Get)("list"),
779
+ _ts_metadata3("design:type", Function),
780
+ _ts_metadata3("design:paramtypes", []),
781
+ _ts_metadata3("design:returntype", typeof ListResponse === "undefined" ? Object : ListResponse)
782
+ ], WebhookController.prototype, "list", null);
552
783
  _ts_decorate5([
553
784
  (0, import_common5.Post)(":capability_id"),
554
785
  _ts_param3(0, (0, import_common5.Param)("capability_id")),
@@ -560,6 +791,19 @@ _ts_decorate5([
560
791
  ]),
561
792
  _ts_metadata3("design:returntype", Promise)
562
793
  ], WebhookController.prototype, "execute", null);
794
+ _ts_decorate5([
795
+ (0, import_common5.Post)(":capability_id/stream"),
796
+ _ts_param3(0, (0, import_common5.Param)("capability_id")),
797
+ _ts_param3(1, (0, import_common5.Body)()),
798
+ _ts_param3(2, (0, import_common5.Res)()),
799
+ _ts_metadata3("design:type", Function),
800
+ _ts_metadata3("design:paramtypes", [
801
+ String,
802
+ typeof ExecuteRequestBody === "undefined" ? Object : ExecuteRequestBody,
803
+ typeof Response === "undefined" ? Object : Response
804
+ ]),
805
+ _ts_metadata3("design:returntype", Promise)
806
+ ], WebhookController.prototype, "executeStream", null);
563
807
  WebhookController = _ts_decorate5([
564
808
  (0, import_common5.Controller)("api/capability"),
565
809
  _ts_metadata3("design:type", Function),