create-windy 0.2.22 → 0.2.24

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 (49) hide show
  1. package/dist/cli.js +25 -3
  2. package/package.json +1 -1
  3. package/template/.agents/skills/windy-business-module/SKILL.md +43 -0
  4. package/template/.windy-template.json +2 -2
  5. package/template/AGENTS.md +2 -0
  6. package/template/apps/server/package.json +3 -1
  7. package/template/apps/server/src/agent/data-scope-routes.test.ts +20 -20
  8. package/template/apps/server/src/agent/remote-runtime.test.ts +59 -0
  9. package/template/apps/server/src/agent/remote-runtime.ts +22 -4
  10. package/template/apps/server/src/agent/routes.test.ts +16 -8
  11. package/template/apps/server/src/agent/routes.ts +103 -169
  12. package/template/apps/server/src/agent/run-facade.test.ts +30 -0
  13. package/template/apps/server/src/agent/runtime-bootstrap.ts +3 -3
  14. package/template/apps/server/src/agent/tool-host.test.ts +61 -8
  15. package/template/apps/server/src/agent/tool-host.ts +17 -73
  16. package/template/apps/server/src/agent/tool-registry.test.ts +117 -0
  17. package/template/apps/server/src/agent/tool-registry.ts +318 -0
  18. package/template/apps/server/src/http-app.test.ts +87 -0
  19. package/template/apps/server/src/http-app.ts +43 -25
  20. package/template/apps/server/src/index.ts +14 -2
  21. package/template/apps/server/src/license/runtime-service.test.ts +6 -3
  22. package/template/apps/server/src/mcp/adapter.test.ts +195 -0
  23. package/template/apps/server/src/mcp/adapter.ts +204 -0
  24. package/template/apps/server/src/mcp/routes.ts +21 -0
  25. package/template/apps/server/src/module-host/tool-handlers.ts +6 -5
  26. package/template/apps/server/src/observability/http-observability.test.ts +21 -0
  27. package/template/apps/server/src/observability/http-observability.ts +1 -0
  28. package/template/apps/server/src/observability/metrics.ts +5 -2
  29. package/template/docs/operations/observability.md +9 -1
  30. package/template/docs/platform/agent-runtime.md +15 -3
  31. package/template/docs/platform/agent-tools.md +88 -28
  32. package/template/packages/modules/index.ts +1 -0
  33. package/template/packages/modules/src/ai-operation-composition.test.ts +30 -0
  34. package/template/packages/modules/src/ai-operation-composition.ts +13 -0
  35. package/template/packages/modules/src/composition.test.ts +42 -0
  36. package/template/packages/modules/src/composition.ts +3 -0
  37. package/template/packages/modules/src/manifest.ts +20 -1
  38. package/template/packages/modules/src/system-agent-tools.ts +53 -1
  39. package/template/packages/modules/src/system-api-permissions.ts +5 -0
  40. package/template/packages/modules/src/system-module-catalog.ts +1 -0
  41. package/template/packages/modules/src/system-modules.test.ts +32 -0
  42. package/template/packages/modules/src/system-modules.ts +9 -1
  43. package/template/packages/modules/src/tool-composition.ts +86 -0
  44. package/template/packages/server-sdk/index.ts +1 -0
  45. package/template/packages/server-sdk/package.json +1 -0
  46. package/template/packages/server-sdk/src/ai-operation-registration.ts +2 -7
  47. package/template/packages/server-sdk/src/tool-registration.ts +6 -7
  48. package/template/packages/shared/index.ts +1 -0
  49. package/template/packages/shared/src/json.ts +8 -0
@@ -6,3 +6,4 @@ export * from "./src/registry.js";
6
6
  export * from "./src/system-modules.js";
7
7
  export * from "./src/system-module-catalog.js";
8
8
  export * from "./src/system-api-permissions.js";
9
+ export * from "./src/tool-composition.js";
@@ -172,15 +172,45 @@ function fixture(name: string): ModuleManifest {
172
172
  ],
173
173
  migrations: [],
174
174
  schemaExports: [],
