@nocobase/plugin-ai 2.3.0-beta.6 → 2.3.0-beta.8

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 (36) hide show
  1. package/dist/client/{244.28ee769f796b7c2f.js → 244.aeb24ce76c510b34.js} +1 -1
  2. package/dist/client/{705.a4fe26945d175e8a.js → 705.1d735e63c25f66a8.js} +1 -1
  3. package/dist/client/index.js +2 -2
  4. package/dist/client-v2/{244.39348a95d627d9e3.js → 244.ee9ec45eac0eb99f.js} +1 -1
  5. package/dist/client-v2/{705.f2e1b26a3f000075.js → 705.00c101bef403fb44.js} +1 -1
  6. package/dist/client-v2/index.js +2 -2
  7. package/dist/client-v2/workflow/types.d.ts +1 -0
  8. package/dist/common/ai-employee-validation.d.ts +2 -0
  9. package/dist/common/ai-employee-validation.js +6 -0
  10. package/dist/common/error-codes.d.ts +1 -0
  11. package/dist/common/error-codes.js +3 -0
  12. package/dist/externalVersion.js +15 -15
  13. package/dist/locale/en-US.json +2 -0
  14. package/dist/locale/zh-CN.json +2 -0
  15. package/dist/node_modules/@langchain/mistralai/package.json +1 -1
  16. package/dist/node_modules/@langchain/xai/package.json +1 -1
  17. package/dist/node_modules/fs-extra/package.json +1 -1
  18. package/dist/node_modules/jsonrepair/package.json +1 -1
  19. package/dist/node_modules/just-bash/package.json +1 -1
  20. package/dist/node_modules/nodejs-snowflake/package.json +1 -1
  21. package/dist/node_modules/openai/package.json +1 -1
  22. package/dist/node_modules/zod/package.json +1 -1
  23. package/dist/server/ai-employees/ai-employee.d.ts +28 -1
  24. package/dist/server/ai-employees/ai-employee.js +257 -34
  25. package/dist/server/ai-employees/ai-knowledge-base.js +11 -4
  26. package/dist/server/ai-employees/middleware/conversation.js +19 -20
  27. package/dist/server/ai-employees/middleware/index.d.ts +1 -0
  28. package/dist/server/ai-employees/middleware/index.js +2 -0
  29. package/dist/server/ai-employees/middleware/tool-result-integrity.d.ts +22 -0
  30. package/dist/server/ai-employees/middleware/tool-result-integrity.js +211 -0
  31. package/dist/server/resource/aiEmployees.js +26 -0
  32. package/dist/server/workflow/nodes/employee/files.d.ts +1 -1
  33. package/dist/server/workflow/nodes/employee/files.js +48 -1
  34. package/dist/server/workflow/nodes/employee/index.js +7 -2
  35. package/dist/server/workflow/nodes/employee/types.d.ts +1 -0
  36. package/package.json +2 -2
