@southwind-ai/modules 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/src/ai-operation-composition.ts +145 -0
- package/src/catalog-validation.ts +19 -2
- package/src/composition.ts +19 -37
- package/src/implementation-bindings.ts +70 -0
- package/src/manifest.ts +37 -1
- package/src/system-api-permissions.ts +4 -0
- package/src/system-api-watermark.ts +26 -0
- package/src/system-features.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@southwind-ai/modules",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"license": "UNLICENSED",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"!src/**/*.test.ts"
|
|
13
13
|
],
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@southwind-ai/shared": "0.1.
|
|
15
|
+
"@southwind-ai/shared": "0.1.2"
|
|
16
16
|
},
|
|
17
17
|
"exports": {
|
|
18
18
|
".": "./index.ts"
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ModuleAiOperationDefinition,
|
|
3
|
+
ModuleManifest,
|
|
4
|
+
} from "./manifest.js";
|
|
5
|
+
|
|
6
|
+
interface AiOperationCatalogs {
|
|
7
|
+
permissions: ReadonlySet<string>;
|
|
8
|
+
features: ReadonlySet<string>;
|
|
9
|
+
audits: ReadonlySet<string>;
|
|
10
|
+
tools: ReadonlySet<string>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function validateAiOperations(
|
|
14
|
+
modules: readonly ModuleManifest[],
|
|
15
|
+
catalogs: AiOperationCatalogs,
|
|
16
|
+
): ModuleAiOperationDefinition[] {
|
|
17
|
+
const operations = modules.flatMap((module) => module.aiOperations || []);
|
|
18
|
+
uniqueValues(
|
|
19
|
+
"AI Operation",
|
|
20
|
+
operations.map(({ key }) => key),
|
|
21
|
+
);
|
|
22
|
+
for (const module of modules) {
|
|
23
|
+
for (const operation of module.aiOperations || []) {
|
|
24
|
+
validateOperation(module.name, operation, catalogs);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return operations;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function validateAiOperationImplementations(
|
|
31
|
+
declarations: readonly ModuleAiOperationDefinition[],
|
|
32
|
+
implementations: readonly string[] | undefined,
|
|
33
|
+
): void {
|
|
34
|
+
if (!implementations) return;
|
|
35
|
+
const declared = new Set(declarations.map(({ key }) => key));
|
|
36
|
+
const registered = uniqueValues("AI Operation Handler", implementations);
|
|
37
|
+
for (const key of declared) {
|
|
38
|
+
if (!registered.has(key)) {
|
|
39
|
+
throw new Error(`AI Operation ${key} 缺少 Handler`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
for (const key of registered) {
|
|
43
|
+
if (!declared.has(key)) {
|
|
44
|
+
throw new Error(`AI Operation Handler ${key} 缺少 Manifest 声明`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function validateOperation(
|
|
50
|
+
moduleName: string,
|
|
51
|
+
operation: ModuleAiOperationDefinition,
|
|
52
|
+
catalogs: AiOperationCatalogs,
|
|
53
|
+
): void {
|
|
54
|
+
const strings = [
|
|
55
|
+
operation.key,
|
|
56
|
+
operation.featureKey,
|
|
57
|
+
operation.permissionKey,
|
|
58
|
+
operation.auditAction,
|
|
59
|
+
operation.inputSchemaKey,
|
|
60
|
+
operation.outputSchemaKey,
|
|
61
|
+
];
|
|
62
|
+
if (strings.some((value) => !value.trim())) {
|
|
63
|
+
throw new Error("AI Operation Key 与引用字段不能为空");
|
|
64
|
+
}
|
|
65
|
+
if (!operation.key.startsWith(`${moduleName}.`)) {
|
|
66
|
+
throw new Error(
|
|
67
|
+
`AI Operation ${operation.key} 必须使用模块命名空间 ${moduleName}`,
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
requiredReference(
|
|
71
|
+
operation.key,
|
|
72
|
+
"权限",
|
|
73
|
+
operation.permissionKey,
|
|
74
|
+
catalogs.permissions,
|
|
75
|
+
);
|
|
76
|
+
requiredReference(
|
|
77
|
+
operation.key,
|
|
78
|
+
"Feature",
|
|
79
|
+
operation.featureKey,
|
|
80
|
+
catalogs.features,
|
|
81
|
+
);
|
|
82
|
+
requiredReference(
|
|
83
|
+
operation.key,
|
|
84
|
+
"审计动作",
|
|
85
|
+
operation.auditAction,
|
|
86
|
+
catalogs.audits,
|
|
87
|
+
);
|
|
88
|
+
validateCapabilities(operation);
|
|
89
|
+
validateTools(operation, catalogs.tools);
|
|
90
|
+
validateLimits(operation);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function validateCapabilities(operation: ModuleAiOperationDefinition): void {
|
|
94
|
+
if (!operation.requiredCapabilities.length) {
|
|
95
|
+
throw new Error(`${operation.key} 至少需要一个 capability`);
|
|
96
|
+
}
|
|
97
|
+
validateNonEmptyValues(
|
|
98
|
+
`${operation.key} capability`,
|
|
99
|
+
operation.requiredCapabilities,
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function validateTools(
|
|
104
|
+
operation: ModuleAiOperationDefinition,
|
|
105
|
+
tools: ReadonlySet<string>,
|
|
106
|
+
): void {
|
|
107
|
+
validateNonEmptyValues(`${operation.key} Tool`, operation.allowedToolKeys);
|
|
108
|
+
for (const toolKey of operation.allowedToolKeys) {
|
|
109
|
+
requiredReference(operation.key, "Agent Tool", toolKey, tools);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function validateLimits(operation: ModuleAiOperationDefinition): void {
|
|
114
|
+
const values = Object.values(operation.limits);
|
|
115
|
+
if (values.some((value) => !Number.isSafeInteger(value) || value <= 0)) {
|
|
116
|
+
throw new Error(`${operation.key} 运行与输入输出上限必须是正整数`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function validateNonEmptyValues(label: string, values: readonly string[]) {
|
|
121
|
+
if (values.some((value) => !value.trim())) {
|
|
122
|
+
throw new Error(`${label} 不能为空`);
|
|
123
|
+
}
|
|
124
|
+
uniqueValues(label, values);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function requiredReference(
|
|
128
|
+
operationKey: string,
|
|
129
|
+
label: string,
|
|
130
|
+
key: string,
|
|
131
|
+
catalog: ReadonlySet<string>,
|
|
132
|
+
): void {
|
|
133
|
+
if (!catalog.has(key)) {
|
|
134
|
+
throw new Error(`${operationKey} 引用了未注册${label}:${key}`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function uniqueValues(label: string, values: readonly string[]): Set<string> {
|
|
139
|
+
const result = new Set<string>();
|
|
140
|
+
for (const value of values) {
|
|
141
|
+
if (result.has(value)) throw new Error(`${label} Key 重复:${value}`);
|
|
142
|
+
result.add(value);
|
|
143
|
+
}
|
|
144
|
+
return result;
|
|
145
|
+
}
|
|
@@ -2,6 +2,9 @@ import {
|
|
|
2
2
|
AUDIT_ACTION_DEFINITIONS,
|
|
3
3
|
isAuditActionDefinition,
|
|
4
4
|
LICENSE_CATALOG,
|
|
5
|
+
normalizeManifestBundles,
|
|
6
|
+
validateAndOrderBundles,
|
|
7
|
+
validateLicenseCatalog,
|
|
5
8
|
type AuditActionDefinition,
|
|
6
9
|
type FeatureFlagDefinition,
|
|
7
10
|
type LicenseCatalogDefinition,
|
|
@@ -15,15 +18,29 @@ export interface PlatformCapabilityCatalogs {
|
|
|
15
18
|
|
|
16
19
|
export function validateCapabilityCatalogs(
|
|
17
20
|
modules: readonly ModuleManifest[],
|
|
21
|
+
licenseCatalog: readonly LicenseCatalogDefinition[] = LICENSE_CATALOG,
|
|
18
22
|
): PlatformCapabilityCatalogs {
|
|
19
23
|
const auditActions = composeAuditActionCatalog(modules);
|
|
20
|
-
|
|
24
|
+
validateLicenseCatalog(licenseCatalog);
|
|
25
|
+
validateFeatureCatalog(modules, licenseCatalog);
|
|
26
|
+
validateMigrationBundleCatalog(modules);
|
|
21
27
|
return {
|
|
22
28
|
auditActions,
|
|
23
|
-
licenseVersions:
|
|
29
|
+
licenseVersions: licenseCatalog,
|
|
24
30
|
};
|
|
25
31
|
}
|
|
26
32
|
|
|
33
|
+
/**
|
|
34
|
+
* 启动期结构校验显式声明 migrationBundle 的模块;无 bundle 的旧式模块
|
|
35
|
+
* 由 drizzle 规范迁移管理,不参与模块级 Bundle 校验。
|
|
36
|
+
*/
|
|
37
|
+
export function validateMigrationBundleCatalog(
|
|
38
|
+
modules: readonly ModuleManifest[],
|
|
39
|
+
): void {
|
|
40
|
+
const bundled = modules.filter((module) => module.migrationBundle);
|
|
41
|
+
validateAndOrderBundles(normalizeManifestBundles(bundled));
|
|
42
|
+
}
|
|
43
|
+
|
|
27
44
|
export function composeAuditActionCatalog(
|
|
28
45
|
modules: readonly ModuleManifest[],
|
|
29
46
|
): AuditActionDefinition[] {
|
package/src/composition.ts
CHANGED
|
@@ -3,6 +3,8 @@ import type {
|
|
|
3
3
|
ModuleImplementationBindings,
|
|
4
4
|
ModuleManifest,
|
|
5
5
|
} from "./manifest.js";
|
|
6
|
+
import { validateAiOperations } from "./ai-operation-composition.js";
|
|
7
|
+
import { validateImplementationBindings } from "./implementation-bindings.js";
|
|
6
8
|
import { composeModuleMenus } from "./menu-composition.js";
|
|
7
9
|
import { validateCapabilityCatalogs } from "./catalog-validation.js";
|
|
8
10
|
|
|
@@ -58,10 +60,16 @@ export function validateModuleComposition(
|
|
|
58
60
|
tasks.map(({ key }) => key),
|
|
59
61
|
);
|
|
60
62
|
const tools = modules.flatMap((module) => module.tools);
|
|
61
|
-
unique(
|
|
63
|
+
const toolKeys = unique(
|
|
62
64
|
"Agent Tool",
|
|
63
65
|
tools.map(({ key }) => key),
|
|
64
66
|
);
|
|
67
|
+
const aiOperations = validateAiOperations(modules, {
|
|
68
|
+
permissions,
|
|
69
|
+
features,
|
|
70
|
+
audits,
|
|
71
|
+
tools: toolKeys,
|
|
72
|
+
});
|
|
65
73
|
const providers = modules.flatMap((module) => module.providers);
|
|
66
74
|
unique(
|
|
67
75
|
"Provider",
|
|
@@ -92,30 +100,16 @@ export function validateModuleComposition(
|
|
|
92
100
|
}
|
|
93
101
|
validateMenuRoutes(menus, adminRoutes);
|
|
94
102
|
validateSchemaMigrations(schemas);
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
validateImplementations(
|
|
106
|
-
"定时任务",
|
|
107
|
-
tasks.map(({ key }) => key),
|
|
108
|
-
implementations.tasks,
|
|
109
|
-
);
|
|
110
|
-
validateImplementations(
|
|
111
|
-
"Agent Tool",
|
|
112
|
-
tools.map(({ key }) => key),
|
|
113
|
-
implementations.tools,
|
|
114
|
-
);
|
|
115
|
-
validateImplementations(
|
|
116
|
-
"Provider",
|
|
117
|
-
providers.map(({ key }) => key),
|
|
118
|
-
implementations.providers,
|
|
103
|
+
validateImplementationBindings(
|
|
104
|
+
{
|
|
105
|
+
adminRoutes: adminRoutes.map(({ key }) => key),
|
|
106
|
+
apiRoutes: apiRoutes.map(apiRouteKey),
|
|
107
|
+
tasks: tasks.map(({ key }) => key),
|
|
108
|
+
tools: tools.map(({ key }) => key),
|
|
109
|
+
providers: providers.map(({ key }) => key),
|
|
110
|
+
},
|
|
111
|
+
aiOperations,
|
|
112
|
+
implementations,
|
|
119
113
|
);
|
|
120
114
|
|
|
121
115
|
return { modules, moduleOrder };
|
|
@@ -318,18 +312,6 @@ function validateSchemaMigrations(
|
|
|
318
312
|
}
|
|
319
313
|
}
|
|
320
314
|
|
|
321
|
-
function validateImplementations(
|
|
322
|
-
label: string,
|
|
323
|
-
declarations: readonly string[],
|
|
324
|
-
implementations: readonly string[] | undefined,
|
|
325
|
-
): void {
|
|
326
|
-
if (!implementations) return;
|
|
327
|
-
const registered = unique(`${label} Handler`, implementations);
|
|
328
|
-
for (const key of declarations) {
|
|
329
|
-
if (!registered.has(key)) throw new Error(`${label} ${key} 缺少 Handler`);
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
|
|
333
315
|
function unique(label: string, values: readonly string[]): Set<string> {
|
|
334
316
|
const result = new Set<string>();
|
|
335
317
|
for (const value of values) {
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { validateAiOperationImplementations } from "./ai-operation-composition.js";
|
|
2
|
+
import type {
|
|
3
|
+
ModuleAiOperationDefinition,
|
|
4
|
+
ModuleImplementationBindings,
|
|
5
|
+
} from "./manifest.js";
|
|
6
|
+
|
|
7
|
+
interface ModuleDeclarationKeys {
|
|
8
|
+
adminRoutes: readonly string[];
|
|
9
|
+
apiRoutes: readonly string[];
|
|
10
|
+
tasks: readonly string[];
|
|
11
|
+
tools: readonly string[];
|
|
12
|
+
providers: readonly string[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function validateImplementationBindings(
|
|
16
|
+
declarations: ModuleDeclarationKeys,
|
|
17
|
+
aiOperations: readonly ModuleAiOperationDefinition[],
|
|
18
|
+
implementations: ModuleImplementationBindings,
|
|
19
|
+
): void {
|
|
20
|
+
validateImplementations(
|
|
21
|
+
"Admin Route",
|
|
22
|
+
declarations.adminRoutes,
|
|
23
|
+
implementations.adminRoutes,
|
|
24
|
+
);
|
|
25
|
+
validateImplementations(
|
|
26
|
+
"API Route",
|
|
27
|
+
declarations.apiRoutes,
|
|
28
|
+
implementations.apiRoutes,
|
|
29
|
+
);
|
|
30
|
+
validateImplementations(
|
|
31
|
+
"定时任务",
|
|
32
|
+
declarations.tasks,
|
|
33
|
+
implementations.tasks,
|
|
34
|
+
);
|
|
35
|
+
validateImplementations(
|
|
36
|
+
"Agent Tool",
|
|
37
|
+
declarations.tools,
|
|
38
|
+
implementations.tools,
|
|
39
|
+
);
|
|
40
|
+
validateAiOperationImplementations(
|
|
41
|
+
aiOperations,
|
|
42
|
+
implementations.aiOperations,
|
|
43
|
+
);
|
|
44
|
+
validateImplementations(
|
|
45
|
+
"Provider",
|
|
46
|
+
declarations.providers,
|
|
47
|
+
implementations.providers,
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function validateImplementations(
|
|
52
|
+
label: string,
|
|
53
|
+
declarations: readonly string[],
|
|
54
|
+
implementations: readonly string[] | undefined,
|
|
55
|
+
): void {
|
|
56
|
+
if (!implementations) return;
|
|
57
|
+
const registered = unique(`${label} Handler`, implementations);
|
|
58
|
+
for (const key of declarations) {
|
|
59
|
+
if (!registered.has(key)) throw new Error(`${label} ${key} 缺少 Handler`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function unique(label: string, values: readonly string[]): Set<string> {
|
|
64
|
+
const result = new Set<string>();
|
|
65
|
+
for (const value of values) {
|
|
66
|
+
if (result.has(value)) throw new Error(`${label} Key 重复:${value}`);
|
|
67
|
+
result.add(value);
|
|
68
|
+
}
|
|
69
|
+
return result;
|
|
70
|
+
}
|
package/src/manifest.ts
CHANGED
|
@@ -11,7 +11,10 @@ import type {
|
|
|
11
11
|
SearchProviderManifestDefinition,
|
|
12
12
|
} from "@southwind-ai/shared";
|
|
13
13
|
|
|
14
|
-
export type {
|
|
14
|
+
export type {
|
|
15
|
+
MigrationDefinition,
|
|
16
|
+
MigrationDirection,
|
|
17
|
+
} from "@southwind-ai/shared";
|
|
15
18
|
|
|
16
19
|
export interface ModuleToolDefinition {
|
|
17
20
|
key: string;
|
|
@@ -59,6 +62,37 @@ export interface ModuleScheduledTaskDefinition {
|
|
|
59
62
|
retryDelaySeconds: number;
|
|
60
63
|
}
|
|
61
64
|
|
|
65
|
+
export type AiOperationKind = "agent" | "document" | "speech";
|
|
66
|
+
export type AiOperationDataClassification = "public" | "internal" | "sensitive";
|
|
67
|
+
export type AiOperationExecution =
|
|
68
|
+
| "interactive"
|
|
69
|
+
| "streaming"
|
|
70
|
+
| "background"
|
|
71
|
+
| "live";
|
|
72
|
+
export type AiOperationPublicNetworkAccess = "forbidden" | "allowed";
|
|
73
|
+
|
|
74
|
+
export interface AiOperationLimits {
|
|
75
|
+
maxDurationSeconds: number;
|
|
76
|
+
maxInputBytes: number;
|
|
77
|
+
maxOutputBytes: number;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface ModuleAiOperationDefinition {
|
|
81
|
+
key: string;
|
|
82
|
+
kind: AiOperationKind;
|
|
83
|
+
featureKey: string;
|
|
84
|
+
permissionKey: string;
|
|
85
|
+
auditAction: AuditActionType;
|
|
86
|
+
inputSchemaKey: string;
|
|
87
|
+
outputSchemaKey: string;
|
|
88
|
+
requiredCapabilities: readonly string[];
|
|
89
|
+
allowedToolKeys: readonly string[];
|
|
90
|
+
dataClassification: AiOperationDataClassification;
|
|
91
|
+
execution: AiOperationExecution;
|
|
92
|
+
publicNetworkAccess: AiOperationPublicNetworkAccess;
|
|
93
|
+
limits: AiOperationLimits;
|
|
94
|
+
}
|
|
95
|
+
|
|
62
96
|
export interface ModuleManifest {
|
|
63
97
|
name: string;
|
|
64
98
|
version: string;
|
|
@@ -75,6 +109,7 @@ export interface ModuleManifest {
|
|
|
75
109
|
schemaExports: ModuleSchemaExportDefinition[];
|
|
76
110
|
tasks: ModuleScheduledTaskDefinition[];
|
|
77
111
|
tools: ModuleToolDefinition[];
|
|
112
|
+
aiOperations?: ModuleAiOperationDefinition[];
|
|
78
113
|
providers: Array<ModuleProviderDefinition | SearchProviderManifestDefinition>;
|
|
79
114
|
dataPolicies?: DataGovernancePolicyDefinition[];
|
|
80
115
|
auditActions: AuditActionRegistration[];
|
|
@@ -88,5 +123,6 @@ export interface ModuleImplementationBindings {
|
|
|
88
123
|
apiRoutes?: readonly string[];
|
|
89
124
|
tasks?: readonly string[];
|
|
90
125
|
tools?: readonly string[];
|
|
126
|
+
aiOperations?: readonly string[];
|
|
91
127
|
providers?: readonly string[];
|
|
92
128
|
}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import type { ApiPermissionBinding } from "@southwind-ai/shared";
|
|
2
2
|
import { binding, resourceBindings } from "./system-api-binding.js";
|
|
3
|
+
// @windy-module system.settings begin
|
|
4
|
+
import { watermarkApiPermissionBindings } from "./system-api-watermark.js";
|
|
5
|
+
// @windy-module system.settings end
|
|
3
6
|
// @windy-module system.data-governance begin
|
|
4
7
|
import { dataGovernanceApiPermissionBindings } from "./system-api-data-governance.js";
|
|
5
8
|
// @windy-module system.data-governance end
|
|
@@ -109,6 +112,7 @@ export function systemApiPermissionBindings(): ApiPermissionBinding[] {
|
|
|
109
112
|
"system.settings.manage",
|
|
110
113
|
"system.settings",
|
|
111
114
|
),
|
|
115
|
+
...watermarkApiPermissionBindings(),
|
|
112
116
|
// @windy-module system.settings end
|
|
113
117
|
// @windy-module system.configuration begin
|
|
114
118
|
binding(
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ApiPermissionBinding } from "@southwind-ai/shared";
|
|
2
|
+
import { binding } from "./system-api-binding.js";
|
|
3
|
+
|
|
4
|
+
export function watermarkApiPermissionBindings(): ApiPermissionBinding[] {
|
|
5
|
+
return [
|
|
6
|
+
binding(
|
|
7
|
+
"GET",
|
|
8
|
+
"/api/platform/watermark-policy",
|
|
9
|
+
"system.watermark.read",
|
|
10
|
+
undefined,
|
|
11
|
+
true,
|
|
12
|
+
),
|
|
13
|
+
binding(
|
|
14
|
+
"GET",
|
|
15
|
+
"/api/system/settings/watermark",
|
|
16
|
+
"system.watermark.read",
|
|
17
|
+
"system.watermark",
|
|
18
|
+
),
|
|
19
|
+
binding(
|
|
20
|
+
"PUT",
|
|
21
|
+
"/api/system/settings/watermark",
|
|
22
|
+
"system.watermark.manage",
|
|
23
|
+
"system.watermark",
|
|
24
|
+
),
|
|
25
|
+
];
|
|
26
|
+
}
|
package/src/system-features.ts
CHANGED