@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.
- package/dist/ai/docs/nocobase/ai-dev/capabilities.md +1 -1
- package/dist/ai/docs/nocobase/ai-dev/index.md +1 -1
- package/dist/ai/docs/nocobase/ai-dev/watermark-plugin.md +1 -1
- package/dist/ai/docs/nocobase/api/cli/app/index.md +4 -4
- package/dist/ai/docs/nocobase/api/cli/app/restart.md +4 -2
- package/dist/ai/docs/nocobase/api/cli/app/start.md +6 -2
- package/dist/ai/docs/nocobase/api/cli/app/stop.md +2 -2
- package/dist/ai/docs/nocobase/api/cli/app/upgrade.md +27 -6
- package/dist/ai/docs/nocobase/api/cli/config/delete.md +4 -1
- package/dist/ai/docs/nocobase/api/cli/config/get.md +4 -1
- package/dist/ai/docs/nocobase/api/cli/config/index.md +21 -0
- package/dist/ai/docs/nocobase/api/cli/config/set.md +9 -2
- package/dist/ai/docs/nocobase/api/cli/init.md +1 -1
- package/dist/ai/docs/nocobase/api/cli/license/plugins/sync.md +4 -0
- package/dist/ai/docs/nocobase/api/cli/self/update.md +2 -0
- package/dist/ai/docs/nocobase/auth-verification/api-keys/index.md +1 -1
- package/dist/ai/docs/nocobase/file-manager/file-preview/index.md +17 -0
- package/dist/ai/docs/nocobase/get-started/installation/env.md +6 -2
- package/dist/ai/docs/nocobase/integration/api-keys/index.md +1 -1
- package/dist/ai/docs/nocobase/integration/embed/index.md +3 -3
- package/dist/ai/docs/nocobase/multi-app/multi-app/app-block-and-switcher.md +68 -0
- package/dist/ai/docs/nocobase/multi-app/multi-app/app-sso.md +71 -0
- package/dist/ai/docs/nocobase/multi-app/multi-app/index.md +9 -1
- package/dist/ai/docs/nocobase/multi-app/multi-app/sub-app-api.md +112 -0
- package/dist/ai/docs/nocobase/plugin-development/build.md +5 -0
- package/dist/ai/docs/nocobase/plugin-development/client/appendix/faq.md +3 -3
- package/dist/ai/docs/nocobase/plugin-development/client/component/index.md +1 -1
- package/dist/ai/docs/nocobase/plugin-development/client/ctx/common-capabilities.md +2 -2
- package/dist/ai/docs/nocobase/plugin-development/client/ctx/index.md +1 -1
- package/dist/ai/docs/nocobase/plugin-development/client/router.md +11 -11
- package/dist/ai/docs/nocobase/plugin-development/write-your-first-plugin.md +28 -0
- package/dist/ai/docs/nocobase/solution/all-in-one/installation.md +181 -0
- package/dist/ai/docs/nocobase/solution/crm/changelog.md +0 -3
- package/dist/ai/docs/nocobase/solution/crm/installation.md +6 -76
- package/dist/ai/docs/nocobase/solution/crm/v1.md +6 -35
- package/dist/ai/docs/nocobase/solution/ticket-system/changelog.md +0 -2
- package/dist/ai/docs/nocobase/solution/ticket-system/installation.md +6 -69
- package/dist/ai/docs/nocobase/workflow/nodes/javascript.md +4 -2
- package/dist/client/{406.15c09d98faa2ccf1.js → 406.9a530334eae8cede.js} +1 -1
- package/dist/client/{428.e9f38da3b0d8b498.js → 428.431a00d29107058e.js} +1 -1
- package/dist/client/462.d172d0fe91519e35.js +10 -0
- package/dist/client/559.a0f2f1cc2be3c039.js +10 -0
- package/dist/client/index.js +2 -2
- package/dist/client/workflow/nodes/employee/index.d.ts +1 -4
- package/dist/client/workflow/nodes/llm/index.d.ts +1 -4
- package/dist/client-v2/index.js +1 -1
- package/dist/collections/ai-employees.d.ts +9 -2
- package/dist/collections/ai-employees.js +2 -1
- package/dist/externalVersion.js +22 -22
- package/dist/locale/de-DE.json +1 -0
- package/dist/locale/en-US.json +2 -1
- package/dist/locale/es-ES.json +1 -0
- package/dist/locale/fr-FR.json +1 -0
- package/dist/locale/hu-HU.json +1 -0
- package/dist/locale/id-ID.json +1 -0
- package/dist/locale/it-IT.json +1 -0
- package/dist/locale/ja-JP.json +1 -0
- package/dist/locale/ko-KR.json +1 -0
- package/dist/locale/nl-NL.json +1 -0
- package/dist/locale/pt-BR.json +1 -0
- package/dist/locale/ru-RU.json +1 -0
- package/dist/locale/tr-TR.json +1 -0
- package/dist/locale/uk-UA.json +1 -0
- package/dist/locale/vi-VN.json +1 -0
- package/dist/locale/zh-CN.json +2 -1
- package/dist/locale/zh-TW.json +1 -0
- package/dist/node_modules/@langchain/xai/dist/index.cjs +16 -2
- package/dist/node_modules/@langchain/xai/package.json +1 -1
- package/dist/node_modules/fs-extra/package.json +1 -1
- package/dist/node_modules/jsonrepair/package.json +1 -1
- package/dist/node_modules/just-bash/package.json +1 -1
- package/dist/node_modules/nodejs-snowflake/package.json +1 -1
- package/dist/node_modules/openai/package.json +1 -1
- package/dist/node_modules/zod/package.json +1 -1
- package/dist/server/ai-employees/ai-employee.d.ts +2 -1
- package/dist/server/ai-employees/ai-employee.js +3 -1
- package/dist/server/llm-providers/anthropic.js +17 -23
- package/dist/server/llm-providers/dashscope.js +3 -3
- package/dist/server/llm-providers/deepseek.js +2 -2
- package/dist/server/llm-providers/google-genai.js +17 -22
- package/dist/server/llm-providers/kimi/provider.js +4 -4
- package/dist/server/llm-providers/mimo.js +2 -2
- package/dist/server/llm-providers/ollama.js +14 -21
- package/dist/server/llm-providers/openai/completions.js +2 -2
- package/dist/server/llm-providers/openai/embedding.js +1 -1
- package/dist/server/llm-providers/openai/responses.js +2 -2
- package/dist/server/llm-providers/provider.d.ts +3 -1
- package/dist/server/llm-providers/provider.js +61 -24
- package/dist/server/llm-providers/xai.js +2 -2
- package/dist/server/resource/ai.js +11 -9
- package/dist/server/utils.d.ts +1 -1
- package/dist/server/workflow/nodes/employee/constants.d.ts +23 -0
- package/dist/server/workflow/nodes/employee/constants.js +50 -0
- package/dist/server/workflow/nodes/employee/handler.js +23 -2
- package/dist/server/workflow/nodes/employee/index.d.ts +1 -0
- package/dist/server/workflow/nodes/employee/index.js +83 -25
- package/dist/server/workflow/nodes/employee/tools.d.ts +2 -2
- package/dist/server/workflow/nodes/employee/tools.js +26 -5
- package/dist/server/workflow/nodes/employee/types.d.ts +4 -3
- package/dist/server/workflow/nodes/llm/index.js +31 -9
- package/package.json +7 -7
- package/dist/client/462.1708385b148779cd.js +0 -10
- 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 {
|
|
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:
|
|
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():
|
|
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
|
|
44
|
-
var
|
|
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 =
|
|
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(
|
|
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
|
|
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
|
|
118
|
-
|
|
119
|
-
|
|
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
|
|
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,
|
|
161
|
+
return (0, import_utils2.parseResponseMessage)(message);
|
|
143
162
|
}
|
|
144
163
|
parseResponseChunk(chunk) {
|
|
145
|
-
return (0,
|
|
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,
|
|
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
|
|
226
|
+
const loaderOptions = typeof ctx.get === "function" ? {
|
|
210
227
|
requestOptions: {
|
|
211
228
|
headers: {
|
|
212
|
-
Referer: referer,
|
|
213
|
-
"User-Agent":
|
|
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 =
|
|
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
|
-
|
|
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 {
|
|
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:
|
|
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
|
-
|
|
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) => {
|
package/dist/server/utils.d.ts
CHANGED
|
@@ -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:
|
|
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 ===
|
|
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 =
|
|
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({
|
|
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
|
-
|
|
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:
|
|
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:
|
|
400
|
+
values: { status: import_constants.AI_WORKFLOW_TASK_STATUS.APPROVED },
|
|
343
401
|
filter: {
|
|
344
402
|
id: aiWorkflowTasks.id,
|
|
345
403
|
status: {
|
|
346
|
-
$ne:
|
|
404
|
+
$ne: import_constants.AI_WORKFLOW_TASK_STATUS.ABORTED
|
|
347
405
|
}
|
|
348
406
|
}
|
|
349
407
|
});
|
|
350
|
-
} else if (requiresApproval !==
|
|
408
|
+
} else if (requiresApproval !== import_constants.REQUIRES_APPROVAL.NO_REQUIRED) {
|
|
351
409
|
await this.workflow.db.getRepository("aiWorkflowTasks").update({
|
|
352
|
-
values: { status:
|
|
410
|
+
values: { status: import_constants.AI_WORKFLOW_TASK_STATUS.PENDING_ACCEPTANCE },
|
|
353
411
|
filter: {
|
|
354
412
|
id: aiWorkflowTasks.id,
|
|
355
413
|
status: {
|
|
356
|
-
$ne:
|
|
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;
|