175
+ jsonSchemas: [
176
+ {
177
+ key: `${name}.tool.input.v1`,
178
+ schema: { type: "object" },
179
+ },
180
+ {
181
+ key: `${name}.tool.output.v1`,
182
+ schema: { type: "object" },
183
+ },
184
+ {
185
+ key: `${name}.ai.input.v1`,
186
+ schema: { type: "object" },
187
+ },
188
+ {
189
+ key: `${name}.ai.output.v1`,
190
+ schema: { type: "object" },
191
+ },
192
+ ],
175
193
  tasks: [],
176
194
  tools: [
177
195
  {
178
196
  key: `${name}.tool`,
179
197
  label: "业务工具",
180
198
  description: "AI Operation 测试工具。",
199
+ inputSchemaKey: `${name}.tool.input.v1`,
200
+ outputSchemaKey: `${name}.tool.output.v1`,
201
+ operation: "query",
181
202
  permissionKey,
182
203
  featureKey,
183
204
  auditAction: `${name}.ai.execute`,
205
+ scopeMode: "request",
206
+ dataClassification: "internal",
207
+ idempotency: "none",
208
+ approval: "none",
209
+ limits: {
210
+ maxDurationSeconds: 10,
211
+ maxInputBytes: 4096,
212
+ maxOutputBytes: 4096,
213
+ },
184
214
  },
185
215
  ],
186
216
  providers: [],
@@ -8,6 +8,7 @@ interface AiOperationCatalogs {
8
8
  features: ReadonlySet<string>;
9
9
  audits: ReadonlySet<string>;
10
10
  tools: ReadonlySet<string>;
11
+ schemas: ReadonlySet<string>;
11
12
  }
12
13
 
13
14
  export function validateAiOperations(
@@ -85,6 +86,18 @@ function validateOperation(
85
86
  operation.auditAction,
86
87
  catalogs.audits,
87
88
  );
89
+ requiredReference(
90
+ operation.key,
91
+ " JSON Schema",
92
+ operation.inputSchemaKey,
93
+ catalogs.schemas,
94
+ );
95
+ requiredReference(
96
+ operation.key,
97
+ " JSON Schema",
98
+ operation.outputSchemaKey,
99
+ catalogs.schemas,
100
+ );
88
101
  validateCapabilities(operation);
89
102
  validateTools(operation, catalogs.tools);
90
103
  validateLimits(operation);
@@ -170,6 +170,26 @@ describe("Module Composition 契约", () => {
170
170
  );
171
171
  });
172
172
 
