@nocobase/plugin-ai 2.1.0-alpha.40 → 2.1.0-alpha.46

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.
Files changed (103) hide show
  1. package/dist/ai/docs/nocobase/ai-dev/capabilities.md +1 -1
  2. package/dist/ai/docs/nocobase/ai-dev/index.md +1 -1
  3. package/dist/ai/docs/nocobase/ai-dev/watermark-plugin.md +1 -1
  4. package/dist/ai/docs/nocobase/api/cli/app/index.md +4 -4
  5. package/dist/ai/docs/nocobase/api/cli/app/restart.md +4 -2
  6. package/dist/ai/docs/nocobase/api/cli/app/start.md +6 -2
  7. package/dist/ai/docs/nocobase/api/cli/app/stop.md +2 -2
  8. package/dist/ai/docs/nocobase/api/cli/app/upgrade.md +27 -6
  9. package/dist/ai/docs/nocobase/api/cli/config/delete.md +4 -1
  10. package/dist/ai/docs/nocobase/api/cli/config/get.md +4 -1
  11. package/dist/ai/docs/nocobase/api/cli/config/index.md +21 -0
  12. package/dist/ai/docs/nocobase/api/cli/config/set.md +9 -2
  13. package/dist/ai/docs/nocobase/api/cli/init.md +1 -1
  14. package/dist/ai/docs/nocobase/api/cli/license/plugins/sync.md +4 -0
  15. package/dist/ai/docs/nocobase/api/cli/self/update.md +2 -0
  16. package/dist/ai/docs/nocobase/auth-verification/api-keys/index.md +1 -1
  17. package/dist/ai/docs/nocobase/file-manager/file-preview/index.md +17 -0
  18. package/dist/ai/docs/nocobase/get-started/installation/env.md +6 -2
  19. package/dist/ai/docs/nocobase/integration/api-keys/index.md +1 -1
  20. package/dist/ai/docs/nocobase/integration/embed/index.md +3 -3
  21. package/dist/ai/docs/nocobase/multi-app/multi-app/app-block-and-switcher.md +68 -0
  22. package/dist/ai/docs/nocobase/multi-app/multi-app/app-sso.md +71 -0
  23. package/dist/ai/docs/nocobase/multi-app/multi-app/index.md +9 -1
  24. package/dist/ai/docs/nocobase/multi-app/multi-app/sub-app-api.md +112 -0
  25. package/dist/ai/docs/nocobase/plugin-development/build.md +5 -0
  26. package/dist/ai/docs/nocobase/plugin-development/client/appendix/faq.md +3 -3
  27. package/dist/ai/docs/nocobase/plugin-development/client/component/index.md +1 -1
  28. package/dist/ai/docs/nocobase/plugin-development/client/ctx/common-capabilities.md +2 -2
  29. package/dist/ai/docs/nocobase/plugin-development/client/ctx/index.md +1 -1
  30. package/dist/ai/docs/nocobase/plugin-development/client/router.md +11 -11
  31. package/dist/ai/docs/nocobase/plugin-development/write-your-first-plugin.md +28 -0
  32. package/dist/ai/docs/nocobase/solution/all-in-one/installation.md +181 -0
  33. package/dist/ai/docs/nocobase/solution/crm/changelog.md +0 -3
  34. package/dist/ai/docs/nocobase/solution/crm/installation.md +6 -76
  35. package/dist/ai/docs/nocobase/solution/crm/v1.md +6 -35
  36. package/dist/ai/docs/nocobase/solution/ticket-system/changelog.md +0 -2
  37. package/dist/ai/docs/nocobase/solution/ticket-system/installation.md +6 -69
  38. package/dist/ai/docs/nocobase/workflow/nodes/javascript.md +4 -2
  39. package/dist/client/{406.15c09d98faa2ccf1.js → 406.9a530334eae8cede.js} +1 -1
  40. package/dist/client/{428.e9f38da3b0d8b498.js → 428.431a00d29107058e.js} +1 -1
  41. package/dist/client/462.d172d0fe91519e35.js +10 -0
  42. package/dist/client/559.a0f2f1cc2be3c039.js +10 -0
  43. package/dist/client/index.js +2 -2
  44. package/dist/client/workflow/nodes/employee/index.d.ts +1 -4
  45. package/dist/client/workflow/nodes/llm/index.d.ts +1 -4
  46. package/dist/client-v2/index.js +1 -1
  47. package/dist/collections/ai-employees.d.ts +9 -2
  48. package/dist/collections/ai-employees.js +2 -1
  49. package/dist/externalVersion.js +22 -22
  50. package/dist/locale/de-DE.json +1 -0
  51. package/dist/locale/en-US.json +2 -1
  52. package/dist/locale/es-ES.json +1 -0
  53. package/dist/locale/fr-FR.json +1 -0
  54. package/dist/locale/hu-HU.json +1 -0
  55. package/dist/locale/id-ID.json +1 -0
  56. package/dist/locale/it-IT.json +1 -0
  57. package/dist/locale/ja-JP.json +1 -0
  58. package/dist/locale/ko-KR.json +1 -0
  59. package/dist/locale/nl-NL.json +1 -0
  60. package/dist/locale/pt-BR.json +1 -0
  61. package/dist/locale/ru-RU.json +1 -0
  62. package/dist/locale/tr-TR.json +1 -0
  63. package/dist/locale/uk-UA.json +1 -0
  64. package/dist/locale/vi-VN.json +1 -0
  65. package/dist/locale/zh-CN.json +2 -1
  66. package/dist/locale/zh-TW.json +1 -0
  67. package/dist/node_modules/@langchain/xai/dist/index.cjs +16 -2
  68. package/dist/node_modules/@langchain/xai/package.json +1 -1
  69. package/dist/node_modules/fs-extra/package.json +1 -1
  70. package/dist/node_modules/jsonrepair/package.json +1 -1
  71. package/dist/node_modules/just-bash/package.json +1 -1
  72. package/dist/node_modules/nodejs-snowflake/package.json +1 -1
  73. package/dist/node_modules/openai/package.json +1 -1
  74. package/dist/node_modules/zod/package.json +1 -1
  75. package/dist/server/ai-employees/ai-employee.d.ts +2 -1
  76. package/dist/server/ai-employees/ai-employee.js +3 -1
  77. package/dist/server/llm-providers/anthropic.js +17 -23
  78. package/dist/server/llm-providers/dashscope.js +3 -3
  79. package/dist/server/llm-providers/deepseek.js +2 -2
  80. package/dist/server/llm-providers/google-genai.js +17 -22
  81. package/dist/server/llm-providers/kimi/provider.js +4 -4
  82. package/dist/server/llm-providers/mimo.js +2 -2
  83. package/dist/server/llm-providers/ollama.js +14 -21
  84. package/dist/server/llm-providers/openai/completions.js +2 -2
  85. package/dist/server/llm-providers/openai/embedding.js +1 -1
  86. package/dist/server/llm-providers/openai/responses.js +2 -2
  87. package/dist/server/llm-providers/provider.d.ts +3 -1
  88. package/dist/server/llm-providers/provider.js +61 -24
  89. package/dist/server/llm-providers/xai.js +2 -2
  90. package/dist/server/resource/ai.js +11 -9
  91. package/dist/server/utils.d.ts +1 -1
  92. package/dist/server/workflow/nodes/employee/constants.d.ts +23 -0
  93. package/dist/server/workflow/nodes/employee/constants.js +50 -0
  94. package/dist/server/workflow/nodes/employee/handler.js +23 -2
  95. package/dist/server/workflow/nodes/employee/index.d.ts +1 -0
  96. package/dist/server/workflow/nodes/employee/index.js +83 -25
  97. package/dist/server/workflow/nodes/employee/tools.d.ts +2 -2
  98. package/dist/server/workflow/nodes/employee/tools.js +26 -5
  99. package/dist/server/workflow/nodes/employee/types.d.ts +4 -3
  100. package/dist/server/workflow/nodes/llm/index.js +31 -9
  101. package/package.json +7 -7
  102. package/dist/client/462.1708385b148779cd.js +0 -10
  103. package/dist/client/559.585f80c3bcea0bed.js +0 -10
