@formio/uag 2.0.0-rc.1 → 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.
Files changed (96) hide show
  1. package/README.md +17 -15
  2. package/bin/uag-eval.js +7 -3
  3. package/bin/uag.js +48 -5
  4. package/lib/UAGFormInterface.d.ts +12 -0
  5. package/lib/UAGFormInterface.js +55 -12
  6. package/lib/UAGProjectInterface.d.ts +9 -1
  7. package/lib/UAGProjectInterface.js +25 -7
  8. package/lib/actionLogs.d.ts +1 -10
  9. package/lib/actionLogs.js +5 -3
  10. package/lib/actions/runTask.js +4 -3
  11. package/lib/agent/anonymous.js +5 -4
  12. package/lib/agent/chat.d.ts +3 -2
  13. package/lib/agent/chat.js +35 -99
  14. package/lib/agent/engines.d.ts +10 -6
  15. package/lib/agent/engines.js +19 -10
  16. package/lib/agent/errors.d.ts +25 -0
  17. package/lib/agent/errors.js +132 -0
  18. package/lib/agent/index.d.ts +1 -1
  19. package/lib/agent/index.js +2 -1
  20. package/lib/agent/remote.d.ts +5 -1
  21. package/lib/agent/remote.js +25 -2
  22. package/lib/agent/router.d.ts +32 -2
  23. package/lib/agent/router.js +37 -25
  24. package/lib/agent/stubs.d.ts +13 -2
  25. package/lib/agent/stubs.js +18 -5
  26. package/lib/agent/tools.d.ts +24 -3
  27. package/lib/agent/tools.js +47 -4
  28. package/lib/config.d.ts +31 -4
  29. package/lib/database.d.ts +3 -8
  30. package/lib/database.js +2 -19
  31. package/lib/documents/convert.d.ts +38 -0
  32. package/lib/documents/convert.js +151 -0
  33. package/lib/documents/converters.d.ts +28 -0
  34. package/lib/documents/converters.js +149 -0
  35. package/lib/documents/detect.d.ts +31 -0
  36. package/lib/documents/detect.js +169 -0
  37. package/lib/documents/index.d.ts +9 -0
  38. package/lib/documents/index.js +43 -0
  39. package/lib/documents/mime.d.ts +39 -0
  40. package/lib/documents/mime.js +100 -0
  41. package/lib/documents/plugins.d.ts +18 -0
  42. package/lib/documents/plugins.js +65 -0
  43. package/lib/documents/readers.d.ts +19 -0
  44. package/lib/documents/readers.js +64 -0
  45. package/lib/documents/routes.d.ts +52 -0
  46. package/lib/documents/routes.js +99 -0
  47. package/lib/documents/types.d.ts +126 -0
  48. package/lib/documents/types.js +2 -0
  49. package/lib/documents/uagJs.d.ts +17 -0
  50. package/lib/documents/uagJs.js +157 -0
  51. package/lib/formflow.d.ts +11 -4
  52. package/lib/formflow.js +5 -5
  53. package/lib/index.d.ts +65 -9
  54. package/lib/index.js +228 -26
  55. package/lib/markdown.d.ts +1 -0
  56. package/lib/markdown.js +166 -0
  57. package/lib/modules/api.d.ts +25 -0
  58. package/lib/modules/api.js +98 -0
  59. package/lib/modules/index.d.ts +4 -0
  60. package/lib/modules/index.js +24 -0
  61. package/lib/modules/routes.d.ts +14 -0
  62. package/lib/modules/routes.js +21 -0
  63. package/lib/modules/types.d.ts +68 -0
  64. package/lib/modules/types.js +5 -0
  65. package/lib/pluginFiles.d.ts +28 -0
  66. package/lib/pluginFiles.js +101 -0
  67. package/lib/plugins.d.ts +17 -0
  68. package/lib/plugins.js +93 -0
  69. package/lib/richText.d.ts +20 -0
  70. package/lib/richText.js +99 -0
  71. package/lib/router.d.ts +9 -0
  72. package/lib/router.js +19 -0
  73. package/lib/runner.d.ts +2 -0
  74. package/lib/runner.js +11 -3
  75. package/lib/runs.d.ts +17 -79
  76. package/lib/runs.js +10 -71
  77. package/lib/testing.d.ts +74 -0
  78. package/lib/testing.js +180 -0
  79. package/lib/tools/SchemaBuilder.d.ts +0 -5
  80. package/lib/tools/SchemaBuilder.js +0 -8
  81. package/lib/tools/agentDecide.js +2 -2
  82. package/lib/tools/agentProvide.d.ts +0 -5
  83. package/lib/tools/agentProvide.js +7 -40
  84. package/lib/tools/collectData.js +2 -1
  85. package/lib/tools/index.d.ts +3 -2
  86. package/lib/tools/index.js +5 -5
  87. package/lib/tools/readDocument.d.ts +17 -0
  88. package/lib/tools/readDocument.js +99 -0
  89. package/lib/tools/submissionUpdate.js +3 -2
  90. package/lib/tools/utils.d.ts +40 -0
  91. package/lib/tools/utils.js +16 -1
  92. package/lib/version.d.ts +2 -0
  93. package/lib/version.js +5 -0
  94. package/package.json +17 -10
  95. package/testing.d.ts +1 -0
  96. package/testing.js +2 -0
