@zerowidth/workbench-sdk 2.1.3 → 2.2.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/nodes/json-to-xml/json-to-xml.config.json +2 -2
- package/nodes/json-to-yaml/json-to-yaml.config.json +2 -2
- package/nodes/json-to-yaml/json-to-yaml.tests.json +9 -0
- package/nodes/message/message.config.json +1 -1
- package/nodes/message/message.process.js +25 -2
- package/nodes/message/message.tests.json +85 -0
- package/nodes/object-merge/object-merge.config.json +2 -2
- package/nodes/object-merge/object-merge.process.js +6 -2
- package/nodes/object-merge/object-merge.tests.json +12 -0
- package/nodes/string-template/string-template.config.json +2 -2
- package/nodes/string-template/string-template.process.js +33 -5
- package/nodes/string-template/string-template.tests.json +52 -0
- package/nodes/system-prompt/system-prompt.config.json +1 -1
- package/nodes/system-prompt/system-prompt.process.js +25 -2
- package/nodes/system-prompt/system-prompt.tests.json +113 -0
- package/package.json +1 -1
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
{
|
|
9
9
|
"name": "json",
|
|
10
10
|
"display_name": "JSON",
|
|
11
|
-
"type": "object",
|
|
12
|
-
"description": "The JSON object to convert to XML",
|
|
11
|
+
"type": "object or array of objects",
|
|
12
|
+
"description": "The JSON object, or array of objects, to convert to XML",
|
|
13
13
|
"required": true
|
|
14
14
|
},
|
|
15
15
|
{
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
{
|
|
9
9
|
"name": "json",
|
|
10
10
|
"display_name": "JSON",
|
|
11
|
-
"type": "object",
|
|
12
|
-
"description": "The JSON object to convert to YAML",
|
|
11
|
+
"type": "object or array of objects",
|
|
12
|
+
"description": "The JSON object, or array of objects, to convert to YAML",
|
|
13
13
|
"required": true
|
|
14
14
|
},
|
|
15
15
|
{
|
|
@@ -97,5 +97,14 @@
|
|
|
97
97
|
"expected": {
|
|
98
98
|
"yaml": "null"
|
|
99
99
|
}
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
"description": "Top-level array of objects",
|
|
103
|
+
"inputs": {
|
|
104
|
+
"json": [{"name": "Alice"}, {"name": "Bob"}]
|
|
105
|
+
},
|
|
106
|
+
"expected": {
|
|
107
|
+
"yaml": "- name: Alice\n- name: Bob"
|
|
108
|
+
}
|
|
100
109
|
}
|
|
101
110
|
]
|
|
@@ -2,6 +2,23 @@
|
|
|
2
2
|
* Process function for the Message node.
|
|
3
3
|
* Outputs a message object, either from the input or from the settings.
|
|
4
4
|
*/
|
|
5
|
+
/**
|
|
6
|
+
* Render a variable value for injection into message text.
|
|
7
|
+
* Strings pass through untouched; everything else is JSON encoded so that
|
|
8
|
+
* nested objects and arrays read as data instead of "[object Object]".
|
|
9
|
+
*/
|
|
10
|
+
const renderVariable = (value) => {
|
|
11
|
+
if (value === null || value === undefined) return "";
|
|
12
|
+
if (typeof value === "string") return value;
|
|
13
|
+
try {
|
|
14
|
+
return JSON.stringify(value, null, 2);
|
|
15
|
+
} catch {
|
|
16
|
+
// Circular structures (or anything else JSON can't encode) fall back to
|
|
17
|
+
// the default coercion rather than failing the whole message.
|
|
18
|
+
return String(value);
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
|
|
5
22
|
export default async ({inputs, settings, config}) => {
|
|
6
23
|
|
|
7
24
|
// If an input value is provided, use it; otherwise use the value from settings
|
|
@@ -17,6 +34,12 @@ export default async ({inputs, settings, config}) => {
|
|
|
17
34
|
if(!inputs.variables) {
|
|
18
35
|
inputs.variables = [];
|
|
19
36
|
}
|
|
37
|
+
|
|
38
|
+
// A single connection can deliver an array of key-value objects, so flatten
|
|
39
|
+
// one level before looking keys up.
|
|
40
|
+
const variables = (Array.isArray(inputs.variables) ? inputs.variables : [inputs.variables])
|
|
41
|
+
.flatMap(entry => Array.isArray(entry) ? entry : [entry])
|
|
42
|
+
.filter(entry => entry !== null && typeof entry === "object");
|
|
20
43
|
|
|
21
44
|
// if we have variables and text content, we need to replace the text content with the variables
|
|
22
45
|
// do we have a text content item and what index is it
|
|
@@ -25,9 +48,9 @@ export default async ({inputs, settings, config}) => {
|
|
|
25
48
|
message.content[textContentIndex].text = message.content[textContentIndex].text.replace(/\{\{(.*?)\}\}/g, (match, p1) => {
|
|
26
49
|
|
|
27
50
|
// look for a variable with the key p1
|
|
28
|
-
let variable =
|
|
51
|
+
let variable = variables.find(variable => Object.keys(variable).find(key => key === p1));
|
|
29
52
|
if(variable) {
|
|
30
|
-
return variable[p1];
|
|
53
|
+
return renderVariable(variable[p1]);
|
|
31
54
|
}
|
|
32
55
|
return match;
|
|
33
56
|
});
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"description": "String variable is injected as-is",
|
|
4
|
+
"inputs": {
|
|
5
|
+
"variables": [
|
|
6
|
+
{
|
|
7
|
+
"name": "Ada"
|
|
8
|
+
}
|
|
9
|
+
]
|
|
10
|
+
},
|
|
11
|
+
"settings": {
|
|
12
|
+
"role": "user",
|
|
13
|
+
"content": "Hello {{name}}!"
|
|
14
|
+
},
|
|
15
|
+
"expected": {
|
|
16
|
+
"message": {
|
|
17
|
+
"role": "user",
|
|
18
|
+
"content": [
|
|
19
|
+
{
|
|
20
|
+
"type": "text",
|
|
21
|
+
"text": "Hello Ada!"
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"description": "Nested object variable is JSON stringified, not [object Object]",
|
|
29
|
+
"inputs": {
|
|
30
|
+
"variables": [
|
|
31
|
+
{
|
|
32
|
+
"order": {
|
|
33
|
+
"id": 7,
|
|
34
|
+
"paid": true
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
]
|
|
38
|
+
},
|
|
39
|
+
"settings": {
|
|
40
|
+
"role": "user",
|
|
41
|
+
"content": "Order: {{order}}"
|
|
42
|
+
},
|
|
43
|
+
"expected": {
|
|
44
|
+
"message": {
|
|
45
|
+
"role": "user",
|
|
46
|
+
"content": [
|
|
47
|
+
{
|
|
48
|
+
"type": "text",
|
|
49
|
+
"text": "Order: {\n \"id\": 7,\n \"paid\": true\n}"
|
|
50
|
+
}
|
|
51
|
+
]
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"description": "Array of objects delivered as a single connection is flattened",
|
|
57
|
+
"inputs": {
|
|
58
|
+
"variables": [
|
|
59
|
+
[
|
|
60
|
+
{
|
|
61
|
+
"first": "Ada"
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"last": "Lovelace"
|
|
65
|
+
}
|
|
66
|
+
]
|
|
67
|
+
]
|
|
68
|
+
},
|
|
69
|
+
"settings": {
|
|
70
|
+
"role": "user",
|
|
71
|
+
"content": "{{first}} {{last}}"
|
|
72
|
+
},
|
|
73
|
+
"expected": {
|
|
74
|
+
"message": {
|
|
75
|
+
"role": "user",
|
|
76
|
+
"content": [
|
|
77
|
+
{
|
|
78
|
+
"type": "text",
|
|
79
|
+
"text": "Ada Lovelace"
|
|
80
|
+
}
|
|
81
|
+
]
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
]
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
{
|
|
9
9
|
"name": "objects",
|
|
10
10
|
"display_name": "Objects",
|
|
11
|
-
"type": "object",
|
|
12
|
-
"description": "Objects to merge (later objects override earlier ones)",
|
|
11
|
+
"type": "object or array of objects",
|
|
12
|
+
"description": "Objects to merge, individually or as arrays of objects (later objects override earlier ones)",
|
|
13
13
|
"required": true,
|
|
14
14
|
"allow_multiple": true
|
|
15
15
|
},
|
|
@@ -2,8 +2,12 @@ export default async ({ inputs, settings, config }) => {
|
|
|
2
2
|
const objects = inputs.objects;
|
|
3
3
|
const arrayMode = inputs.array_mode ?? "replace";
|
|
4
4
|
|
|
5
|
-
// Normalize to array
|
|
6
|
-
|
|
5
|
+
// Normalize to a flat array. A single connection can deliver an array of
|
|
6
|
+
// objects, and a multi-connection input can deliver arrays alongside plain
|
|
7
|
+
// objects, so flatten one level before filtering.
|
|
8
|
+
const objectList = (Array.isArray(objects) ? objects : [objects]).flatMap(
|
|
9
|
+
(entry) => (Array.isArray(entry) ? entry : [entry])
|
|
10
|
+
);
|
|
7
11
|
|
|
8
12
|
// Filter out non-objects
|
|
9
13
|
const validObjects = objectList.filter(
|
|
@@ -142,5 +142,17 @@
|
|
|
142
142
|
"expected": {
|
|
143
143
|
"merged": {"level1": {"level2": {"level3": {"a": 1, "b": 2, "c": 3}}}}
|
|
144
144
|
}
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
"description": "Array of objects delivered as a single connection is flattened",
|
|
148
|
+
"inputs": {
|
|
149
|
+
"objects": [
|
|
150
|
+
[{"a": 1}, {"b": 2}],
|
|
151
|
+
{"c": 3}
|
|
152
|
+
]
|
|
153
|
+
},
|
|
154
|
+
"expected": {
|
|
155
|
+
"merged": {"a": 1, "b": 2, "c": 3}
|
|
156
|
+
}
|
|
145
157
|
}
|
|
146
158
|
]
|
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
{
|
|
15
15
|
"name": "variables",
|
|
16
16
|
"display_name": "Variables",
|
|
17
|
-
"description": "Object containing variable values",
|
|
18
|
-
"type": "object",
|
|
17
|
+
"description": "Object containing variable values, or an array of such objects to merge",
|
|
18
|
+
"type": "object or array of objects",
|
|
19
19
|
"required": true
|
|
20
20
|
}
|
|
21
21
|
],
|
|
@@ -1,15 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Render a variable value for injection into the template.
|
|
3
|
+
* Strings pass through untouched; everything else is JSON encoded so that
|
|
4
|
+
* nested objects and arrays read as data instead of "[object Object]".
|
|
5
|
+
*/
|
|
6
|
+
const renderVariable = (value) => {
|
|
7
|
+
if (value === null || value === undefined) return "";
|
|
8
|
+
if (typeof value === "string") return value;
|
|
9
|
+
try {
|
|
10
|
+
return JSON.stringify(value, null, 2);
|
|
11
|
+
} catch {
|
|
12
|
+
// Circular structures (or anything else JSON can't encode) fall back to
|
|
13
|
+
// the default coercion rather than failing the whole template.
|
|
14
|
+
return String(value);
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
|
|
1
18
|
export default async ({inputs, settings, config}) => {
|
|
2
19
|
|
|
3
20
|
const template = String(inputs.template || "");
|
|
4
|
-
|
|
21
|
+
|
|
22
|
+
// The input may arrive as a single object, or as an array of objects when
|
|
23
|
+
// it's fed from an "array of objects" output. Merge arrays into one lookup.
|
|
24
|
+
const raw = inputs.variables || {};
|
|
25
|
+
const variables = Array.isArray(raw)
|
|
26
|
+
? raw.reduce((acc, entry) => {
|
|
27
|
+
if (Array.isArray(entry)) return Object.assign(acc, ...entry.filter(e => e && typeof e === "object"));
|
|
28
|
+
if (entry && typeof entry === "object") return Object.assign(acc, entry);
|
|
29
|
+
return acc;
|
|
30
|
+
}, {})
|
|
31
|
+
: raw;
|
|
32
|
+
|
|
5
33
|
const keepMissing = settings.keep_missing || false;
|
|
6
|
-
|
|
34
|
+
|
|
7
35
|
const text = template.replace(/\{([^}]+)\}/g, (match, key) => {
|
|
8
36
|
if (key in variables) {
|
|
9
|
-
return
|
|
37
|
+
return renderVariable(variables[key]);
|
|
10
38
|
}
|
|
11
39
|
return keepMissing ? match : "";
|
|
12
40
|
});
|
|
13
|
-
|
|
41
|
+
|
|
14
42
|
return { text };
|
|
15
|
-
};
|
|
43
|
+
};
|
|
@@ -59,5 +59,57 @@
|
|
|
59
59
|
"expected": {
|
|
60
60
|
"text": "Hello World! "
|
|
61
61
|
}
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"description": "Object variable is JSON stringified, not [object Object]",
|
|
65
|
+
"inputs": {
|
|
66
|
+
"template": "Config: {config}",
|
|
67
|
+
"variables": {"config": {"host": "localhost", "port": 8080}}
|
|
68
|
+
},
|
|
69
|
+
"settings": {
|
|
70
|
+
"keep_missing": false
|
|
71
|
+
},
|
|
72
|
+
"expected": {
|
|
73
|
+
"text": "Config: {\n \"host\": \"localhost\",\n \"port\": 8080\n}"
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"description": "Array variable is JSON stringified",
|
|
78
|
+
"inputs": {
|
|
79
|
+
"template": "Items: {items}",
|
|
80
|
+
"variables": {"items": [1, 2]}
|
|
81
|
+
},
|
|
82
|
+
"settings": {
|
|
83
|
+
"keep_missing": false
|
|
84
|
+
},
|
|
85
|
+
"expected": {
|
|
86
|
+
"text": "Items: [\n 1,\n 2\n]"
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"description": "Array of objects input is merged into one lookup",
|
|
91
|
+
"inputs": {
|
|
92
|
+
"template": "{greeting} {name}!",
|
|
93
|
+
"variables": [{"greeting": "Hello"}, {"name": "World"}]
|
|
94
|
+
},
|
|
95
|
+
"settings": {
|
|
96
|
+
"keep_missing": false
|
|
97
|
+
},
|
|
98
|
+
"expected": {
|
|
99
|
+
"text": "Hello World!"
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"description": "Null variable renders as empty string",
|
|
104
|
+
"inputs": {
|
|
105
|
+
"template": "Value: {value}.",
|
|
106
|
+
"variables": {"value": null}
|
|
107
|
+
},
|
|
108
|
+
"settings": {
|
|
109
|
+
"keep_missing": false
|
|
110
|
+
},
|
|
111
|
+
"expected": {
|
|
112
|
+
"text": "Value: ."
|
|
113
|
+
}
|
|
62
114
|
}
|
|
63
115
|
]
|
|
@@ -2,12 +2,35 @@
|
|
|
2
2
|
* Process function for the System Prompt node.
|
|
3
3
|
* Outputs a message object, containing the prompt text and system role and text
|
|
4
4
|
*/
|
|
5
|
+
/**
|
|
6
|
+
* Render a variable value for injection into prompt text.
|
|
7
|
+
* Strings pass through untouched; everything else is JSON encoded so that
|
|
8
|
+
* nested objects and arrays read as data instead of "[object Object]".
|
|
9
|
+
*/
|
|
10
|
+
const renderVariable = (value) => {
|
|
11
|
+
if (value === null || value === undefined) return "";
|
|
12
|
+
if (typeof value === "string") return value;
|
|
13
|
+
try {
|
|
14
|
+
return JSON.stringify(value, null, 2);
|
|
15
|
+
} catch {
|
|
16
|
+
// Circular structures (or anything else JSON can't encode) fall back to
|
|
17
|
+
// the default coercion rather than failing the whole prompt.
|
|
18
|
+
return String(value);
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
|
|
5
22
|
export default async ({inputs, settings, config}) => {
|
|
6
23
|
// Initialize variables array if not provided
|
|
7
24
|
if(!inputs.variables) {
|
|
8
25
|
inputs.variables = [];
|
|
9
26
|
}
|
|
10
27
|
|
|
28
|
+
// A single connection can deliver an array of key-value objects, so flatten
|
|
29
|
+
// one level before looking keys up.
|
|
30
|
+
const variables = (Array.isArray(inputs.variables) ? inputs.variables : [inputs.variables])
|
|
31
|
+
.flatMap(entry => Array.isArray(entry) ? entry : [entry])
|
|
32
|
+
.filter(entry => entry !== null && typeof entry === "object");
|
|
33
|
+
|
|
11
34
|
// Get the base content from settings
|
|
12
35
|
let baseContent = settings.content || "";
|
|
13
36
|
|
|
@@ -47,9 +70,9 @@ export default async ({inputs, settings, config}) => {
|
|
|
47
70
|
// Process variables
|
|
48
71
|
message.content[0].text = message.content[0].text.replace(/\{\{(.*?)\}\}/g, (match, p1) => {
|
|
49
72
|
// look for a variable with the key p1
|
|
50
|
-
let variable =
|
|
73
|
+
let variable = variables.find(variable => Object.keys(variable).find(key => key === p1));
|
|
51
74
|
if(variable) {
|
|
52
|
-
return variable[p1];
|
|
75
|
+
return renderVariable(variable[p1]);
|
|
53
76
|
}
|
|
54
77
|
return match;
|
|
55
78
|
});
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"description": "String variable is injected as-is",
|
|
4
|
+
"inputs": {
|
|
5
|
+
"variables": [
|
|
6
|
+
{
|
|
7
|
+
"name": "Ada"
|
|
8
|
+
}
|
|
9
|
+
]
|
|
10
|
+
},
|
|
11
|
+
"settings": {
|
|
12
|
+
"content": "Hello {{name}}!"
|
|
13
|
+
},
|
|
14
|
+
"expected": {
|
|
15
|
+
"message": {
|
|
16
|
+
"role": "system",
|
|
17
|
+
"content": [
|
|
18
|
+
{
|
|
19
|
+
"type": "text",
|
|
20
|
+
"text": "Hello Ada!"
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
},
|
|
24
|
+
"prompt": "Hello Ada!"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"description": "Nested object variable is JSON stringified, not [object Object]",
|
|
29
|
+
"inputs": {
|
|
30
|
+
"variables": [
|
|
31
|
+
{
|
|
32
|
+
"profile": {
|
|
33
|
+
"name": "Ada",
|
|
34
|
+
"langs": [
|
|
35
|
+
"js",
|
|
36
|
+
"py"
|
|
37
|
+
]
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
]
|
|
41
|
+
},
|
|
42
|
+
"settings": {
|
|
43
|
+
"content": "Profile: {{profile}}"
|
|
44
|
+
},
|
|
45
|
+
"expected": {
|
|
46
|
+
"message": {
|
|
47
|
+
"role": "system",
|
|
48
|
+
"content": [
|
|
49
|
+
{
|
|
50
|
+
"type": "text",
|
|
51
|
+
"text": "Profile: {\n \"name\": \"Ada\",\n \"langs\": [\n \"js\",\n \"py\"\n ]\n}"
|
|
52
|
+
}
|
|
53
|
+
]
|
|
54
|
+
},
|
|
55
|
+
"prompt": "Profile: {\n \"name\": \"Ada\",\n \"langs\": [\n \"js\",\n \"py\"\n ]\n}"
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"description": "Array of objects delivered as a single connection is flattened",
|
|
60
|
+
"inputs": {
|
|
61
|
+
"variables": [
|
|
62
|
+
[
|
|
63
|
+
{
|
|
64
|
+
"first": "Ada"
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"last": "Lovelace"
|
|
68
|
+
}
|
|
69
|
+
]
|
|
70
|
+
]
|
|
71
|
+
},
|
|
72
|
+
"settings": {
|
|
73
|
+
"content": "{{first}} {{last}}"
|
|
74
|
+
},
|
|
75
|
+
"expected": {
|
|
76
|
+
"message": {
|
|
77
|
+
"role": "system",
|
|
78
|
+
"content": [
|
|
79
|
+
{
|
|
80
|
+
"type": "text",
|
|
81
|
+
"text": "Ada Lovelace"
|
|
82
|
+
}
|
|
83
|
+
]
|
|
84
|
+
},
|
|
85
|
+
"prompt": "Ada Lovelace"
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
"description": "Unmatched placeholder is left intact",
|
|
90
|
+
"inputs": {
|
|
91
|
+
"variables": [
|
|
92
|
+
{
|
|
93
|
+
"name": "Ada"
|
|
94
|
+
}
|
|
95
|
+
]
|
|
96
|
+
},
|
|
97
|
+
"settings": {
|
|
98
|
+
"content": "Hello {{missing}}!"
|
|
99
|
+
},
|
|
100
|
+
"expected": {
|
|
101
|
+
"message": {
|
|
102
|
+
"role": "system",
|
|
103
|
+
"content": [
|
|
104
|
+
{
|
|
105
|
+
"type": "text",
|
|
106
|
+
"text": "Hello {{missing}}!"
|
|
107
|
+
}
|
|
108
|
+
]
|
|
109
|
+
},
|
|
110
|
+
"prompt": "Hello {{missing}}!"
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
]
|