@@ -37,7 +37,7 @@ class OpenAIResponsesProvider extends import_provider.LLMProvider {
37
37
  return "https://api.openai.com/v1";
38
38
  }
39
39
  createModel() {
40
- const { baseURL, apiKey } = this.serviceOptions || {};
40
+ const { apiKey } = this.serviceOptions || {};
41
41
  const { responseFormat, structuredOutput } = this.modelOptions || {};
42
42
  const { schema } = structuredOutput || {};
43
43
  const responseFormatOptions = {
@@ -56,7 +56,7 @@ class OpenAIResponsesProvider extends import_provider.LLMProvider {
56
56
  }
57
57
  },
58
58
  configuration: {
59
- baseURL: baseURL || this.baseURL
59
+ baseURL: this.getResolvedBaseURL()
60
60
  },
61
61
  verbose: false,
62
62
  useResponsesApi: true
@@ -91,6 +91,8 @@ export declare abstract class LLMProvider {
91
91
  parseResponseMetadata(output: LLMResult): any;
92
92
  parseResponseError(err: any): any;
93
93
  protected get documentLoader(): CachedDocumentLoader;
94
+ protected getResolvedBaseURL(): string;
95
+ protected buildRequestURL(pathname: string): string;
94
96
  protected get aiPlugin(): PluginAIServer;
95
97
  }
96
98
  export interface EmbeddingProviderOptions {
@@ -107,6 +109,6 @@ export declare abstract class EmbeddingProvider {
107
109
  abstract createEmbedding(): EmbeddingsInterface;
108
110
  protected abstract getDefaultUrl(): string;
109
111
  protected get apiKey(): any;
110
- protected get baseURL(): any;
112
+ protected get baseURL(): string;
111
113
  protected get model(): any;
112
114
  }
@@ -40,12 +40,26 @@ __export(provider_exports, {
40
40
  LLMProvider: () => LLMProvider
41
41
  });
42
42
  module.exports = __toCommonJS(provider_exports);
43
- var import_axios = __toESM(require("axios"));
44
- var import_utils = require("../utils");
43
+ var import_utils = require("@nocobase/utils");
44
+ var import_utils2 = require("../utils");
45
45
  var import_stream = require("@langchain/core/utils/stream");
46
46
  var import_document_loader = require("../document-loader");
47
47
  var import_node_path = __toESM(require("node:path"));
48
48
  var import_reasoning = require("./common/reasoning");
49
+ function normalizeBaseURL(baseURL) {
50
+ (0, import_utils.checkUrlAgainstWhitelist)(baseURL);
51
+ return new URL(baseURL).toString().replace(/\/$/, "");
52
+ }
53
+ function resolveServiceOptions(serviceOptions, app) {
54
+ const rendered = app.environment.renderJsonTemplate(serviceOptions ?? {});
55
+ if ((rendered == null ? void 0 : rendered.baseURL) != null) {
56
+ if (typeof rendered.baseURL !== "string") {
57
+ throw new Error("baseURL must be a string");
58
+ }
59
+ rendered.baseURL = normalizeBaseURL(rendered.baseURL);
60
+ }
61
+ return rendered;
62
+ }
49
63
  class LLMProvider {
50
64
  app;
51
65
  serviceOptions;
@@ -57,7 +71,7 @@ class LLMProvider {
57
71
  constructor(opts) {
58
72
  const { app, serviceOptions, modelOptions } = opts;
59
73
  this.app = app;
60
- this.serviceOptions = app.environment.renderJsonTemplate(serviceOptions);
74
+ this.serviceOptions = resolveServiceOptions(serviceOptions, app);
61
75
  if (modelOptions) {
62
76
  this.modelOptions = modelOptions;
63
77
  this.chatModel = this.createModel();
@@ -69,7 +83,7 @@ class LLMProvider {
69
83
  prepareChain(context) {
70
84
  var _a, _b, _c, _d;
71
85
  let chain = this.chatModel;
72
- const toolDefinitions = (_a = context.tools) == null ? void 0 : _a.map(import_utils.buildTool);
86
+ const toolDefinitions = (_a = context.tools) == null ? void 0 : _a.map(import_utils2.buildTool);
73
87
  if ((_b = this.builtInTools()) == null ? void 0 : _b.length) {
74
88
  const tools = [...this.builtInTools()];
75
89
  if (!this.isToolConflict() && (toolDefinitions == null ? void 0 : toolDefinitions.length)) {
@@ -88,11 +102,15 @@ class LLMProvider {
88
102
  return chain;
89
103
  }
90
104
  async invoke(context, options) {
91
- var _a;
105
+ var _a, _b;
92
106
  const builder = this.getModelRequestBuilder((_a = this.modelOptions) == null ? void 0 : _a.model);
93
107
  const request = (builder == null ? void 0 : builder({ context, options })) || { context, options };
94
108
  const chain = this.prepareChain(request.context);
95
- const { modelKwargs, modelRequestParams, options: requestOptions, ...restOptions } = request.options || {};
109
+ const requestInvokeOptions = (options == null ? void 0 : options.signal) ? {
110
+ ...request.options || {},
111
+ signal: ((_b = request.options) == null ? void 0 : _b.signal) ?? options.signal
112
+ } : request.options;
113
+ const { modelKwargs, modelRequestParams, options: requestOptions, ...restOptions } = requestInvokeOptions || {};
96
114
  const invokeOptions = modelKwargs ? {
97
115
  ...restOptions,
98
116
  [import_reasoning.MODEL_KWARGS_KEY]: modelKwargs,
@@ -114,18 +132,19 @@ class LLMProvider {
114
132
  var _a, _b, _c, _d;
115
133
  const options = this.serviceOptions || {};
116
134
  const apiKey = options.apiKey;
117
- let baseURL = options.baseURL || this.baseURL;
118
- if (!baseURL) {
119
- return { code: 400, errMsg: "baseURL is required" };
135
+ let url;
136
+ try {
137
+ url = this.buildRequestURL("models");
138
+ } catch (e) {
139
+ return { code: 400, errMsg: e instanceof Error ? e.message : String(e) };
120
140
  }
121
141
  if (!apiKey) {
122
142
  return { code: 400, errMsg: "API Key required" };
123
143
  }
124
- if (baseURL && baseURL.endsWith("/")) {
125
- baseURL = baseURL.slice(0, -1);
126
- }
127
144
  try {
128
- const res = await import_axios.default.get(`${baseURL}/models`, {
145
+ const res = await (0, import_utils.serverRequest)({
146
+ method: "GET",
147
+ url,
129
148
  headers: {
130
149
  Authorization: `Bearer ${apiKey}`
131
150
  }
@@ -139,10 +158,10 @@ class LLMProvider {
139
158
  }
140
159
  }
141
160
  parseResponseMessage(message) {
142
- return (0, import_utils.parseResponseMessage)(message);
161
+ return (0, import_utils2.parseResponseMessage)(message);
143
162
  }
144
163
  parseResponseChunk(chunk) {
145
- return (0, import_utils.stripToolCallTags)(chunk);
164
+ return (0, import_utils2.stripToolCallTags)(chunk);
146
165
  }
147
166
  async parseAttachment(ctx, attachment) {
148
167
  if (this.isApiSupportedAttachment(attachment)) {
@@ -177,7 +196,7 @@ class LLMProvider {
177
196
  async convertToContent(ctx, attachment) {
178
197
  const fileManager = this.app.pm.get("file-manager");
179
198
  const url = await fileManager.getFileURL(attachment);
180
- const data = await (0, import_utils.encodeFile)(ctx, decodeURIComponent(url));
199
+ const data = await (0, import_utils2.encodeFile)(ctx, decodeURIComponent(url));
181
200
  if (attachment.mimetype.startsWith("image/")) {
182
201
  return {
183
202
  placement: "contentBlocks",
@@ -203,17 +222,16 @@ class LLMProvider {
203
222
  }
204
223
  }
205
224
  async loadDocument(ctx, attachment) {
206
- const referer = ctx.get("referer") || "";
207
- const ua = ctx.get("user-agent") || "";
208
225
  const safeFilename = attachment.filename ? import_node_path.default.basename(attachment.filename) : "document";
209
- const parsed = await this.documentLoader.load(attachment, {
226
+ const loaderOptions = typeof ctx.get === "function" ? {
210
227
  requestOptions: {
211
228
  headers: {
212
- Referer: referer,
213
- "User-Agent": ua
229
+ Referer: ctx.get("referer") || "",
230
+ "User-Agent": ctx.get("user-agent") || ""
214
231
  }
215
232
  }
216
- });
233
+ } : void 0;
234
+ const parsed = await this.documentLoader.load(attachment, loaderOptions);
217
235
  if (!parsed.supported) {
218
236
  return {
219
237
  placement: "system",
@@ -303,6 +321,22 @@ ${parsed.text}
303
321
  get documentLoader() {
304
322
  return this.aiPlugin.documentLoaders.cached;
305
323
  }
324
+ getResolvedBaseURL() {
325
+ var _a;
326
+ const baseURL = ((_a = this.serviceOptions) == null ? void 0 : _a.baseURL) ?? this.baseURL;
327
+ if (!baseURL) {
328
+ throw new Error("baseURL is required");
329
+ }
330
+ if (typeof baseURL !== "string") {
331
+ throw new Error("baseURL must be a string");
332
+ }
333
+ return normalizeBaseURL(baseURL);
334
+ }
335
+ buildRequestURL(pathname) {
336
+ const url = new URL(pathname.replace(/^\/+/, ""), `${this.getResolvedBaseURL()}/`).toString();
337
+ (0, import_utils.checkUrlAgainstWhitelist)(url);
338
+ return url;
339
+ }
306
340
  get aiPlugin() {
307
341
  return this.app.pm.get("ai");
308
342
  }
@@ -312,7 +346,7 @@ class EmbeddingProvider {
312
346
  this.opts = opts;
313
347
  const { app, serviceOptions, modelOptions } = this.opts;
314
348
  this.app = app;
315
- this.serviceOptions = app.environment.renderJsonTemplate(serviceOptions ?? {});
349
+ this.serviceOptions = resolveServiceOptions(serviceOptions, app);
316
350
  this.modelOptions = modelOptions;
317
351
  }
318
352
  app;
@@ -331,7 +365,10 @@ class EmbeddingProvider {
331
365
  if (!baseURL) {
332
366
  throw new Error("baseURL is required");
333
367
  }
334
- return baseURL;
368
+ if (typeof baseURL !== "string") {
369
+ throw new Error("baseURL must be a string");
370
+ }
371
+ return normalizeBaseURL(baseURL);
335
372
  }
336
373
  get model() {
337
374
  const { model } = this.modelOptions ?? {};
@@ -38,7 +38,7 @@ class XAIProvider extends import_provider.LLMProvider {
38
38
  return "https://api.x.ai/v1";
39
39
  }
40
40
  createModel() {
41
- const { baseURL, apiKey } = this.serviceOptions || {};
41
+ const { apiKey } = this.serviceOptions || {};
42
42
  const { responseFormat, structuredOutput, ...restModelOptions } = this.modelOptions || {};
43
43
  const { schema } = structuredOutput || {};
44
44
  const responseFormatOptions = {
@@ -50,7 +50,7 @@ class XAIProvider extends import_provider.LLMProvider {
50
50
  return new import_xai.ChatXAI({
51
51
  apiKey,
52
52
  ...restModelOptions,
53
- baseURL: baseURL || this.baseURL,
53
+ baseURL: this.getResolvedBaseURL(),
54
54
  modelKwargs: {
55
55
  response_format: responseFormatOptions
56
56
  }
@@ -39,7 +39,10 @@ __export(ai_exports, {
39
39
  default: () => ai_default
40
40
  });
41
41
  module.exports = __toCommonJS(ai_exports);
42
+ var import_package = require("../../../package.json");
42
43
  var import_lodash = __toESM(require("lodash"));
44
+ const getModelsListFailedMessage = "Get models list failed, you can enter a model name manually.";
45
+ const testFlightFailedMessage = "LLM service test failed. Please check the service configuration.";
43
46
  const aiResource = {
44
47
  name: "ai",
45
48
  actions: {
@@ -98,10 +101,7 @@ const aiResource = {
98
101
  const res = await provider.listModels();
99
102
  if (res.errMsg) {
100
103
  ctx.log.error(res.errMsg);
101
- ctx.throw(
102
- res.code || 500,
103
- `${ctx.t("Get models list failed, you can enter a model name manually.")} ${res.errMsg}`
104
- );
104
+ ctx.throw(res.code || 500, ctx.t(getModelsListFailedMessage, { ns: import_package.name }));
105
105
  }
106
106
  ctx.body = res.models || [];
107
107
  }
@@ -122,10 +122,7 @@ const aiResource = {
122
122
  const res = await providerClient.listModels();
123
123
  if (res.errMsg) {
124
124
  ctx.log.error(res.errMsg);
125
- ctx.throw(
126
- res.code || 500,
127
- `${ctx.t("Get models list failed, you can enter a model name manually.")} ${res.errMsg}`
128
- );
125
+ ctx.throw(res.code || 500, ctx.t(getModelsListFailedMessage, { ns: import_package.name }));
129
126
  }
130
127
  const models = res.models || [];
131
128
  if (model) {
@@ -151,7 +148,12 @@ const aiResource = {
151
148
  responseFormat: "text"
152
149
  }
153
150
  });
154
- ctx.body = await providerClient.testFlight();
151
+ const result = await providerClient.testFlight();
152
+ if (result.status === "error") {
153
+ ctx.log.error(result.message);
154
+ result.message = ctx.t(testFlightFailedMessage, { ns: import_package.name });
155
+ }
156
+ ctx.body = result;
155
157
  return next();
156
158
  },
157
159
  listAllEnabledModels: async (ctx, next) => {
@@ -20,7 +20,7 @@ export declare function parseResponseMessage(row: Model): {
20
20
  export declare function encodeLocalFile(url: string): Promise<string>;
21
21
  export declare function encodeFile(ctx: Context, url: string): Promise<string>;
22
22
  export declare function parseVariables(ctx: Context, value: string): Promise<any>;
23
- export declare const buildTool: (toolsEntry: ToolsEntry) => import("@langchain/core/dist/tools").DynamicTool<any>;
23
+ export declare const buildTool: (toolsEntry: ToolsEntry) => import("@langchain/core/dist/tools").DynamicTool<any, unknown>;
24
24
  export declare class ResourceActionError extends Error {
25
25
  readonly status: number;
26
26
  constructor(status: number, message: string, options?: ErrorOptions);
@@ -0,0 +1,23 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+ export declare const AI_WORKFLOW_TASK_STATUS: {
10
+ readonly PROCESSING: "processing";
11
+ readonly PENDING_ACCEPTANCE: "pending_acceptance";
12
+ readonly PENDING_APPROVAL: "pending_approval";
13
+ readonly APPROVED: "approved";
14
+ readonly REJECTED: "rejected";
15
+ readonly ABORTED: "aborted";
16
+ };
17
+ export type AIWorkflowTaskStatus = (typeof AI_WORKFLOW_TASK_STATUS)[keyof typeof AI_WORKFLOW_TASK_STATUS];
18
+ export declare const REQUIRES_APPROVAL: {
19
+ readonly NO_REQUIRED: "no_required";
20
+ readonly AI_DECISION: "ai_decision";
21
+ readonly HUMAN_DECISION: "human_decision";
22
+ };
23
+ export type RequiresApproval = (typeof REQUIRES_APPROVAL)[keyof typeof REQUIRES_APPROVAL];
@@ -0,0 +1,50 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+
10
+ var __defProp = Object.defineProperty;
11
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
12
+ var __getOwnPropNames = Object.getOwnPropertyNames;
13
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
14
+ var __export = (target, all) => {
15
+ for (var name in all)
16
+ __defProp(target, name, { get: all[name], enumerable: true });
17
+ };
18
+ var __copyProps = (to, from, except, desc) => {
19
+ if (from && typeof from === "object" || typeof from === "function") {
20
+ for (let key of __getOwnPropNames(from))
21
+ if (!__hasOwnProp.call(to, key) && key !== except)
22
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
23
+ }
24
+ return to;
25
+ };
26
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
27
+ var constants_exports = {};
28
+ __export(constants_exports, {
29
+ AI_WORKFLOW_TASK_STATUS: () => AI_WORKFLOW_TASK_STATUS,
30
+ REQUIRES_APPROVAL: () => REQUIRES_APPROVAL
31
+ });
32
+ module.exports = __toCommonJS(constants_exports);
33
+ const AI_WORKFLOW_TASK_STATUS = {
34
+ PROCESSING: "processing",
35
+ PENDING_ACCEPTANCE: "pending_acceptance",
36
+ PENDING_APPROVAL: "pending_approval",
37
+ APPROVED: "approved",
38
+ REJECTED: "rejected",
39
+ ABORTED: "aborted"
40
+ };
41
+ const REQUIRES_APPROVAL = {
42
+ NO_REQUIRED: "no_required",
43
+ AI_DECISION: "ai_decision",
44
+ HUMAN_DECISION: "human_decision"
45
+ };
46
+ // Annotate the CommonJS export names for ESM import in node:
47
+ 0 && (module.exports = {
48
+ AI_WORKFLOW_TASK_STATUS,
49
+ REQUIRES_APPROVAL
50
+ });
@@ -31,6 +31,7 @@ __export(handler_exports, {
31
31
  });
32
32
  module.exports = __toCommonJS(handler_exports);
33
33
  var import_plugin_workflow = require("@nocobase/plugin-workflow");
34
+ var import_constants = require("./constants");
34
35
  const registerOnJobAbortedHandler = (plugin) => {
35
36
  plugin.db.on("jobs.afterBulkUpdate", async (options) => {
36
37
  const { model, attributes, where, transaction } = options;
@@ -47,15 +48,35 @@ const registerOnJobAbortedHandler = (plugin) => {
47
48
  if (!aiWorkflowTasks.length) {
48
49
  return;
49
50
  }
51
+ const sessionIds = aiWorkflowTasks.map((task) => task.sessionId).filter(Boolean);
50
52
  for (const aiWorkflowTask of aiWorkflowTasks) {
51
53
  await plugin.db.getRepository("aiWorkflowTasks").update({
52
- values: { status: "aborted" },
54
+ values: { status: import_constants.AI_WORKFLOW_TASK_STATUS.ABORTED },
53
55
  filter: {
54
56
  id: aiWorkflowTask.id
55
57
  },
56
58
  transaction
57
59
  });
58
60
  }
61
+ if (sessionIds.length) {
62
+ await plugin.db.getRepository("aiToolMessages").update({
63
+ values: {
64
+ invokeStatus: "done",
65
+ status: "error",
66
+ content: "Workflow execution aborted.",
67
+ invokeEndTime: /* @__PURE__ */ new Date()
68
+ },
69
+ filter: {
70
+ sessionId: {
71
+ $in: sessionIds
72
+ },
73
+ invokeStatus: {
74
+ $in: ["init", "interrupted", "waiting", "pending"]
75
+ }
76
+ },
77
+ transaction
78
+ });
79
+ }
59
80
  });
60
81
  };
61
82
  const registerAIEmployeeTaskNotification = (plugin) => {
@@ -74,7 +95,7 @@ const registerAIEmployeeTaskNotification = (plugin) => {
74
95
  if (!(assignees == null ? void 0 : assignees.length)) {
75
96
  return;
76
97
  }
77
- if (values.status === "pending_acceptance") {
98
+ if (values.status === import_constants.AI_WORKFLOW_TASK_STATUS.PENDING_ACCEPTANCE) {
78
99
  await plugin.db.getRepository("usersAiWorkflowTasks").update({
79
100
  values: {
80
101
  read: false
@@ -11,6 +11,7 @@ export declare class AIEmployeeInstruction extends Instruction {
11
11
  run(node: FlowNodeModel, input: any, processor: Processor): Promise<void>;
12
12
  resume(node: FlowNodeModel, job: any, processor: Processor): any;
13
13
  private createWorkflowTask;
14
+ private abortAIWorkflowTask;
14
15
  private checkApproval;
15
16
  }
16
17
  export * from './handler';
@@ -46,6 +46,7 @@ var import_ai_employee = require("../../../ai-employees/ai-employee");
46
46
  var import_files = require("./files");
47
47
  var import_utils = require("@nocobase/utils");
48
48
  var import_ai = require("@nocobase/ai");
49
+ var import_constants = require("./constants");
49
50
  __reExport(employee_exports, require("./handler"), module.exports);
50
51
  __reExport(employee_exports, require("./tools"), module.exports);
51
52
  class AIEmployeeInstruction extends import_plugin_workflow.Instruction {
@@ -56,7 +57,7 @@ class AIEmployeeInstruction extends import_plugin_workflow.Instruction {
56
57
  skillSettings,
57
58
  webSearch,
58
59
  model,
59
- requiresApproval = "no_required",
60
+ requiresApproval = import_constants.REQUIRES_APPROVAL.NO_REQUIRED,
60
61
  userId,
61
62
  files
62
63
  } = processor.getParsedValue(node.config, node.id);
@@ -80,10 +81,12 @@ ${typeof message.system === "object" ? JSON.stringify(message.system) : message.
80
81
  nodeKey: node.key,
81
82
  upstreamId: (input == null ? void 0 : input.id) ?? null
82
83
  });
84
+ const abortHandle = processor.createBackgroundAbortHandle();
83
85
  await processor.exit();
84
86
  const runner = async () => {
85
87
  var _a, _b, _c, _d, _e;
86
88
  try {
89
+ abortHandle.throwIfAborted();
87
90
  if (skillSettings && skillSettings.skillsVersion == null) {
88
91
  skillSettings.skillsVersion = 2;
89
92
  }
@@ -169,6 +172,7 @@ ${typeof message.system === "object" ? JSON.stringify(message.system) : message.
169
172
  ];
170
173
  if (retry > 0) {
171
174
  if (retry < 2) {
175
+ abortHandle.throwIfAborted();
172
176
  const firstUserMessage = await this.workflow.db.getRepository("aiConversations.messages", conversation.sessionId).findOne({
173
177
  filter: {
174
178
  role: "user"
@@ -176,9 +180,10 @@ ${typeof message.system === "object" ? JSON.stringify(message.system) : message.
176
180
  sort: ["messageId"]
177
181
  });
178
182
  const messageId = firstUserMessage == null ? void 0 : firstUserMessage.messageId;
179
- result = await aiEmployee.invoke({ messageId, userMessages });
183
+ result = await aiEmployee.invoke({ messageId, userMessages, signal: abortHandle.signal });
180
184
  } else {
181
185
  result = await aiEmployee.invoke({
186
+ signal: abortHandle.signal,
182
187
  userMessages: [
183
188
  {
184
189
  role: "user",
@@ -194,15 +199,28 @@ Do not send another normal assistant response without invoking it.
194
199
  });
195
200
  }
196
201
  } else {
197
- result = await aiEmployee.invoke({ userMessages });
202
+ result = await aiEmployee.invoke({ userMessages, signal: abortHandle.signal });
198
203
  }
204
+ abortHandle.throwIfAborted();
199
205
  isToolInvoke = result.messages.filter((it) => it.type === "ai").flatMap((it) => it.tool_calls).some((it) => it.name === toolName);
200
206
  } while (!isToolInvoke && retry++ < 2);
201
207
  if (!isToolInvoke) {
202
208
  throw new Error("AI employee not do job correctly");
203
209
  }
204
- await this.checkApproval({ requiresApproval, conversation, aiWorkflowTasks, result, aiEmployee, toolName });
210
+ await this.checkApproval({
211
+ requiresApproval,
212
+ conversation,
213
+ aiWorkflowTasks,
214
+ result,
215
+ aiEmployee,
216
+ toolName,
217
+ signal: abortHandle.signal
218
+ });
205
219
  } catch (e) {
220
+ if ((0, import_plugin_workflow.isWorkflowTimeoutError)(e) || abortHandle.signal.aborted) {
221
+ await this.abortAIWorkflowTask(id);
222
+ return;
223
+ }
206
224
  processor.logger.error(`ai employee invoke failed, ${e.message}`, {
207
225
  node: node.id,
208
226
  stack: e.stack,
@@ -211,24 +229,21 @@ Do not send another normal assistant response without invoking it.
211
229
  const job = await this.workflow.app.db.getRepository("jobs").findOne({
212
230
  filterByTk: id
213
231
  });
232
+ if (!job) {
233
+ await this.abortAIWorkflowTask(id);
234
+ return;
235
+ }
236
+ if (job.status !== import_plugin_workflow.JOB_STATUS.PENDING) {
237
+ return;
238
+ }
214
239
  job.set({
215
240
  status: import_plugin_workflow.JOB_STATUS.ERROR,
216
241
  result: e.message
217
242
  });
218
- const aiWorkflowTask = await this.workflow.db.getRepository("aiWorkflowTasks").findOne({
219
- filter: {
220
- jobId: id
221
- }
222
- });
223
- if (aiWorkflowTask) {
224
- await this.workflow.db.getRepository("aiWorkflowTasks").update({
225
- values: { status: "aborted" },
226
- filter: {
227
- id: aiWorkflowTask.id
228
- }
229
- });
230
- }
243
+ await this.abortAIWorkflowTask(id);
231
244
  await this.workflow.resume(job);
245
+ } finally {
246
+ abortHandle.dispose();
232
247
  }
233
248
  };
234
249
  runner();
@@ -270,7 +285,7 @@ Do not send another normal assistant response without invoking it.
270
285
  workflowTitle: (_a = processor.execution.workflow) == null ? void 0 : _a.title,
271
286
  nodeTitle: node.title,
272
287
  requiresApproval,
273
- status: "processing",
288
+ status: import_constants.AI_WORKFLOW_TASK_STATUS.PROCESSING,
274
289
  sessionId: conversation.sessionId,
275
290
  jobId,
276
291
  executionId: processor.execution.id,
@@ -293,15 +308,55 @@ Do not send another normal assistant response without invoking it.
293
308
  return { conversation, aiWorkflowTasks };
294
309
  });
295
310
  }
311
+ async abortAIWorkflowTask(jobId) {
312
+ const tasks = await this.workflow.db.getRepository("aiWorkflowTasks").find({
313
+ filter: {
314
+ jobId
315
+ },
316
+ fields: ["id", "sessionId"]
317
+ });
318
+ const sessionIds = tasks.map((task) => task.sessionId).filter(Boolean);
319
+ await this.workflow.db.getRepository("aiWorkflowTasks").update({
320
+ values: { status: import_constants.AI_WORKFLOW_TASK_STATUS.ABORTED },
321
+ filter: {
322
+ jobId,
323
+ status: {
324
+ $ne: import_constants.AI_WORKFLOW_TASK_STATUS.ABORTED
325
+ }
326
+ }
327
+ });
328
+ if (sessionIds.length) {
329
+ await this.workflow.db.getRepository("aiToolMessages").update({
330
+ values: {
331
+ invokeStatus: "done",
332
+ status: "error",
333
+ content: "Workflow execution aborted.",
334
+ invokeEndTime: /* @__PURE__ */ new Date()
335
+ },
336
+ filter: {
337
+ sessionId: {
338
+ $in: sessionIds
339
+ },
340
+ invokeStatus: {
341
+ $in: ["init", "interrupted", "waiting", "pending"]
342
+ }
343
+ }
344
+ });
345
+ }
346
+ }
296
347
  async checkApproval({
297
348
  requiresApproval,
298
349
  conversation,
299
350
  aiWorkflowTasks,
300
351
  result,
301
352
  aiEmployee,
302
- toolName
353
+ toolName,
354
+ signal
303
355
  }) {
304
356
  var _a;
357
+ if (signal == null ? void 0 : signal.aborted) {
358
+ throw signal.reason ?? new import_plugin_workflow.WorkflowTimeoutError();
359
+ }
305
360
  const ai = this.workflow.app.pm.get(import_plugin.default);
306
361
  const aiToolMessage = await this.workflow.db.getRepository("aiToolMessages").findOne({
307
362
  filter: {
@@ -337,23 +392,26 @@ Do not send another normal assistant response without invoking it.
337
392
  return;
338
393
  }
339
394
  const userDecisions = await ai.aiConversationsManager.getUserDecisions(result.messageId);
340
- await aiEmployee.invoke({ userDecisions });
395
+ await aiEmployee.invoke({ userDecisions, signal });
396
+ if (signal == null ? void 0 : signal.aborted) {
397
+ throw signal.reason ?? new import_plugin_workflow.WorkflowTimeoutError();
398
+ }
341
399
  await this.workflow.db.getRepository("aiWorkflowTasks").update({
342
- values: { status: "approved" },
400
+ values: { status: import_constants.AI_WORKFLOW_TASK_STATUS.APPROVED },
343
401
  filter: {
344
402
  id: aiWorkflowTasks.id,
345
403
  status: {
346
- $ne: "aborted"
404
+ $ne: import_constants.AI_WORKFLOW_TASK_STATUS.ABORTED
347
405
  }
348
406
  }
349
407
  });
350
- } else if (requiresApproval !== "no_required") {
408
+ } else if (requiresApproval !== import_constants.REQUIRES_APPROVAL.NO_REQUIRED) {
351
409
  await this.workflow.db.getRepository("aiWorkflowTasks").update({
352
- values: { status: "pending_acceptance" },
410
+ values: { status: import_constants.AI_WORKFLOW_TASK_STATUS.PENDING_ACCEPTANCE },
353
411
  filter: {
354
412
  id: aiWorkflowTasks.id,
355
413
  status: {
356
- $ne: "aborted"
414
+ $ne: import_constants.AI_WORKFLOW_TASK_STATUS.ABORTED
357
415
  }
358
416
  }
359
417
  });
@@ -6,7 +6,7 @@
6
6
  * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
7
  * For more information, please refer to: https://www.nocobase.com/agreement.
8
8
  */
9
- import { DynamicToolsProvider } from '@nocobase/ai';
10
- import { Plugin } from '@nocobase/server';
9
+ import type { DynamicToolsProvider } from '@nocobase/ai';
10
+ import type { Plugin } from '@nocobase/server';
11
11
  export type WorkflowTaskToolProvider = (plugin: Plugin) => DynamicToolsProvider;
12
12
  export declare const getWorkflowTasks: WorkflowTaskToolProvider;