create-windy 0.2.8 → 0.2.9
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/cli.js +2 -1
- package/package.json +1 -1
- package/template/.windy-template.json +2 -2
- package/template/apps/server/src/index.ts +1 -0
- package/template/packages/modules/src/ai-operation-composition.test.ts +195 -0
- package/template/packages/modules/src/ai-operation-composition.ts +145 -0
- package/template/packages/modules/src/composition.ts +19 -37
- package/template/packages/modules/src/implementation-bindings.ts +70 -0
- package/template/packages/modules/src/manifest.ts +37 -1
package/dist/cli.js
CHANGED
|
@@ -4001,7 +4001,8 @@ var recipes = [
|
|
|
4001
4001
|
{ id: "starter-0.2.4-to-0.2.5", from: "0.2.4", to: "0.2.5" },
|
|
4002
4002
|
{ id: "starter-0.2.5-to-0.2.6", from: "0.2.5", to: "0.2.6" },
|
|
4003
4003
|
{ id: "starter-0.2.6-to-0.2.7", from: "0.2.6", to: "0.2.7" },
|
|
4004
|
-
{ id: "starter-0.2.7-to-0.2.8", from: "0.2.7", to: "0.2.8" }
|
|
4004
|
+
{ id: "starter-0.2.7-to-0.2.8", from: "0.2.7", to: "0.2.8" },
|
|
4005
|
+
{ id: "starter-0.2.8-to-0.2.9", from: "0.2.8", to: "0.2.9" }
|
|
4005
4006
|
];
|
|
4006
4007
|
function resolveRecipeChain(sourceVersion, targetVersion) {
|
|
4007
4008
|
if (sourceVersion === targetVersion)
|
package/package.json
CHANGED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import type {
|
|
3
|
+
ModuleAiOperationDefinition,
|
|
4
|
+
ModuleManifest,
|
|
5
|
+
} from "./manifest.js";
|
|
6
|
+
import { validateModuleComposition } from "./composition.js";
|
|
7
|
+
|
|
8
|
+
describe("AI Operation Composition", () => {
|
|
9
|
+
test("统一注册 Agent、Document 与 Speech Operation 并绑定实现", () => {
|
|
10
|
+
const module = fixture("business");
|
|
11
|
+
module.aiOperations = [
|
|
12
|
+
operation("business.agent", "agent", ["business.tool"]),
|
|
13
|
+
operation("business.document", "document"),
|
|
14
|
+
operation("business.speech", "speech"),
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
expect(() =>
|
|
18
|
+
validateModuleComposition([module], {
|
|
19
|
+
aiOperations: module.aiOperations.map(({ key }) => key),
|
|
20
|
+
}),
|
|
21
|
+
).not.toThrow();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test("Operation Key 全局唯一且必须使用所属模块命名空间", () => {
|
|
25
|
+
const first = fixture("first");
|
|
26
|
+
const second = fixture("second");
|
|
27
|
+
first.aiOperations = [operation("first.generate")];
|
|
28
|
+
second.aiOperations = [operation("first.generate")];
|
|
29
|
+
|
|
30
|
+
expect(() => validateModuleComposition([first, second])).toThrow(
|
|
31
|
+
"AI Operation Key 重复:first.generate",
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
second.aiOperations = [operation("foreign.generate")];
|
|
35
|
+
expect(() => validateModuleComposition([second])).toThrow(
|
|
36
|
+
"必须使用模块命名空间 second",
|
|
37
|
+
);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test.each([
|
|
41
|
+
["permissionKey", "missing.permission", "未注册权限"],
|
|
42
|
+
["featureKey", "missing.feature", "未注册Feature"],
|
|
43
|
+
["auditAction", "missing.audit", "未注册审计动作"],
|
|
44
|
+
] as const)("拒绝未知 %s 引用", (field, value, message) => {
|
|
45
|
+
const module = fixture("business");
|
|
46
|
+
const definition = operation("business.generate");
|
|
47
|
+
module.aiOperations = [{ ...definition, [field]: value }];
|
|
48
|
+
expect(() => validateModuleComposition([module])).toThrow(message);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("拒绝未知 Tool、空 capability 和非法限制", () => {
|
|
52
|
+
const module = fixture("business");
|
|
53
|
+
module.aiOperations = [
|
|
54
|
+
operation("business.generate", "agent", ["missing.tool"]),
|
|
55
|
+
];
|
|
56
|
+
expect(() => validateModuleComposition([module])).toThrow(
|
|
57
|
+
"未注册Agent Tool",
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
module.aiOperations[0] = {
|
|
61
|
+
...operation("business.generate"),
|
|
62
|
+
requiredCapabilities: [],
|
|
63
|
+
};
|
|
64
|
+
expect(() => validateModuleComposition([module])).toThrow(
|
|
65
|
+
"至少需要一个 capability",
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
module.aiOperations[0] = {
|
|
69
|
+
...operation("business.generate"),
|
|
70
|
+
limits: { ...operation("business.generate").limits, maxInputBytes: 0 },
|
|
71
|
+
};
|
|
72
|
+
expect(() => validateModuleComposition([module])).toThrow(
|
|
73
|
+
"上限必须是正整数",
|
|
74
|
+
);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("拒绝空 schema、capability 和 Tool Key", () => {
|
|
78
|
+
const module = fixture("business");
|
|
79
|
+
module.aiOperations = [
|
|
80
|
+
{ ...operation("business.generate"), inputSchemaKey: " " },
|
|
81
|
+
];
|
|
82
|
+
expect(() => validateModuleComposition([module])).toThrow(
|
|
83
|
+
"Key 与引用字段不能为空",
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
module.aiOperations[0] = {
|
|
87
|
+
...operation("business.generate"),
|
|
88
|
+
requiredCapabilities: [" "],
|
|
89
|
+
};
|
|
90
|
+
expect(() => validateModuleComposition([module])).toThrow(
|
|
91
|
+
"capability 不能为空",
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
module.aiOperations[0] = {
|
|
95
|
+
...operation("business.generate"),
|
|
96
|
+
allowedToolKeys: [" "],
|
|
97
|
+
};
|
|
98
|
+
expect(() => validateModuleComposition([module])).toThrow("Tool 不能为空");
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test("Operation Handler 必须与 Manifest 声明一一对应", () => {
|
|
102
|
+
const module = fixture("business");
|
|
103
|
+
module.aiOperations = [operation("business.generate")];
|
|
104
|
+
|
|
105
|
+
expect(() =>
|
|
106
|
+
validateModuleComposition([module], { aiOperations: [] }),
|
|
107
|
+
).toThrow("AI Operation business.generate 缺少 Handler");
|
|
108
|
+
expect(() =>
|
|
109
|
+
validateModuleComposition([module], {
|
|
110
|
+
aiOperations: ["business.generate", "business.undeclared"],
|
|
111
|
+
}),
|
|
112
|
+
).toThrow("AI Operation Handler business.undeclared 缺少 Manifest 声明");
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
function operation(
|
|
117
|
+
key: string,
|
|
118
|
+
kind: ModuleAiOperationDefinition["kind"] = "agent",
|
|
119
|
+
allowedToolKeys: readonly string[] = [],
|
|
120
|
+
): ModuleAiOperationDefinition {
|
|
121
|
+
const namespace = key.split(".")[0]!;
|
|
122
|
+
return {
|
|
123
|
+
key,
|
|
124
|
+
kind,
|
|
125
|
+
featureKey: `${namespace}.ai`,
|
|
126
|
+
permissionKey: `${namespace}.ai.execute`,
|
|
127
|
+
auditAction: `${namespace}.ai.execute`,
|
|
128
|
+
inputSchemaKey: `${namespace}.ai.input.v1`,
|
|
129
|
+
outputSchemaKey: `${namespace}.ai.output.v1`,
|
|
130
|
+
requiredCapabilities: ["text-generation"],
|
|
131
|
+
allowedToolKeys,
|
|
132
|
+
dataClassification: "internal",
|
|
133
|
+
execution: kind === "speech" ? "live" : "streaming",
|
|
134
|
+
publicNetworkAccess: "forbidden",
|
|
135
|
+
limits: {
|
|
136
|
+
maxDurationSeconds: 60,
|
|
137
|
+
maxInputBytes: 1_048_576,
|
|
138
|
+
maxOutputBytes: 1_048_576,
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function fixture(name: string): ModuleManifest {
|
|
144
|
+
const permissionKey = `${name}.ai.execute`;
|
|
145
|
+
const featureKey = `${name}.ai`;
|
|
146
|
+
return {
|
|
147
|
+
name,
|
|
148
|
+
version: "0.1.0",
|
|
149
|
+
label: name,
|
|
150
|
+
description: name,
|
|
151
|
+
menus: [],
|
|
152
|
+
adminRoutes: [],
|
|
153
|
+
permissions: [
|
|
154
|
+
{
|
|
155
|
+
key: permissionKey,
|
|
156
|
+
label: "执行 AI",
|
|
157
|
+
module: name,
|
|
158
|
+
resource: "ai",
|
|
159
|
+
action: "manage",
|
|
160
|
+
},
|
|
161
|
+
],
|
|
162
|
+
apiPermissions: [],
|
|
163
|
+
features: [
|
|
164
|
+
{
|
|
165
|
+
key: featureKey,
|
|
166
|
+
label: "AI",
|
|
167
|
+
module: name,
|
|
168
|
+
visible: "visible",
|
|
169
|
+
enabled: true,
|
|
170
|
+
stage: "stable",
|
|
171
|
+
},
|
|
172
|
+
],
|
|
173
|
+
migrations: [],
|
|
174
|
+
schemaExports: [],
|
|
175
|
+
tasks: [],
|
|
176
|
+
tools: [
|
|
177
|
+
{
|
|
178
|
+
key: `${name}.tool`,
|
|
179
|
+
label: "业务工具",
|
|
180
|
+
description: "AI Operation 测试工具。",
|
|
181
|
+
permissionKey,
|
|
182
|
+
featureKey,
|
|
183
|
+
auditAction: `${name}.ai.execute`,
|
|
184
|
+
},
|
|
185
|
+
],
|
|
186
|
+
providers: [],
|
|
187
|
+
auditActions: [
|
|
188
|
+
{
|
|
189
|
+
key: `${name}.ai.execute`,
|
|
190
|
+
label: "执行 AI",
|
|
191
|
+
description: "执行 AI Operation。",
|
|
192
|
+
},
|
|
193
|
+
],
|
|
194
|
+
};
|
|
195
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -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
|
}
|