173
+ test("Agent Tool 必须引用已注册 object Schema 并声明正数限制", () => {
174
+ const missing = fixture("system");
175
+ missing.tools[0]!.inputSchemaKey = "system.missing.v1";
176
+ expect(() => validateModuleComposition([missing])).toThrow(
177
+ "引用了未注册 JSON Schema",
178
+ );
179
+
180
+ const invalidSchema = fixture("system");
181
+ invalidSchema.jsonSchemas![0]!.schema = { type: "array" };
182
+ expect(() => validateModuleComposition([invalidSchema])).toThrow(
183
+ "顶层类型必须是 object",
184
+ );
185
+
186
+ const invalidLimit = fixture("system");
187
+ invalidLimit.tools[0]!.limits.maxInputBytes = 0;
188
+ expect(() => validateModuleComposition([invalidLimit])).toThrow(
189
+ "上限必须是正整数",
190
+ );
191
+ });
192
+
173
193
  test("字段治理策略校验唯一资源、权限风险和已注册字段", () => {
174
194
  const module = fixture("system");
175
195
  module.permissions.push({
@@ -266,6 +286,16 @@ function fixture(name: string, dependencies: string[] = []): ModuleManifest {
266
286
  migrationIds: [`${name}.001`],
267
287
  },
268
288
  ],
289
+ jsonSchemas: [
290
+ {
291
+ key: `${name}.tool.input.v1`,
292
+ schema: { type: "object" },
293
+ },
294
+ {
295
+ key: `${name}.tool.output.v1`,
296
+ schema: { type: "object" },
297
+ },
298
+ ],
269
299
  tasks: [
270
300
  {
271
301
  key: `${name}.task`,
@@ -283,9 +313,21 @@ function fixture(name: string, dependencies: string[] = []): ModuleManifest {
283
313
  key: `${name}.tool`,
284
314
  label: name,
285
315
  description: name,
316
+ inputSchemaKey: `${name}.tool.input.v1`,
317
+ outputSchemaKey: `${name}.tool.output.v1`,
318
+ operation: "query",
286
319
  permissionKey: readPermission,
287
320
  featureKey,
288
321
  auditAction: "agent.tool.execute",
322
+ scopeMode: "request",
323
+ dataClassification: "internal",
324
+ idempotency: "none",
325
+ approval: "none",
326
+ limits: {
327
+ maxDurationSeconds: 10,
328
+ maxInputBytes: 4096,
329
+ maxOutputBytes: 4096,
330
+ },
289
331
  },
290
332
  ],
291
333
  providers: [
@@ -7,6 +7,7 @@ import { validateAiOperations } from "./ai-operation-composition.js";
7
7
  import { validateImplementationBindings } from "./implementation-bindings.js";
8
8
  import { composeModuleMenus } from "./menu-composition.js";
9
9
  import { validateCapabilityCatalogs } from "./catalog-validation.js";
10
+ import { validateToolContracts } from "./tool-composition.js";
10
11
 
11
12
  export interface ValidatedModuleComposition {
12
13
  modules: readonly ModuleManifest[];
@@ -35,6 +36,7 @@ export function validateModuleComposition(
35
36
  modules.flatMap((module) => module.features.map(({ key }) => key)),
36
37
  );
37
38
  const catalogs = validateCapabilityCatalogs(modules);
39
+ const jsonSchemas = validateToolContracts(modules);
38
40
  const audits = new Set(catalogs.auditActions.map(({ key }) => key));
39
41
  const menus = composeModuleMenus(modules).flatMap((menu) =>
40
42
  flattenMenus([menu]),
@@ -69,6 +71,7 @@ export function validateModuleComposition(
69
71
  features,
70
72
  audits,
71
73
  tools: toolKeys,
74
+ schemas: new Set(jsonSchemas.keys()),
72
75
  });
73
76
  const providers = modules.flatMap((module) => module.providers);
74
77
  unique(
@@ -4,6 +4,7 @@ import type {
4
4
  AuditActionType,
5
5
  DataGovernancePolicyDefinition,
6
6
  FeatureFlagDefinition,
7
+ JsonObject,
7
8
  MigrationBundle,
8
9
  MigrationDefinition,
9
10
  MenuNode,
@@ -20,9 +21,26 @@ export interface ModuleToolDefinition {
20
21
  key: string;
21
22
  label: string;
22
23
  description: string;
24
+ inputSchemaKey: string;
25
+ outputSchemaKey: string;
26
+ operation: "query" | "command" | "background";
23
27
  permissionKey: string;
24
- featureKey?: string;
28
+ featureKey: string;
25
29
  auditAction: AuditActionType;
30
+ scopeMode: "platform" | "request";
31
+ dataClassification: "public" | "internal" | "sensitive";
32
+ idempotency: "none" | "supported" | "required";
33
+ approval: "none" | "required";
34
+ limits: {
35
+ maxDurationSeconds: number;
36
+ maxInputBytes: number;
37
+ maxOutputBytes: number;
38
+ };
39
+ }
40
+
41
+ export interface ModuleJsonSchemaDefinition {
42
+ key: string;
43
+ schema: JsonObject;
26
44
  }
27
45
 
28
46
  export type AdminRouteSurface = "sidebar" | "account" | "hidden";
@@ -107,6 +125,7 @@ export interface ModuleManifest {
107
125
  migrations: MigrationDefinition[];
108
126
  migrationBundle?: MigrationBundle;
109
127
  schemaExports: ModuleSchemaExportDefinition[];
128
+ jsonSchemas?: ModuleJsonSchemaDefinition[];
110
129
  tasks: ModuleScheduledTaskDefinition[];
111
130
  tools: ModuleToolDefinition[];
112
131
  aiOperations?: ModuleAiOperationDefinition[];
@@ -1,15 +1,66 @@
1
- import type { ModuleManifest } from "./manifest.js";
1
+ import type { ModuleJsonSchemaDefinition, ModuleManifest } from "./manifest.js";
2
+
3
+ export const systemAgentToolSchemas: ModuleJsonSchemaDefinition[] = [
4
+ {
5
+ key: "system.pagination.input.v1",
6
+ schema: {
7
+ type: "object",
8
+ properties: {
9
+ page: { type: "integer", minimum: 1, default: 1 },
10
+ pageSize: {
11
+ type: "integer",
12
+ minimum: 1,
13
+ maximum: 50,
14
+ default: 10,
15
+ },
16
+ },
17
+ additionalProperties: false,
18
+ },
19
+ },
20
+ {
21
+ key: "system.page.output.v1",
22
+ schema: {
23
+ type: "object",
24
+ properties: {
25
+ items: { type: "array", items: { type: "object" } },
26
+ total: { type: "integer", minimum: 0 },
27
+ page: { type: "integer", minimum: 1 },
28
+ pageSize: { type: "integer", minimum: 1 },
29
+ },
30
+ required: ["items", "total", "page", "pageSize"],
31
+ additionalProperties: false,
32
+ },
33
+ },
34
+ ];
35
+
36
+ const queryContract = {
37
+ inputSchemaKey: "system.pagination.input.v1",
38
+ outputSchemaKey: "system.page.output.v1",
39
+ operation: "query" as const,
40
+ scopeMode: "platform" as const,
41
+ dataClassification: "internal" as const,
42
+ idempotency: "none" as const,
43
+ approval: "none" as const,
44
+ limits: {
45
+ maxDurationSeconds: 10,
46
+ maxInputBytes: 4_096,
47
+ maxOutputBytes: 1_048_576,
48
+ },
49
+ };
2
50
 
3
51
  export const systemAgentTools: ModuleManifest["tools"] = [
4
52
  {
53
+ ...queryContract,
5
54
  key: "system.users.list",
6
55
  label: "查询用户",
7
56
  description: "分页查询平台用户账号。",
8
57
  permissionKey: "system.user.read",
9
58
  featureKey: "system.rbac",
10
59
  auditAction: "agent.tool.execute",
60
+ scopeMode: "request",
11
61
  },
12
62
  {
63
+ ...queryContract,
13
64
  key: "system.features.list",
14
65
  label: "查询功能开关",
15
66
  description: "分页查询平台功能开关状态。",
@@ -18,6 +69,7 @@ export const systemAgentTools: ModuleManifest["tools"] = [
18
69
  auditAction: "agent.tool.execute",
19
70
  },
20
71
  {
72
+ ...queryContract,
21
73
  key: "system.audit.list",
22
74
  label: "查询审计日志",
23
75
  description: "分页查询平台审计事件。",
@@ -25,6 +25,11 @@ export function systemApiPermissionBindings(): ApiPermissionBinding[] {
25
25
  ),
26
26
  binding("POST", "/api/search", "system.search.read", "system.search"),
27
27
  // @windy-module system.search end
28
+ // @windy-module system.agent begin
29
+ binding("GET", "/api/mcp", "system.agent.read", "system.agent"),
30
+ binding("POST", "/api/mcp", "system.agent.read", "system.agent"),
31
+ binding("DELETE", "/api/mcp", "system.agent.read", "system.agent"),
32
+ // @windy-module system.agent end
28
33
  ...resourceBindings({
29
34
  path: "users",
30
35
  permissionResource: "user",
@@ -218,6 +218,7 @@ function createSystemCapabilityModule(
218
218
  .map((feature) => ({ ...feature, module: definition.name })),
219
219
  migrations: isCore ? aggregate.migrations : [],
220
220
  schemaExports: isCore ? aggregate.schemaExports : [],
221
+ jsonSchemas: isCore ? aggregate.jsonSchemas : [],
221
222
  tasks: aggregate.tasks.filter(ownsGuard),
222
223
  tools: aggregate.tools.filter(ownsGuard),
223
224
  providers: aggregate.providers.filter(ownsGuard),
@@ -326,6 +326,38 @@ describe("系统模块能力注册", () => {
326
326
  anonymous: undefined,
327
327
  });
328
328
  });
329
+
330
+ // @windy-module system.agent begin
331
+ test("MCP Streamable HTTP 入口统一绑定 Agent Feature 与读取权限", () => {
332
+ const bindings = systemFoundationModule().apiPermissions.filter(
333
+ ({ path }) => path === "/api/mcp",
334
+ );
335
+
336
+ expect(bindings).toEqual([
337
+ {
338
+ method: "GET",
339
+ path: "/api/mcp",
340
+ permissionKey: "system.agent.read",
341
+ featureKey: "system.agent",
342
+ anonymous: undefined,
343
+ },
344
+ {
345
+ method: "POST",
346
+ path: "/api/mcp",
347
+ permissionKey: "system.agent.read",
348
+ featureKey: "system.agent",
349
+ anonymous: undefined,
350
+ },
351
+ {
352
+ method: "DELETE",
353
+ path: "/api/mcp",
354
+ permissionKey: "system.agent.read",
355
+ featureKey: "system.agent",
356
+ anonymous: undefined,
357
+ },
358
+ ]);
359
+ });
360
+ // @windy-module system.agent end
329
361
  });
330
362
 
331
363
  function flattenMenuKeys(
@@ -1,6 +1,9 @@
1
1
  import type { ModuleManifest } from "./manifest.js";
2
2
  // @windy-module system.agent begin
3
- import { systemAgentTools } from "./system-agent-tools.js";
3
+ import {
4
+ systemAgentToolSchemas,
5
+ systemAgentTools,
6
+ } from "./system-agent-tools.js";
4
7
  // @windy-module system.agent end
5
8
  import { systemAdminRoutes } from "./system-admin-routes.js";
6
9
  import { systemApiPermissionBindings } from "./system-api-permissions.js";
@@ -249,6 +252,11 @@ export function systemFoundationModule(): ModuleManifest {
249
252
  migrationIds: ["0000..0028"],
250
253
  },
251
254
  ],
255
+ jsonSchemas: [
256
+ // @windy-module system.agent begin
257
+ ...systemAgentToolSchemas,
258
+ // @windy-module system.agent end
259
+ ],
252
260
  tasks: [
253
261
  // @windy-module system.scheduler begin
254
262
  {
@@ -0,0 +1,86 @@
1
+ import type {
2
+ ModuleJsonSchemaDefinition,
3
+ ModuleManifest,
4
+ ModuleToolDefinition,
5
+ } from "./manifest.js";
6
+
7
+ export function validateToolContracts(
8
+ modules: readonly ModuleManifest[],
9
+ ): ReadonlyMap<string, ModuleJsonSchemaDefinition> {
10
+ const schemas = new Map<string, ModuleJsonSchemaDefinition>();
11
+ for (const module of modules) {
12
+ for (const definition of module.jsonSchemas ?? []) {
13
+ validateSchema(module.name, definition);
14
+ if (schemas.has(definition.key)) {
15
+ throw new Error(`JSON Schema Key 重复:${definition.key}`);
16
+ }
17
+ schemas.set(definition.key, definition);
18
+ }
19
+ }
20
+ for (const module of modules) {
21
+ for (const tool of module.tools) {
22
+ validateTool(module.name, tool, schemas);
23
+ }
24
+ }
25
+ return schemas;
26
+ }
27
+
28
+ function validateSchema(
29
+ moduleName: string,
30
+ definition: ModuleJsonSchemaDefinition,
31
+ ): void {
32
+ if (!definition.key.trim()) throw new Error("JSON Schema Key 不能为空");
33
+ if (!definition.key.startsWith(`${moduleName}.`)) {
34
+ throw new Error(
35
+ `JSON Schema ${definition.key} 必须使用模块命名空间 ${moduleName}`,
36
+ );
37
+ }
38
+ if (definition.schema.type !== "object") {
39
+ throw new Error(`JSON Schema ${definition.key} 顶层类型必须是 object`);
40
+ }
41
+ }
42
+
43
+ function validateTool(
44
+ moduleName: string,
45
+ tool: ModuleToolDefinition,
46
+ schemas: ReadonlyMap<string, ModuleJsonSchemaDefinition>,
47
+ ): void {
48
+ const fields: Array<string | undefined> = [
49
+ tool.key,
50
+ tool.label,
51
+ tool.description,
52
+ tool.inputSchemaKey,
53
+ tool.outputSchemaKey,
54
+ tool.featureKey,
55
+ tool.permissionKey,
56
+ tool.auditAction,
57
+ ];
58
+ if (fields.some((value) => !value?.trim())) {
59
+ throw new Error("Agent Tool Key、展示信息与引用字段不能为空");
60
+ }
61
+ const rootNamespace = moduleName.split(".")[0]!;
62
+ if (!tool.key.startsWith(`${rootNamespace}.`)) {
63
+ throw new Error(
64
+ `Agent Tool ${tool.key} 必须使用模块根命名空间 ${rootNamespace}`,
65
+ );
66
+ }
67
+ requireSchema(tool.key, tool.inputSchemaKey, schemas);
68
+ requireSchema(tool.key, tool.outputSchemaKey, schemas);
69
+ if (tool.operation === "query" && tool.approval !== "none") {
70
+ throw new Error(`${tool.key} 查询能力不能要求执行审批`);
71
+ }
72
+ const limits = Object.values(tool.limits);
73
+ if (limits.some((value) => !Number.isSafeInteger(value) || value <= 0)) {
74
+ throw new Error(`${tool.key} 运行与输入输出上限必须是正整数`);
75
+ }
76
+ }
77
+
78
+ function requireSchema(
79
+ toolKey: string,
80
+ schemaKey: string,
81
+ schemas: ReadonlyMap<string, ModuleJsonSchemaDefinition>,
82
+ ): void {
83
+ if (!schemas.has(schemaKey)) {
84
+ throw new Error(`${toolKey} 引用了未注册 JSON Schema:${schemaKey}`);
85
+ }
86
+ }
@@ -48,6 +48,7 @@ export type {
48
48
  export type {
49
49
  ModuleToolContext,
50
50
  ModuleToolHandlerRegistration,
51
+ ModuleToolInput,
51
52
  ModuleToolResult,
52
53
  } from "./src/tool-registration.js";
53
54
  export type {
@@ -29,6 +29,7 @@
29
29
  },
30
30
  "dependencies": {
31
31
  "@southwind-ai/jobs": "workspace:*",
32
+ "@southwind-ai/shared": "workspace:*",
32
33
  "@southwind-ai/storage": "workspace:*"
33
34
  },
34
35
  "exports": {
@@ -1,12 +1,7 @@
1
+ import type { JsonValue } from "@southwind-ai/shared";
1
2
  import type { ModuleActor } from "./actor.js";
2
3
 
3
- export type ModuleAiJsonValue =
4
- | string
5
- | number
6
- | boolean
7
- | null
8
- | ModuleAiJsonValue[]
9
- | { [key: string]: ModuleAiJsonValue };
4
+ export type ModuleAiJsonValue = JsonValue;
10
5
 
11
6
  export interface ModuleAiOperationContext {
12
7
  readonly actor: ModuleActor;
@@ -1,16 +1,15 @@
1
1
  import type { ModuleActor } from "./actor.js";
2
+ import type { ModuleAiJsonValue } from "./ai-operation-registration.js";
2
3
 
3
4
  export interface ModuleToolContext {
4
5
  readonly actor: ModuleActor;
5
6
  readonly requestId: string;
7
+ readonly signal: AbortSignal;
6
8
  }
7
9
 
8
- export interface ModuleToolResult {
9
- readonly items: unknown[];
10
- readonly total: number;
11
- readonly page: number;
12
- readonly pageSize: number;
13
- }
10
+ export type ModuleToolInput = Readonly<Record<string, ModuleAiJsonValue>>;
11
+
12
+ export type ModuleToolResult = Readonly<Record<string, ModuleAiJsonValue>>;
14
13
 
15
14
  /**
16
15
  * 模块 Agent Tool 的 Handler 注册。
@@ -23,7 +22,7 @@ export interface ModuleToolHandlerRegistration {
23
22
  readonly toolKey: string;
24
23
  readonly scopeMode: "platform" | "request";
25
24
  execute(
26
- query: Record<string, string | undefined>,
25
+ input: ModuleToolInput,
27
26
  context: ModuleToolContext,
28
27
  ): Promise<ModuleToolResult> | ModuleToolResult;
29
28
  }
@@ -4,6 +4,7 @@ export * from "./src/data-governance.js";
4
4
  export * from "./src/feature.js";
5
5
  export * from "./src/license.js";
6
6
  export * from "./src/license-catalog.js";
7
+ export * from "./src/json.js";
7
8
  export * from "./src/migration.js";
8
9
  export * from "./src/migration-bundle-validation.js";
9
10
  export * from "./src/module-configuration.js";
@@ -0,0 +1,8 @@
1
+ export type JsonPrimitive = string | number | boolean | null;
2
+
3
+ export type JsonValue =
4
+ | JsonPrimitive
5
+ | JsonValue[]
6
+ | { [key: string]: JsonValue };
7
+
8
+ export type JsonObject = { [key: string]: JsonValue };