@formio/uag 1.13.2 → 2.0.0-rc.10
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 +237 -535
- package/bin/uag-eval.js +99 -0
- package/bin/uag.js +116 -0
- package/lib/UAGFormInterface.d.ts +72 -2
- package/lib/UAGFormInterface.js +226 -57
- package/lib/UAGProjectInterface.d.ts +45 -5
- package/lib/UAGProjectInterface.js +187 -36
- package/lib/actionLogs.d.ts +24 -0
- package/lib/actionLogs.js +149 -0
- package/lib/actions/runTask.d.ts +43 -0
- package/lib/actions/runTask.js +324 -0
- package/lib/agent/anonymous.d.ts +34 -0
- package/lib/agent/anonymous.js +99 -0
- package/lib/agent/chat.d.ts +114 -0
- package/lib/agent/chat.js +475 -0
- package/lib/agent/engines.d.ts +17 -0
- package/lib/agent/engines.js +46 -0
- package/lib/agent/errors.d.ts +25 -0
- package/lib/agent/errors.js +132 -0
- package/lib/agent/index.d.ts +4 -0
- package/lib/agent/index.js +19 -0
- package/lib/agent/remote.d.ts +71 -0
- package/lib/agent/remote.js +252 -0
- package/lib/agent/requestId.d.ts +4 -0
- package/lib/agent/requestId.js +19 -0
- package/lib/agent/router.d.ts +61 -0
- package/lib/agent/router.js +307 -0
- package/lib/agent/stubs.d.ts +37 -0
- package/lib/agent/stubs.js +39 -0
- package/lib/agent/tools.d.ts +31 -0
- package/lib/agent/tools.js +76 -0
- package/lib/config.d.ts +97 -3
- package/lib/config.js +19 -0
- package/lib/database.d.ts +70 -0
- package/lib/database.js +212 -0
- package/lib/documents/convert.d.ts +38 -0
- package/lib/documents/convert.js +151 -0
- package/lib/documents/converters.d.ts +28 -0
- package/lib/documents/converters.js +149 -0
- package/lib/documents/detect.d.ts +31 -0
- package/lib/documents/detect.js +169 -0
- package/lib/documents/index.d.ts +9 -0
- package/lib/documents/index.js +43 -0
- package/lib/documents/mime.d.ts +39 -0
- package/lib/documents/mime.js +100 -0
- package/lib/documents/plugins.d.ts +18 -0
- package/lib/documents/plugins.js +65 -0
- package/lib/documents/readers.d.ts +19 -0
- package/lib/documents/readers.js +64 -0
- package/lib/documents/routes.d.ts +52 -0
- package/lib/documents/routes.js +99 -0
- package/lib/documents/types.d.ts +126 -0
- package/lib/documents/types.js +2 -0
- package/lib/documents/uagJs.d.ts +17 -0
- package/lib/documents/uagJs.js +157 -0
- package/lib/formflow.d.ts +47 -0
- package/lib/formflow.js +28 -0
- package/lib/identity.d.ts +53 -0
- package/lib/identity.js +107 -0
- package/lib/index.d.ts +98 -1
- package/lib/index.js +382 -8
- package/lib/markdown.d.ts +1 -0
- package/lib/markdown.js +166 -0
- package/lib/modules/api.d.ts +25 -0
- package/lib/modules/api.js +98 -0
- package/lib/modules/index.d.ts +4 -0
- package/lib/modules/index.js +24 -0
- package/lib/modules/routes.d.ts +14 -0
- package/lib/modules/routes.js +21 -0
- package/lib/modules/types.d.ts +68 -0
- package/lib/modules/types.js +5 -0
- package/lib/pluginFiles.d.ts +28 -0
- package/lib/pluginFiles.js +101 -0
- package/lib/plugins.d.ts +17 -0
- package/lib/plugins.js +93 -0
- package/lib/richText.d.ts +20 -0
- package/lib/richText.js +99 -0
- package/lib/router.d.ts +9 -0
- package/lib/router.js +34 -25
- package/lib/runner.d.ts +149 -0
- package/lib/runner.js +353 -0
- package/lib/runs.d.ts +27 -0
- package/lib/runs.js +13 -0
- package/lib/template.js +7 -9
- package/lib/templates/uagComponentNotFound.md +5 -5
- package/lib/testing.d.ts +74 -0
- package/lib/testing.js +180 -0
- package/lib/tools/SchemaBuilder.d.ts +3 -3
- package/lib/tools/SchemaBuilder.js +4 -4
- package/lib/tools/agentDecide.d.ts +8 -0
- package/lib/tools/agentDecide.js +57 -0
- package/lib/tools/agentProvide.d.ts +8 -0
- package/lib/tools/agentProvide.js +97 -0
- package/lib/tools/collectData.js +3 -2
- package/lib/tools/confirmSubmission.js +1 -1
- package/lib/tools/fetchExternalData.js +8 -17
- package/lib/tools/findSubmission.js +62 -56
- package/lib/tools/getFieldInfo.js +1 -1
- package/lib/tools/getFormFields.js +1 -1
- package/lib/tools/getForms.js +1 -1
- package/lib/tools/index.d.ts +4 -1
- package/lib/tools/index.js +21 -14
- package/lib/tools/readDocument.d.ts +17 -0
- package/lib/tools/readDocument.js +99 -0
- package/lib/tools/submissionUpdate.js +4 -3
- package/lib/tools/submitForm.js +1 -1
- package/lib/tools/utils.d.ts +40 -0
- package/lib/tools/utils.js +16 -1
- package/lib/version.d.ts +2 -0
- package/lib/version.js +5 -0
- package/package.json +49 -19
- package/testing.d.ts +1 -0
- package/testing.js +2 -0
- package/lib/tools/agentProvideData.d.ts +0 -3
- package/lib/tools/agentProvideData.js +0 -62
package/lib/index.d.ts
CHANGED
|
@@ -1,17 +1,114 @@
|
|
|
1
1
|
import { Server, ServerModule } from '@formio/appserver';
|
|
2
|
+
import type { LoadedModule, ModuleApi, ModuleRoute, ModuleRouteHandlers, ProjectInterface } from '@formio/appserver';
|
|
3
|
+
import { Request, Response, Router } from 'express';
|
|
2
4
|
import { UAGProjectInterface } from './UAGProjectInterface';
|
|
3
5
|
import { UAGFormInterface } from './UAGFormInterface';
|
|
4
6
|
import { UAGConfig } from './config';
|
|
5
|
-
|
|
7
|
+
import { UAGDatabase } from './database';
|
|
8
|
+
import type { UAGModuleApi } from './modules';
|
|
9
|
+
export type UAGModule = ServerModule<UAGModuleApi> & {
|
|
6
10
|
config?: UAGConfig;
|
|
7
11
|
};
|
|
12
|
+
/**
|
|
13
|
+
* Agentic Formflow keeps its license state inside @formio/appserver-core, and
|
|
14
|
+
* modules are built on its module framework. The UAG loads it only through
|
|
15
|
+
* @formio/appserver, so the two always share one copy, which must be 2.26.4 or
|
|
16
|
+
* later. An older @formio/appserver, or an old lockfile, can still resolve an
|
|
17
|
+
* older one, which would fail in confusing ways; say what to do instead.
|
|
18
|
+
*/
|
|
19
|
+
export declare function assertSingleCore(version?: string): void;
|
|
20
|
+
/**
|
|
21
|
+
* isolated-vm, which runs form logic, needs Node's startup snapshot off. Without
|
|
22
|
+
* it nothing fails at once: the process can crash later, in native code. So an
|
|
23
|
+
* embedding server that forgot the flag is stopped here, saying what to do.
|
|
24
|
+
*/
|
|
25
|
+
export declare function assertNodeSnapshotDisabled(execArgv?: string[], nodeOptions?: string): void;
|
|
8
26
|
export declare class UAGServer extends Server {
|
|
27
|
+
config: UAGConfig;
|
|
28
|
+
/** The UAG's own database, once it may be used. */
|
|
29
|
+
database?: UAGDatabase;
|
|
30
|
+
/** The database between connecting and the license check. */
|
|
31
|
+
private pendingDatabase?;
|
|
32
|
+
/**
|
|
33
|
+
* Use a module, or a list of modules (each in turn). A module used twice (by
|
|
34
|
+
* its `name`) is used once. `config.tools` adds up across modules; an MCP
|
|
35
|
+
* tool or engine type that two modules define stops startup, naming both.
|
|
36
|
+
* A route's `rateLimit` is the default for its name, unless the operator set one.
|
|
37
|
+
*/
|
|
38
|
+
use(module: any, source?: string): void;
|
|
39
|
+
/** MCP tools and engine types, which only one module may define. */
|
|
40
|
+
protected moduleClaims(definition: Record<string, any>): string[];
|
|
41
|
+
/**
|
|
42
|
+
* MCP tools add up across modules (lodash would merge them index by index),
|
|
43
|
+
* and a named module's tools get its API as `extra.module`. Its routes'
|
|
44
|
+
* `rateLimit`s become the defaults for their names.
|
|
45
|
+
*/
|
|
46
|
+
protected mergeModuleConfig(config: any, module?: LoadedModule): void;
|
|
47
|
+
/** Route rate-limit defaults, for the names the operator left unset. */
|
|
48
|
+
private routeRateLimits;
|
|
9
49
|
constructor(config?: UAGConfig);
|
|
50
|
+
/**
|
|
51
|
+
* Connect the UAG's own database before the project loads, and check it
|
|
52
|
+
* without writing: a database the UAG may not use stops startup. It is used
|
|
53
|
+
* (its collections created, its stores handed out) only once the license is
|
|
54
|
+
* known; see enableDatabase. Until then nothing writes to it.
|
|
55
|
+
*/
|
|
56
|
+
protected connectDatabase(): Promise<void>;
|
|
57
|
+
/**
|
|
58
|
+
* Use the database once the project and its license are loaded: with a
|
|
59
|
+
* license that includes the UAG (or the project and the database both on
|
|
60
|
+
* development hosts), create its collections and hand out its stores;
|
|
61
|
+
* otherwise leave it untouched, close it, and say why.
|
|
62
|
+
*/
|
|
63
|
+
protected enableDatabase(project: UAGProjectInterface): Promise<void>;
|
|
64
|
+
/**
|
|
65
|
+
* An Agent API request that failed before its handlers (a body that is not JSON,
|
|
66
|
+
* one over the size limit) is answered in the Agent API's error shape; every other
|
|
67
|
+
* route keeps Form.io's plain-text answer.
|
|
68
|
+
*/
|
|
69
|
+
protected sendError(req: Request, res: Response, status: number, message: string, err?: any): void;
|
|
70
|
+
/** Whether a request is for a module's form route: its method, and a path ending in the route's. */
|
|
71
|
+
private isModuleRouteRequest;
|
|
72
|
+
/** Browsers reading Agent API responses need the request id too. */
|
|
73
|
+
get exposedHeaders(): string[];
|
|
74
|
+
/** The loaded UAG project, once the project router has been built. */
|
|
75
|
+
get uagProject(): UAGProjectInterface | undefined;
|
|
76
|
+
/**
|
|
77
|
+
* Mount `/agent/capabilities` ahead of the project routes. The project routes,
|
|
78
|
+
* and the admin passthrough after them, would otherwise answer for it and
|
|
79
|
+
* forward admin requests to the Form.io server. A form's agent operations are
|
|
80
|
+
* on the form's own routes.
|
|
81
|
+
*/
|
|
82
|
+
protected extendRouter(router: Router): Promise<void>;
|
|
83
|
+
/**
|
|
84
|
+
* List the UAG's engines and MCP tools in the plugin registry, beside
|
|
85
|
+
* appserver-core's form actions. They keep loading their own way.
|
|
86
|
+
*/
|
|
87
|
+
protected listPlugins(): Promise<void>;
|
|
88
|
+
/** A form's own route segments, which module routes may not use: appserver's, and the agent operations. */
|
|
89
|
+
protected get reservedFormPaths(): string[];
|
|
90
|
+
/** Module routes may not take the names of the UAG's own rate-limited operations. */
|
|
91
|
+
protected get reservedRouteNames(): string[];
|
|
92
|
+
protected get hostName(): string;
|
|
93
|
+
protected get hostVersion(): string;
|
|
94
|
+
/** The module API: appserver's, with the UAG's license check, engines, converters and helpers. */
|
|
95
|
+
protected createModuleApi(module: LoadedModule): ModuleApi;
|
|
96
|
+
/** Module routes run behind what the agent operations run behind: the caller check, body limit, request id, rate limits and error shape. */
|
|
97
|
+
protected moduleRouteHandlers(project: ProjectInterface, route: ModuleRoute<any>, module: LoadedModule): ModuleRouteHandlers;
|
|
98
|
+
protected moduleErrorBody(req: Request, err: unknown): Record<string, any> & {
|
|
99
|
+
status: number;
|
|
100
|
+
};
|
|
10
101
|
router(): Promise<any>;
|
|
11
102
|
}
|
|
12
103
|
export * from '@formio/appserver';
|
|
13
104
|
export * from './config';
|
|
105
|
+
export * from './agent';
|
|
14
106
|
export * from './tools';
|
|
107
|
+
export * from './documents';
|
|
108
|
+
export * from './modules';
|
|
109
|
+
export { sanitizeMarkdown } from './markdown';
|
|
110
|
+
export { usePluginFiles, pluginFiles, pluginFilesIn, DEFAULT_PLUGINS_DIR } from './pluginFiles';
|
|
15
111
|
export * from './router';
|
|
16
112
|
export * from './template';
|
|
113
|
+
export { sanitizeRichText, isRichText, RICH_TEXT_TAGS, RICH_TEXT_RULE } from './richText';
|
|
17
114
|
export { UAGProjectInterface, UAGFormInterface };
|
package/lib/index.js
CHANGED
|
@@ -13,35 +13,372 @@ var __createBinding = (this && this.__createBinding) || (Object.create ? (functi
|
|
|
13
13
|
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
17
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
18
|
+
};
|
|
16
19
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.UAGFormInterface = exports.UAGProjectInterface = exports.UAGServer = void 0;
|
|
20
|
+
exports.UAGFormInterface = exports.UAGProjectInterface = exports.RICH_TEXT_RULE = exports.RICH_TEXT_TAGS = exports.isRichText = exports.sanitizeRichText = exports.DEFAULT_PLUGINS_DIR = exports.pluginFilesIn = exports.pluginFiles = exports.usePluginFiles = exports.sanitizeMarkdown = exports.UAGServer = void 0;
|
|
21
|
+
exports.assertSingleCore = assertSingleCore;
|
|
22
|
+
exports.assertNodeSnapshotDisabled = assertNodeSnapshotDisabled;
|
|
18
23
|
const appserver_1 = require("@formio/appserver");
|
|
24
|
+
const formflow_1 = require("./formflow");
|
|
25
|
+
const path_1 = __importDefault(require("path"));
|
|
19
26
|
const UAGProjectInterface_1 = require("./UAGProjectInterface");
|
|
20
27
|
Object.defineProperty(exports, "UAGProjectInterface", { enumerable: true, get: function () { return UAGProjectInterface_1.UAGProjectInterface; } });
|
|
21
28
|
const UAGFormInterface_1 = require("./UAGFormInterface");
|
|
22
29
|
Object.defineProperty(exports, "UAGFormInterface", { enumerable: true, get: function () { return UAGFormInterface_1.UAGFormInterface; } });
|
|
23
30
|
const lodash_1 = require("lodash");
|
|
31
|
+
const engines_1 = require("./agent/engines");
|
|
32
|
+
const router_1 = require("./agent/router");
|
|
33
|
+
const errors_1 = require("./agent/errors");
|
|
34
|
+
const database_1 = require("./database");
|
|
35
|
+
const runs_1 = require("./runs");
|
|
36
|
+
const actionLogs_1 = require("./actionLogs");
|
|
37
|
+
const runner_1 = require("./runner");
|
|
38
|
+
const runTask_1 = require("./actions/runTask");
|
|
39
|
+
const version_1 = require("./version");
|
|
40
|
+
const utils_1 = require("./tools/utils");
|
|
41
|
+
const plugins_1 = require("./plugins");
|
|
42
|
+
const plugins_2 = require("./documents/plugins");
|
|
43
|
+
const uagJs_1 = require("./documents/uagJs");
|
|
44
|
+
const modules_1 = require("./modules");
|
|
45
|
+
const tools_1 = require("./agent/tools");
|
|
46
|
+
/** Projects whose engines were loaded while the plugins were listed. */
|
|
47
|
+
const enginesLoaded = new WeakSet();
|
|
48
|
+
const debug = require('debug')('formio:uag');
|
|
49
|
+
/**
|
|
50
|
+
* Agentic Formflow keeps its license state inside @formio/appserver-core, and
|
|
51
|
+
* modules are built on its module framework. The UAG loads it only through
|
|
52
|
+
* @formio/appserver, so the two always share one copy, which must be 2.26.4 or
|
|
53
|
+
* later. An older @formio/appserver, or an old lockfile, can still resolve an
|
|
54
|
+
* older one, which would fail in confusing ways; say what to do instead.
|
|
55
|
+
*/
|
|
56
|
+
function assertSingleCore(version = appserverCoreVersion()) {
|
|
57
|
+
const [major, minor, patch] = version.split('.').map((part) => parseInt(part, 10) || 0);
|
|
58
|
+
if (major < 2 || (major === 2 && (minor < 26 || (minor === 26 && patch < 4)))) {
|
|
59
|
+
throw new Error(`@formio/uag needs @formio/appserver-core 2.26.4 or later, through @formio/appserver; ${version} is installed. Upgrade @formio/appserver.`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/** The version of the @formio/appserver-core that @formio/appserver loads. */
|
|
63
|
+
function appserverCoreVersion() {
|
|
64
|
+
const appserver = path_1.default.dirname(require.resolve('@formio/appserver/package.json'));
|
|
65
|
+
return String(require(require.resolve('@formio/appserver-core/package.json', { paths: [appserver] })).version || '0.0.0');
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* isolated-vm, which runs form logic, needs Node's startup snapshot off. Without
|
|
69
|
+
* it nothing fails at once: the process can crash later, in native code. So an
|
|
70
|
+
* embedding server that forgot the flag is stopped here, saying what to do.
|
|
71
|
+
*/
|
|
72
|
+
function assertNodeSnapshotDisabled(execArgv = process.execArgv, nodeOptions = process.env.NODE_OPTIONS || '') {
|
|
73
|
+
if (execArgv.includes('--no-node-snapshot') || /(^|\s)--no-node-snapshot(\s|$)/.test(nodeOptions))
|
|
74
|
+
return;
|
|
75
|
+
throw new Error('@formio/uag needs Node started with --no-node-snapshot: isolated-vm, which runs form logic, can crash the process without it. ' +
|
|
76
|
+
'Start node with --no-node-snapshot, or set NODE_OPTIONS=--no-node-snapshot. The uag command and the formio/uag image do this for you.');
|
|
77
|
+
}
|
|
78
|
+
/** A host name a browser cannot reach: a container or service name, with no dot. */
|
|
79
|
+
function internalHost(url) {
|
|
80
|
+
try {
|
|
81
|
+
const { hostname } = new URL(url);
|
|
82
|
+
return !!hostname && hostname !== 'localhost' && !hostname.includes('.') && !hostname.includes(':');
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/** Warnings for settings that start fine and fail later, in ways far from their cause. */
|
|
89
|
+
function warnAboutSettings(config, secretGiven) {
|
|
90
|
+
if (!secretGiven) {
|
|
91
|
+
console.warn('[formio-uag] JWT_SECRET is not set, so the UAG signs its tokens with a random secret that lasts only as long as this process: ' +
|
|
92
|
+
'every token stops working when it restarts, and other instances refuse it. Set JWT_SECRET, the same on every instance.');
|
|
93
|
+
}
|
|
94
|
+
const auth = config.auth || {};
|
|
95
|
+
if (auth.mcpOAuth && !auth.loginForm && internalHost(String(config.project || ''))) {
|
|
96
|
+
console.warn(`[formio-uag] MCP_OAUTH is on, but LOGIN_FORM is not set: the sign-in page loads ${String(config.project).replace(/\/+$/, '')}/user/login, ` +
|
|
97
|
+
'an address a person\'s browser cannot reach. Set LOGIN_FORM to the login form\'s public URL.');
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/** The UAG's own database from the environment, when `MONGO` is set. */
|
|
101
|
+
function databaseFromEnv(env = process.env) {
|
|
102
|
+
const url = (0, lodash_1.get)(env, 'MONGO', '').toString();
|
|
103
|
+
if (!url)
|
|
104
|
+
return undefined;
|
|
105
|
+
const retention = (0, lodash_1.get)(env, 'TASK_RUN_RETENTION_DAYS', '').toString();
|
|
106
|
+
return {
|
|
107
|
+
url,
|
|
108
|
+
name: (0, lodash_1.get)(env, 'UAG_DATABASE', '').toString() || 'uag',
|
|
109
|
+
config: (0, lodash_1.get)(env, 'MONGO_CONFIG', '').toString() || undefined,
|
|
110
|
+
...(retention && { retentionDays: parseInt(retention, 10) })
|
|
111
|
+
};
|
|
112
|
+
}
|
|
24
113
|
class UAGServer extends appserver_1.Server {
|
|
114
|
+
/**
|
|
115
|
+
* Use a module, or a list of modules (each in turn). A module used twice (by
|
|
116
|
+
* its `name`) is used once. `config.tools` adds up across modules; an MCP
|
|
117
|
+
* tool or engine type that two modules define stops startup, naming both.
|
|
118
|
+
* A route's `rateLimit` is the default for its name, unless the operator set one.
|
|
119
|
+
*/
|
|
120
|
+
use(module, source) {
|
|
121
|
+
module = appserver_1.Server.defaultExport(module);
|
|
122
|
+
if (module && typeof module === 'object' && !Array.isArray(module) && module.uag !== undefined) {
|
|
123
|
+
const label = source || (typeof module.name === 'string' && module.name) || 'a module';
|
|
124
|
+
throw new Error(`The module ${label} has a "uag" key, which UAG modules no longer use: move its keys (name, apiVersion, settings, routes, components, initialize) onto the module itself.`);
|
|
125
|
+
}
|
|
126
|
+
super.use(module, source);
|
|
127
|
+
if (typeof module?.name === 'string' && this.modules.get(module.name)?.definition === module)
|
|
128
|
+
this.routeRateLimits(module);
|
|
129
|
+
}
|
|
130
|
+
/** MCP tools and engine types, which only one module may define. */
|
|
131
|
+
moduleClaims(definition) {
|
|
132
|
+
return [
|
|
133
|
+
...(definition.config?.tools || []).filter((tool) => tool?.name).map((tool) => `MCP tool "${tool.name}"`),
|
|
134
|
+
...Object.keys(definition.config?.engineTypes || {}).map((name) => `engine type "${name}"`)
|
|
135
|
+
];
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* MCP tools add up across modules (lodash would merge them index by index),
|
|
139
|
+
* and a named module's tools get its API as `extra.module`. Its routes'
|
|
140
|
+
* `rateLimit`s become the defaults for their names.
|
|
141
|
+
*/
|
|
142
|
+
mergeModuleConfig(config, module) {
|
|
143
|
+
const { tools, ...rest } = config;
|
|
144
|
+
super.mergeModuleConfig(rest, module);
|
|
145
|
+
if (Array.isArray(tools) && tools.length) {
|
|
146
|
+
if (module)
|
|
147
|
+
for (const tool of tools)
|
|
148
|
+
(0, tools_1.setToolModule)(tool, this.modules.apiFor(module));
|
|
149
|
+
this.config.tools = [...(this.config.tools || []), ...tools];
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/** Route rate-limit defaults, for the names the operator left unset. */
|
|
153
|
+
routeRateLimits(module) {
|
|
154
|
+
for (const route of Object.values(module?.routes?.form || {})) {
|
|
155
|
+
const name = route?.name || module.name;
|
|
156
|
+
if (!route?.rateLimit || (0, lodash_1.get)(this.config, ['agentApi', 'rateLimits', 'caller', name]))
|
|
157
|
+
continue;
|
|
158
|
+
(0, lodash_1.set)(this.config, ['agentApi', 'rateLimits', 'caller', name], route.rateLimit);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
25
161
|
constructor(config) {
|
|
162
|
+
assertNodeSnapshotDisabled();
|
|
163
|
+
assertSingleCore();
|
|
164
|
+
const configuredDb = !!config?.db;
|
|
165
|
+
// Read before the server fills in a random one.
|
|
166
|
+
const secretGiven = !!(config?.auth?.secret || (0, lodash_1.get)(process.env, 'JWT_SECRET', '').toString());
|
|
167
|
+
const uagLicense = (0, lodash_1.get)(process.env, 'UAG_LICENSE', '').toString();
|
|
168
|
+
if (uagLicense) {
|
|
169
|
+
console.warn('[formio-uag] UAG_LICENSE is deprecated. Put a Form.io license that includes the UAG in LICENSE_KEY instead; UAG_LICENSE keeps working until UAG 3.0.');
|
|
170
|
+
}
|
|
26
171
|
super((0, lodash_1.defaultsDeep)(config || {}, {
|
|
27
172
|
baseUrl: (0, lodash_1.get)(process.env, 'BASE_URL', '').toString(),
|
|
28
|
-
|
|
173
|
+
// Deprecated: core also reads LICENSE_KEY, the Form.io license, and a
|
|
174
|
+
// feature is on when either key grants it.
|
|
175
|
+
license: uagLicense,
|
|
176
|
+
licenseName: 'UAG_LICENSE',
|
|
177
|
+
// The license server counts each UAG environment as a "uagServer", and the
|
|
178
|
+
// UAG runs on the "uag" feature: a key the license server accepts as a
|
|
179
|
+
// uagServer, or one whose terms carry the "uag" option.
|
|
180
|
+
licenseType: 'uagServer',
|
|
181
|
+
licenseFeature: 'uag',
|
|
29
182
|
// A project that is still starting up (a Form.io server in the same compose
|
|
30
183
|
// file, for example) is retried with backoff before the load is given up on.
|
|
31
184
|
retries: parseInt((0, lodash_1.get)(process.env, 'PROJECT_RETRIES', 5).toString(), 10),
|
|
32
185
|
retryDelay: parseInt((0, lodash_1.get)(process.env, 'PROJECT_RETRY_DELAY', 5000).toString(), 10),
|
|
33
186
|
submissionProxy: true,
|
|
34
187
|
processAsClient: true,
|
|
35
|
-
|
|
188
|
+
// GET /status names the UAG's version beside the Form.io packages it runs on.
|
|
189
|
+
status: { '@formio/uag': version_1.UAG_VERSION },
|
|
190
|
+
// Bodies are parsed before authentication, so keep them to what Form.io accepts.
|
|
191
|
+
maxBodySize: (0, lodash_1.get)(process.env, 'MAX_BODY_SIZE', '16mb').toString(),
|
|
192
|
+
auth: { pkce: true, mcpOAuth: (0, lodash_1.get)(process.env, 'MCP_OAUTH', '').toString() === 'true' },
|
|
193
|
+
database: databaseFromEnv(),
|
|
194
|
+
// Modules an operator turns off, such as one an image includes.
|
|
195
|
+
modules: { disabled: (0, lodash_1.get)(process.env, 'MODULES_DISABLED', '').toString().split(',').map((name) => name.trim()).filter(Boolean) }
|
|
36
196
|
}));
|
|
197
|
+
// The server's own MCP tools and engine types: a module may not define them again.
|
|
198
|
+
for (const tool of (this.config.tools || []))
|
|
199
|
+
if (tool?.name)
|
|
200
|
+
this.modules.claim(`MCP tool "${tool.name}"`, "the server's configuration");
|
|
201
|
+
for (const name of Object.keys(this.config.engineTypes || {}))
|
|
202
|
+
this.modules.claim(`engine type "${name}"`, "the server's configuration");
|
|
203
|
+
// MONGO is the UAG's own database, not a database for the appserver to keep
|
|
204
|
+
// submissions in: Form.io stores those.
|
|
205
|
+
if (!configuredDb && this.module.db) {
|
|
206
|
+
delete this.module.db;
|
|
207
|
+
delete this.config.db;
|
|
208
|
+
}
|
|
209
|
+
warnAboutSettings(this.config, secretGiven);
|
|
37
210
|
this.use({
|
|
38
211
|
ProjectInterface: UAGProjectInterface_1.UAGProjectInterface,
|
|
39
|
-
FormInterface: UAGFormInterface_1.UAGFormInterface
|
|
212
|
+
FormInterface: UAGFormInterface_1.UAGFormInterface,
|
|
213
|
+
actions: { agentRunTask: runTask_1.RunTaskAction },
|
|
214
|
+
hooks: (0, runTask_1.runTaskHooks)()
|
|
40
215
|
});
|
|
41
216
|
}
|
|
217
|
+
/**
|
|
218
|
+
* Connect the UAG's own database before the project loads, and check it
|
|
219
|
+
* without writing: a database the UAG may not use stops startup. It is used
|
|
220
|
+
* (its collections created, its stores handed out) only once the license is
|
|
221
|
+
* known; see enableDatabase. Until then nothing writes to it.
|
|
222
|
+
*/
|
|
223
|
+
async connectDatabase() {
|
|
224
|
+
const options = this.config.database;
|
|
225
|
+
if (!options?.url) {
|
|
226
|
+
console.log('[formio-uag] No database (MONGO): runs after the save are kept in memory, and remote MCP sign-in (MCP_OAUTH) is unavailable.');
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
if (this.database || this.pendingDatabase)
|
|
230
|
+
return;
|
|
231
|
+
const database = await database_1.UAGDatabase.connect(options);
|
|
232
|
+
try {
|
|
233
|
+
await database.inspect();
|
|
234
|
+
}
|
|
235
|
+
catch (err) {
|
|
236
|
+
await database.close().catch(() => { });
|
|
237
|
+
throw err;
|
|
238
|
+
}
|
|
239
|
+
this.pendingDatabase = database;
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Use the database once the project and its license are loaded: with a
|
|
243
|
+
* license that includes the UAG (or the project and the database both on
|
|
244
|
+
* development hosts), create its collections and hand out its stores;
|
|
245
|
+
* otherwise leave it untouched, close it, and say why.
|
|
246
|
+
*/
|
|
247
|
+
async enableDatabase(project) {
|
|
248
|
+
const database = this.pendingDatabase;
|
|
249
|
+
this.pendingDatabase = undefined;
|
|
250
|
+
if (database) {
|
|
251
|
+
let refusal = '';
|
|
252
|
+
let grant;
|
|
253
|
+
try {
|
|
254
|
+
grant = await (0, formflow_1.useDatabase)(project, {
|
|
255
|
+
url: this.config.database.url,
|
|
256
|
+
authCodes: database.authCodeStore(),
|
|
257
|
+
taskRuns: { collection: database.taskRuns, ids: runs_1.OBJECT_IDS, project: String(project.project?._id || '') }
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
catch (err) {
|
|
261
|
+
refusal = err?.message || String(err);
|
|
262
|
+
}
|
|
263
|
+
if (!grant) {
|
|
264
|
+
console.warn(`[formio-uag] The database (MONGO) is not used: ${refusal} Runs after the save are kept in memory, and remote MCP sign-in (MCP_OAUTH) is off.`);
|
|
265
|
+
await database.close().catch(() => { });
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
await database.bootstrap();
|
|
269
|
+
this.database = database;
|
|
270
|
+
(0, lodash_1.set)(this.config, 'auth.authCodeStore', grant.authCodeStore);
|
|
271
|
+
if (grant.taskRuns)
|
|
272
|
+
(0, runner_1.taskRunner)(project).useStore(grant.taskRuns);
|
|
273
|
+
console.log(`[formio-uag] Using the database "${database.name}": runs are recorded, and runs that act with an agent role survive a restart.` +
|
|
274
|
+
(this.config.auth?.mcpOAuth ? ' Remote MCP sign-in (MCP_OAUTH) is on.' : ''));
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
if (this.config.auth?.mcpOAuth && !this.database) {
|
|
278
|
+
console.warn('[formio-uag] MCP_OAUTH is set, but remote MCP sign-in is off: it needs the UAG\'s database (MONGO), and a license that includes the UAG to use it.');
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* An Agent API request that failed before its handlers (a body that is not JSON,
|
|
283
|
+
* one over the size limit) is answered in the Agent API's error shape; every other
|
|
284
|
+
* route keeps Form.io's plain-text answer.
|
|
285
|
+
*/
|
|
286
|
+
sendError(req, res, status, message, err) {
|
|
287
|
+
if (!(0, errors_1.isAgentApiRequest)(req) && !this.isModuleRouteRequest(req))
|
|
288
|
+
return super.sendError(req, res, status, message, err);
|
|
289
|
+
const error = err?.type === 'entity.parse.failed' ? 'The request body is not valid JSON.' : message;
|
|
290
|
+
// Written past any shaping wrapper, which may not have run for this request.
|
|
291
|
+
res.status(status).type('application/json').end(JSON.stringify((0, errors_1.errorBody)((0, errors_1.requestIdOf)(req, res), status, { error })));
|
|
292
|
+
}
|
|
293
|
+
/** Whether a request is for a module's form route: its method, and a path ending in the route's. */
|
|
294
|
+
isModuleRouteRequest(req) {
|
|
295
|
+
const path = String(req.path || '').toLowerCase().replace(/\/+$/, '');
|
|
296
|
+
return this.modules.list.some((module) => Object.keys(module.definition.routes?.form || {}).some((key) => {
|
|
297
|
+
const [method, route] = key.split(' ');
|
|
298
|
+
return req.method === method && path.endsWith(String(route).toLowerCase().replace(/\/+$/, ''));
|
|
299
|
+
}));
|
|
300
|
+
}
|
|
301
|
+
/** Browsers reading Agent API responses need the request id too. */
|
|
302
|
+
get exposedHeaders() {
|
|
303
|
+
return [...super.exposedHeaders, 'X-Request-Id'];
|
|
304
|
+
}
|
|
305
|
+
/** The loaded UAG project, once the project router has been built. */
|
|
306
|
+
get uagProject() {
|
|
307
|
+
return Object.values(this.projectRouter?.projects || {})[0];
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Mount `/agent/capabilities` ahead of the project routes. The project routes,
|
|
311
|
+
* and the admin passthrough after them, would otherwise answer for it and
|
|
312
|
+
* forward admin requests to the Form.io server. A form's agent operations are
|
|
313
|
+
* on the form's own routes.
|
|
314
|
+
*/
|
|
315
|
+
async extendRouter(router) {
|
|
316
|
+
// A bare function in config.tools (the unreleased factory form) stops startup.
|
|
317
|
+
(0, utils_1.assertToolEntries)(this.config.tools);
|
|
318
|
+
await super.extendRouter(router);
|
|
319
|
+
// @formio/uag-js, the portal script and stylesheet: public, ahead of the project routes.
|
|
320
|
+
router.use((0, uagJs_1.uagJsRouter)(() => this.plugins, () => this.modules.components()));
|
|
321
|
+
const uagProject = this.uagProject;
|
|
322
|
+
if (uagProject) {
|
|
323
|
+
// First: the Agent API's request id and one error shape, for every refusal after it.
|
|
324
|
+
router.use((0, errors_1.agentErrorShape)());
|
|
325
|
+
router.use(uagProject.agentRouter());
|
|
326
|
+
// An agent operation of a form the project does not have is answered here, not passed to Form.io.
|
|
327
|
+
router.use((0, router_1.agentNotFound)(uagProject));
|
|
328
|
+
// Action Logs in a connected portal stage, with the UAG's run records.
|
|
329
|
+
router.use((0, actionLogs_1.ActionLogRouter)(uagProject));
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* List the UAG's engines and MCP tools in the plugin registry, beside
|
|
334
|
+
* appserver-core's form actions. They keep loading their own way.
|
|
335
|
+
*/
|
|
336
|
+
async listPlugins() {
|
|
337
|
+
await super.listPlugins();
|
|
338
|
+
// Converters: the converter type and the built-in converters, for task file inputs, read_document and modules.
|
|
339
|
+
(0, plugins_2.registerDocuments)(this.plugins);
|
|
340
|
+
const project = this.uagProject;
|
|
341
|
+
if (project instanceof UAGProjectInterface_1.UAGProjectInterface)
|
|
342
|
+
project.plugins = this.plugins;
|
|
343
|
+
if (project instanceof UAGProjectInterface_1.UAGProjectInterface) {
|
|
344
|
+
// Decide which engines are on before serving anything, so a misconfigured
|
|
345
|
+
// engine stops startup with its name in the error instead of failing the
|
|
346
|
+
// first request.
|
|
347
|
+
project.engines = await (0, engines_1.loadUAGEngines)(project.config || {}, process.env);
|
|
348
|
+
enginesLoaded.add(project);
|
|
349
|
+
(0, plugins_1.listEngines)(this.plugins, project);
|
|
350
|
+
await (0, plugins_1.listTools)(this.plugins, project);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
/** A form's own route segments, which module routes may not use: appserver's, and the agent operations. */
|
|
354
|
+
get reservedFormPaths() {
|
|
355
|
+
return [...super.reservedFormPaths, 'agent'];
|
|
356
|
+
}
|
|
357
|
+
/** Module routes may not take the names of the UAG's own rate-limited operations. */
|
|
358
|
+
get reservedRouteNames() {
|
|
359
|
+
return modules_1.UAG_OPERATIONS;
|
|
360
|
+
}
|
|
361
|
+
get hostName() {
|
|
362
|
+
return 'uag';
|
|
363
|
+
}
|
|
364
|
+
get hostVersion() {
|
|
365
|
+
return version_1.UAG_VERSION;
|
|
366
|
+
}
|
|
367
|
+
/** The module API: appserver's, with the UAG's license check, engines, converters and helpers. */
|
|
368
|
+
createModuleApi(module) {
|
|
369
|
+
return (0, modules_1.uagModuleApi)(super.createModuleApi(module), (0, modules_1.serverDeps)(() => this.uagProject));
|
|
370
|
+
}
|
|
371
|
+
/** Module routes run behind what the agent operations run behind: the caller check, body limit, request id, rate limits and error shape. */
|
|
372
|
+
moduleRouteHandlers(project, route, module) {
|
|
373
|
+
return (0, modules_1.uagRouteHandlers)(project, route.name);
|
|
374
|
+
}
|
|
375
|
+
moduleErrorBody(req, err) {
|
|
376
|
+
return (0, modules_1.uagErrorBody)(req, err);
|
|
377
|
+
}
|
|
42
378
|
async router() {
|
|
379
|
+
await this.connectDatabase();
|
|
43
380
|
const router = await super.router();
|
|
44
|
-
const uagProject =
|
|
381
|
+
const uagProject = this.uagProject;
|
|
45
382
|
// Without a project there are no forms to expose, so there is nothing to
|
|
46
383
|
// serve. Say why: this used to throw "Cannot read properties of undefined
|
|
47
384
|
// (reading 'uagRouter')", which told the operator nothing about the
|
|
@@ -49,16 +386,53 @@ class UAGServer extends appserver_1.Server {
|
|
|
49
386
|
if (!uagProject) {
|
|
50
387
|
throw new Error('No Form.io project could be loaded, so the UAG has nothing to serve. Check that PROJECT ' +
|
|
51
388
|
'points at a reachable Form.io deployment, and that it is paired with the right ' +
|
|
52
|
-
'credential — ADMIN_KEY for an Open Source server,
|
|
53
|
-
'
|
|
389
|
+
'credential — ADMIN_KEY for an Open Source server, PROJECT_KEY for a project on Form.io\'s ' +
|
|
390
|
+
'hosted service, or PROJECT_KEY plus a license (LICENSE_KEY) for a self-hosted Enterprise project. ' +
|
|
391
|
+
'Earlier log lines report why the project load failed.');
|
|
392
|
+
}
|
|
393
|
+
// A database of the UAG's own needs the license, on every edition.
|
|
394
|
+
await this.enableDatabase(uagProject);
|
|
395
|
+
// The engines are loaded while the plugins are listed (listPlugins); a
|
|
396
|
+
// project interface that is not the UAG's own loads them here.
|
|
397
|
+
if (!enginesLoaded.has(uagProject)) {
|
|
398
|
+
uagProject.engines = await (0, engines_1.loadUAGEngines)(uagProject.config || {}, process.env);
|
|
399
|
+
}
|
|
400
|
+
// Say which edition this is, and whether Agentic Formflow will run, so an
|
|
401
|
+
// operator knows before the first request is refused.
|
|
402
|
+
// A failure here only loses the message; it never stops the UAG starting.
|
|
403
|
+
try {
|
|
404
|
+
const formflowState = await (0, formflow_1.formflow)(uagProject).state();
|
|
405
|
+
// A project on Form.io's hosted service runs under Form.io's license.
|
|
406
|
+
const hosted = formflowState.reason === 'hosted' ? ', on Form.io\'s hosted service (no license needed)' : '';
|
|
407
|
+
const reason = formflowState.licensedBy ? `licensed by ${formflowState.licensedBy}` : formflowState.reason;
|
|
408
|
+
console.log(`[formio-uag] ${formflowState.edition === 'enterprise' ? 'Enterprise' : 'Open Source'} edition${hosted}. Agentic Formflow ` +
|
|
409
|
+
(formflowState.allowed ? `is enabled (${reason}).` : 'requires a license (LICENSE_KEY) that includes the UAG, or a project on Form.io\'s hosted service; MCP and conversation forms are unaffected.'));
|
|
410
|
+
}
|
|
411
|
+
catch (err) {
|
|
412
|
+
debug(`Could not report the edition: ${err?.message || err}`);
|
|
54
413
|
}
|
|
55
|
-
router.use(uagProject.uagRouter());
|
|
414
|
+
router.use(uagProject.uagRouter(false));
|
|
56
415
|
return router;
|
|
57
416
|
}
|
|
58
417
|
}
|
|
59
418
|
exports.UAGServer = UAGServer;
|
|
60
419
|
__exportStar(require("@formio/appserver"), exports);
|
|
61
420
|
__exportStar(require("./config"), exports);
|
|
421
|
+
__exportStar(require("./agent"), exports);
|
|
62
422
|
__exportStar(require("./tools"), exports);
|
|
423
|
+
__exportStar(require("./documents"), exports);
|
|
424
|
+
__exportStar(require("./modules"), exports);
|
|
425
|
+
var markdown_1 = require("./markdown");
|
|
426
|
+
Object.defineProperty(exports, "sanitizeMarkdown", { enumerable: true, get: function () { return markdown_1.sanitizeMarkdown; } });
|
|
427
|
+
var pluginFiles_1 = require("./pluginFiles");
|
|
428
|
+
Object.defineProperty(exports, "usePluginFiles", { enumerable: true, get: function () { return pluginFiles_1.usePluginFiles; } });
|
|
429
|
+
Object.defineProperty(exports, "pluginFiles", { enumerable: true, get: function () { return pluginFiles_1.pluginFiles; } });
|
|
430
|
+
Object.defineProperty(exports, "pluginFilesIn", { enumerable: true, get: function () { return pluginFiles_1.pluginFilesIn; } });
|
|
431
|
+
Object.defineProperty(exports, "DEFAULT_PLUGINS_DIR", { enumerable: true, get: function () { return pluginFiles_1.DEFAULT_PLUGINS_DIR; } });
|
|
63
432
|
__exportStar(require("./router"), exports);
|
|
64
433
|
__exportStar(require("./template"), exports);
|
|
434
|
+
var richText_1 = require("./richText");
|
|
435
|
+
Object.defineProperty(exports, "sanitizeRichText", { enumerable: true, get: function () { return richText_1.sanitizeRichText; } });
|
|
436
|
+
Object.defineProperty(exports, "isRichText", { enumerable: true, get: function () { return richText_1.isRichText; } });
|
|
437
|
+
Object.defineProperty(exports, "RICH_TEXT_TAGS", { enumerable: true, get: function () { return richText_1.RICH_TEXT_TAGS; } });
|
|
438
|
+
Object.defineProperty(exports, "RICH_TEXT_RULE", { enumerable: true, get: function () { return richText_1.RICH_TEXT_RULE; } });
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function sanitizeMarkdown(markdown: string): string;
|