@formio/uag 1.12.1 → 1.12.3
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 +4 -10
- package/lib/tools/fetchExternalData.js +2 -1
- package/lib/tools/findSubmission.js +18 -3
- package/lib/tools/utils.d.ts +20 -0
- package/lib/tools/utils.js +23 -0
- package/package.json +12 -4
package/README.md
CHANGED
|
@@ -6,24 +6,20 @@ The Universal Agent Gateway (UAG) leverages the [Model Context Protocol (MCP)](h
|
|
|
6
6
|
|
|
7
7
|

|
|
8
8
|
|
|
9
|
-
Official Documentation
|
|
10
|
-
--------------------------
|
|
11
|
-
For the latest documentation, release information, and guides, always refer to the official Form.io Help Documentation available here:
|
|
12
|
-
|
|
13
|
-
**[https://help.form.io](https://help.form.io/dev/ai/uag)**
|
|
14
|
-
|
|
15
9
|
--------------------------
|
|
16
10
|
There are two primary scenarios that the UAG enables:
|
|
17
11
|
|
|
18
12
|
- **Unstructured 'chat' input to deterministic form submission**: This capability uses the JSON schema of a Form.io form to produce a deterministic structured data object provided natural language conversational input.
|
|
19
13
|
|
|
20
14
|

|
|
15
|
+
|
|
16
|
+
For a working example of this flow, please try out the [Conversation Form Example](./examples/conversation-form).
|
|
21
17
|
|
|
22
18
|
- **Agentic Form.io Workflows**: UAG provides an AI Agent the ability to take existing submission data + "context" criteria in order to prompt an AI Agent to provide its own data and analysis within a workflow scenario. The following example, which shows a college application process using a Form.io application form, shows how this primary scenario works.
|
|
23
19
|
|
|
24
20
|

|
|
25
21
|
|
|
26
|
-
|
|
22
|
+
For more information on Agentic workflows, checkout the dedicated [Agentic Workflows](#agentic-workflows) section. For a working example of this flow, please try out the [Agentic Workflow Example](./examples/agentic-workflow).
|
|
27
23
|
|
|
28
24
|
---
|
|
29
25
|
|
|
@@ -36,12 +32,10 @@ There are three working examples in this repo, each spinning up a Form.io server
|
|
|
36
32
|
|---------|---------|-----------|
|
|
37
33
|
| **[Custom Module](examples/custom-module)** | **Conversational, existing client.** Connect Claude Desktop to your forms and talk to them: "I would like to add a new customer." Built as a custom module, so roles and permissions decide what the agent may do. | Open Source — free |
|
|
38
34
|
| **[Conversation Form](examples/conversation-form)** | **Conversational, your own UI.** A chat application whose questions come entirely from a form. About 200 lines, no field names in the code — edit the form and the conversation changes. | Open Source — free |
|
|
39
|
-
| **[Agentic Workflow](examples/agentic-workflow)** | **Autonomous.** A submission triggers an agent that scores it against a rubric and writes its decision back, which in turn triggers a second agent. No human, no chat window. | Enterprise
|
|
35
|
+
| **[Agentic Workflow](examples/agentic-workflow)** | **Autonomous.** A submission triggers an agent that scores it against a rubric and writes its decision back, which in turn triggers a second agent. No human, no chat window. | Enterprise |
|
|
40
36
|
|
|
41
37
|
There is also a [Flow Viewer](examples/flow-viewer) — a small proxy that shows every leg of an agentic run live, which is the fastest way to see what an agent is actually doing.
|
|
42
38
|
|
|
43
|
-
**[Conversation »](examples/conversation-form)** | **[Claude Desktop »](examples/custom-module)** | **[Autonomous »](examples/agentic-workflow)**
|
|
44
|
-
|
|
45
39
|
## Documentation in this repository
|
|
46
40
|
|
|
47
41
|
This file is the main reference: what the UAG is, how to configure a form for it, how to deploy it, and how to troubleshoot it. The rest of the documentation lives next to the code it describes.
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.fetchExternalData = void 0;
|
|
4
4
|
const template_1 = require("../template");
|
|
5
|
+
const utils_1 = require("./utils");
|
|
5
6
|
const core_1 = require("@formio/core");
|
|
6
7
|
const SchemaBuilder_1 = require("./SchemaBuilder");
|
|
7
8
|
const lodash_1 = require("lodash");
|
|
@@ -251,7 +252,7 @@ async function fetchResourceData(component, search_value, authInfo, project, con
|
|
|
251
252
|
const query = {};
|
|
252
253
|
// Add search filter if provided
|
|
253
254
|
if (search_value && component.searchField) {
|
|
254
|
-
query[`data.${component.searchField}__regex`] = `/${search_value}/i`;
|
|
255
|
+
query[`data.${component.searchField}__regex`] = `/${(0, utils_1.escapeSearchPattern)(search_value)}/i`;
|
|
255
256
|
}
|
|
256
257
|
// Add component filter if configured (interpolate the filter string)
|
|
257
258
|
if (component.filter) {
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.findSubmission = void 0;
|
|
4
4
|
const template_1 = require("../template");
|
|
5
|
+
const utils_1 = require("./utils");
|
|
5
6
|
const lodash_1 = require("lodash");
|
|
6
7
|
const SchemaBuilder_1 = require("./SchemaBuilder");
|
|
7
8
|
const error = require('debug')('formio:uag:findSubmission:error');
|
|
@@ -34,19 +35,33 @@ const findSubmission = async (project) => {
|
|
|
34
35
|
}, true);
|
|
35
36
|
}
|
|
36
37
|
if (criterion.operator === 'regex') {
|
|
38
|
+
// The value is a pattern by request here, so it is passed through
|
|
39
|
+
// unescaped. It is still compiled first: the server drops a filter
|
|
40
|
+
// it cannot compile, which would widen the search instead of
|
|
41
|
+
// failing it, so a bad pattern is better reported back.
|
|
42
|
+
try {
|
|
43
|
+
new RegExp(criterion.search_value);
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
return project.mcpResponse(template_1.ResponseTemplate.submissionSearchError, {
|
|
47
|
+
form: form.form,
|
|
48
|
+
searchQuery: search_query,
|
|
49
|
+
error: `"${criterion.search_value}" is not a valid regular expression for "${criterion.data_path}". Use the "contains" operator to search for it literally.`
|
|
50
|
+
}, true);
|
|
51
|
+
}
|
|
37
52
|
query[`data.${criterion.data_path}__regex`] = `/${criterion.search_value}/i`;
|
|
38
53
|
}
|
|
39
54
|
else if (criterion.operator === 'equals') {
|
|
40
55
|
query[`data.${criterion.data_path}`] = criterion.search_value;
|
|
41
56
|
}
|
|
42
57
|
else if (criterion.operator === 'contains') {
|
|
43
|
-
query[`data.${criterion.data_path}__regex`] = `/${criterion.search_value}/i`;
|
|
58
|
+
query[`data.${criterion.data_path}__regex`] = `/${(0, utils_1.escapeSearchPattern)(criterion.search_value)}/i`;
|
|
44
59
|
}
|
|
45
60
|
else if (criterion.operator === 'starts_with') {
|
|
46
|
-
query[`data.${criterion.data_path}__regex`] = `/^${criterion.search_value}/i`;
|
|
61
|
+
query[`data.${criterion.data_path}__regex`] = `/^${(0, utils_1.escapeSearchPattern)(criterion.search_value)}/i`;
|
|
47
62
|
}
|
|
48
63
|
else if (criterion.operator === 'ends_with') {
|
|
49
|
-
query[`data.${criterion.data_path}__regex`] = `/${criterion.search_value}$/i`;
|
|
64
|
+
query[`data.${criterion.data_path}__regex`] = `/${(0, utils_1.escapeSearchPattern)(criterion.search_value)}$/i`;
|
|
50
65
|
}
|
|
51
66
|
else if (criterion.operator === 'greater_than') {
|
|
52
67
|
query[`data.${criterion.data_path}__gt`] = criterion.search_value;
|
package/lib/tools/utils.d.ts
CHANGED
|
@@ -1,4 +1,24 @@
|
|
|
1
1
|
import { ZodRawShape } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* Escape a literal value so it can be carried in a `__regex` query parameter.
|
|
4
|
+
*
|
|
5
|
+
* Operators like "contains" promise a literal match, so every regular expression
|
|
6
|
+
* metacharacter in the value has to be neutered first. Left raw, a search for
|
|
7
|
+
* "joe.thompson@example.com" becomes a pattern whose dots match any character,
|
|
8
|
+
* and a value such as "[" is not a valid pattern at all — the server compiles
|
|
9
|
+
* these with a try/catch and silently drops the filter when compilation fails,
|
|
10
|
+
* which turns a narrow search into one that matches everything.
|
|
11
|
+
*
|
|
12
|
+
* Forward slashes get a further step. The server reads the parameter as
|
|
13
|
+
* `/pattern/flags` and takes the pattern with `[^/]+`, so an embedded slash
|
|
14
|
+
* truncates the pattern and the remainder is parsed as flags — again dropping
|
|
15
|
+
* the filter. Writing it as the equivalent `\x2f` escape keeps the character
|
|
16
|
+
* out of the transport while still matching a literal slash.
|
|
17
|
+
*
|
|
18
|
+
* @param value - The literal text to search for.
|
|
19
|
+
* @returns The value as a regular expression pattern that matches it literally.
|
|
20
|
+
*/
|
|
21
|
+
export declare const escapeSearchPattern: (value: string) => string;
|
|
2
22
|
export type ToolInfo = {
|
|
3
23
|
name?: string;
|
|
4
24
|
title?: string;
|
package/lib/tools/utils.js
CHANGED
|
@@ -1,2 +1,25 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.escapeSearchPattern = void 0;
|
|
4
|
+
const lodash_1 = require("lodash");
|
|
5
|
+
/**
|
|
6
|
+
* Escape a literal value so it can be carried in a `__regex` query parameter.
|
|
7
|
+
*
|
|
8
|
+
* Operators like "contains" promise a literal match, so every regular expression
|
|
9
|
+
* metacharacter in the value has to be neutered first. Left raw, a search for
|
|
10
|
+
* "joe.thompson@example.com" becomes a pattern whose dots match any character,
|
|
11
|
+
* and a value such as "[" is not a valid pattern at all — the server compiles
|
|
12
|
+
* these with a try/catch and silently drops the filter when compilation fails,
|
|
13
|
+
* which turns a narrow search into one that matches everything.
|
|
14
|
+
*
|
|
15
|
+
* Forward slashes get a further step. The server reads the parameter as
|
|
16
|
+
* `/pattern/flags` and takes the pattern with `[^/]+`, so an embedded slash
|
|
17
|
+
* truncates the pattern and the remainder is parsed as flags — again dropping
|
|
18
|
+
* the filter. Writing it as the equivalent `\x2f` escape keeps the character
|
|
19
|
+
* out of the transport while still matching a literal slash.
|
|
20
|
+
*
|
|
21
|
+
* @param value - The literal text to search for.
|
|
22
|
+
* @returns The value as a regular expression pattern that matches it literally.
|
|
23
|
+
*/
|
|
24
|
+
const escapeSearchPattern = (value) => (0, lodash_1.escapeRegExp)(value).replace(/\//g, '\\x2f');
|
|
25
|
+
exports.escapeSearchPattern = escapeSearchPattern;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@formio/uag",
|
|
3
|
-
"version": "1.12.
|
|
4
|
-
"mcpName": "io.form/uag",
|
|
3
|
+
"version": "1.12.3",
|
|
4
|
+
"mcpName": "io.form/formio-uag",
|
|
5
5
|
"description": "The Form.io Universal Agent Gateway (UAG).",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"types": "lib/index.d.ts",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"author": "Form.io Developers",
|
|
31
31
|
"license": "MIT",
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@formio/appserver": "^2.13.
|
|
33
|
+
"@formio/appserver": "^2.13.2",
|
|
34
34
|
"@formio/core": "2.8.0",
|
|
35
35
|
"@modelcontextprotocol/sdk": "1.30.0",
|
|
36
36
|
"cors": "^2.8.6",
|
|
@@ -70,5 +70,13 @@
|
|
|
70
70
|
"tsx": "^4.23.1",
|
|
71
71
|
"typescript": "^7.0.2"
|
|
72
72
|
},
|
|
73
|
-
"packageManager": "yarn@4.17.1"
|
|
73
|
+
"packageManager": "yarn@4.17.1",
|
|
74
|
+
"dependenciesMeta": {
|
|
75
|
+
"esbuild": {
|
|
76
|
+
"built": true
|
|
77
|
+
},
|
|
78
|
+
"isolated-vm": {
|
|
79
|
+
"built": true
|
|
80
|
+
}
|
|
81
|
+
}
|
|
74
82
|
}
|