experimental-ash 0.20.1 → 0.22.0
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/CHANGELOG.md +13 -0
- package/dist/docs/public/channels/README.md +3 -1
- package/dist/docs/public/hooks.md +76 -6
- package/dist/docs/public/skills.md +6 -1
- package/dist/docs/public/tools.md +4 -0
- package/dist/src/chunks/{dev-authored-source-watcher-BR7XYOW0.js → dev-authored-source-watcher-DKDaaPea.js} +1 -1
- package/dist/src/chunks/{host-Dxf7CGaE.js → host-Btr4S69C.js} +7 -7
- package/dist/src/chunks/paths-DZTgjrW-.js +88 -0
- package/dist/src/chunks/{prewarm-vAT2QBqc.js → prewarm-BELT37PI.js} +1 -1
- package/dist/src/cli/commands/info.js +1 -1
- package/dist/src/cli/run.js +1 -1
- package/dist/src/compiled/.vendor-stamp.json +3 -3
- package/dist/src/compiled/@ai-sdk/anthropic/_provider-utils.d.ts +15 -0
- package/dist/src/compiled/@ai-sdk/anthropic/index.d.ts +1259 -6
- package/dist/src/compiled/@ai-sdk/google/_provider-utils.d.ts +11 -0
- package/dist/src/compiled/@ai-sdk/google/index.d.ts +561 -6
- package/dist/src/compiled/@ai-sdk/google/index.js +4 -4
- package/dist/src/compiled/@ai-sdk/google/package.json +1 -1
- package/dist/src/compiled/@ai-sdk/openai/_provider-utils.d.ts +15 -0
- package/dist/src/compiled/@ai-sdk/openai/index.d.ts +1255 -6
- package/dist/src/compiled/@ai-sdk/openai/index.js +1 -1
- package/dist/src/compiled/@ai-sdk/openai/package.json +1 -1
- package/dist/src/compiler/normalize-skill.js +5 -1
- package/dist/src/compiler/workspace-resources.js +12 -5
- package/dist/src/context/hook-lifecycle.d.ts +8 -8
- package/dist/src/context/hook-lifecycle.js +88 -36
- package/dist/src/evals/cli/eval.js +1 -1
- package/dist/src/execution/skills/instructions.d.ts +8 -4
- package/dist/src/execution/skills/instructions.js +3 -2
- package/dist/src/harness/attachment-staging.js +5 -1
- package/dist/src/harness/tool-loop.js +11 -3
- package/dist/src/internal/application/package.js +1 -1
- package/dist/src/internal/authored-definition/core.js +16 -1
- package/dist/src/internal/nitro/routes/workflow-route-helpers.d.ts +4 -4
- package/dist/src/internal/nitro/routes/workflow-route-helpers.js +6 -5
- package/dist/src/internal/nitro/routes/workflow-run-events.d.ts +1 -1
- package/dist/src/internal/nitro/routes/workflow-run-events.js +1 -1
- package/dist/src/internal/nitro/routes/workflow-run-steps.d.ts +1 -1
- package/dist/src/internal/nitro/routes/workflow-run-steps.js +1 -1
- package/dist/src/internal/nitro/routes/workflow-run.d.ts +1 -1
- package/dist/src/internal/nitro/routes/workflow-run.js +1 -1
- package/dist/src/internal/nitro/routes/workflow-runs.d.ts +1 -1
- package/dist/src/internal/nitro/routes/workflow-runs.js +1 -1
- package/dist/src/internal/workflow-bundle/vercel-workflow-output.js +2 -0
- package/dist/src/public/definitions/hook.d.ts +11 -3
- package/dist/src/public/definitions/skill.d.ts +4 -3
- package/dist/src/public/definitions/skill.js +1 -1
- package/dist/src/public/next/index.d.ts +74 -0
- package/dist/src/public/next/index.js +150 -0
- package/dist/src/public/next/server.d.ts +6 -0
- package/dist/src/public/next/server.js +348 -0
- package/dist/src/public/next/vercel-json.d.ts +8 -0
- package/dist/src/public/next/vercel-json.js +76 -0
- package/dist/src/public/skills/index.d.ts +1 -1
- package/dist/src/public/skills/index.js +1 -1
- package/dist/src/public/tool-result-narrowing.d.ts +68 -0
- package/dist/src/public/tool-result-narrowing.js +58 -0
- package/dist/src/public/tools/index.d.ts +1 -0
- package/dist/src/public/tools/index.js +1 -0
- package/dist/src/runtime/resolve-connection.js +2 -0
- package/dist/src/runtime/resolve-tool.js +2 -0
- package/dist/src/shared/skill-definition.d.ts +20 -2
- package/dist/src/shared/skill-package.d.ts +43 -0
- package/dist/src/shared/skill-package.js +100 -0
- package/package.json +13 -3
- package/dist/src/chunks/paths-BcM3el4Q.js +0 -88
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
const DEFINITION_SOURCE = Symbol("ash.definitionSource");
|
|
2
|
+
/**
|
|
3
|
+
* Stamps the path-derived runtime name onto an authored definition
|
|
4
|
+
* object as a non-enumerable symbol property.
|
|
5
|
+
*
|
|
6
|
+
* Called by the resolution pipeline (`resolveToolDefinition`,
|
|
7
|
+
* `resolveConnectionDefinition`) so {@link toolResultFrom} can read
|
|
8
|
+
* the name back without a separate lookup structure.
|
|
9
|
+
*/
|
|
10
|
+
export function stampDefinitionSource(definition, entry) {
|
|
11
|
+
Object.defineProperty(definition, DEFINITION_SOURCE, { value: entry });
|
|
12
|
+
}
|
|
13
|
+
function readDefinitionSource(definition) {
|
|
14
|
+
if (DEFINITION_SOURCE in definition) {
|
|
15
|
+
return definition[DEFINITION_SOURCE];
|
|
16
|
+
}
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
const CONNECTION_TOOL_SEPARATOR = "__";
|
|
20
|
+
/**
|
|
21
|
+
* Narrows a {@link RuntimeActionResult} to a typed tool or connection
|
|
22
|
+
* result by matching against an authored definition object.
|
|
23
|
+
*
|
|
24
|
+
* Pass a `ToolDefinition` to get a typed `output`; pass a
|
|
25
|
+
* `McpClientConnectionDefinition` to match any tool from that
|
|
26
|
+
* connection (`output` stays `unknown`).
|
|
27
|
+
*
|
|
28
|
+
* Returns `undefined` when the result doesn't match, or when
|
|
29
|
+
* `isError` is `true`.
|
|
30
|
+
*/
|
|
31
|
+
export const toolResultFrom = toolResultFromImpl;
|
|
32
|
+
function toolResultFromImpl(result, source) {
|
|
33
|
+
if (result.kind !== "tool-result")
|
|
34
|
+
return undefined;
|
|
35
|
+
if (result.isError === true)
|
|
36
|
+
return undefined;
|
|
37
|
+
const entry = readDefinitionSource(source);
|
|
38
|
+
if (entry === undefined)
|
|
39
|
+
return undefined;
|
|
40
|
+
if (entry.kind === "tool") {
|
|
41
|
+
if (result.toolName !== entry.name)
|
|
42
|
+
return undefined;
|
|
43
|
+
return {
|
|
44
|
+
callId: result.callId,
|
|
45
|
+
output: result.output,
|
|
46
|
+
toolName: result.toolName,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
const prefix = entry.name + CONNECTION_TOOL_SEPARATOR;
|
|
50
|
+
if (!result.toolName.startsWith(prefix))
|
|
51
|
+
return undefined;
|
|
52
|
+
return {
|
|
53
|
+
callId: result.callId,
|
|
54
|
+
connectionToolName: result.toolName.slice(prefix.length),
|
|
55
|
+
output: result.output,
|
|
56
|
+
toolName: result.toolName,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Tool authoring helpers for `agent/tools/*.ts` files.
|
|
3
3
|
*/
|
|
4
4
|
export { type CompactionHookInput, type CompactionHookResult, type DisabledToolSentinel, defineTool, disableTool, isDisabledToolSentinel, type NeedsApprovalContext, type RetentionSummary, type ToolDefinition, type ToolModelOutput, type ToolRetentionPolicy, } from "#public/definitions/tool.js";
|
|
5
|
+
export { toolResultFrom, type MatchedConnectionResult, type MatchedToolResult, type ToolResultFromFn, } from "#public/tool-result-narrowing.js";
|
|
5
6
|
export { type DefineBashToolInput, defineBashTool } from "#public/tools/define-bash-tool.js";
|
|
6
7
|
export { type DefineGlobToolInput, defineGlobTool } from "#public/tools/define-glob-tool.js";
|
|
7
8
|
export { type DefineGrepToolInput, defineGrepTool } from "#public/tools/define-grep-tool.js";
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Tool authoring helpers for `agent/tools/*.ts` files.
|
|
3
3
|
*/
|
|
4
4
|
export { defineTool, disableTool, isDisabledToolSentinel, } from "#public/definitions/tool.js";
|
|
5
|
+
export { toolResultFrom, } from "#public/tool-result-narrowing.js";
|
|
5
6
|
export { defineBashTool } from "#public/tools/define-bash-tool.js";
|
|
6
7
|
export { defineGlobTool } from "#public/tools/define-glob-tool.js";
|
|
7
8
|
export { defineGrepTool } from "#public/tools/define-grep-tool.js";
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { expectObjectRecord } from "#internal/authored-module.js";
|
|
2
|
+
import { stampDefinitionSource } from "#public/tool-result-narrowing.js";
|
|
2
3
|
import { toErrorMessage } from "#shared/errors.js";
|
|
3
4
|
import { normalizeAuthorizationSpec } from "#runtime/connections/validate-authorization.js";
|
|
4
5
|
import { loadResolvedModuleExport, ResolveAgentError } from "#runtime/resolve-helpers.js";
|
|
@@ -22,6 +23,7 @@ export async function resolveConnectionDefinition(definition, moduleMap, nodeId)
|
|
|
22
23
|
nodeId,
|
|
23
24
|
});
|
|
24
25
|
const resolvedRecord = expectObjectRecord(resolvedExportValue, `Expected the connection export "${definition.exportName ?? "default"}" from "${definition.logicalPath}" to return an object.`);
|
|
26
|
+
stampDefinitionSource(resolvedRecord, { kind: "connection", name: definition.connectionName });
|
|
25
27
|
const hasAuth = resolvedRecord.auth !== undefined;
|
|
26
28
|
const hasHeaders = resolvedRecord.headers !== undefined;
|
|
27
29
|
const result = {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { expectFunction, expectObjectRecord } from "#internal/authored-module.js";
|
|
2
|
+
import { stampDefinitionSource } from "#public/tool-result-narrowing.js";
|
|
2
3
|
import { toErrorMessage } from "#shared/errors.js";
|
|
3
4
|
import { loadResolvedModuleExport, ResolveAgentError } from "#runtime/resolve-helpers.js";
|
|
4
5
|
/**
|
|
@@ -20,6 +21,7 @@ export async function resolveToolDefinition(definition, moduleMap, nodeId) {
|
|
|
20
21
|
nodeId,
|
|
21
22
|
});
|
|
22
23
|
const resolvedRecord = expectObjectRecord(resolvedExportValue, describe(definition, "to return an object"));
|
|
24
|
+
stampDefinitionSource(resolvedRecord, { kind: "tool", name: definition.name });
|
|
23
25
|
const execute = expectFunction(resolvedRecord.execute, describe(definition, "to provide an execute function"));
|
|
24
26
|
return {
|
|
25
27
|
description: definition.description,
|
|
@@ -1,7 +1,25 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* File payload accepted for authored or runtime-created skill package
|
|
3
|
+
* siblings. Strings are written as UTF-8; bytes are preserved exactly.
|
|
4
|
+
*/
|
|
5
|
+
export type SkillFileContent = string | Uint8Array;
|
|
6
|
+
/**
|
|
7
|
+
* Public skill package content shared by TypeScript-authored skills and
|
|
8
|
+
* runtime hook contributions.
|
|
9
|
+
*/
|
|
10
|
+
export interface SkillPackageDefinition {
|
|
3
11
|
description: string;
|
|
4
12
|
license?: string;
|
|
5
13
|
markdown: string;
|
|
6
14
|
metadata?: Record<string, string>;
|
|
15
|
+
files?: Readonly<Record<string, SkillFileContent>>;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Runtime-contributed skill package. Hook-created skills do not have an
|
|
19
|
+
* authored file path, so they carry their name explicitly.
|
|
20
|
+
*/
|
|
21
|
+
export interface NamedSkillDefinition extends SkillPackageDefinition {
|
|
22
|
+
name: string;
|
|
23
|
+
}
|
|
24
|
+
export interface InternalSkillDefinition extends NamedSkillDefinition {
|
|
7
25
|
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Buffer } from "node:buffer";
|
|
2
|
+
import type { SandboxSession } from "#shared/sandbox-session.js";
|
|
3
|
+
import type { NamedSkillDefinition } from "#shared/skill-definition.js";
|
|
4
|
+
export interface NormalizedSkillPackageFile {
|
|
5
|
+
readonly content: Buffer;
|
|
6
|
+
readonly relativePath: string;
|
|
7
|
+
}
|
|
8
|
+
export interface MaterializableSkillPackage {
|
|
9
|
+
readonly description: string;
|
|
10
|
+
readonly files: readonly NormalizedSkillPackageFile[];
|
|
11
|
+
readonly license?: string;
|
|
12
|
+
readonly markdown: string;
|
|
13
|
+
readonly metadata?: Readonly<Record<string, string>>;
|
|
14
|
+
readonly name: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Normalizes one named skill package into the concrete files Ash writes to
|
|
18
|
+
* workspace resources or the live sandbox.
|
|
19
|
+
*/
|
|
20
|
+
export declare function normalizeSkillPackage(input: NamedSkillDefinition): MaterializableSkillPackage;
|
|
21
|
+
/**
|
|
22
|
+
* Writes a normalized package under a compiled workspace resource node root.
|
|
23
|
+
*
|
|
24
|
+
* For a `rootPath` of `.ash/compile/workspace-resources/__root__` and a skill
|
|
25
|
+
* named `research`, files land under `skills/research/`.
|
|
26
|
+
*/
|
|
27
|
+
export declare function writeSkillPackageDirectory(input: {
|
|
28
|
+
readonly rootPath: string;
|
|
29
|
+
readonly skill: MaterializableSkillPackage;
|
|
30
|
+
}): Promise<void>;
|
|
31
|
+
/**
|
|
32
|
+
* Writes a normalized package into the live sandbox under
|
|
33
|
+
* `/workspace/skills/<name>/`.
|
|
34
|
+
*/
|
|
35
|
+
export declare function writeSkillPackageToSandbox(input: {
|
|
36
|
+
readonly sandbox: SandboxSession;
|
|
37
|
+
readonly skill: MaterializableSkillPackage;
|
|
38
|
+
}): Promise<void>;
|
|
39
|
+
/**
|
|
40
|
+
* Validates a runtime-contributed skill name before it becomes one path
|
|
41
|
+
* segment under `/workspace/skills`.
|
|
42
|
+
*/
|
|
43
|
+
export declare function assertSafeSkillPackageName(name: string): void;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { Buffer } from "node:buffer";
|
|
2
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
const WORKSPACE_ROOT = "/workspace";
|
|
5
|
+
/**
|
|
6
|
+
* Normalizes one named skill package into the concrete files Ash writes to
|
|
7
|
+
* workspace resources or the live sandbox.
|
|
8
|
+
*/
|
|
9
|
+
export function normalizeSkillPackage(input) {
|
|
10
|
+
assertSafeSkillPackageName(input.name);
|
|
11
|
+
const files = [
|
|
12
|
+
{
|
|
13
|
+
content: Buffer.from(input.markdown, "utf8"),
|
|
14
|
+
relativePath: "SKILL.md",
|
|
15
|
+
},
|
|
16
|
+
];
|
|
17
|
+
for (const [relativePath, content] of Object.entries(input.files ?? {})) {
|
|
18
|
+
assertSafeSkillPackageFilePath(relativePath);
|
|
19
|
+
files.push({
|
|
20
|
+
content: contentToBuffer(content),
|
|
21
|
+
relativePath,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
files.sort((left, right) => comparePaths(left.relativePath, right.relativePath));
|
|
25
|
+
return {
|
|
26
|
+
description: input.description,
|
|
27
|
+
files,
|
|
28
|
+
license: input.license,
|
|
29
|
+
markdown: input.markdown,
|
|
30
|
+
metadata: input.metadata === undefined ? undefined : { ...input.metadata },
|
|
31
|
+
name: input.name,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Writes a normalized package under a compiled workspace resource node root.
|
|
36
|
+
*
|
|
37
|
+
* For a `rootPath` of `.ash/compile/workspace-resources/__root__` and a skill
|
|
38
|
+
* named `research`, files land under `skills/research/`.
|
|
39
|
+
*/
|
|
40
|
+
export async function writeSkillPackageDirectory(input) {
|
|
41
|
+
for (const file of input.skill.files) {
|
|
42
|
+
const filePath = join(input.rootPath, "skills", input.skill.name, file.relativePath);
|
|
43
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
44
|
+
await writeFile(filePath, file.content);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Writes a normalized package into the live sandbox under
|
|
49
|
+
* `/workspace/skills/<name>/`.
|
|
50
|
+
*/
|
|
51
|
+
export async function writeSkillPackageToSandbox(input) {
|
|
52
|
+
for (const file of input.skill.files) {
|
|
53
|
+
await input.sandbox.writeBinaryFile({
|
|
54
|
+
content: file.content,
|
|
55
|
+
path: `${WORKSPACE_ROOT}/skills/${input.skill.name}/${file.relativePath}`,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Validates a runtime-contributed skill name before it becomes one path
|
|
61
|
+
* segment under `/workspace/skills`.
|
|
62
|
+
*/
|
|
63
|
+
export function assertSafeSkillPackageName(name) {
|
|
64
|
+
if (name.length === 0 ||
|
|
65
|
+
name.trim() !== name ||
|
|
66
|
+
name.startsWith(".") ||
|
|
67
|
+
name.includes("/") ||
|
|
68
|
+
name.includes("\\") ||
|
|
69
|
+
name.includes("..") ||
|
|
70
|
+
/^[A-Za-z]:/.test(name)) {
|
|
71
|
+
throw new Error('Expected skill name to be a non-empty safe path segment without whitespace, separators, "." prefix, or "..".');
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function assertSafeSkillPackageFilePath(relativePath) {
|
|
75
|
+
if (relativePath === "SKILL.md") {
|
|
76
|
+
throw new Error('Skill package files must not include "SKILL.md"; Ash generates it.');
|
|
77
|
+
}
|
|
78
|
+
if (relativePath.length === 0 ||
|
|
79
|
+
relativePath.startsWith("/") ||
|
|
80
|
+
relativePath.includes("\\") ||
|
|
81
|
+
/^[A-Za-z]:/.test(relativePath) ||
|
|
82
|
+
relativePath
|
|
83
|
+
.split("/")
|
|
84
|
+
.some((segment) => segment.length === 0 || segment === "." || segment === "..")) {
|
|
85
|
+
throw new Error("Expected skill package file paths to be relative POSIX paths.");
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function contentToBuffer(content) {
|
|
89
|
+
if (typeof content === "string") {
|
|
90
|
+
return Buffer.from(content, "utf8");
|
|
91
|
+
}
|
|
92
|
+
return Buffer.from(content);
|
|
93
|
+
}
|
|
94
|
+
function comparePaths(left, right) {
|
|
95
|
+
if (left < right)
|
|
96
|
+
return -1;
|
|
97
|
+
if (left > right)
|
|
98
|
+
return 1;
|
|
99
|
+
return 0;
|
|
100
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "experimental-ash",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.0",
|
|
4
4
|
"bin": {
|
|
5
5
|
"ash": "./bin/ash.js",
|
|
6
6
|
"experimental-ash": "./bin/ash.js"
|
|
@@ -41,6 +41,11 @@
|
|
|
41
41
|
"import": "./dist/src/react/index.js",
|
|
42
42
|
"default": "./dist/src/react/index.js"
|
|
43
43
|
},
|
|
44
|
+
"./next": {
|
|
45
|
+
"types": "./dist/src/public/next/index.d.ts",
|
|
46
|
+
"import": "./dist/src/public/next/index.js",
|
|
47
|
+
"default": "./dist/src/public/next/index.js"
|
|
48
|
+
},
|
|
44
49
|
"./tools/approval": {
|
|
45
50
|
"types": "./dist/src/public/tools/approval/index.d.ts",
|
|
46
51
|
"import": "./dist/src/public/tools/approval/index.js",
|
|
@@ -156,9 +161,9 @@
|
|
|
156
161
|
},
|
|
157
162
|
"devDependencies": {
|
|
158
163
|
"@ai-sdk/anthropic": "4.0.0-canary.57",
|
|
159
|
-
"@ai-sdk/google": "4.0.0-canary.
|
|
164
|
+
"@ai-sdk/google": "4.0.0-canary.69",
|
|
160
165
|
"@ai-sdk/mcp": "2.0.0-canary.52",
|
|
161
|
-
"@ai-sdk/openai": "4.0.0-canary.
|
|
166
|
+
"@ai-sdk/openai": "4.0.0-canary.62",
|
|
162
167
|
"@ai-sdk/otel": "1.0.0-canary.90",
|
|
163
168
|
"@ai-sdk/provider": "4.0.0-canary.17",
|
|
164
169
|
"@chat-adapter/slack": "4.28.1",
|
|
@@ -181,6 +186,7 @@
|
|
|
181
186
|
"jose": "6.2.3",
|
|
182
187
|
"jsonc-parser": "3.3.1",
|
|
183
188
|
"just-bash": "2.14.5",
|
|
189
|
+
"next": "^16.2.6",
|
|
184
190
|
"picocolors": "^1.1.1",
|
|
185
191
|
"react": "^19.2.6",
|
|
186
192
|
"react-test-renderer": "19.2.6",
|
|
@@ -192,6 +198,7 @@
|
|
|
192
198
|
"@opentelemetry/api": "^1.9.0",
|
|
193
199
|
"ai": "7.0.0-canary.144",
|
|
194
200
|
"braintrust": ">=3.0.0",
|
|
201
|
+
"next": "^16.2.6",
|
|
195
202
|
"react": "^19.2.6"
|
|
196
203
|
},
|
|
197
204
|
"peerDependenciesMeta": {
|
|
@@ -201,6 +208,9 @@
|
|
|
201
208
|
"braintrust": {
|
|
202
209
|
"optional": true
|
|
203
210
|
},
|
|
211
|
+
"next": {
|
|
212
|
+
"optional": true
|
|
213
|
+
},
|
|
204
214
|
"react": {
|
|
205
215
|
"optional": true
|
|
206
216
|
}
|