@nocobase/ai 2.1.0-beta.2 → 2.1.0-beta.20
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/LICENSE +201 -661
- package/README.md +79 -10
- package/lib/ai-employee-manager/index.d.ts +29 -0
- package/lib/ai-employee-manager/index.js +167 -0
- package/lib/ai-employee-manager/types.d.ts +56 -0
- package/lib/ai-employee-manager/types.js +24 -0
- package/lib/ai-manager.d.ts +8 -0
- package/lib/ai-manager.js +12 -0
- package/lib/document-loader/index.d.ts +10 -0
- package/lib/document-loader/index.js +90 -0
- package/lib/document-loader/loader.worker.d.ts +9 -0
- package/lib/document-loader/loader.worker.js +83 -0
- package/lib/document-loader/vendor/langchain/document_loaders/fs/text.d.ts +20 -0
- package/lib/document-loader/vendor/langchain/document_loaders/fs/text.js +99 -0
- package/lib/document-loader/xlsx.d.ts +10 -0
- package/lib/document-loader/xlsx.js +100 -0
- package/lib/index.d.ts +5 -0
- package/lib/index.js +11 -1
- package/lib/loader/employee.d.ts +37 -0
- package/lib/loader/employee.js +207 -0
- package/lib/loader/index.d.ts +3 -0
- package/lib/loader/index.js +7 -1
- package/lib/loader/mcp.d.ts +35 -0
- package/lib/loader/mcp.js +108 -0
- package/lib/loader/skills.d.ts +43 -0
- package/lib/loader/skills.js +138 -0
- package/lib/loader/tools.d.ts +1 -0
- package/lib/loader/tools.js +4 -3
- package/lib/mcp-manager/index.d.ts +43 -0
- package/lib/mcp-manager/index.js +341 -0
- package/lib/mcp-manager/types.d.ts +61 -0
- package/lib/mcp-manager/types.js +24 -0
- package/lib/mcp-tools-manager.d.ts +43 -0
- package/lib/mcp-tools-manager.js +77 -0
- package/lib/skills-manager/index.d.ts +29 -0
- package/lib/skills-manager/index.js +169 -0
- package/lib/skills-manager/types.d.ts +33 -0
- package/lib/skills-manager/types.js +24 -0
- package/lib/tools-manager/index.d.ts +2 -1
- package/lib/tools-manager/index.js +17 -7
- package/lib/tools-manager/types.d.ts +12 -4
- package/package.json +27 -7
- package/src/__tests__/ai-employees.test.ts +108 -0
- package/src/__tests__/mcp.test.ts +105 -0
- package/src/__tests__/resource/ai/ai-employees/index-employee/index.ts +16 -0
- package/src/__tests__/resource/ai/ai-employees/index-employee/prompt.md +1 -0
- package/src/__tests__/resource/ai/ai-employees/named-file-employee.ts +16 -0
- package/src/__tests__/resource/ai/ai-employees/with-skills/index.ts +16 -0
- package/src/__tests__/resource/ai/ai-employees/with-skills/skills/analysis/SKILLS.md +6 -0
- package/src/__tests__/resource/ai/ai-employees/with-skills-merge/index.ts +17 -0
- package/src/__tests__/resource/ai/ai-employees/with-skills-merge/skills/discovered-skill/SKILLS.md +6 -0
- package/src/__tests__/resource/ai/ai-employees/with-tools/index.ts +16 -0
- package/src/__tests__/resource/ai/ai-employees/with-tools/tools/discoveredTool.ts +23 -0
- package/src/__tests__/resource/ai/ai-employees/with-tools-merge/index.ts +16 -0
- package/src/__tests__/resource/ai/ai-employees/with-tools-merge/tools/discoveredTool.ts +23 -0
- package/src/__tests__/resource/ai/mcp/weather.ts +25 -0
- package/src/__tests__/resource/ai/skills/data-modeling/SKILLS.md +24 -0
- package/src/__tests__/resource/ai/skills/data-modeling/tools/read.ts +23 -0
- package/src/__tests__/resource/ai/skills/data-modeling/tools/search/description.md +1 -0
- package/src/__tests__/resource/ai/skills/data-modeling/tools/search/index.ts +23 -0
- package/src/__tests__/resource/ai/skills/document/tools/read.ts +1 -1
- package/src/__tests__/resource/ai/skills/document/tools/search/index.ts +1 -1
- package/src/__tests__/resource/ai/tools/desc/index.ts +1 -1
- package/src/__tests__/resource/ai/tools/group/group1.ts +1 -1
- package/src/__tests__/resource/ai/tools/group/group2.ts +1 -1
- package/src/__tests__/resource/ai/tools/group/group3/index.ts +1 -1
- package/src/__tests__/resource/ai/tools/hallow/index.ts +1 -1
- package/src/__tests__/resource/ai/tools/print.ts +1 -1
- package/src/__tests__/skills.test.ts +55 -0
- package/src/__tests__/tools.test.ts +5 -3
- package/src/ai-employee-manager/index.ts +148 -0
- package/src/ai-employee-manager/types.ts +63 -0
- package/src/ai-manager.ts +12 -0
- package/src/document-loader/index.ts +57 -0
- package/src/document-loader/loader.worker.ts +100 -0
- package/src/document-loader/vendor/langchain/document_loaders/fs/text.ts +72 -0
- package/src/document-loader/xlsx.ts +82 -0
- package/src/index.ts +5 -0
- package/src/loader/employee.ts +194 -0
- package/src/loader/index.ts +3 -0
- package/src/loader/mcp.ts +101 -0
- package/src/loader/skills.ts +129 -0
- package/src/loader/tools.ts +3 -2
- package/src/mcp-manager/index.ts +364 -0
- package/src/mcp-manager/types.ts +68 -0
- package/src/mcp-tools-manager.ts +90 -0
- package/src/skills-manager/index.ts +148 -0
- package/src/skills-manager/types.ts +38 -0
- package/src/tools-manager/index.ts +18 -7
- package/src/tools-manager/types.ts +13 -4
|
@@ -0,0 +1,194 @@
|
|
|
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
|
+
import { importModule } from '@nocobase/utils';
|
|
11
|
+
import { DirectoryScanner, DirectoryScannerOptions, FileDescriptor } from './scanner';
|
|
12
|
+
import { existsSync } from 'fs';
|
|
13
|
+
import { readFile } from 'fs/promises';
|
|
14
|
+
import { AIManager } from '../ai-manager';
|
|
15
|
+
import { LoadAndRegister } from './types';
|
|
16
|
+
import { Logger } from '@nocobase/logger';
|
|
17
|
+
import path from 'path';
|
|
18
|
+
import { AIEmployeeOptions, AIEmployeeToolSetting } from '../ai-employee-manager';
|
|
19
|
+
|
|
20
|
+
export type AIEmployeeLoaderOptions = { pluginName: string; scan: DirectoryScannerOptions; log?: Logger };
|
|
21
|
+
|
|
22
|
+
export class AIEmployeeLoader extends LoadAndRegister<AIEmployeeLoaderOptions> {
|
|
23
|
+
protected readonly scanner: DirectoryScanner;
|
|
24
|
+
|
|
25
|
+
protected files: FileDescriptor[] = [];
|
|
26
|
+
protected employeeDescriptors: AIEmployeeDescriptor[] = [];
|
|
27
|
+
protected log: Logger;
|
|
28
|
+
|
|
29
|
+
constructor(
|
|
30
|
+
protected readonly ai: AIManager,
|
|
31
|
+
protected readonly options: AIEmployeeLoaderOptions,
|
|
32
|
+
) {
|
|
33
|
+
super(ai, options);
|
|
34
|
+
this.log = options.log;
|
|
35
|
+
this.scanner = new DirectoryScanner(this.options.scan);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
protected async scan(): Promise<void> {
|
|
39
|
+
this.files = await this.scanner.scan();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
protected async import(): Promise<void> {
|
|
43
|
+
if (!this.files.length) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const grouped = new Map<string, FileDescriptor[]>();
|
|
48
|
+
for (const fd of this.files) {
|
|
49
|
+
const employeeRoot = getEmployeeRoot(fd);
|
|
50
|
+
const group = grouped.get(employeeRoot) ?? [];
|
|
51
|
+
group.push(fd);
|
|
52
|
+
grouped.set(employeeRoot, group);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const descriptors = await Promise.all(
|
|
56
|
+
Array.from(grouped.entries()).map(async ([employeeRoot, fds]) => {
|
|
57
|
+
const file = fds.find((fd) => fd.extname === '.ts' || fd.extname === '.js');
|
|
58
|
+
if (!file || !existsSync(file.path)) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
const promptFile = fds.find((fd) => fd.basename === 'prompt.md');
|
|
62
|
+
|
|
63
|
+
const name = path.basename(employeeRoot);
|
|
64
|
+
try {
|
|
65
|
+
const imported = await importModule(file.path);
|
|
66
|
+
const mod = imported?.default ?? imported;
|
|
67
|
+
const employeeOptions = typeof mod === 'function' ? mod() : mod;
|
|
68
|
+
if (!employeeOptions || !employeeOptions.username) {
|
|
69
|
+
this.log?.warn(`ai employee [${name}] register ignored: invalid definition at ${file.path}`);
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
const { skills = [], tools = [] } = employeeOptions;
|
|
73
|
+
|
|
74
|
+
if (promptFile && existsSync(promptFile.path)) {
|
|
75
|
+
try {
|
|
76
|
+
employeeOptions.systemPrompt = await readFile(promptFile.path, 'utf-8');
|
|
77
|
+
} catch (e) {
|
|
78
|
+
this.log?.error(
|
|
79
|
+
`ai employee [${name}] load fail: error occur when reading prompt.md at ${promptFile.path}`,
|
|
80
|
+
e,
|
|
81
|
+
);
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
name,
|
|
88
|
+
employeeRoot,
|
|
89
|
+
file,
|
|
90
|
+
options: {
|
|
91
|
+
...employeeOptions,
|
|
92
|
+
skills: [...skills, ...(await discoverSkills(employeeRoot))],
|
|
93
|
+
tools: [...tools, ...(await discoverTools(employeeRoot))],
|
|
94
|
+
} as AIEmployeeOptions,
|
|
95
|
+
} satisfies AIEmployeeDescriptor;
|
|
96
|
+
} catch (e) {
|
|
97
|
+
this.log?.error(`ai employee [${name}] load fail: error occur when import ${file.path}`, e);
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
}),
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
this.employeeDescriptors = descriptors.filter((item): item is AIEmployeeDescriptor => Boolean(item));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
protected async register(): Promise<void> {
|
|
107
|
+
if (!this.employeeDescriptors.length) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
const { employeeManager } = this.ai;
|
|
111
|
+
for (const descriptor of this.employeeDescriptors) {
|
|
112
|
+
await employeeManager.registerEmployee(descriptor.options);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export type AIEmployeeDescriptor = {
|
|
118
|
+
name: string;
|
|
119
|
+
employeeRoot: string;
|
|
120
|
+
file: FileDescriptor;
|
|
121
|
+
options: AIEmployeeOptions;
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
function getEmployeeRoot(fd: FileDescriptor) {
|
|
125
|
+
if (fd.basename === 'index.ts' || fd.basename === 'index.js' || fd.basename === 'prompt.md') {
|
|
126
|
+
return path.dirname(fd.path);
|
|
127
|
+
}
|
|
128
|
+
return fd.path;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async function discoverSkills(employeeRoot: string): Promise<string[]> {
|
|
132
|
+
const skillsDir = path.join(employeeRoot, 'skills');
|
|
133
|
+
if (!existsSync(skillsDir)) {
|
|
134
|
+
return [];
|
|
135
|
+
}
|
|
136
|
+
const scanner = new DirectoryScanner({
|
|
137
|
+
basePath: employeeRoot,
|
|
138
|
+
pattern: ['skills/**/SKILLS.md'],
|
|
139
|
+
});
|
|
140
|
+
const skillFiles = await scanner.scan();
|
|
141
|
+
return uniq(skillFiles.map((it) => it.directory).filter(Boolean));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async function discoverTools(employeeRoot: string): Promise<AIEmployeeToolSetting[]> {
|
|
145
|
+
const toolsDir = path.join(employeeRoot, 'tools');
|
|
146
|
+
if (!existsSync(toolsDir)) {
|
|
147
|
+
return [];
|
|
148
|
+
}
|
|
149
|
+
const scanner = new DirectoryScanner({
|
|
150
|
+
basePath: employeeRoot,
|
|
151
|
+
pattern: ['tools/**/*.ts', 'tools/**/*.js', '!tools/**/*.d.ts'],
|
|
152
|
+
});
|
|
153
|
+
const toolFiles = await scanner.scan();
|
|
154
|
+
return uniqBy(
|
|
155
|
+
toolFiles
|
|
156
|
+
.map((it) =>
|
|
157
|
+
normalizeAIEmployeeToolSetting(
|
|
158
|
+
it.basename === 'index.ts' || it.basename === 'index.js' ? it.directory : it.name,
|
|
159
|
+
),
|
|
160
|
+
)
|
|
161
|
+
.filter((tool): tool is AIEmployeeToolSetting => Boolean(tool?.name)),
|
|
162
|
+
'name',
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function uniq<T>(values: T[]) {
|
|
167
|
+
return [...new Set(values)];
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function uniqBy<T extends Record<string, unknown>>(values: T[], key: keyof T) {
|
|
171
|
+
const valueMap = new Map<unknown, T>();
|
|
172
|
+
for (const value of values) {
|
|
173
|
+
valueMap.set(value[key], value);
|
|
174
|
+
}
|
|
175
|
+
return [...valueMap.values()];
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function normalizeAIEmployeeToolSetting(
|
|
179
|
+
tool?: string | AIEmployeeToolSetting,
|
|
180
|
+
): AIEmployeeToolSetting | undefined {
|
|
181
|
+
if (!tool) {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
if (typeof tool === 'string') {
|
|
185
|
+
return { name: tool };
|
|
186
|
+
}
|
|
187
|
+
if (!tool.name) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
return {
|
|
191
|
+
name: tool.name,
|
|
192
|
+
...(tool.autoCall != null ? { autoCall: tool.autoCall } : {}),
|
|
193
|
+
};
|
|
194
|
+
}
|
package/src/loader/index.ts
CHANGED
|
@@ -0,0 +1,101 @@
|
|
|
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
|
+
import { importModule } from '@nocobase/utils';
|
|
11
|
+
import { existsSync } from 'fs';
|
|
12
|
+
import { Logger } from '@nocobase/logger';
|
|
13
|
+
import { AIManager } from '../ai-manager';
|
|
14
|
+
import { MCPOptions } from '../mcp-manager';
|
|
15
|
+
import { LoadAndRegister } from './types';
|
|
16
|
+
import { DirectoryScanner, DirectoryScannerOptions, FileDescriptor } from './scanner';
|
|
17
|
+
import { isNonEmptyObject } from './utils';
|
|
18
|
+
|
|
19
|
+
export type MCPLoaderOptions = { pluginName: string; scan: DirectoryScannerOptions; log?: Logger };
|
|
20
|
+
|
|
21
|
+
export class MCPLoader extends LoadAndRegister<MCPLoaderOptions> {
|
|
22
|
+
protected readonly scanner: DirectoryScanner;
|
|
23
|
+
|
|
24
|
+
protected files: FileDescriptor[] = [];
|
|
25
|
+
protected mcpDescriptors: MCPDescriptor[] = [];
|
|
26
|
+
protected log: Logger;
|
|
27
|
+
|
|
28
|
+
constructor(
|
|
29
|
+
protected readonly ai: AIManager,
|
|
30
|
+
protected readonly options: MCPLoaderOptions,
|
|
31
|
+
) {
|
|
32
|
+
super(ai, options);
|
|
33
|
+
this.log = options.log;
|
|
34
|
+
this.scanner = new DirectoryScanner(this.options.scan);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
protected async scan(): Promise<void> {
|
|
38
|
+
this.files = await this.scanner.scan();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
protected async import(): Promise<void> {
|
|
42
|
+
if (!this.files.length) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const descriptors = await Promise.all(
|
|
47
|
+
this.files.map(async (file) => {
|
|
48
|
+
const name = file.name;
|
|
49
|
+
if (!existsSync(file.path)) {
|
|
50
|
+
this.log?.error(`mcp [${name}] ignored: can not find definition file at ${file.path}`);
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
const imported = await importModule(file.path);
|
|
56
|
+
const mod = imported?.default ?? imported;
|
|
57
|
+
const options = typeof mod === 'function' ? mod() : mod;
|
|
58
|
+
|
|
59
|
+
if (!isNonEmptyObject(options)) {
|
|
60
|
+
this.log?.warn(`mcp [${name}] register ignored: invalid definition at ${file.path}`);
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
name,
|
|
66
|
+
file,
|
|
67
|
+
options: options as MCPOptions,
|
|
68
|
+
} satisfies MCPDescriptor;
|
|
69
|
+
} catch (e) {
|
|
70
|
+
this.log?.error(`mcp [${name}] load fail: error occur when import ${file.path}`, e);
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}),
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
this.mcpDescriptors = descriptors.filter((item): item is MCPDescriptor => Boolean(item));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
protected async register(): Promise<void> {
|
|
80
|
+
if (!this.mcpDescriptors.length) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const { mcpManager } = this.ai;
|
|
85
|
+
for (const descriptor of this.mcpDescriptors) {
|
|
86
|
+
try {
|
|
87
|
+
await mcpManager.registerMCP({
|
|
88
|
+
[descriptor.name]: descriptor.options,
|
|
89
|
+
});
|
|
90
|
+
} catch (e) {
|
|
91
|
+
this.log?.error(`mcp [${descriptor.name}] register ignored: error occur when invoke registerMCP`, e);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export type MCPDescriptor = {
|
|
98
|
+
name: string;
|
|
99
|
+
file: FileDescriptor;
|
|
100
|
+
options: MCPOptions;
|
|
101
|
+
};
|
|
@@ -0,0 +1,129 @@
|
|
|
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
|
+
import { DirectoryScanner, DirectoryScannerOptions, FileDescriptor } from './scanner';
|
|
11
|
+
import { readFile } from 'fs/promises';
|
|
12
|
+
import _ from 'lodash';
|
|
13
|
+
import { existsSync } from 'fs';
|
|
14
|
+
import { AIManager } from '../ai-manager';
|
|
15
|
+
import { LoadAndRegister } from './types';
|
|
16
|
+
import { Logger } from '@nocobase/logger';
|
|
17
|
+
import matter from 'gray-matter';
|
|
18
|
+
import path from 'path';
|
|
19
|
+
import { SkillsScope } from '../skills-manager';
|
|
20
|
+
|
|
21
|
+
export type SkillsLoaderOptions = { pluginName: string; scan: DirectoryScannerOptions; log?: Logger };
|
|
22
|
+
export class SkillsLoader extends LoadAndRegister<SkillsLoaderOptions> {
|
|
23
|
+
protected readonly scanner: DirectoryScanner;
|
|
24
|
+
|
|
25
|
+
protected files: FileDescriptor[] = [];
|
|
26
|
+
protected skillsDescriptors: SkillsDescriptor[] = [];
|
|
27
|
+
protected log: Logger;
|
|
28
|
+
|
|
29
|
+
constructor(
|
|
30
|
+
protected readonly ai: AIManager,
|
|
31
|
+
protected readonly options: SkillsLoaderOptions,
|
|
32
|
+
) {
|
|
33
|
+
super(ai, options);
|
|
34
|
+
this.log = options.log;
|
|
35
|
+
this.scanner = new DirectoryScanner(this.options.scan);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
protected async scan(): Promise<void> {
|
|
39
|
+
this.files = await this.scanner.scan();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
protected async import(): Promise<void> {
|
|
43
|
+
if (!this.files.length) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
this.skillsDescriptors = await Promise.all(
|
|
48
|
+
this.files
|
|
49
|
+
.map(async (skillsFile) => {
|
|
50
|
+
if (skillsFile.basename !== 'SKILLS.md') {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
if (!existsSync(skillsFile.path)) {
|
|
54
|
+
this.log?.error(`skills [${skillsFile.directory}] ignored: can not find SKILLS.md at ${skillsFile.path}`);
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
const skillsDir = new FileDescriptor(path.dirname(skillsFile.path));
|
|
58
|
+
const name = skillsFile.directory;
|
|
59
|
+
const entry: Partial<SkillsDescriptor> = { name, skillsFile, skillsDir };
|
|
60
|
+
try {
|
|
61
|
+
const skills = await readFile(skillsFile.path, 'utf-8');
|
|
62
|
+
const { data, content } = matter(skills);
|
|
63
|
+
entry.scope = data['scope'] ?? 'SPECIFIED';
|
|
64
|
+
entry.name = data['name'];
|
|
65
|
+
entry.description = data['description'];
|
|
66
|
+
entry.content = content;
|
|
67
|
+
entry.introduction = data['introduction'];
|
|
68
|
+
entry.tools = data['tools'] ?? [];
|
|
69
|
+
} catch (e) {
|
|
70
|
+
this.log?.error(`skills [${name}] load fail: error occur when reading SKILLS.md at ${skillsFile.path}`, e);
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
const toolsScanner = new DirectoryScanner({
|
|
76
|
+
basePath: skillsDir.path,
|
|
77
|
+
pattern: ['tools/**/*.ts', 'tools/**/*.js', '!tools/**/*.d.ts'],
|
|
78
|
+
});
|
|
79
|
+
const toolsFiles = await toolsScanner.scan();
|
|
80
|
+
entry.tools = Array.from(
|
|
81
|
+
new Set([
|
|
82
|
+
...entry.tools,
|
|
83
|
+
...toolsFiles.map((it) =>
|
|
84
|
+
it.basename === 'index.ts' || it.basename === 'index.js' ? it.directory : it.name,
|
|
85
|
+
),
|
|
86
|
+
]),
|
|
87
|
+
);
|
|
88
|
+
} catch (e) {
|
|
89
|
+
this.log?.error(`skills [${name}] load fail: error occur when loading tools at ${skillsDir.path}`, e);
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return entry as SkillsDescriptor;
|
|
94
|
+
})
|
|
95
|
+
.filter((it) => it != null),
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
protected async register(): Promise<void> {
|
|
100
|
+
if (!this.skillsDescriptors.length) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const { skillsManager } = this.ai;
|
|
104
|
+
for (const descriptor of this.skillsDescriptors) {
|
|
105
|
+
await skillsManager.registerSkills({
|
|
106
|
+
scope: descriptor.scope,
|
|
107
|
+
name: descriptor.name,
|
|
108
|
+
description: descriptor.description,
|
|
109
|
+
content: descriptor.content,
|
|
110
|
+
tools: descriptor.tools,
|
|
111
|
+
introduction: descriptor.introduction,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export type SkillsDescriptor = {
|
|
118
|
+
scope: SkillsScope;
|
|
119
|
+
name: string;
|
|
120
|
+
description: string;
|
|
121
|
+
content: string;
|
|
122
|
+
skillsFile: FileDescriptor;
|
|
123
|
+
skillsDir: FileDescriptor;
|
|
124
|
+
tools?: string[];
|
|
125
|
+
introduction?: {
|
|
126
|
+
title: string;
|
|
127
|
+
about?: string;
|
|
128
|
+
};
|
|
129
|
+
};
|
package/src/loader/tools.ts
CHANGED
|
@@ -18,7 +18,7 @@ import { LoadAndRegister } from './types';
|
|
|
18
18
|
import { Logger } from '@nocobase/logger';
|
|
19
19
|
import { isNonEmptyObject } from './utils';
|
|
20
20
|
|
|
21
|
-
export type ToolsLoaderOptions = { scan: DirectoryScannerOptions; log?: Logger };
|
|
21
|
+
export type ToolsLoaderOptions = { pluginName: string; scan: DirectoryScannerOptions; log?: Logger };
|
|
22
22
|
export class ToolsLoader extends LoadAndRegister<ToolsLoaderOptions> {
|
|
23
23
|
protected readonly scanner: DirectoryScanner;
|
|
24
24
|
|
|
@@ -106,7 +106,7 @@ export class ToolsLoader extends LoadAndRegister<ToolsLoaderOptions> {
|
|
|
106
106
|
continue;
|
|
107
107
|
}
|
|
108
108
|
const { name, toolsOptions, description } = descriptor;
|
|
109
|
-
if (
|
|
109
|
+
if (toolsManager.isToolsExisted(name)) {
|
|
110
110
|
this.log?.warn(`tools [${descriptor.name}] register ignored: duplicate register for tools`);
|
|
111
111
|
continue;
|
|
112
112
|
}
|
|
@@ -118,6 +118,7 @@ export class ToolsLoader extends LoadAndRegister<ToolsLoaderOptions> {
|
|
|
118
118
|
}
|
|
119
119
|
try {
|
|
120
120
|
toolsManager.registerTools(toolsOptions);
|
|
121
|
+
this.log?.info(`tools [${toolsOptions.definition.name}] registered from plugin [${this.options.pluginName}]`);
|
|
121
122
|
} catch (e) {
|
|
122
123
|
this.log?.error(`tools [${descriptor.name}] register ignored: error occur when invoke registerTools`, e);
|
|
123
124
|
continue;
|