@@ -0,0 +1,211 @@
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 tool_result_integrity_exports = {};
28
+ __export(tool_result_integrity_exports, {
29
+ isToolCallHistoryValid: () => isToolCallHistoryValid,
30
+ normalizeToolCallHistory: () => normalizeToolCallHistory,
31
+ toolResultIntegrityMiddleware: () => toolResultIntegrityMiddleware
32
+ });
33
+ module.exports = __toCommonJS(tool_result_integrity_exports);
34
+ var import_messages = require("@langchain/core/messages");
35
+ var import_langchain = require("langchain");
36
+ const SYNTHETIC_TOOL_RESULT_ERROR = "Tool execution was interrupted before a result was recorded.";
37
+ const getValidToolCalls = (message) => (message.tool_calls ?? []).filter((toolCall) => typeof toolCall.id === "string" && toolCall.id.length > 0);
38
+ const isToolCallHistoryValid = (messages) => {
39
+ for (let index = 0; index < messages.length; index++) {
40
+ const message = messages[index];
41
+ if (import_messages.ToolMessage.isInstance(message)) {
42
+ return false;
43
+ }
44
+ if (!import_messages.AIMessage.isInstance(message)) {
45
+ continue;
46
+ }
47
+ const toolCalls = getValidToolCalls(message);
48
+ if (!toolCalls.length) {
49
+ continue;
50
+ }
51
+ const expectedToolCallIds = new Set(toolCalls.map((toolCall) => toolCall.id));
52
+ const seenToolResultIds = /* @__PURE__ */ new Set();
53
+ for (let resultIndex = 0; resultIndex < toolCalls.length; resultIndex++) {
54
+ index++;
55
+ if (index >= messages.length) {
56
+ return false;
57
+ }
58
+ const result = messages[index];
59
+ if (!import_messages.ToolMessage.isInstance(result) || !expectedToolCallIds.has(result.tool_call_id) || seenToolResultIds.has(result.tool_call_id)) {
60
+ return false;
61
+ }
62
+ seenToolResultIds.add(result.tool_call_id);
63
+ }
64
+ }
65
+ return true;
66
+ };
67
+ const serializeToolResultContent = (content) => {
68
+ if (typeof content === "string") {
69
+ return content;
70
+ }
71
+ return JSON.stringify(content ?? null);
72
+ };
73
+ const createSyntheticToolResult = (sessionId, toolCall) => new import_messages.ToolMessage({
74
+ id: `synthetic-tool-result:${sessionId}:${toolCall.id}`,
75
+ tool_call_id: toolCall.id,
76
+ name: toolCall.name,
77
+ status: "error",
78
+ content: JSON.stringify({
79
+ status: "error",
80
+ error: SYNTHETIC_TOOL_RESULT_ERROR
81
+ })
82
+ });
83
+ const createRestoredToolResult = (sessionId, toolCall, persisted) => {
84
+ if (!persisted || persisted.invokeStatus !== "confirmed" && persisted.invokeStatus !== "done") {
85
+ return;
86
+ }
87
+ try {
88
+ return new import_messages.ToolMessage({
89
+ id: `restored-tool-result:${sessionId}:${toolCall.id}`,
90
+ tool_call_id: toolCall.id,
91
+ name: toolCall.name,
92
+ status: persisted.status === "error" ? "error" : "success",
93
+ content: serializeToolResultContent(persisted.content)
94
+ });
95
+ } catch {
96
+ return;
97
+ }
98
+ };
99
+ const countMisplacedResults = (messages) => {
100
+ let misplacedResultCount = 0;
101
+ for (let index = 0; index < messages.length; index++) {
102
+ const message = messages[index];
103
+ if (!import_messages.AIMessage.isInstance(message)) {
104
+ continue;
105
+ }
106
+ const toolCalls = getValidToolCalls(message);
107
+ if (!toolCalls.length) {
108
+ continue;
109
+ }
110
+ const expectedToolCallIds = new Set(toolCalls.map((toolCall) => toolCall.id));
111
+ const contiguousResultIds = /* @__PURE__ */ new Set();
112
+ for (let offset = 1; offset <= toolCalls.length; offset++) {
113
+ const result = messages[index + offset];
114
+ if (!import_messages.ToolMessage.isInstance(result)) {
115
+ break;
116
+ }
117
+ if (expectedToolCallIds.has(result.tool_call_id)) {
118
+ contiguousResultIds.add(result.tool_call_id);
119
+ }
120
+ }
121
+ misplacedResultCount += expectedToolCallIds.size - contiguousResultIds.size;
122
+ }
123
+ return misplacedResultCount;
124
+ };
125
+ const normalizeToolCallHistory = async (messages, options) => {
126
+ var _a;
127
+ const toolResultsByCallId = /* @__PURE__ */ new Map();
128
+ const referencedToolCallIds = /* @__PURE__ */ new Set();
129
+ let toolCallCount = 0;
130
+ let toolResultCount = 0;
131
+ for (const message of messages) {
132
+ if (import_messages.AIMessage.isInstance(message)) {
133
+ for (const toolCall of getValidToolCalls(message)) {
134
+ toolCallCount++;
135
+ referencedToolCallIds.add(toolCall.id);
136
+ }
137
+ } else if (import_messages.ToolMessage.isInstance(message)) {
138
+ toolResultCount++;
139
+ if (message.tool_call_id) {
140
+ const results = toolResultsByCallId.get(message.tool_call_id) ?? [];
141
+ results.push(message);
142
+ toolResultsByCallId.set(message.tool_call_id, results);
143
+ }
144
+ }
145
+ }
146
+ const missingToolCallIds = Array.from(referencedToolCallIds).filter((id) => !toolResultsByCallId.has(id));
147
+ const persistedResults = missingToolCallIds.length ? await options.loadToolResults(missingToolCallIds) : /* @__PURE__ */ new Map();
148
+ const normalized = [];
149
+ const nextToolResultIndexByCallId = /* @__PURE__ */ new Map();
150
+ let restoredResultCount = 0;
151
+ let syntheticResultCount = 0;
152
+ let consumedExistingResultCount = 0;
153
+ for (const message of messages) {
154
+ if (import_messages.ToolMessage.isInstance(message)) {
155
+ continue;
156
+ }
157
+ normalized.push(message);
158
+ if (!import_messages.AIMessage.isInstance(message)) {
159
+ continue;
160
+ }
161
+ for (const toolCall of getValidToolCalls(message)) {
162
+ const existingResults = toolResultsByCallId.get(toolCall.id);
163
+ const existingResultIndex = nextToolResultIndexByCallId.get(toolCall.id) ?? 0;
164
+ const existingResult = existingResults == null ? void 0 : existingResults[existingResultIndex];
165
+ if (existingResult) {
166
+ nextToolResultIndexByCallId.set(toolCall.id, existingResultIndex + 1);
167
+ normalized.push(existingResult);
168
+ consumedExistingResultCount++;
169
+ continue;
170
+ }
171
+ const restoredResult = createRestoredToolResult(options.sessionId, toolCall, persistedResults.get(toolCall.id));
172
+ if (restoredResult) {
173
+ normalized.push(restoredResult);
174
+ restoredResultCount++;
175
+ continue;
176
+ }
177
+ normalized.push(createSyntheticToolResult(options.sessionId, toolCall));
178
+ syntheticResultCount++;
179
+ }
180
+ }
181
+ const stats = {
182
+ messageCount: messages.length,
183
+ toolCallCount,
184
+ missingResultCount: missingToolCallIds.length,
185
+ misplacedResultCount: countMisplacedResults(messages),
186
+ orphanResultCount: toolResultCount - consumedExistingResultCount,
187
+ restoredResultCount,
188
+ syntheticResultCount
189
+ };
190
+ (_a = options.logger) == null ? void 0 : _a.warn("Normalize invalid tool call history before model call", {
191
+ sessionId: options.sessionId,
192
+ ...stats
193
+ });
194
+ return normalized;
195
+ };
196
+ const toolResultIntegrityMiddleware = (options) => (0, import_langchain.createMiddleware)({
197
+ name: "ToolResultIntegrityMiddleware",
198
+ wrapModelCall: async (request, handler) => {
199
+ if (isToolCallHistoryValid(request.messages)) {
200
+ return handler(request);
201
+ }
202
+ request.messages = await normalizeToolCallHistory(request.messages, options);
203
+ return handler(request);
204
+ }
205
+ });
206
+ // Annotate the CommonJS export names for ESM import in node:
207
+ 0 && (module.exports = {
208
+ isToolCallHistoryValid,
209
+ normalizeToolCallHistory,
210
+ toolResultIntegrityMiddleware
211
+ });
@@ -88,10 +88,30 @@ const setDefaultKnowledgeBaseRetrievalStrategy = (ctx) => {
88
88
  values.knowledgeBase = (0, import_ai_knowledge_base.withDefaultKnowledgeBaseRetrievalStrategy)(values.knowledgeBase);
89
89
  }
