@zhijiewang/openharness 2.16.0 → 2.18.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/README.md +52 -24
- package/README.zh-CN.md +785 -0
- package/dist/commands/hooks-report.d.ts +7 -0
- package/dist/commands/hooks-report.js +29 -0
- package/dist/commands/info.d.ts +1 -1
- package/dist/commands/info.js +7 -1
- package/dist/harness/config.d.ts +21 -1
- package/dist/harness/config.js +57 -35
- package/dist/harness/hooks.d.ts +2 -1
- package/dist/harness/hooks.js +1 -1
- package/dist/harness/language.d.ts +8 -0
- package/dist/harness/language.js +13 -0
- package/dist/main.js +107 -10
- package/dist/mcp/loader.d.ts +7 -0
- package/dist/mcp/loader.js +18 -0
- package/dist/outputStyles/index.d.ts +30 -0
- package/dist/outputStyles/index.js +89 -0
- package/dist/tools/ListMcpResourcesTool/index.d.ts +23 -0
- package/dist/tools/ListMcpResourcesTool/index.js +53 -0
- package/dist/tools/MemoryTool/index.d.ts +2 -2
- package/dist/tools/ReadMcpResourceTool/index.d.ts +20 -0
- package/dist/tools/ReadMcpResourceTool/index.js +51 -0
- package/dist/tools.js +4 -0
- package/dist/utils/json-schema.d.ts +24 -0
- package/dist/utils/json-schema.js +110 -0
- package/package.json +1 -1
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Output styles — pluggable system-prompt prefaces that swap the agent's
|
|
3
|
+
* personality without touching the underlying instructions. Mirrors Claude
|
|
4
|
+
* Code's `outputStyle` setting. Built-ins: default, explanatory, learning.
|
|
5
|
+
*
|
|
6
|
+
* Custom styles are markdown files with YAML frontmatter under:
|
|
7
|
+
* - .oh/output-styles/<name>.md (project-level; shadows user)
|
|
8
|
+
* - ~/.oh/output-styles/<name>.md (user-level; shadows built-ins)
|
|
9
|
+
*
|
|
10
|
+
* A style's `prompt` is prepended to the system prompt by buildSystemPrompt.
|
|
11
|
+
*/
|
|
12
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
13
|
+
import { homedir } from "node:os";
|
|
14
|
+
import { join } from "node:path";
|
|
15
|
+
import { parse as parseYaml } from "yaml";
|
|
16
|
+
export const DEFAULT_STYLE = {
|
|
17
|
+
name: "default",
|
|
18
|
+
description: "Standard software engineering assistant",
|
|
19
|
+
prompt: "",
|
|
20
|
+
};
|
|
21
|
+
export const EXPLANATORY_STYLE = {
|
|
22
|
+
name: "explanatory",
|
|
23
|
+
description: "Educational mode — adds an 'Insights' section between tasks",
|
|
24
|
+
prompt: "You are in Explanatory mode. After completing each task, add a short '## Insights' section explaining *why* you made the choices you did — trade-offs you considered, alternatives you rejected, and one concept the user may want to learn more about. Keep insights concise (2–3 sentences).",
|
|
25
|
+
};
|
|
26
|
+
export const LEARNING_STYLE = {
|
|
27
|
+
name: "learning",
|
|
28
|
+
description: "Collaborative learn-by-doing — leaves TODO(human) markers",
|
|
29
|
+
prompt: "You are in Learning mode. When implementing features, leave small `TODO(human)` comments at 1–3 strategic points per task — places where the user will learn the most by writing the code themselves. Explain what each TODO should do in the surrounding comment. Never leave more than 3 TODOs per task.",
|
|
30
|
+
};
|
|
31
|
+
export const BUILTIN_STYLES = [DEFAULT_STYLE, EXPLANATORY_STYLE, LEARNING_STYLE];
|
|
32
|
+
export function resolveStyleName(raw) {
|
|
33
|
+
const trimmed = raw?.trim();
|
|
34
|
+
if (!trimmed)
|
|
35
|
+
return "default";
|
|
36
|
+
return trimmed.toLowerCase();
|
|
37
|
+
}
|
|
38
|
+
export function loadOutputStyle(name, opts = {}) {
|
|
39
|
+
const resolved = resolveStyleName(name);
|
|
40
|
+
const projectRoot = opts.projectRoot ?? process.cwd();
|
|
41
|
+
const userHome = opts.userHome ?? homedir();
|
|
42
|
+
// Precedence: project → user → built-in → default fallback
|
|
43
|
+
const projectStyle = tryLoadFromDir(join(projectRoot, ".oh", "output-styles"), resolved);
|
|
44
|
+
if (projectStyle)
|
|
45
|
+
return projectStyle;
|
|
46
|
+
const userStyle = tryLoadFromDir(join(userHome, ".oh", "output-styles"), resolved);
|
|
47
|
+
if (userStyle)
|
|
48
|
+
return userStyle;
|
|
49
|
+
const builtin = BUILTIN_STYLES.find((s) => s.name === resolved);
|
|
50
|
+
if (builtin)
|
|
51
|
+
return builtin;
|
|
52
|
+
return DEFAULT_STYLE;
|
|
53
|
+
}
|
|
54
|
+
function tryLoadFromDir(dir, name) {
|
|
55
|
+
const path = join(dir, `${name}.md`);
|
|
56
|
+
if (!existsSync(path))
|
|
57
|
+
return null;
|
|
58
|
+
try {
|
|
59
|
+
const raw = readFileSync(path, "utf-8");
|
|
60
|
+
return parseStyleFile(raw, name);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function parseStyleFile(content, fallbackName) {
|
|
67
|
+
const match = content.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
|
|
68
|
+
if (!match) {
|
|
69
|
+
// No frontmatter — treat the entire content as the prompt body.
|
|
70
|
+
return { name: fallbackName, description: "", prompt: content.trim() };
|
|
71
|
+
}
|
|
72
|
+
const frontmatter = match[1];
|
|
73
|
+
const body = match[2] ?? "";
|
|
74
|
+
let parsed = {};
|
|
75
|
+
try {
|
|
76
|
+
const result = parseYaml(frontmatter);
|
|
77
|
+
if (result && typeof result === "object")
|
|
78
|
+
parsed = result;
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
/* malformed frontmatter — fall back to raw body with defaults */
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
name: typeof parsed.name === "string" ? parsed.name : fallbackName,
|
|
85
|
+
description: typeof parsed.description === "string" ? parsed.description : "",
|
|
86
|
+
prompt: body.trim(),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { Tool } from "../../Tool.js";
|
|
3
|
+
declare const inputSchema: z.ZodObject<{
|
|
4
|
+
server: z.ZodOptional<z.ZodString>;
|
|
5
|
+
}, "strip", z.ZodTypeAny, {
|
|
6
|
+
server?: string | undefined;
|
|
7
|
+
}, {
|
|
8
|
+
server?: string | undefined;
|
|
9
|
+
}>;
|
|
10
|
+
export type McpResourceEntry = {
|
|
11
|
+
server: string;
|
|
12
|
+
uri: string;
|
|
13
|
+
name: string;
|
|
14
|
+
description?: string;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Pure formatter — renders the resource list as a markdown table.
|
|
18
|
+
* Exported for testing; production callers should use the tool's `.call()`.
|
|
19
|
+
*/
|
|
20
|
+
export declare function formatResourcesList(resources: McpResourceEntry[], serverFilter?: string): string;
|
|
21
|
+
export declare const ListMcpResourcesTool: Tool<typeof inputSchema>;
|
|
22
|
+
export {};
|
|
23
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { listMcpResources } from "../../mcp/loader.js";
|
|
3
|
+
const inputSchema = z.object({
|
|
4
|
+
server: z.string().optional(),
|
|
5
|
+
});
|
|
6
|
+
/**
|
|
7
|
+
* Pure formatter — renders the resource list as a markdown table.
|
|
8
|
+
* Exported for testing; production callers should use the tool's `.call()`.
|
|
9
|
+
*/
|
|
10
|
+
export function formatResourcesList(resources, serverFilter) {
|
|
11
|
+
const filtered = serverFilter ? resources.filter((r) => r.server === serverFilter) : resources;
|
|
12
|
+
if (filtered.length === 0) {
|
|
13
|
+
if (serverFilter) {
|
|
14
|
+
return `No MCP resources available from server '${serverFilter}'.`;
|
|
15
|
+
}
|
|
16
|
+
return "No MCP resources available. Connect an MCP server that exposes resources under mcpServers in .oh/config.yaml.";
|
|
17
|
+
}
|
|
18
|
+
const lines = ["| Server | URI | Name | Description |", "|--------|-----|------|-------------|"];
|
|
19
|
+
for (const r of filtered) {
|
|
20
|
+
const desc = (r.description ?? "").replace(/\|/g, "\\|").slice(0, 80);
|
|
21
|
+
const name = r.name.replace(/\|/g, "\\|");
|
|
22
|
+
const uri = r.uri.replace(/\|/g, "\\|");
|
|
23
|
+
lines.push(`| ${r.server} | ${uri} | ${name} | ${desc} |`);
|
|
24
|
+
}
|
|
25
|
+
return lines.join("\n");
|
|
26
|
+
}
|
|
27
|
+
export const ListMcpResourcesTool = {
|
|
28
|
+
name: "ListMcpResources",
|
|
29
|
+
description: "List resources exposed by connected MCP servers.",
|
|
30
|
+
inputSchema,
|
|
31
|
+
riskLevel: "low",
|
|
32
|
+
isReadOnly() {
|
|
33
|
+
return true;
|
|
34
|
+
},
|
|
35
|
+
isConcurrencySafe() {
|
|
36
|
+
return true;
|
|
37
|
+
},
|
|
38
|
+
async call(input) {
|
|
39
|
+
try {
|
|
40
|
+
const resources = await listMcpResources();
|
|
41
|
+
return { output: formatResourcesList(resources, input.server), isError: false };
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
return { output: `Error listing MCP resources: ${err.message}`, isError: true };
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
prompt() {
|
|
48
|
+
return `List resources exposed by connected MCP servers. Parameters:
|
|
49
|
+
- server (string, optional): restrict to this server's resources.
|
|
50
|
+
Returns a markdown table with columns: Server, URI, Name, Description. Use ReadMcpResource with a URI from the table to fetch the content. Resources are read-only data sources (docs, indices, state) — distinct from MCP tools, which are actions.`;
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -11,7 +11,7 @@ declare const inputSchema: z.ZodObject<{
|
|
|
11
11
|
}, "strip", z.ZodTypeAny, {
|
|
12
12
|
action: "search" | "save" | "list";
|
|
13
13
|
content?: string | undefined;
|
|
14
|
-
type?: "user" | "
|
|
14
|
+
type?: "user" | "project" | "convention" | "preference" | "debugging" | "feedback" | "reference" | undefined;
|
|
15
15
|
name?: string | undefined;
|
|
16
16
|
description?: string | undefined;
|
|
17
17
|
global?: boolean | undefined;
|
|
@@ -19,7 +19,7 @@ declare const inputSchema: z.ZodObject<{
|
|
|
19
19
|
}, {
|
|
20
20
|
action: "search" | "save" | "list";
|
|
21
21
|
content?: string | undefined;
|
|
22
|
-
type?: "user" | "
|
|
22
|
+
type?: "user" | "project" | "convention" | "preference" | "debugging" | "feedback" | "reference" | undefined;
|
|
23
23
|
name?: string | undefined;
|
|
24
24
|
description?: string | undefined;
|
|
25
25
|
global?: boolean | undefined;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { Tool } from "../../Tool.js";
|
|
3
|
+
declare const inputSchema: z.ZodObject<{
|
|
4
|
+
uri: z.ZodString;
|
|
5
|
+
server: z.ZodOptional<z.ZodString>;
|
|
6
|
+
}, "strip", z.ZodTypeAny, {
|
|
7
|
+
uri: string;
|
|
8
|
+
server?: string | undefined;
|
|
9
|
+
}, {
|
|
10
|
+
uri: string;
|
|
11
|
+
server?: string | undefined;
|
|
12
|
+
}>;
|
|
13
|
+
/**
|
|
14
|
+
* Pure helper — truncates resource content to MAX_OUTPUT_CHARS with a
|
|
15
|
+
* trailing `[...truncated]` marker when exceeded. Exported for testing.
|
|
16
|
+
*/
|
|
17
|
+
export declare function formatResourceContent(content: string, maxChars?: number): string;
|
|
18
|
+
export declare const ReadMcpResourceTool: Tool<typeof inputSchema>;
|
|
19
|
+
export {};
|
|
20
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { readMcpResource } from "../../mcp/loader.js";
|
|
3
|
+
const inputSchema = z.object({
|
|
4
|
+
uri: z.string(),
|
|
5
|
+
server: z.string().optional(),
|
|
6
|
+
});
|
|
7
|
+
const MAX_OUTPUT_CHARS = 50_000;
|
|
8
|
+
/**
|
|
9
|
+
* Pure helper — truncates resource content to MAX_OUTPUT_CHARS with a
|
|
10
|
+
* trailing `[...truncated]` marker when exceeded. Exported for testing.
|
|
11
|
+
*/
|
|
12
|
+
export function formatResourceContent(content, maxChars = MAX_OUTPUT_CHARS) {
|
|
13
|
+
if (content.length <= maxChars)
|
|
14
|
+
return content;
|
|
15
|
+
return `${content.slice(0, maxChars)}\n[...truncated at ${maxChars} chars, original length ${content.length}]`;
|
|
16
|
+
}
|
|
17
|
+
export const ReadMcpResourceTool = {
|
|
18
|
+
name: "ReadMcpResource",
|
|
19
|
+
description: "Read a specific MCP resource by URI from a connected MCP server.",
|
|
20
|
+
inputSchema,
|
|
21
|
+
riskLevel: "low",
|
|
22
|
+
isReadOnly() {
|
|
23
|
+
return true;
|
|
24
|
+
},
|
|
25
|
+
isConcurrencySafe() {
|
|
26
|
+
return true;
|
|
27
|
+
},
|
|
28
|
+
async call(input) {
|
|
29
|
+
try {
|
|
30
|
+
const content = await readMcpResource(input.uri, input.server);
|
|
31
|
+
if (content === null) {
|
|
32
|
+
const where = input.server ? ` from server '${input.server}'` : "";
|
|
33
|
+
return {
|
|
34
|
+
output: `Resource '${input.uri}' not found${where}. Run ListMcpResources to see available URIs.`,
|
|
35
|
+
isError: true,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
return { output: formatResourceContent(content), isError: false };
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
return { output: `Error reading MCP resource: ${err.message}`, isError: true };
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
prompt() {
|
|
45
|
+
return `Read a specific resource from an MCP server by URI. Parameters:
|
|
46
|
+
- uri (string, required): the resource URI, as shown by ListMcpResources.
|
|
47
|
+
- server (string, optional): restrict lookup to this server. When omitted, the first server whose readResource call succeeds is used.
|
|
48
|
+
Output is truncated at ~50KB. For discovery, call ListMcpResources first to get URIs.`;
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
//# sourceMappingURL=index.js.map
|
package/dist/tools.js
CHANGED
|
@@ -23,6 +23,7 @@ import { GlobTool } from "./tools/GlobTool/index.js";
|
|
|
23
23
|
import { GrepTool } from "./tools/GrepTool/index.js";
|
|
24
24
|
import { ImageReadTool } from "./tools/ImageReadTool/index.js";
|
|
25
25
|
import { KillProcessTool } from "./tools/KillProcessTool/index.js";
|
|
26
|
+
import { ListMcpResourcesTool } from "./tools/ListMcpResourcesTool/index.js";
|
|
26
27
|
import { LSTool } from "./tools/LSTool/index.js";
|
|
27
28
|
import { MemoryTool } from "./tools/MemoryTool/index.js";
|
|
28
29
|
import { MonitorTool } from "./tools/MonitorTool/index.js";
|
|
@@ -31,6 +32,7 @@ import { NotebookEditTool } from "./tools/NotebookEditTool/index.js";
|
|
|
31
32
|
import { ParallelAgentTool } from "./tools/ParallelAgentTool/index.js";
|
|
32
33
|
import { PipelineTool } from "./tools/PipelineTool/index.js";
|
|
33
34
|
import { PowerShellTool } from "./tools/PowerShellTool/index.js";
|
|
35
|
+
import { ReadMcpResourceTool } from "./tools/ReadMcpResourceTool/index.js";
|
|
34
36
|
import { RemoteTriggerTool } from "./tools/RemoteTriggerTool/index.js";
|
|
35
37
|
import { ScheduleWakeupTool } from "./tools/ScheduleWakeupTool/index.js";
|
|
36
38
|
import { SendMessageTool } from "./tools/SendMessageTool/index.js";
|
|
@@ -106,6 +108,8 @@ export function getAllTools() {
|
|
|
106
108
|
ScheduleWakeupTool,
|
|
107
109
|
SessionSearchTool,
|
|
108
110
|
TodoWriteTool,
|
|
111
|
+
ListMcpResourcesTool,
|
|
112
|
+
ReadMcpResourceTool,
|
|
109
113
|
];
|
|
110
114
|
return [...core, ...extended.map((t) => new DeferredTool(t))];
|
|
111
115
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal JSON Schema validator — covers the common subset sufficient for
|
|
3
|
+
* constraining LLM output in headless mode. Supported keywords:
|
|
4
|
+
*
|
|
5
|
+
* - `type`: "string" | "number" | "integer" | "boolean" | "object" | "array" | "null"
|
|
6
|
+
* (or an array of those for union types)
|
|
7
|
+
* - `properties`: object → sub-schema per field
|
|
8
|
+
* - `required`: array of field names that must be present
|
|
9
|
+
* - `items`: sub-schema for array elements
|
|
10
|
+
* - `enum`: array of allowed literal values (compared with strict equality)
|
|
11
|
+
*
|
|
12
|
+
* Anything else is silently accepted. This is intentional — we don't want to
|
|
13
|
+
* ship a full JSON Schema engine. For cases that need more (e.g. `pattern`,
|
|
14
|
+
* `oneOf`, `$ref`), use an external validator.
|
|
15
|
+
*/
|
|
16
|
+
export type JsonSchema = Record<string, unknown>;
|
|
17
|
+
export type ValidationResult = {
|
|
18
|
+
ok: true;
|
|
19
|
+
} | {
|
|
20
|
+
ok: false;
|
|
21
|
+
errors: string[];
|
|
22
|
+
};
|
|
23
|
+
export declare function validateAgainstJsonSchema(value: unknown, schema: JsonSchema): ValidationResult;
|
|
24
|
+
//# sourceMappingURL=json-schema.d.ts.map
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal JSON Schema validator — covers the common subset sufficient for
|
|
3
|
+
* constraining LLM output in headless mode. Supported keywords:
|
|
4
|
+
*
|
|
5
|
+
* - `type`: "string" | "number" | "integer" | "boolean" | "object" | "array" | "null"
|
|
6
|
+
* (or an array of those for union types)
|
|
7
|
+
* - `properties`: object → sub-schema per field
|
|
8
|
+
* - `required`: array of field names that must be present
|
|
9
|
+
* - `items`: sub-schema for array elements
|
|
10
|
+
* - `enum`: array of allowed literal values (compared with strict equality)
|
|
11
|
+
*
|
|
12
|
+
* Anything else is silently accepted. This is intentional — we don't want to
|
|
13
|
+
* ship a full JSON Schema engine. For cases that need more (e.g. `pattern`,
|
|
14
|
+
* `oneOf`, `$ref`), use an external validator.
|
|
15
|
+
*/
|
|
16
|
+
export function validateAgainstJsonSchema(value, schema) {
|
|
17
|
+
const errors = [];
|
|
18
|
+
validate(value, schema, "", errors);
|
|
19
|
+
return errors.length === 0 ? { ok: true } : { ok: false, errors };
|
|
20
|
+
}
|
|
21
|
+
function validate(value, schema, path, errors) {
|
|
22
|
+
if (schema.enum !== undefined && Array.isArray(schema.enum)) {
|
|
23
|
+
if (!schema.enum.some((allowed) => deepEqual(allowed, value))) {
|
|
24
|
+
errors.push(`${prefix(path)}value ${JSON.stringify(value)} is not one of the enum values`);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
if (schema.type !== undefined) {
|
|
29
|
+
const types = Array.isArray(schema.type) ? schema.type : [schema.type];
|
|
30
|
+
if (!types.some((t) => matchesType(value, t))) {
|
|
31
|
+
errors.push(`${prefix(path)}expected ${types.join(" or ")}, got ${describeActual(value)}`);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (matchesType(value, "object") && schema.properties) {
|
|
36
|
+
const properties = schema.properties;
|
|
37
|
+
const required = schema.required ?? [];
|
|
38
|
+
const obj = value;
|
|
39
|
+
for (const field of required) {
|
|
40
|
+
if (!(field in obj)) {
|
|
41
|
+
const fullPath = path ? `${path}.${field}` : field;
|
|
42
|
+
errors.push(`missing required property '${fullPath}'`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
for (const [field, subSchema] of Object.entries(properties)) {
|
|
46
|
+
if (field in obj) {
|
|
47
|
+
validate(obj[field], subSchema, path ? `${path}.${field}` : field, errors);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (matchesType(value, "array") && schema.items) {
|
|
52
|
+
const items = schema.items;
|
|
53
|
+
const arr = value;
|
|
54
|
+
arr.forEach((item, i) => {
|
|
55
|
+
validate(item, items, `${path}[${i}]`, errors);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function matchesType(value, type) {
|
|
60
|
+
switch (type) {
|
|
61
|
+
case "string":
|
|
62
|
+
return typeof value === "string";
|
|
63
|
+
case "number":
|
|
64
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
65
|
+
case "integer":
|
|
66
|
+
return typeof value === "number" && Number.isInteger(value);
|
|
67
|
+
case "boolean":
|
|
68
|
+
return typeof value === "boolean";
|
|
69
|
+
case "null":
|
|
70
|
+
return value === null;
|
|
71
|
+
case "array":
|
|
72
|
+
return Array.isArray(value);
|
|
73
|
+
case "object":
|
|
74
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
75
|
+
default:
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function describeActual(value) {
|
|
80
|
+
if (value === null)
|
|
81
|
+
return "null";
|
|
82
|
+
if (Array.isArray(value))
|
|
83
|
+
return "array";
|
|
84
|
+
return typeof value;
|
|
85
|
+
}
|
|
86
|
+
function prefix(path) {
|
|
87
|
+
return path ? `${path}: ` : "";
|
|
88
|
+
}
|
|
89
|
+
function deepEqual(a, b) {
|
|
90
|
+
if (a === b)
|
|
91
|
+
return true;
|
|
92
|
+
if (typeof a !== typeof b)
|
|
93
|
+
return false;
|
|
94
|
+
if (a === null || b === null)
|
|
95
|
+
return a === b;
|
|
96
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
97
|
+
if (a.length !== b.length)
|
|
98
|
+
return false;
|
|
99
|
+
return a.every((x, i) => deepEqual(x, b[i]));
|
|
100
|
+
}
|
|
101
|
+
if (typeof a === "object" && typeof b === "object") {
|
|
102
|
+
const ka = Object.keys(a);
|
|
103
|
+
const kb = Object.keys(b);
|
|
104
|
+
if (ka.length !== kb.length)
|
|
105
|
+
return false;
|
|
106
|
+
return ka.every((k) => deepEqual(a[k], b[k]));
|
|
107
|
+
}
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=json-schema.js.map
|