@zerowidth/workbench-sdk 2.6.0 → 2.7.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.
@@ -19,6 +19,32 @@ const renderVariable = (value) => {
19
19
  }
20
20
  };
21
21
 
22
+ /**
23
+ * A token's key and its fallback: `name`, or `name:"Unknown Name"` for the
24
+ * text to use when `name` is missing or empty. Inside the quotes, `\"` is a
25
+ * quote, `\\` a backslash and `\n` a newline. Same rules as the System
26
+ * Prompt node.
27
+ */
28
+ const TOKEN = /^\s*([A-Za-z_][A-Za-z0-9_]*)\s*(?::\s*"((?:[^"\\]|\\.)*)")?\s*$/;
29
+
30
+ const isEmpty = (value) =>
31
+ value === null || value === undefined || (typeof value === "string" && value.trim() === "");
32
+
33
+ /** The text a `{{…}}` token becomes, or null to leave it as written. */
34
+ const resolveToken = (variables, inner) => {
35
+ const exact = variables.find((v) => Object.keys(v).includes(inner));
36
+ if (exact) return renderVariable(exact[inner]);
37
+ const m = TOKEN.exec(inner);
38
+ if (!m) return null;
39
+ const key = m[1];
40
+ const fallback = m[2] === undefined ? undefined : m[2].replace(/\\(.)/g, (_, c) => (c === "n" ? "\n" : c));
41
+ const found = variables.find((v) => Object.keys(v).includes(key));
42
+ const value = found ? found[key] : undefined;
43
+ if (!isEmpty(value)) return renderVariable(value);
44
+ if (fallback !== undefined) return fallback;
45
+ return found ? renderVariable(value) : null;
46
+ };
47
+
22
48
  export default async ({inputs, settings, config}) => {
23
49
 
24
50
  // If an input value is provided, use it; otherwise use the value from settings
@@ -45,15 +71,10 @@ export default async ({inputs, settings, config}) => {
45
71
  // do we have a text content item and what index is it
46
72
  let textContentIndex = message.content.findIndex(item => item.type === 'text');
47
73
  if(textContentIndex !== -1) {
48
- message.content[textContentIndex].text = message.content[textContentIndex].text.replace(/\{\{(.*?)\}\}/g, (match, p1) => {
49
-
50
- // look for a variable with the key p1
51
- let variable = variables.find(variable => Object.keys(variable).find(key => key === p1));
52
- if(variable) {
53
- return renderVariable(variable[p1]);
54
- }
55
- return match;
56
- });
74
+ message.content[textContentIndex].text = message.content[textContentIndex].text.replace(
75
+ /\{\{(.*?)\}\}/g,
76
+ (match, p1) => resolveToken(variables, p1) ?? match,
77
+ );
57
78
  }
58
79
 
59
80
  // Return the string value
@@ -19,6 +19,44 @@ const renderVariable = (value) => {
19
19
  }
20
20
  };
21
21
 
22
+ /**
23
+ * A token's key and its fallback: `name`, or `name:"Unknown Name"` for the
24
+ * text to use when `name` is missing or empty. Inside the quotes, `\"` is a
25
+ * quote, `\\` a backslash and `\n` a newline. Spaces around the parts are
26
+ * allowed. A fallback can't hold `}}`, since that ends the token. Null when
27
+ * the token isn't in either form.
28
+ */
29
+ const TOKEN = /^\s*([A-Za-z_][A-Za-z0-9_]*)\s*(?::\s*"((?:[^"\\]|\\.)*)")?\s*$/;
30
+ const parseToken = (inner) => {
31
+ const m = TOKEN.exec(inner);
32
+ if (!m) return null;
33
+ return {
34
+ key: m[1],
35
+ fallback: m[2] === undefined ? undefined : m[2].replace(/\\(.)/g, (_, c) => (c === "n" ? "\n" : c)),
36
+ };
37
+ };
38
+
39
+ const isEmpty = (value) =>
40
+ value === null || value === undefined || (typeof value === "string" && value.trim() === "");
41
+
42
+ /**
43
+ * The text a `{{…}}` token becomes, or null to leave it as written: a
44
+ * variable's value, else the token's fallback when the value is missing or
45
+ * empty. A token naming nothing, with no fallback, stays in the text.
46
+ */
47
+ const resolveToken = (variables, inner) => {
48
+ // An exact key first, so keys that aren't plain names keep working.
49
+ const exact = variables.find((v) => Object.keys(v).includes(inner));
50
+ if (exact) return renderVariable(exact[inner]);
51
+ const token = parseToken(inner);
52
+ if (!token) return null;
53
+ const found = variables.find((v) => Object.keys(v).includes(token.key));
54
+ const value = found ? found[token.key] : undefined;
55
+ if (!isEmpty(value)) return renderVariable(value);
56
+ if (token.fallback !== undefined) return token.fallback;
57
+ return found ? renderVariable(value) : null;
58
+ };
59
+
22
60
  export default async ({inputs, settings, config}) => {
23
61
  // Initialize variables array if not provided
24
62
  if(!inputs.variables) {
@@ -57,14 +95,7 @@ export default async ({inputs, settings, config}) => {
57
95
  let fullContent = chainedContent ? `${chainedContent}\n\n${baseContent}` : baseContent;
58
96
 
59
97
  const fill = (text) =>
60
- text.replace(/\{\{(.*?)\}\}/g, (match, p1) => {
61
- // look for a variable with the key p1
62
- let variable = variables.find(variable => Object.keys(variable).find(key => key === p1));
63
- if(variable) {
64
- return renderVariable(variable[p1]);
65
- }
66
- return match;
67
- });
98
+ text.replace(/\{\{(.*?)\}\}/g, (match, p1) => resolveToken(variables, p1) ?? match);
68
99
 
69
100
  // Values filled in can change from run to run (the time, memory, search
70
101
  // results); the text before the first of them doesn't. The block records
@@ -73,7 +104,8 @@ export default async ({inputs, settings, config}) => {
73
104
  // removes the hint before any model sees it.
74
105
  let firstFilled = -1;
75
106
  for (const m of fullContent.matchAll(/\{\{(.*?)\}\}/g)) {
76
- if (variables.some(variable => Object.keys(variable).includes(m[1]))) {
107
+ // A fallback is filled in too, so it ends the fixed part as a value does.
108
+ if (resolveToken(variables, m[1]) !== null) {
77
109
  firstFilled = m.index;
78
110
  break;
79
111
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zerowidth/workbench-sdk",
3
- "version": "2.6.0",
3
+ "version": "2.7.0",
4
4
  "dependencies": {
5
5
  "adm-zip": "^0.5.16",
6
6
  "ajv": "^8.17.1",