@@ -0,0 +1,166 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.sanitizeMarkdown = sanitizeMarkdown;
7
+ const markdown_it_1 = __importDefault(require("markdown-it"));
8
+ /**
9
+ * The Markdown contract of AI-generated content: GitHub-flavored Markdown,
10
+ * UTF-8, `\n` line endings, no code fence around the whole value.
11
+ *
12
+ * `sanitizeMarkdown` keeps what the AI Document's renderer shows and nothing
13
+ * that could act in a browser:
14
+ * - raw HTML is removed, keeping its text;
15
+ * - links keep only http, https and mailto targets (others become their text);
16
+ * - images are dropped;
17
+ * - a code fence wrapping the whole value is removed;
18
+ * - trailing whitespace is trimmed and more than two blank lines collapsed.
19
+ * Code blocks and inline code are kept as they are: the renderer escapes them.
20
+ *
21
+ * The result is then parsed as a CommonMark renderer parses it (markdown-it,
22
+ * with tables and strikethrough). A block still holding raw HTML, an image or a
23
+ * link to another target (Markdown that line-by-line cleaning misreads, such as
24
+ * a link whose label holds brackets, or HTML spanning lines) has its `<`, `[`,
25
+ * `]` and `\` escaped, so it shows as text, until the parse finds none.
26
+ */
27
+ const SAFE_LINK = /^(https?:|mailto:)/i;
28
+ // Up to three spaces: a tab, or four spaces, begins an indented code block, never a fence.
29
+ const FENCE = /^ {0,3}(`{3,}|~{3,})/;
30
+ /** HTML recognized, so that it is found, and every link target kept, so that it is checked. */
31
+ const parser = new markdown_it_1.default('default', { html: true, linkify: false });
32
+ parser.validateLink = () => true;
33
+ /** The line ranges (from, to: 0-based, end excluded) of blocks holding HTML, an image or a link sanitizeMarkdown never keeps. */
34
+ function unsafeBlocks(markdown) {
35
+ const ranges = [];
36
+ let block = null;
37
+ for (const token of parser.parse(markdown, {})) {
38
+ if (token.map)
39
+ block = token.map;
40
+ if (token.type === 'html_block') {
41
+ if (token.map)
42
+ ranges.push(token.map);
43
+ continue;
44
+ }
45
+ if (token.type !== 'inline' || !token.children)
46
+ continue;
47
+ const unsafe = token.children.some((child) => child.type === 'html_inline' || child.type === 'image' ||
48
+ (child.type === 'link_open' && !SAFE_LINK.test(String(child.attrGet('href') || '').trim())));
49
+ const lines = token.map || block;
50
+ if (unsafe && lines)
51
+ ranges.push(lines);
52
+ }
53
+ return ranges;
54
+ }
55
+ /** A line whose markup can form no HTML, image or link: its text shows as it is. */
56
+ function escapeLine(line) {
57
+ return line.replace(/[\\<[\]]/g, (char) => `\\${char}`);
58
+ }
59
+ /** The lines of fenced and indented code blocks, which can hold nothing that acts. */
60
+ function codeLines(markdown) {
61
+ const lines = new Set();
62
+ for (const token of parser.parse(markdown, {})) {
63
+ if ((token.type === 'fence' || token.type === 'code_block') && token.map) {
64
+ for (let i = token.map[0]; i < token.map[1]; i++)
65
+ lines.add(i);
66
+ }
67
+ }
68
+ return lines;
69
+ }
70
+ /** Escape the blocks the parse finds unsafe until it finds none; at worst, every line outside code. */
71
+ function escapeUnsafeBlocks(markdown) {
72
+ let text = markdown;
73
+ for (let pass = 0; pass < 5; pass++) {
74
+ const ranges = unsafeBlocks(text);
75
+ if (!ranges.length)
76
+ return text;
77
+ const lines = text.split('\n');
78
+ for (const [from, to] of ranges) {
79
+ for (let i = from; i < to && i < lines.length; i++)
80
+ lines[i] = escapeLine(lines[i]);
81
+ }
82
+ text = lines.join('\n');
83
+ }
84
+ const code = codeLines(text);
85
+ text = text.split('\n').map((line, i) => (code.has(i) ? line : escapeLine(line))).join('\n');
86
+ return unsafeBlocks(text).length ? text.split('\n').map(escapeLine).join('\n') : text;
87
+ }
88
+ /** A whole value wrapped in one code fence: its contents. */
89
+ function unwrapFence(text) {
90
+ const trimmed = text.trim();
91
+ const match = /^(`{3,}|~{3,})[^\n]*\n([\s\S]*?)\n?\1\s*$/.exec(trimmed);
92
+ if (!match)
93
+ return text;
94
+ // Only when no other fence of the same kind is inside: then it really is one block.
95
+ if (new RegExp(`^\\s{0,3}${match[1][0] === '`' ? '`' : '~'}{3,}`, 'm').test(match[2]))
96
+ return text;
97
+ return match[2];
98
+ }
99
+ /** Clean the text of one line outside code: HTML, images and unsafe links. */
100
+ function cleanLine(line) {
101
+ // Inline code spans are kept as they are.
102
+ const parts = line.split(/(`+[^`]*`+)/);
103
+ return parts.map((part, i) => {
104
+ if (i % 2 === 1)
105
+ return part;
106
+ let out = part;
107
+ // Images: dropped, inline and reference style.
108
+ out = out.replace(/!\[[^\]]*\]\([^)]*\)/g, '').replace(/!\[[^\]]*\]\[[^\]]*\]/g, '');
109
+ // Links: kept only for safe targets.
110
+ out = out.replace(/\[([^\]]*)\]\(\s*<?((?:[^()\s<>]|\([^()\s]*\))*)>?(?:\s+"[^"]*")?\s*\)/g, (whole, label, target) => SAFE_LINK.test(target) ? `[${label}](${target})` : label);
111
+ // Autolinks: <https://...> kept when safe; anything else in angle brackets is HTML.
112
+ const autolinks = [];
113
+ out = out.replace(/<([a-z][a-z0-9+.-]*:[^>\s]*)>/gi, (whole, target) => {
114
+ if (!SAFE_LINK.test(target))
115
+ return '';
116
+ autolinks.push(`<${target}>`);
117
+ return `\u0000${autolinks.length - 1}\u0000`;
118
+ });
119
+ // Raw HTML: tags removed, their text kept.
120
+ out = out.replace(/<!--[\s\S]*?-->/g, '').replace(/<\/?[A-Za-z][^<>]*>/g, '');
121
+ return out.replace(/\u0000(\d+)\u0000/g, (whole, index) => autolinks[Number(index)]);
122
+ }).join('');
123
+ }
124
+ function sanitizeMarkdown(markdown) {
125
+ const text = unwrapFence(String(markdown ?? '').replace(/\r\n?/g, '\n'));
126
+ const out = [];
127
+ let fence = null;
128
+ // HTML blocks spanning several lines (a comment, a script) are removed whole.
129
+ let inComment = false;
130
+ for (const raw of text.split('\n')) {
131
+ let line = raw;
132
+ const opener = FENCE.exec(line);
133
+ if (fence) {
134
+ if (opener && opener[1][0] === fence[0] && opener[1].length >= fence.length && line.trim().replace(/[`~]/g, '') === '')
135
+ fence = null;
136
+ out.push(line.replace(/\s+$/, ''));
137
+ continue;
138
+ }
139
+ if (opener) {
140
+ fence = opener[1];
141
+ out.push(line.replace(/\s+$/, ''));
142
+ continue;
143
+ }
144
+ if (inComment) {
145
+ const end = line.indexOf('-->');
146
+ if (end < 0)
147
+ continue;
148
+ line = line.slice(end + 3);
149
+ inComment = false;
150
+ }
151
+ const start = line.indexOf('<!--');
152
+ if (start >= 0 && line.indexOf('-->', start) < 0) {
153
+ line = line.slice(0, start);
154
+ inComment = true;
155
+ }
156
+ // Reference definitions with an unsafe target are dropped.
157
+ const definition = /^\s{0,3}\[[^\]]+\]:\s*<?(\S+?)>?(\s.*)?$/.exec(line);
158
+ if (definition) {
159
+ if (SAFE_LINK.test(definition[1]))
160
+ out.push(line.replace(/\s+$/, ''));
161
+ continue;
162
+ }
163
+ out.push(cleanLine(line).replace(/\s+$/, ''));
164
+ }
165
+ return escapeUnsafeBlocks(out.join('\n').replace(/\n{4,}/g, '\n\n\n').trim());
166
+ }
@@ -0,0 +1,25 @@
1
+ import type { ModuleApi } from '@formio/appserver';
2
+ import type { UAGProjectInterface } from '../UAGProjectInterface';
3
+ import type { PluginRegistry } from '@formio/appserver';
4
+ import type { UAGModuleApi } from './types';
5
+ /** What the UAG's members of the module API run on: the server's, or a test kit's. */
6
+ export type UAGModuleApiDeps = {
7
+ registry(): PluginRegistry;
8
+ /** The enabled engines: by name, and the agent engines in preference order. */
9
+ engines(): {
10
+ get(name: string): any;
11
+ agent: any[];
12
+ } | undefined;
13
+ /** Generate with an engine from the registry. */
14
+ generate(engine: any, request: any): Promise<any>;
15
+ /** Throw 403 license_required unless Agentic Formflow is allowed. */
16
+ requireLicense(): Promise<void>;
17
+ requireAccess?: ModuleApi['requireAccess'];
18
+ };
19
+ /** The server's: its one project, looked up when a member is called. */
20
+ export declare function serverDeps(projectOf: () => UAGProjectInterface | undefined): UAGModuleApiDeps;
21
+ /**
22
+ * The UAG's module API (version 1): appserver's, with the Agent API's access
23
+ * check, the license check, converters, engines and helpers, behind stable names.
24
+ */
25
+ export declare function uagModuleApi(base: ModuleApi, deps: UAGModuleApiDeps): UAGModuleApi;
@@ -0,0 +1,98 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.serverDeps = serverDeps;
4
+ exports.uagModuleApi = uagModuleApi;
5
+ const node_html_markdown_1 = require("node-html-markdown");
6
+ const formflow_1 = require("../formflow");
7
+ const markdown_1 = require("../markdown");
8
+ const detect_1 = require("../documents/detect");
9
+ const convert_1 = require("../documents/convert");
10
+ const plugins_1 = require("../documents/plugins");
11
+ const routes_1 = require("../documents/routes");
12
+ async function pageCount(pdf) {
13
+ try {
14
+ const { PDFDocument } = require('pdf-lib');
15
+ return (await PDFDocument.load(pdf, { ignoreEncryption: true, updateMetadata: false })).getPageCount();
16
+ }
17
+ catch {
18
+ return undefined;
19
+ }
20
+ }
21
+ const readsOf = (engine) => (Array.isArray(engine?.accepts) ? engine.accepts : ['text']);
22
+ /** The server's: its one project, looked up when a member is called. */
23
+ function serverDeps(projectOf) {
24
+ const project = () => {
25
+ const found = projectOf();
26
+ if (!found)
27
+ throw new formflow_1.FormflowError(503, 'The server is still starting.', 'not_ready');
28
+ return found;
29
+ };
30
+ return {
31
+ registry: () => {
32
+ const plugins = project().plugins;
33
+ if (!plugins)
34
+ throw new formflow_1.FormflowError(503, 'The server is still starting.', 'not_ready');
35
+ return plugins;
36
+ },
37
+ engines: () => project().formflowEngines(),
38
+ generate: (engine, request) => (0, formflow_1.formflow)(project()).engineGenerate(engine, request),
39
+ requireLicense: () => (0, formflow_1.formflow)(project()).requireLicense(),
40
+ // The Agent API's own check, so a module's refusals read as the agent operations' do.
41
+ requireAccess: (auth, form, methods) => (0, formflow_1.formflow)(project()).requireAccess(auth, form, methods)
42
+ };
43
+ }
44
+ /**
45
+ * The UAG's module API (version 1): appserver's, with the Agent API's access
46
+ * check, the license check, converters, engines and helpers, behind stable names.
47
+ */
48
+ function uagModuleApi(base, deps) {
49
+ const registry = deps.registry;
50
+ const generate = deps.generate;
51
+ const api = {
52
+ ...base,
53
+ ...(deps.requireAccess && { requireAccess: deps.requireAccess }),
54
+ requireLicense: deps.requireLicense,
55
+ converters: {
56
+ list: () => registry().list('converter'),
57
+ identify: (buffer, hint) => (0, detect_1.identify)(buffer, hint, registry().list('converter')),
58
+ route: (type, inputs, pins) => (0, plugins_1.documentsOf)(registry()).routes.route(type, inputs, pins || {}),
59
+ accept: (inputs) => (0, plugins_1.documentsOf)(registry()).routes.accept(inputs),
60
+ run: (steps, input, to, options) => (0, convert_1.runRoute)(steps, input, to, {
61
+ signal: options.signal,
62
+ log: base.log,
63
+ settingsOf: (converter) => registry().settings('converter', converter.name)
64
+ }),
65
+ stepName: (converter) => (0, routes_1.stepName)(converter)
66
+ },
67
+ engines: {
68
+ select({ name, needs = [] }) {
69
+ const engines = deps.engines();
70
+ if (!engines)
71
+ throw new formflow_1.FormflowError(503, 'The engines are not loaded yet.', 'not_ready');
72
+ const capable = (engine) => engine?.kind === 'agent' && typeof engine.generate === 'function' && needs.every((kind) => readsOf(engine).includes(kind));
73
+ const engine = name ? engines.get(name) : engines.agent.find(capable);
74
+ if (!engine || !capable(engine)) {
75
+ const missing = needs.find((kind) => !engine || !readsOf(engine).includes(kind)) || needs[0] || 'text';
76
+ throw new formflow_1.FormflowError(503, name && !engine ? `The engine "${name}" is not enabled.` : `no enabled engine can read ${missing} documents`, 'no_engine');
77
+ }
78
+ // A handle, not the engine.
79
+ return {
80
+ name: engine.name,
81
+ model: engine.model,
82
+ accepts: readsOf(engine),
83
+ generate: (request) => generate(engine, request)
84
+ };
85
+ }
86
+ },
87
+ markdown: {
88
+ sanitize: (text) => (0, markdown_1.sanitizeMarkdown)(text),
89
+ fromHtml: (html) => node_html_markdown_1.NodeHtmlMarkdown.translate(String(html || '')).trim()
90
+ },
91
+ pdf: { pageCount },
92
+ callerKey: (req) => {
93
+ const key = (0, formflow_1.callerKey)(req.auth);
94
+ return key === 'anonymous' ? `anonymous:${req.ip || req.socket?.remoteAddress || ''}` : key;
95
+ }
96
+ };
97
+ return api;
98
+ }
@@ -0,0 +1,4 @@
1
+ export * from './types';
2
+ export { uagModuleApi, serverDeps } from './api';
3
+ export type { UAGModuleApiDeps } from './api';
4
+ export { uagRouteHandlers, uagErrorBody } from './routes';
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.uagErrorBody = exports.uagRouteHandlers = exports.serverDeps = exports.uagModuleApi = void 0;
18
+ __exportStar(require("./types"), exports);
19
+ var api_1 = require("./api");
20
+ Object.defineProperty(exports, "uagModuleApi", { enumerable: true, get: function () { return api_1.uagModuleApi; } });
21
+ Object.defineProperty(exports, "serverDeps", { enumerable: true, get: function () { return api_1.serverDeps; } });
22
+ var routes_1 = require("./routes");
23
+ Object.defineProperty(exports, "uagRouteHandlers", { enumerable: true, get: function () { return routes_1.uagRouteHandlers; } });
24
+ Object.defineProperty(exports, "uagErrorBody", { enumerable: true, get: function () { return routes_1.uagErrorBody; } });
@@ -0,0 +1,14 @@
1
+ import type { Request } from 'express';
2
+ import type { ModuleRouteHandlers } from '@formio/appserver';
3
+ import type { UAGProjectInterface } from '../UAGProjectInterface';
4
+ /**
5
+ * What runs around each module route in the UAG, as around the agent
6
+ * operations: the caller check, the agent operations' JSON body limit and a
7
+ * request id before it; the named operation's rate limits and the Agent API's
8
+ * error shape around its run.
9
+ */
10
+ export declare function uagRouteHandlers(project: UAGProjectInterface, name: string): ModuleRouteHandlers;
11
+ /** A module route's `failed` event: the Agent API's error shape, with the status the event cannot carry otherwise. */
12
+ export declare function uagErrorBody(req: Request, err: unknown): Record<string, any> & {
13
+ status: number;
14
+ };
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.uagRouteHandlers = uagRouteHandlers;
4
+ exports.uagErrorBody = uagErrorBody;
5
+ const router_1 = require("../agent/router");
6
+ const errors_1 = require("../agent/errors");
7
+ /**
8
+ * What runs around each module route in the UAG, as around the agent
9
+ * operations: the caller check, the agent operations' JSON body limit and a
10
+ * request id before it; the named operation's rate limits and the Agent API's
11
+ * error shape around its run.
12
+ */
13
+ function uagRouteHandlers(project, name) {
14
+ const { json, requestId, handle } = (0, router_1.agentEndpoints)(project);
15
+ return { before: [(0, router_1.agentCredentials)(project), json, requestId], wrap: (run) => handle(name, run) };
16
+ }
17
+ /** A module route's `failed` event: the Agent API's error shape, with the status the event cannot carry otherwise. */
18
+ function uagErrorBody(req, err) {
19
+ const { status, body } = (0, errors_1.agentError)(req.requestId, err);
20
+ return { status, ...body };
21
+ }
@@ -0,0 +1,68 @@
1
+ import type { Request } from 'express';
2
+ import type { ModuleApi, ModuleRequest, ModuleRoute } from '@formio/appserver';
3
+ import type { Converter, ConverterOutput, Representation } from '../documents/types';
4
+ import type { FileRoute, Pins, AcceptEntry } from '../documents/routes';
5
+ /** A module route's request in the UAG: also its request id. */
6
+ export type UAGModuleRequest = ModuleRequest & {
7
+ requestId: string;
8
+ };
9
+ /** The names of the UAG's own rate-limited operations, which module routes may not take. */
10
+ export declare const UAG_OPERATIONS: string[];
11
+ /** An agent engine a module may use: its name and model, and generate, which checks the license first. */
12
+ export type GeneratingEngine = {
13
+ name: string;
14
+ model: string;
15
+ accepts: Representation[];
16
+ generate(request: any): Promise<any>;
17
+ };
18
+ /**
19
+ * The module API the UAG gives a module (version 1): appserver's, with the
20
+ * license check, engines, converters and the UAG's helpers. The project and
21
+ * the engines themselves are never handed out.
22
+ */
23
+ export type UAGModuleApi<Settings extends Record<string, any> = Record<string, any>> = ModuleApi<Settings> & {
24
+ /** Throws 403 license_required unless Agentic Formflow is allowed. */
25
+ requireLicense(): Promise<void>;
26
+ converters: {
27
+ list(): Converter[];
28
+ identify(buffer: Buffer, hint: {
29
+ name: string;
30
+ type?: string;
31
+ }): string;
32
+ route(type: string, inputs: Representation[][], pins?: Pins): FileRoute | null;
33
+ accept(inputs: Representation[][]): AcceptEntry[];
34
+ run(steps: Converter[], input: {
35
+ buffer: Buffer;
36
+ type: string;
37
+ name: string;
38
+ }, to: Representation, options: {
39
+ signal: AbortSignal;
40
+ }): Promise<ConverterOutput>;
41
+ stepName(converter: {
42
+ name: string;
43
+ version?: string;
44
+ }): string;
45
+ };
46
+ engines: {
47
+ /**
48
+ * The named engine, or the first agent engine that generates and reads every
49
+ * representation in `needs`; throws 503 no_engine. Its `generate` checks the
50
+ * license first, so no module calls a model outside it.
51
+ */
52
+ select(options: {
53
+ name?: string;
54
+ needs?: Representation[];
55
+ }): GeneratingEngine;
56
+ };
57
+ markdown: {
58
+ sanitize(text: string): string;
59
+ fromHtml(html: string): string;
60
+ };
61
+ pdf: {
62
+ pageCount(pdf: Buffer): Promise<number | undefined>;
63
+ };
64
+ /** Who the request is from, for limits: the user or token, or, for an anonymous caller, `anonymous:<ip>`. */
65
+ callerKey(req: Request): string;
66
+ };
67
+ /** A form route of a UAG module. */
68
+ export type UAGModuleRoute = ModuleRoute<UAGModuleApi>;
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.UAG_OPERATIONS = void 0;
4
+ /** The names of the UAG's own rate-limited operations, which module routes may not take. */
5
+ exports.UAG_OPERATIONS = ['extract', 'decide', 'generate', 'chat', 'capabilities'];
@@ -0,0 +1,28 @@
1
+ import type { UAGServer } from './index';
2
+ /** The image's plugins folder: add-ons are mounted in folders under it. */
3
+ export declare const DEFAULT_PLUGINS_DIR = "/app/plugins";
4
+ /**
5
+ * The plugins files a path holds, in load order:
6
+ * - a file: that file;
7
+ * - a folder with an `index.mjs`: that file alone (one plugins folder);
8
+ * - any other folder: the `index.mjs` of each folder directly in it, in
9
+ * name order (one per add-on), skipping folders whose name starts with
10
+ * "." and `node_modules`.
11
+ */
12
+ export declare function pluginFilesIn(target: string): string[];
13
+ /**
14
+ * The plugins files to load: those UAG_PLUGINS names (a file, a folder, or
15
+ * "off"), or else those in the image's plugins folder when it exists.
16
+ */
17
+ export declare function pluginFiles(env?: Record<string, string | undefined>, defaultDir?: string): string[];
18
+ /**
19
+ * Load the plugins files, if any, and use each in turn, named by its path in
20
+ * error messages. A file's default export is a plugin or a list of plugins;
21
+ * or a module (an extra, with its plugins), or a list mixing modules and
22
+ * plugins, each module used in turn. Several files merge as several modules
23
+ * do: a module used again by name is used once, and two files that register
24
+ * the same plugin, plugin type, route or component stop startup, naming both.
25
+ * Plugins are trusted code: they run in the UAG process. Returns the files
26
+ * loaded.
27
+ */
28
+ export declare function usePluginFiles(server: Pick<UAGServer, 'use'>, env?: Record<string, string | undefined>, defaultDir?: string): Promise<string[]>;
@@ -0,0 +1,101 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.DEFAULT_PLUGINS_DIR = void 0;
7
+ exports.pluginFilesIn = pluginFilesIn;
8
+ exports.pluginFiles = pluginFiles;
9
+ exports.usePluginFiles = usePluginFiles;
10
+ const fs_1 = __importDefault(require("fs"));
11
+ const path_1 = __importDefault(require("path"));
12
+ const url_1 = require("url");
13
+ const appserver_1 = require("@formio/appserver");
14
+ /** The image's plugins folder: add-ons are mounted in folders under it. */
15
+ exports.DEFAULT_PLUGINS_DIR = '/app/plugins';
16
+ /** The file that makes a folder one plugins folder. */
17
+ const INDEX = 'index.mjs';
18
+ const isDirectory = (target) => {
19
+ try {
20
+ return fs_1.default.statSync(target).isDirectory();
21
+ }
22
+ catch {
23
+ return false;
24
+ }
25
+ };
26
+ /**
27
+ * The plugins files a path holds, in load order:
28
+ * - a file: that file;
29
+ * - a folder with an `index.mjs`: that file alone (one plugins folder);
30
+ * - any other folder: the `index.mjs` of each folder directly in it, in
31
+ * name order (one per add-on), skipping folders whose name starts with
32
+ * "." and `node_modules`.
33
+ */
34
+ function pluginFilesIn(target) {
35
+ if (!isDirectory(target))
36
+ return [target];
37
+ const own = path_1.default.join(target, INDEX);
38
+ if (fs_1.default.existsSync(own))
39
+ return [own];
40
+ return fs_1.default.readdirSync(target, { withFileTypes: true })
41
+ .filter((entry) => !entry.name.startsWith('.') && entry.name !== 'node_modules')
42
+ .filter((entry) => entry.isDirectory() || (entry.isSymbolicLink() && isDirectory(path_1.default.join(target, entry.name))))
43
+ .map((entry) => entry.name)
44
+ .sort()
45
+ .map((name) => path_1.default.join(target, name, INDEX))
46
+ .filter((file) => fs_1.default.existsSync(file));
47
+ }
48
+ /**
49
+ * The plugins files to load: those UAG_PLUGINS names (a file, a folder, or
50
+ * "off"), or else those in the image's plugins folder when it exists.
51
+ */
52
+ function pluginFiles(env = process.env, defaultDir = exports.DEFAULT_PLUGINS_DIR) {
53
+ const setting = String(env.UAG_PLUGINS || '').trim();
54
+ if (setting.toLowerCase() === 'off')
55
+ return [];
56
+ if (setting)
57
+ return pluginFilesIn(path_1.default.resolve(setting));
58
+ return fs_1.default.existsSync(defaultDir) ? pluginFilesIn(defaultDir) : [];
59
+ }
60
+ /** A real dynamic import(), kept one after compilation to CommonJS. */
61
+ const dynamicImport = new Function('specifier', 'return import(specifier)');
62
+ /**
63
+ * The keys only a module has: an entry with one of them (and no plugin `type`)
64
+ * is a module, anything else a plugin. Plugins have a `name` and `settings`
65
+ * too, so a name says nothing, and `settings` counts only as a function.
66
+ * (`uag` too, so a module written for the old shape is refused saying what to
67
+ * change, not read as a plugin.)
68
+ */
69
+ const MODULE_KEYS = ['uag', 'routes', 'components', 'initialize', 'plugins', 'pluginTypes', 'config', 'formRoutes', 'actions', 'hooks', 'providers', 'processors'];
70
+ const isModule = (entry) => !!entry && typeof entry === 'object' && !Array.isArray(entry) && typeof entry.type !== 'string'
71
+ && (MODULE_KEYS.some((key) => key in entry) || typeof entry.settings === 'function');
72
+ /** Use one plugins file, named by its path in error messages. */
73
+ async function usePluginFile(server, file) {
74
+ const exported = (await dynamicImport((0, url_1.pathToFileURL)(file).href))?.default;
75
+ const entries = [].concat(exported ?? []);
76
+ if (entries.some(isModule)) {
77
+ const plugins = entries.filter((entry) => !isModule(entry));
78
+ server.use([...entries.filter(isModule), ...(plugins.length ? [{ plugins }] : [])], file);
79
+ console.log(`[formio-uag] Plugins and modules loaded from ${file}.`);
80
+ return;
81
+ }
82
+ const plugins = await (0, appserver_1.loadPluginFile)(file);
83
+ server.use({ plugins }, file);
84
+ console.log(`[formio-uag] Plugins loaded from ${file}.`);
85
+ }
86
+ /**
87
+ * Load the plugins files, if any, and use each in turn, named by its path in
88
+ * error messages. A file's default export is a plugin or a list of plugins;
89
+ * or a module (an extra, with its plugins), or a list mixing modules and
90
+ * plugins, each module used in turn. Several files merge as several modules
91
+ * do: a module used again by name is used once, and two files that register
92
+ * the same plugin, plugin type, route or component stop startup, naming both.
93
+ * Plugins are trusted code: they run in the UAG process. Returns the files
94
+ * loaded.
95
+ */
96
+ async function usePluginFiles(server, env = process.env, defaultDir = exports.DEFAULT_PLUGINS_DIR) {
97
+ const files = pluginFiles(env, defaultDir);
98
+ for (const file of files)
99
+ await usePluginFile(server, file);
100
+ return files;
101
+ }
@@ -0,0 +1,17 @@
1
+ import type { PluginLog, PluginRegistry, PluginType } from '@formio/appserver';
2
+ import type { UAGProjectInterface } from './UAGProjectInterface';
3
+ /**
4
+ * Engines and MCP tools, listed in the plugin registry (phase 1). The UAG keeps
5
+ * loading them its own way (UAG_ENGINES, config.tools); the registry only lists
6
+ * them, so they appear in GET {project}/plugins. Settings values are never listed.
7
+ */
8
+ export declare const enginePluginType: PluginType;
9
+ export declare const toolPluginType: PluginType;
10
+ /** List each loaded engine type: its instances, kinds, what agent instances read, and whether they generate. */
11
+ export declare function listEngines(registry: PluginRegistry, project: UAGProjectInterface): void;
12
+ /**
13
+ * List every MCP tool the project serves (built-in, then config.tools). A name
14
+ * served twice is listed once, with a warning: listing never refuses what the
15
+ * tool loader allows.
16
+ */
17
+ export declare function listTools(registry: PluginRegistry, project: UAGProjectInterface, log?: PluginLog): Promise<void>;