90
90
  };
91
+ const readEmployeeValue = (employee, key) => typeof (employee == null ? void 0 : employee.get) === "function" ? employee.get(key) : employee == null ? void 0 : employee[key];
92
+ const validateKnowledgeBasePrompt = (ctx, employee) => {
93
+ const values = ctx.action.params.values;
94
+ if (!isRecord(values)) {
95
+ return;
96
+ }
97
+ const enableKnowledgeBase = typeof values.enableKnowledgeBase === "boolean" ? values.enableKnowledgeBase : readEmployeeValue(employee, "enableKnowledgeBase") === true;
98
+ const knowledgeBasePrompt = "knowledgeBasePrompt" in values ? values.knowledgeBasePrompt : readEmployeeValue(employee, "knowledgeBasePrompt");
99
+ if (enableKnowledgeBase && !(0, import_ai_employee_validation.hasKnowledgeBaseDataPlaceholder)(knowledgeBasePrompt)) {
100
+ ctx.throw(400, {
101
+ code: import_error_codes.AI_EMPLOYEE_KNOWLEDGE_BASE_PROMPT_INVALID,
102
+ message: ctx.t("The Knowledge Base Prompt must include {knowledgeBaseData} before you can save it."),
103
+ data: { field: "knowledgeBasePrompt" }
104
+ });
105
+ }
106
+ };
107
+ const hasKnowledgeBaseConfigurationChanges = (values) => isRecord(values) && ["enableKnowledgeBase", "knowledgeBase", "knowledgeBasePrompt"].some(
108
+ (key) => Object.prototype.hasOwnProperty.call(values, key)
109
+ );
91
110
  const create = async (ctx, next) => {
92
111
  var _a;
93
112
  validateAndNormalizeProfileValues(ctx);
94
113
  setDefaultKnowledgeBaseRetrievalStrategy(ctx);
114
+ validateKnowledgeBasePrompt(ctx);
95
115
  const username = (_a = ctx.action.params.values) == null ? void 0 : _a.username;
96
116
  if (typeof username === "string") {
97
117
  const existingEmployee = await ctx.db.getRepository("aiEmployees").findOne({
@@ -112,6 +132,12 @@ const create = async (ctx, next) => {
112
132
  };
113
133
  const update = async (ctx, next) => {
114
134
  validateAndNormalizeProfileValues(ctx);
135
+ if (hasKnowledgeBaseConfigurationChanges(ctx.action.params.values)) {
136
+ const employee = await ctx.db.getRepository("aiEmployees").findOne({
137
+ filterByTk: ctx.action.params.filterByTk
138
+ });
139
+ validateKnowledgeBasePrompt(ctx, employee);
140
+ }
115
141
  await import_actions.default.update(ctx, next);
116
142
  };
117
143
  const list = async (ctx, next) => {
@@ -11,7 +11,7 @@ import { Plugin } from '@nocobase/server';
11
11
  export declare abstract class Files {
12
12
  static resolvers(plugin: Plugin, attachmentPart: {
13
13
  attachments?: unknown[];
14
- }): {
14
+ }, fileUrlOrigin?: string): {
15
15
  resolveAttachments: (files: AIEmployeeInstructionFiles[]) => Promise<void>;
16
16
  resolveFileIds: (files: AIEmployeeInstructionFiles[]) => Promise<void>;
17
17
  resolveUrls: (files: AIEmployeeInstructionFiles[]) => Promise<void>;
@@ -44,6 +44,7 @@ var import_node_os = __toESM(require("node:os"));
44
44
  var import_node_path = __toESM(require("node:path"));
45
45
  var import_lodash = __toESM(require("lodash"));
46
46
  var import_axios = __toESM(require("axios"));
47
+ var import_plugin_file_manager = require("@nocobase/plugin-file-manager");
47
48
  var import_utils = require("../../utils");
48
49
  var import_attachments = require("../../../attachments");
49
50
  function appendSource(record, source) {
@@ -55,8 +56,45 @@ function appendSource(record, source) {
55
56
  source
56
57
  };
57
58
  }
59
+ function toFilePlainObject(record) {
60
+ const value = typeof record.toJSON === "function" ? record.toJSON() : record;
61
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
62
+ throw new Error("Invalid file record");
63
+ }
64
+ return value;
65
+ }
66
+ async function resolveInternalFileURL(plugin, url, origin) {
67
+ var _a;
68
+ const reference = (0, import_plugin_file_manager.parsePermanentFileReference)(url, origin);
69
+ if (!reference) {
70
+ return null;
71
+ }
72
+ if (reference.appName !== (plugin.app.name || "main")) {
73
+ throw new Error("File not found");
74
+ }
75
+ const dataSource = plugin.app.dataSourceManager.get(reference.dataSourceKey);
76
+ const collection = dataSource == null ? void 0 : dataSource.collectionManager.getCollection(reference.collectionName);
77
+ if (!dataSource || !collection || collection.name !== "attachments" && ((_a = collection.options) == null ? void 0 : _a.template) !== "file") {
78
+ throw new Error("File not found");
79
+ }
80
+ const record = await dataSource.collectionManager.getRepository(collection.name).findOne({
81
+ filter: { id: reference.id }
82
+ });
83
+ if (!record) {
84
+ throw new Error("File not found");
85
+ }
86
+ const file = toFilePlainObject(record);
87
+ if (file.storageId == null || reference.extname && reference.extname !== file.extname) {
88
+ throw new Error("File not found");
89
+ }
90
+ return appendSource(file, {
91
+ dataSourceKey: reference.dataSourceKey,
92
+ collectionName: collection.name,
93
+ trustworthy: true
94
+ });
95
+ }
58
96
  class Files {
59
- static resolvers(plugin, attachmentPart) {
97
+ static resolvers(plugin, attachmentPart, fileUrlOrigin) {
60
98
  const resolveAttachments = async (files) => {
61
99
  const attachments = files.filter((it) => it.type === "attachments").flatMap((it) => import_lodash.default.isArray(it.value) ? it.value : [it.value]).map(
62
100
  (attachment) => appendSource(attachment, {
@@ -114,6 +152,15 @@ class Files {
114
152
  const storageName = (_a = settings == null ? void 0 : settings.options) == null ? void 0 : _a.storage;
115
153
  const attachments = await Promise.all(
116
154
  urls.map(async (url) => {
155
+ const internalFile = await resolveInternalFileURL(plugin, url, fileUrlOrigin);
156
+ if (internalFile) {
157
+ return internalFile;
158
+ }
159
+ try {
160
+ new URL(url);
161
+ } catch (error) {
162
+ throw new Error(`File URL must be an absolute URL or a NocoBase permanent file URL: ${url}`);
163
+ }
117
164
  const response = await import_axios.default.get(url, {
118
165
  responseType: "arraybuffer"
119
166
  });
@@ -59,7 +59,8 @@ class AIEmployeeInstruction extends import_plugin_workflow.Instruction {
59
59
  model,
60
60
  requiresApproval = import_constants.REQUIRES_APPROVAL.NO_REQUIRED,
61
61
  userId,
62
- files
62
+ files,
63
+ fileUrlOrigin
63
64
  } = processor.getParsedValue(node.config, node.id);
64
65
  const toolName = import_ai.SYSTEM_TOOLS.WORK_FLOW_TASK_OUTPUT;
65
66
  const workflowSystemPrompt = `
@@ -153,7 +154,11 @@ ${typeof message.system === "object" ? JSON.stringify(message.system) : message.
153
154
  });
154
155
  const attachmentPart = {};
155
156
  if (files == null ? void 0 : files.length) {
156
- const { resolveAttachments, resolveFileIds, resolveUrls } = import_files.Files.resolvers(this.workflow, attachmentPart);
157
+ const { resolveAttachments, resolveFileIds, resolveUrls } = import_files.Files.resolvers(
158
+ this.workflow,
159
+ attachmentPart,
160
+ fileUrlOrigin
161
+ );
157
162
  await resolveAttachments(files);
158
163
  await resolveFileIds(files);
159
164
  await resolveUrls(files);
@@ -29,6 +29,7 @@ export type AIEmployeeInstructionConfig = {
29
29
  assignees?: string[];
30
30
  userId: string;
31
31
  files: AIEmployeeInstructionFiles[];
32
+ fileUrlOrigin?: string;
32
33
  };
33
34
  export type AIEmployeeInstructionFiles = {
34
35
  type: 'attachments' | 'file_id' | 'file_url';
package/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "description": "Create AI employees with diverse skills to collaborate with humans, build systems, and handle business operations.",
7
7
  "description.ru-RU": "Поддержка интеграции с AI-сервисами: предоставляются AI-узлы для рабочих процессов, расширяя возможности бизнес-обработки.",
8
8
  "description.zh-CN": "创建各种技能的 AI 员工,与人类协同,搭建系统,处理业务。",
9
- "version": "2.3.0-beta.6",
9
+ "version": "2.3.0-beta.8",
10
10
  "main": "dist/server/index.js",
11
11
  "homepage": "https://docs.nocobase.com/handbook/action-ai",
12
12
  "homepage.ru-RU": "https://docs-ru.nocobase.com/handbook/action-ai",
@@ -66,5 +66,5 @@
66
66
  "keywords": [
67
67
  "AI"
68
68
  ],
69
- "gitHead": "45b23791f5245d1417047b122d1a76764aaf5473"
69
+ "gitHead": "5d458884528593b2d3142a6a33de93bf0ad96543"
70
70
  }