@darkhorseprojects/circuitry 0.3.9 → 0.4.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/dist/node.js CHANGED
@@ -9,165 +9,142 @@ import YAML2 from "yaml";
9
9
 
10
10
  // src/graph.ts
11
11
  import YAML from "yaml";
12
- var CIRCUITRY_SPEC_VERSION = "0.3.2";
13
- var DEFAULT_CIRCUITRY_VALIDATION_RULES = [
14
- "no-self-loops",
15
- "no-unknown-edge-endpoints",
16
- "require-executable-inputs",
17
- "no-cycles"
18
- ];
12
+ var CIRCUITRY_SPEC_VERSION = "0.4";
19
13
  var DEFAULT_CIRCUITRY_VALIDATION_STANDARD = {
20
14
  version: CIRCUITRY_SPEC_VERSION,
21
- requireSpecVersion: true,
22
- rules: DEFAULT_CIRCUITRY_VALIDATION_RULES,
23
- executableKinds: ["agent", "tool", "output"]
15
+ requireSpecVersion: true
24
16
  };
25
- var runtimeInputToText = (value) => {
26
- if (typeof value === "string") return value;
27
- if (value === void 0) return "";
28
- return YAML.stringify(value).trimEnd();
17
+ var createCircuitryValidationStandard = (standard = {}) => ({
18
+ version: standard.version || DEFAULT_CIRCUITRY_VALIDATION_STANDARD.version,
19
+ requireSpecVersion: standard.requireSpecVersion ?? DEFAULT_CIRCUITRY_VALIDATION_STANDARD.requireSpecVersion
20
+ });
21
+ var getResourceInputs = (resource) => {
22
+ const inputs = resource.inputs;
23
+ if (!inputs) return [];
24
+ if (Array.isArray(inputs)) return inputs.map((from) => ({ from, to: "" }));
25
+ return Object.entries(inputs).map(([name, from]) => ({ from, to: "", name }));
29
26
  };
30
- var applyCircuitryRuntimeInputs = (graph, inputs = {}) => {
31
- const next = { ...graph, resources: graph.resources ? { ...graph.resources } : graph.resources };
32
- for (const [id, value] of Object.entries(inputs)) {
33
- const resource = next.resources?.[id];
34
- if (!resource) throw new Error(`unknown arg: ${id}`);
35
- if (resource.type !== "text") throw new Error(`arg is not text: ${id}`);
36
- next.resources[id] = { ...resource, value: runtimeInputToText(value) };
27
+ var resourceInputIds = (resource) => getResourceInputs(resource).map((dependency) => dependency.from);
28
+ var getCircuitryDependencies = (graph) => {
29
+ const dependencies = [];
30
+ for (const [to, resource] of Object.entries(graph.resources || {})) {
31
+ for (const dependency of getResourceInputs(resource)) dependencies.push({ ...dependency, to });
37
32
  }
38
- return next;
33
+ return dependencies;
39
34
  };
40
- var ruleName = (rule) => typeof rule === "string" ? rule : rule.rule;
41
- var executableKindsFromRules = (rules) => rules.find((rule) => typeof rule !== "string" && rule.rule === "require-executable-inputs")?.executableKinds;
42
- var createCircuitryValidationStandard = (standard = {}, graph) => {
43
- const rules = [...DEFAULT_CIRCUITRY_VALIDATION_RULES, ...graph?.validation?.rules || [], ...standard.rules || []];
44
- return {
45
- version: standard.version || DEFAULT_CIRCUITRY_VALIDATION_STANDARD.version,
46
- requireSpecVersion: standard.requireSpecVersion ?? DEFAULT_CIRCUITRY_VALIDATION_STANDARD.requireSpecVersion,
47
- rules,
48
- executableKinds: standard.executableKinds || executableKindsFromRules(rules) || [...DEFAULT_CIRCUITRY_VALIDATION_STANDARD.executableKinds]
35
+ var getCircuitryRunSet = (graph, entry = graph.entry) => {
36
+ if (!entry) return [];
37
+ const visited = /* @__PURE__ */ new Set();
38
+ const visit = (id) => {
39
+ if (visited.has(id)) return;
40
+ const resource = graph.resources?.[id];
41
+ if (!resource) return;
42
+ for (const input of resourceInputIds(resource)) visit(input);
43
+ visited.add(id);
49
44
  };
45
+ visit(entry);
46
+ return [...visited];
50
47
  };
51
- var expandResources = (resources) => {
52
- const nodes = [];
53
- const edges = [];
54
- for (const [id, resource] of Object.entries(resources)) {
55
- if (resource.type === "text") {
56
- nodes.push({ id, kind: "input", label: resource.label || id, input: { type: "text", value: resource.value } });
57
- } else if (resource.type === "agent") {
58
- const agent = resource;
59
- nodes.push({
60
- id,
61
- kind: "agent",
62
- label: agent.label || agent.identity || id,
63
- agent: {
64
- identity: agent.identity || agent.label || id,
65
- model: agent.model,
66
- tools: agent.tools,
67
- instructions: agent.instructions,
68
- personality: agent.personality,
69
- context: agent.context
70
- },
71
- skills: agent.skills,
72
- expect: agent.expect
73
- });
74
- for (const input of agent.inputs || []) edges.push({ from: input, to: id, kind: "dependency" });
75
- } else if (resource.type === "tool") {
76
- const tool = resource;
77
- nodes.push({ id, kind: "tool", label: tool.label || id, agent: { instructions: tool.instructions } });
78
- for (const input of tool.inputs || []) edges.push({ from: input, to: id, kind: "dependency" });
79
- } else {
80
- const input = resource;
81
- nodes.push({ id, kind: "input", label: input.label || id, input: { type: input.type, value: input.value, uri: input.uri || input.path, mimeType: input.mimeType, data: input.data } });
48
+ var toposortCircuitryResources = (graph, entry) => {
49
+ const ids = entry ? new Set(getCircuitryRunSet(graph, entry)) : new Set(Object.keys(graph.resources || {}));
50
+ const visiting = /* @__PURE__ */ new Set();
51
+ const visited = /* @__PURE__ */ new Set();
52
+ const ordered = [];
53
+ const visit = (id) => {
54
+ if (!ids.has(id) || visited.has(id)) return;
55
+ if (visiting.has(id)) throw new Error(`Circuitry resource dependency cycle at ${id}`);
56
+ visiting.add(id);
57
+ const resource = graph.resources?.[id];
58
+ if (resource) for (const input of resourceInputIds(resource)) visit(input);
59
+ visiting.delete(id);
60
+ visited.add(id);
61
+ ordered.push(id);
62
+ };
63
+ for (const id of ids) visit(id);
64
+ return ordered;
65
+ };
66
+ var runtimeInputToText = (value) => {
67
+ if (typeof value === "string") return value;
68
+ if (value === void 0) return "";
69
+ return YAML.stringify(value).trimEnd();
70
+ };
71
+ var resolveCircuitrySource = (graph) => {
72
+ const inputs = graph.inputs || graph.args || {};
73
+ const resources = { ...graph.resources || {} };
74
+ for (const [id, input] of Object.entries(inputs)) {
75
+ if (!resources[id]) {
76
+ resources[id] = {
77
+ type: "input",
78
+ from: id,
79
+ label: input.label,
80
+ description: input.description,
81
+ mime: input.mime || input.mimeType
82
+ };
82
83
  }
83
84
  }
84
- return { nodes, edges };
85
+ return { ...graph, circuitry: CIRCUITRY_SPEC_VERSION, imports: graph.imports || [], inputs, resources };
85
86
  };
86
- var normalizeCircuitryGraph = (graph) => {
87
- if (!graph.resources) return { ...graph };
88
- return { ...graph, ...expandResources(graph.resources) };
87
+ var applyCircuitryRuntimeInputs = (graph, inputs = {}) => {
88
+ const next = resolveCircuitrySource(graph);
89
+ const resources = { ...next.resources || {} };
90
+ for (const [id, value] of Object.entries(inputs)) {
91
+ const resource = resources[id];
92
+ if (!resource) throw new Error(`unknown input: ${id}`);
93
+ if (resource.type === "input") resources[id] = { ...resource, value, from: resource.from || id };
94
+ else resources[id] = { ...resource, value: runtimeInputToText(value) };
95
+ }
96
+ return { ...next, resources };
89
97
  };
90
- var validateCircuitryGraphInternal = (graph, standard = {}, source) => {
91
- const resolved = createCircuitryValidationStandard(standard, graph && typeof graph === "object" && !Array.isArray(graph) ? graph : void 0);
98
+ var validateCircuitrySource = (graph, standard = {}) => {
99
+ const resolvedStandard = createCircuitryValidationStandard(standard);
92
100
  const errors = [];
93
101
  const issue = (code, message = code, path2) => errors.push({ code, message, ...path2 ? { path: path2 } : {} });
94
- if (!graph || typeof graph !== "object" || Array.isArray(graph)) return { ok: false, errors: [{ code: "invalid_graph", message: "invalid graph" }], standard: resolved };
102
+ if (!graph || typeof graph !== "object" || Array.isArray(graph)) {
103
+ return { ok: false, errors: [{ code: "invalid_graph", message: "invalid graph" }], standard: resolvedStandard };
104
+ }
95
105
  const g = graph;
96
- if (resolved.requireSpecVersion && g.circuitry !== resolved.version) issue("invalid_version", "invalid version", ["circuitry"]);
97
- if (source) {
98
- const raw = g;
99
- if (!g.resources || Object.keys(g.resources).length === 0) issue("missing_resources", "missing resources", ["resources"]);
100
- if (raw.agents) issue("forbidden_agents", "forbidden agents", ["agents"]);
101
- if (raw.inputs) issue("forbidden_inputs", "forbidden inputs", ["inputs"]);
102
- if (g.nodes?.length) issue("forbidden_nodes", "forbidden nodes", ["nodes"]);
103
- if (g.edges?.length) issue("forbidden_edges", "forbidden edges", ["edges"]);
104
- if (raw.links) issue("forbidden_links", "forbidden links", ["links"]);
105
- }
106
- const rules = new Set(resolved.rules.map(ruleName));
107
- const normalized = normalizeCircuitryGraph(g);
108
- const ids = /* @__PURE__ */ new Set();
109
- for (const [index, node] of (normalized.nodes || []).entries()) {
110
- if (!node.id) {
111
- issue("missing_node_id", "missing node id", ["nodes", index, "id"]);
112
- continue;
113
- }
114
- if (ids.has(node.id)) issue("duplicate_node_id", "duplicate node id", ["nodes", index, "id"]);
115
- ids.add(node.id);
116
- if (!node.kind) issue("missing_node_kind", "missing node kind", ["nodes", index, "kind"]);
117
- }
118
- const adjacency = /* @__PURE__ */ new Map();
119
- const incoming = /* @__PURE__ */ new Map();
120
- for (const id of ids) {
121
- adjacency.set(id, []);
122
- incoming.set(id, 0);
123
- }
124
- for (const [index, edge] of (normalized.edges || []).entries()) {
125
- if (!edge.from || !edge.to) {
126
- issue("missing_edge_endpoint", "missing edge endpoint", ["edges", index]);
127
- continue;
128
- }
129
- if (rules.has("no-self-loops") && edge.from === edge.to) {
130
- issue("self_loop", "self loop", ["edges", index]);
131
- continue;
132
- }
133
- const ok = ids.has(edge.from) && ids.has(edge.to);
134
- if (rules.has("no-unknown-edge-endpoints")) {
135
- if (!ids.has(edge.from)) issue("unknown_edge_source", "unknown edge source", ["edges", index, "from"]);
136
- if (!ids.has(edge.to)) issue("unknown_edge_target", "unknown edge target", ["edges", index, "to"]);
137
- }
138
- if (ok) {
139
- adjacency.get(edge.from).push(edge.to);
140
- incoming.set(edge.to, (incoming.get(edge.to) || 0) + 1);
141
- }
106
+ if (resolvedStandard.requireSpecVersion && g.circuitry !== resolvedStandard.version) issue("invalid_version", "invalid version", ["circuitry"]);
107
+ if (!g.resources || Object.keys(g.resources).length === 0) issue("missing_resources", "missing resources", ["resources"]);
108
+ if (g.nodes) issue("forbidden_nodes", "forbidden nodes", ["nodes"]);
109
+ if (g.edges) issue("forbidden_edges", "forbidden edges", ["edges"]);
110
+ if (g.agents) issue("forbidden_agents", "forbidden agents", ["agents"]);
111
+ const resolved = resolveCircuitrySource(g);
112
+ const ids = new Set(Object.keys(resolved.resources || {}));
113
+ if (resolved.entry && !ids.has(resolved.entry)) issue("unknown_entry", "unknown entry", ["entry"]);
114
+ for (const [name, target] of Object.entries(resolved.entries || {})) {
115
+ if (!ids.has(target)) issue("unknown_entry", `unknown entry: ${name}`, ["entries", name]);
116
+ }
117
+ for (const [id, resource] of Object.entries(resolved.resources || {})) {
118
+ if (!resource.type) issue("missing_resource_type", "missing resource type", ["resources", id, "type"]);
119
+ for (const input of resourceInputIds(resource)) {
120
+ if (!ids.has(input)) issue("unknown_resource_input", `unknown resource input: ${input}`, ["resources", id, "inputs"]);
121
+ if (input === id) issue("self_input", "self input", ["resources", id, "inputs"]);
122
+ }
123
+ }
124
+ for (const [name, output] of Object.entries(resolved.outputs || {})) {
125
+ const root = output.from.split(".")[0];
126
+ if (!ids.has(root)) issue("unknown_output_source", `unknown output source: ${output.from}`, ["outputs", name, "from"]);
142
127
  }
143
- if (rules.has("require-executable-inputs")) {
144
- const executable = new Set(resolved.executableKinds);
145
- for (const [index, node] of (normalized.nodes || []).entries()) {
146
- if (executable.has(node.kind) && (incoming.get(node.id) || 0) === 0) issue("missing_executable_input", "missing executable input", ["nodes", index]);
147
- }
128
+ try {
129
+ toposortCircuitryResources(resolved);
130
+ } catch {
131
+ issue("cycle", "cycle");
148
132
  }
149
- if (rules.has("no-cycles")) {
150
- const visiting = /* @__PURE__ */ new Set();
151
- const visited = /* @__PURE__ */ new Set();
152
- const visit = (id) => {
153
- if (visiting.has(id)) return true;
154
- if (visited.has(id)) return false;
155
- visiting.add(id);
156
- for (const next of adjacency.get(id) || []) if (visit(next)) return true;
157
- visiting.delete(id);
158
- visited.add(id);
159
- return false;
160
- };
161
- for (const id of ids) if (visit(id)) {
162
- issue("cycle", "cycle");
163
- break;
164
- }
133
+ return { ok: errors.length === 0, errors, standard: resolvedStandard };
134
+ };
135
+ var validateCircuitryGraphWithStandard = validateCircuitrySource;
136
+ var validateCircuitryExecutionGraphWithStandard = validateCircuitrySource;
137
+ var validateCircuitryGraph = (graph, standard = {}) => validateCircuitrySource(graph, standard).errors.map((error) => error.message);
138
+ var normalizeCircuitryGraph = (graph) => {
139
+ const resolved = resolveCircuitrySource(graph);
140
+ const nodes = [];
141
+ for (const [id, resource] of Object.entries(resolved.resources || {})) {
142
+ if (resource.type === "agent") nodes.push({ id, kind: "agent", label: resource.label || resource.identity || id, agent: { identity: resource.identity, model: resource.model, tools: resource.tools, instructions: resource.instructions, personality: resource.personality, context: resource.context }, skills: resource.skills, expect: resource.expect });
143
+ else nodes.push({ id, kind: resource.type === "tool" ? "tool" : "input", label: resource.label || id, input: { type: resource.type, value: typeof resource.value === "string" ? resource.value : void 0, uri: resource.uri || resource.path, mimeType: resource.mime || resource.mimeType, data: resource.data } });
165
144
  }
166
- return { ok: errors.length === 0, errors, standard: resolved };
145
+ const edges = getCircuitryDependencies(resolved).map(({ from, to }) => ({ from, to, kind: "dependency" }));
146
+ return { ...resolved, nodes, edges };
167
147
  };
168
- var validateCircuitryGraphWithStandard = (graph, standard = {}) => validateCircuitryGraphInternal(graph, standard, true);
169
- var validateCircuitryExecutionGraphWithStandard = (graph, standard = {}) => validateCircuitryGraphInternal(graph, standard, false);
170
- var validateCircuitryGraph = (graph, standard = {}) => validateCircuitryGraphWithStandard(graph, standard).errors.map((error) => error.message);
171
148
 
172
149
  // src/yaml.ts
173
150
  var parseYamlData = (text) => {
@@ -446,7 +423,7 @@ var runCircuitryGraphExecution = async ({
446
423
  const executionGraph = buildCircuitryExecutionGraph(graph);
447
424
  const runs = [];
448
425
  const parallelism = Math.max(1, Math.floor(maxParallelRuns));
449
- const defaultModel = graph.runtime?.model;
426
+ const defaultModel = typeof graph.runtime?.model === "string" ? graph.runtime.model : void 0;
450
427
  const runOne = async (nodeId, outputs2) => {
451
428
  const cycle = false;
452
429
  const item = executionGraph.nodes.get(nodeId);
@@ -645,10 +622,19 @@ var runNodeWithPiSDK = async ({
645
622
  }
646
623
  };
647
624
  var defaultFilename = "graph.circuitry.yaml";
648
- var resourceInputs = (resource) => "inputs" in resource && Array.isArray(resource.inputs) ? resource.inputs : [];
649
- var withResourceInputs = (resource, inputs) => "inputs" in resource ? { ...resource, inputs } : resource;
625
+ var resourceInputs = (resource) => {
626
+ if (!("inputs" in resource) || !resource.inputs) return [];
627
+ return Array.isArray(resource.inputs) ? resource.inputs : Object.values(resource.inputs);
628
+ };
629
+ var withResourceInputs = (resource, inputs) => {
630
+ if (!("inputs" in resource) || !resource.inputs) return resource;
631
+ if (Array.isArray(resource.inputs)) return { ...resource, inputs };
632
+ const inputMap = resource.inputs;
633
+ const names = Object.keys(inputMap);
634
+ return { ...resource, inputs: Object.fromEntries(names.map((name, index) => [name, inputs[index] || inputMap[name]])) };
635
+ };
650
636
  var selectedImportResources = (imp, resources) => {
651
- if (imp.resources === "*") return Object.fromEntries(Object.keys(resources).map((id) => [id, id]));
637
+ if (!imp.resources || imp.resources === "*") return Object.fromEntries(Object.keys(resources).map((id) => [id, id]));
652
638
  if (Array.isArray(imp.resources)) return Object.fromEntries(imp.resources.map((id) => [id, id]));
653
639
  return imp.resources;
654
640
  };
@@ -686,7 +672,7 @@ var resolveCircuitryGraph = async (parsed, graphFile, text, standard, stack = []
686
672
  resources[id] = resource;
687
673
  origins[id] = graphFile;
688
674
  }
689
- const graph = normalizeCircuitryGraph({ ...parsed, imports: [], resources });
675
+ const graph = { ...parsed, circuitry: "0.4", imports: [], inputs: parsed.inputs || parsed.args || {}, resources };
690
676
  if (stack.length === 0) {
691
677
  const validation = validateCircuitryExecutionGraphWithStandard(graph, standard);
692
678
  if (validation.errors.length) {
@@ -0,0 +1,24 @@
1
+ circuitry: "0.4"
2
+ title: Agent with prompt input
3
+ inputs:
4
+ user_turn:
5
+ type: text
6
+ required: true
7
+ entry: assistant
8
+ resources:
9
+ user_turn:
10
+ type: input
11
+ from: user_turn
12
+ guide:
13
+ type: text
14
+ value: Be direct, accurate, and natural.
15
+ assistant:
16
+ type: agent
17
+ inputs: [guide, user_turn]
18
+ tools: [read, write, bash, run_graph]
19
+ instructions: Use the guide and answer the user.
20
+ expect:
21
+ response: str
22
+ outputs:
23
+ response:
24
+ from: assistant.response
@@ -0,0 +1,13 @@
1
+ circuitry: "0.4"
2
+ title: Imports and entries
3
+ imports:
4
+ - path: ./agent.circuitry.yaml
5
+ resources: "*"
6
+ prefix: imported_
7
+ entry: imported_assistant
8
+ entries:
9
+ default: imported_assistant
10
+ resources:
11
+ local_note:
12
+ type: text
13
+ value: Local resource.
@@ -0,0 +1,20 @@
1
+ circuitry: "0.4"
2
+ title: Minimal
3
+ inputs:
4
+ user_turn:
5
+ type: text
6
+ required: true
7
+ entry: assistant
8
+ resources:
9
+ user_turn:
10
+ type: input
11
+ from: user_turn
12
+ assistant:
13
+ type: agent
14
+ inputs: [user_turn]
15
+ instructions: Answer the user.
16
+ expect:
17
+ response: str
18
+ outputs:
19
+ response:
20
+ from: assistant.response
@@ -0,0 +1,24 @@
1
+ circuitry: "0.4"
2
+ title: Run resource
3
+ inputs:
4
+ user_turn:
5
+ type: text
6
+ required: true
7
+ entry: assistant
8
+ resources:
9
+ user_turn:
10
+ type: input
11
+ from: user_turn
12
+ recovered_context:
13
+ type: run
14
+ graph: ./context-recovery.circuitry.yaml
15
+ entry: recovery
16
+ inputs:
17
+ user_turn: user_turn
18
+ assistant:
19
+ type: agent
20
+ inputs: [user_turn, recovered_context]
21
+ instructions: Answer with recovered context when useful.
22
+ outputs:
23
+ response:
24
+ from: assistant
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@darkhorseprojects/circuitry",
3
- "version": "0.3.9",
3
+ "version": "0.4.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -17,9 +17,12 @@
17
17
  "bin": {
18
18
  "circuitry": "./dist/cli.js"
19
19
  },
20
- "license": "LGPL-3.0-only",
20
+ "license": "MPL-2.0",
21
21
  "files": [
22
22
  "dist",
23
+ "SPEC.md",
24
+ "schema.json",
25
+ "examples",
23
26
  "LICENSE"
24
27
  ],
25
28
  "scripts": {
package/schema.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://darkhorseprojects.github.io/circuitry/schema/0.4/schema.json",
4
+ "title": "Circuitry 0.4",
5
+ "type": "object",
6
+ "required": ["circuitry", "resources"],
7
+ "properties": {
8
+ "circuitry": { "const": "0.4" },
9
+ "title": { "type": "string" },
10
+ "description": { "type": "string" },
11
+ "imports": { "type": "array" },
12
+ "inputs": { "type": "object" },
13
+ "entry": { "type": "string" },
14
+ "entries": { "type": "object", "additionalProperties": { "type": "string" } },
15
+ "resources": {
16
+ "type": "object",
17
+ "additionalProperties": { "$ref": "#/$defs/resource" }
18
+ },
19
+ "outputs": {
20
+ "type": "object",
21
+ "additionalProperties": { "$ref": "#/$defs/output" }
22
+ }
23
+ },
24
+ "additionalProperties": true,
25
+ "$defs": {
26
+ "resource": {
27
+ "type": "object",
28
+ "required": ["type"],
29
+ "properties": {
30
+ "type": { "type": "string" },
31
+ "label": { "type": "string" },
32
+ "description": { "type": "string" },
33
+ "inputs": {
34
+ "oneOf": [
35
+ { "type": "array", "items": { "type": "string" } },
36
+ { "type": "object", "additionalProperties": { "type": "string" } }
37
+ ]
38
+ },
39
+ "from": { "type": "string" },
40
+ "value": true,
41
+ "path": { "type": "string" },
42
+ "uri": { "type": "string" },
43
+ "mime": { "type": "string" },
44
+ "identity": { "type": "string" },
45
+ "instructions": { "type": "string" },
46
+ "tools": { "type": "array", "items": { "type": "string" } },
47
+ "graph": { "type": "string" },
48
+ "entry": { "type": "string" },
49
+ "expect": true
50
+ },
51
+ "additionalProperties": true
52
+ },
53
+ "output": {
54
+ "type": "object",
55
+ "required": ["from"],
56
+ "properties": {
57
+ "from": { "type": "string" },
58
+ "type": { "type": "string" },
59
+ "mime": { "type": "string" },
60
+ "schema": true
61
+ },
62
+ "additionalProperties": true
63
+ }
64
+ }
65
+ }