@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,19 @@
1
+ import type { PluginRegistry } from '@formio/appserver';
2
+ import type { FormflowFileReader } from '../formflow';
3
+ /** Read a file as text through the converters: its detected type, its text, and its page count when known. */
4
+ export declare function readAsText(registry: PluginRegistry, buffer: Buffer, meta: {
5
+ name: string;
6
+ type?: string;
7
+ }, options?: {
8
+ signal?: AbortSignal;
9
+ }): Promise<{
10
+ type: string;
11
+ text: string;
12
+ pages?: number;
13
+ }>;
14
+ /**
15
+ * A reader for task file inputs, built from the converters: it accepts a file
16
+ * whose type has a route to text, and reads it through that route. Task file
17
+ * inputs therefore read every type the installed converters handle.
18
+ */
19
+ export declare function converterFileReader(registry: PluginRegistry): FormflowFileReader;
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.readAsText = readAsText;
4
+ exports.converterFileReader = converterFileReader;
5
+ const detect_1 = require("./detect");
6
+ const convert_1 = require("./convert");
7
+ const mime_1 = require("./mime");
8
+ const plugins_1 = require("./plugins");
9
+ /** What a ZIP file is, from its extension: the first bytes cannot tell a Word file from any other ZIP. */
10
+ const ZIP_BY_EXTENSION = {
11
+ '.docx': mime_1.MIME.docx, '.xlsx': mime_1.MIME.xlsx, '.pptx': mime_1.MIME.pptx,
12
+ '.odt': 'application/vnd.oasis.opendocument.text', '.ods': 'application/vnd.oasis.opendocument.spreadsheet', '.odp': 'application/vnd.oasis.opendocument.presentation'
13
+ };
14
+ const log = {
15
+ info: () => undefined,
16
+ warn: (msg) => console.warn(`[formio-uag] ${msg}`),
17
+ error: (msg) => console.error(`[formio-uag] ${msg}`)
18
+ };
19
+ /** Read a file as text through the converters: its detected type, its text, and its page count when known. */
20
+ async function readAsText(registry, buffer, meta, options = {}) {
21
+ const converters = registry.list('converter');
22
+ const type = (0, detect_1.identify)(buffer, { name: meta.name, type: meta.type }, converters);
23
+ const steps = (0, plugins_1.documentsOf)(registry).routes.toText(type);
24
+ if (!steps) {
25
+ throw new Error(`"${meta.name}" (${type}) cannot be read as text: no installed converter reads it.`);
26
+ }
27
+ const output = await (0, convert_1.runRoute)(steps, { buffer, type, name: meta.name }, 'text', {
28
+ log,
29
+ signal: options.signal,
30
+ settingsOf: (converter) => registry.settings('converter', converter.name)
31
+ });
32
+ // No text, with the converters saying why (an image, a scan): an error, never an empty document.
33
+ if (!String(output.text).trim() && Array.isArray(output.warnings) && output.warnings.length) {
34
+ throw new convert_1.ConversionError(`"${meta.name}" has no text to read: ${output.warnings.join(' ')}`);
35
+ }
36
+ return { type, text: output.text, ...(Array.isArray(output.pages) && { pages: output.pages.length }) };
37
+ }
38
+ /**
39
+ * A reader for task file inputs, built from the converters: it accepts a file
40
+ * whose type has a route to text, and reads it through that route. Task file
41
+ * inputs therefore read every type the installed converters handle.
42
+ */
43
+ function converterFileReader(registry) {
44
+ const converters = () => registry.list('converter');
45
+ return {
46
+ name: 'converters',
47
+ accepts(meta, head) {
48
+ let type;
49
+ try {
50
+ type = (0, detect_1.identify)(head, { name: meta.name, type: meta.type }, converters());
51
+ }
52
+ catch {
53
+ // Too few bytes to be sure, or a name that contradicts them: read() decides on the whole file.
54
+ return true;
55
+ }
56
+ if (type === mime_1.MIME.zip)
57
+ type = ZIP_BY_EXTENSION[(0, mime_1.extensionOf)(meta.name)] || type;
58
+ return !!(0, plugins_1.documentsOf)(registry).routes.toText(type);
59
+ },
60
+ async read(buffer, meta, options) {
61
+ return { text: (await readAsText(registry, buffer, meta, { signal: options?.signal })).text };
62
+ }
63
+ };
64
+ }
@@ -0,0 +1,52 @@
1
+ import type { Converter, Representation } from './types';
2
+ /** How one file reaches the representations of one of a plugin's input sets. */
3
+ export type FileRoute = {
4
+ inputs: Representation[];
5
+ /** The converters that make the PDF ([] for a PDF: identity). */
6
+ pdf?: Converter[];
7
+ /** The converters that make the text; with a PDF in the set, the PDF's own steps then PDF to text. */
8
+ text?: Converter[];
9
+ };
10
+ /** Converter pins by source type: a component's `uag.converters`. */
11
+ export type Pins = Record<string, string>;
12
+ /** A source type a document plugin can take, as its manifest's `accept` lists it. */
13
+ export type AcceptEntry = {
14
+ type: string;
15
+ ext: string[];
16
+ /** The input set it reaches, and the converter names of each representation's route. */
17
+ routes: {
18
+ inputs: Representation[];
19
+ pdf?: string[];
20
+ text?: string[];
21
+ };
22
+ /** The converters that read this type: a pin chooses among them when there are several. */
23
+ converters: string[];
24
+ };
25
+ /** A converter as `name@version`, as results record routes. */
26
+ export declare const stepName: (converter: Converter) => string;
27
+ /**
28
+ * Routes from source types to representations over a set of converters.
29
+ * When several converters can do a step, the component's pin wins, then the
30
+ * operator's `documents.prefer`, then the highest rank, then the name.
31
+ */
32
+ export declare class ConverterRoutes {
33
+ readonly converters: Converter[];
34
+ private readonly prefer;
35
+ constructor(converters: Converter[], prefer?: Record<string, string>);
36
+ /** The converter for one step from `source` to `target`, or undefined when none can do it. */
37
+ choose(source: string, target: Representation, pins?: Pins): Converter | undefined;
38
+ /** The steps from `source` to a PDF: [] for a PDF, one converter otherwise, or null. */
39
+ toPdf(source: string, pins?: Pins): Converter[] | null;
40
+ /** The steps from `source` to text: one converter, or to a PDF and then its text; null when none. */
41
+ toText(source: string, pins?: Pins): Converter[] | null;
42
+ /**
43
+ * The first of a plugin's input sets every representation of which the
44
+ * file can reach. With both PDF and text, the text is always that same
45
+ * PDF's text, so content made from the PDF is checked against it.
46
+ */
47
+ route(source: string, inputs: Representation[][], pins?: Pins): FileRoute | null;
48
+ /** Every source type a converter reads, and PDF. */
49
+ sourceTypes(): string[];
50
+ /** The source types a plugin with these inputs can take, with their routes: its manifest's `accept`. */
51
+ accept(inputs: Representation[][]): AcceptEntry[];
52
+ }
@@ -0,0 +1,99 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ConverterRoutes = exports.stepName = void 0;
4
+ const mime_1 = require("./mime");
5
+ const detect_1 = require("./detect");
6
+ /** A converter as `name@version`, as results record routes. */
7
+ const stepName = (converter) => `${converter.name}@${converter.version}`;
8
+ exports.stepName = stepName;
9
+ /**
10
+ * Routes from source types to representations over a set of converters.
11
+ * When several converters can do a step, the component's pin wins, then the
12
+ * operator's `documents.prefer`, then the highest rank, then the name.
13
+ */
14
+ class ConverterRoutes {
15
+ constructor(converters, prefer = {}) {
16
+ this.converters = converters;
17
+ this.prefer = prefer;
18
+ }
19
+ /** The converter for one step from `source` to `target`, or undefined when none can do it. */
20
+ choose(source, target, pins = {}) {
21
+ const candidates = this.converters.filter((converter) => converter.produces.includes(target) && converter.accepts.some((accept) => accept.type === source));
22
+ if (!candidates.length)
23
+ return undefined;
24
+ for (const preferred of [pins[source], this.prefer[source]]) {
25
+ const found = preferred && candidates.find((converter) => converter.name === preferred);
26
+ if (found)
27
+ return found;
28
+ }
29
+ return [...candidates].sort((a, b) => ((b.rank || 0) - (a.rank || 0)) || a.name.localeCompare(b.name))[0];
30
+ }
31
+ /** The steps from `source` to a PDF: [] for a PDF, one converter otherwise, or null. */
32
+ toPdf(source, pins = {}) {
33
+ if (source === mime_1.MIME.pdf)
34
+ return [];
35
+ const converter = this.choose(source, 'pdf', pins);
36
+ return converter ? [converter] : null;
37
+ }
38
+ /** The steps from `source` to text: one converter, or to a PDF and then its text; null when none. */
39
+ toText(source, pins = {}) {
40
+ const direct = this.choose(source, 'text', pins);
41
+ if (direct)
42
+ return [direct];
43
+ const pdf = this.toPdf(source, pins);
44
+ const fromPdf = pdf && this.choose(mime_1.MIME.pdf, 'text', pins);
45
+ return pdf && fromPdf ? [...pdf, fromPdf] : null;
46
+ }
47
+ /**
48
+ * The first of a plugin's input sets every representation of which the
49
+ * file can reach. With both PDF and text, the text is always that same
50
+ * PDF's text, so content made from the PDF is checked against it.
51
+ */
52
+ route(source, inputs, pins = {}) {
53
+ for (const set of inputs) {
54
+ if (set.includes('pdf')) {
55
+ const pdf = this.toPdf(source, pins);
56
+ if (!pdf)
57
+ continue;
58
+ if (!set.includes('text'))
59
+ return { inputs: set, pdf };
60
+ const fromPdf = this.choose(mime_1.MIME.pdf, 'text', pins);
61
+ if (!fromPdf)
62
+ continue;
63
+ return { inputs: set, pdf, text: [...pdf, fromPdf] };
64
+ }
65
+ if (set.includes('text')) {
66
+ const text = this.toText(source, pins);
67
+ if (text)
68
+ return { inputs: set, text };
69
+ }
70
+ }
71
+ return null;
72
+ }
73
+ /** Every source type a converter reads, and PDF. */
74
+ sourceTypes() {
75
+ return [...new Set([mime_1.MIME.pdf, ...mime_1.TEXT_TYPES.filter((type) => this.converters.some((c) => c.accepts.some((a) => a.type === type))),
76
+ ...this.converters.flatMap((converter) => converter.accepts.map((accept) => accept.type))])];
77
+ }
78
+ /** The source types a plugin with these inputs can take, with their routes: its manifest's `accept`. */
79
+ accept(inputs) {
80
+ const entries = [];
81
+ for (const type of this.sourceTypes()) {
82
+ const route = this.route(type, inputs);
83
+ if (!route)
84
+ continue;
85
+ entries.push({
86
+ type,
87
+ ext: (0, detect_1.extensionsOf)(type, this.converters),
88
+ routes: {
89
+ inputs: route.inputs,
90
+ ...(route.pdf && { pdf: route.pdf.map((c) => c.name) }),
91
+ ...(route.text && { text: route.text.map((c) => c.name) })
92
+ },
93
+ converters: this.converters.filter((c) => c.accepts.some((a) => a.type === type)).map((c) => c.name)
94
+ });
95
+ }
96
+ return entries;
97
+ }
98
+ }
99
+ exports.ConverterRoutes = ConverterRoutes;
@@ -0,0 +1,126 @@
1
+ import type { Plugin, PluginContext } from '@formio/appserver';
2
+ import type { GenerateRequest, GenerateResponse } from '@formio/uag-sdk';
3
+ export type Representation = 'pdf' | 'text';
4
+ export type TextFormat = 'plain' | 'markdown' | 'csv';
5
+ /** Plugin type `converter` (apiVersion 1, experimental): a file into a PDF or text. */
6
+ export interface Converter extends Plugin {
7
+ type: 'converter';
8
+ /** MIME types (canonical) and extensions it reads. */
9
+ accepts: Array<{
10
+ type: string;
11
+ ext?: string[];
12
+ }>;
13
+ produces: Representation[];
14
+ /** Default 0; the higher wins when several converters can do a step. */
15
+ rank?: number;
16
+ /** A canonical type for formats the shared detection does not know, or null. */
17
+ detect?(head: Buffer, hint: {
18
+ name: string;
19
+ type: string;
20
+ }): string | null;
21
+ /** Defaults: 25 MB, 120000 ms, 2 at a time. */
22
+ limits?: {
23
+ maxBytes?: number;
24
+ timeoutMs?: number;
25
+ concurrency?: number;
26
+ };
27
+ convert(input: {
28
+ buffer: Buffer;
29
+ type: string;
30
+ name: string;
31
+ }, options: {
32
+ to: Representation;
33
+ settings: Record<string, any>;
34
+ signal: AbortSignal;
35
+ tmpdir: string;
36
+ log: PluginContext['log'];
37
+ }): Promise<ConverterOutput>;
38
+ }
39
+ export type ConverterOutput = {
40
+ pdf: Buffer;
41
+ pages?: number;
42
+ warnings?: string[];
43
+ } | {
44
+ text: string;
45
+ textFormat: TextFormat;
46
+ pages?: Array<{
47
+ page: number;
48
+ text: string;
49
+ }>;
50
+ warnings?: string[];
51
+ };
52
+ export type DocumentFile = {
53
+ name: string;
54
+ type: string;
55
+ size: number;
56
+ sha256: string;
57
+ document: {
58
+ pdf?: Buffer;
59
+ pages?: number;
60
+ text?: string;
61
+ textFormat?: string;
62
+ textPages?: Array<{
63
+ page: number;
64
+ text: string;
65
+ }>;
66
+ };
67
+ /** The converters it went through, as name@version. */
68
+ route: string[];
69
+ original(): Promise<Buffer>;
70
+ };
71
+ export type DocumentContext = {
72
+ files: DocumentFile[];
73
+ /** Markdown. */
74
+ instructions: string;
75
+ /** This component's plugin settings, defaults applied. */
76
+ settings: Record<string, any>;
77
+ /** Operator settings. */
78
+ config: Record<string, any>;
79
+ form: {
80
+ id: string;
81
+ path: string;
82
+ title: string;
83
+ };
84
+ component: {
85
+ key: string;
86
+ path: string;
87
+ label: string;
88
+ };
89
+ /** Submission data sent with the request: context only, untrusted. */
90
+ data: Record<string, any>;
91
+ agent: {
92
+ generate(request: Omit<GenerateRequest, 'signal'>): Promise<GenerateResponse>;
93
+ };
94
+ progress(fraction: number, message?: string): void;
95
+ signal: AbortSignal;
96
+ log: PluginContext['log'];
97
+ };
98
+ export type Issue = {
99
+ kind: string;
100
+ message: string;
101
+ output?: string;
102
+ document?: string;
103
+ page?: number;
104
+ file?: string;
105
+ };
106
+ export type DocumentResult = {
107
+ content: string;
108
+ status: 'ok' | 'flagged';
109
+ issues?: Issue[];
110
+ summary?: string;
111
+ };
112
+ /** Plugin type `document` (apiVersion 1, experimental): files and instructions into content. */
113
+ export interface DocumentPlugin extends Plugin {
114
+ type: 'document';
115
+ /** Acceptable representation sets, in order of preference. */
116
+ inputs: Representation[][];
117
+ /** JSON only; keys are relative to the plugin's settings object. */
118
+ editForm?: any[];
119
+ defaults?: Record<string, any>;
120
+ /** May people edit the content? Default false. */
121
+ editable?: boolean;
122
+ /** Default 'markdown'. */
123
+ output?: 'markdown' | 'text';
124
+ validateSettings?(settings: Record<string, any>): string[];
125
+ process(ctx: DocumentContext): Promise<DocumentResult>;
126
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,17 @@
1
+ import { Router } from 'express';
2
+ import type { PluginRegistry, ServedComponent } from '@formio/appserver';
3
+ /**
4
+ * `@formio/uag-js` from the UAG's own installation, at stable URLs (never a
5
+ * version in the path: the portal remembers its one-time prompt by URL):
6
+ *
7
+ * - `/uag-js/uag.use.js`: the bundle, registering itself with this UAG's URL and the manifest;
8
+ * - `/uag-js/portal.js`: the same in portal mode (the component only), then `?chain=` (https only);
9
+ * - `/uag-js/portal.css`: the stylesheet, after `@import url(<chain>)` when `?chain=` is given;
10
+ * - `/uag-js/uag-js.css`: the stylesheet.
11
+ *
12
+ * The modules' components come after: each script after the registration in
13
+ * `uag.use.js` and `portal.js`, each stylesheet after `uag-js.css` (and in
14
+ * `portal.css`), and each on its own at `/uag-js/components/<type>.js|css`.
15
+ * They are read on every request, as the bundle is, so the ETag follows them.
16
+ */
17
+ export declare function uagJsRouter(registry: () => PluginRegistry | undefined, components?: () => ServedComponent[]): Router;
@@ -0,0 +1,157 @@
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.uagJsRouter = uagJsRouter;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const vm_1 = __importDefault(require("vm"));
9
+ const path_1 = __importDefault(require("path"));
10
+ const crypto_1 = require("crypto");
11
+ const express_1 = require("express");
12
+ /** Where @formio/uag-js is installed: as a package, or the UAG's own workspace copy. */
13
+ function uagJsDirectory() {
14
+ try {
15
+ return path_1.default.dirname(require.resolve('@formio/uag-js/package.json'));
16
+ }
17
+ catch {
18
+ const local = path_1.default.resolve(__dirname, '..', '..', 'packages', 'uag-js');
19
+ return fs_1.default.existsSync(path_1.default.join(local, 'index.js')) ? local : undefined;
20
+ }
21
+ }
22
+ const read = (file) => fs_1.default.readFileSync(path_1.default.join(uagJsDirectory() || '', file), 'utf8');
23
+ /** A chained script or stylesheet URL from `?chain=`, when it is an absolute URL of the allowed schemes. */
24
+ function chainOf(req, schemes) {
25
+ const chain = typeof req.query.chain === 'string' ? req.query.chain : '';
26
+ if (!chain)
27
+ return undefined;
28
+ try {
29
+ const url = new URL(chain);
30
+ return schemes.includes(url.protocol) ? url.href : undefined;
31
+ }
32
+ catch {
33
+ return undefined;
34
+ }
35
+ }
36
+ /**
37
+ * The script that registers the bundle once formio.js is on the page: with
38
+ * this UAG's URL (from the script's own src) and the manifest, and in portal
39
+ * mode for the portal script, then loads the chained script over https only.
40
+ */
41
+ function registration(manifest, options) {
42
+ return `
43
+ ;(function () {
44
+ var manifest = ${JSON.stringify(manifest).replace(/</g, '\\u003c')};
45
+ var script = document.currentScript;
46
+ var src = script && script.src ? new URL(script.src, location.href) : null;
47
+ var uagUrl = src ? src.href.slice(0, src.href.indexOf('/uag-js/')) : location.origin;
48
+ function register() {
49
+ var Formio = window.Formio;
50
+ var lib = window.FormioUAG;
51
+ if (!Formio || !lib || typeof Formio.use !== 'function') return false;
52
+ Formio.use(lib.UAG(uagUrl, { ${options.portal ? 'portal: true, ' : ''}manifest: manifest }));
53
+ return true;
54
+ }
55
+ if (!register()) {
56
+ var tries = 0;
57
+ var timer = setInterval(function () { if (register() || ++tries > 600) clearInterval(timer); }, 50);
58
+ }
59
+ ${options.chain ? `var chain = document.createElement('script');
60
+ chain.src = ${JSON.stringify(options.chain)};
61
+ document.head.appendChild(chain);` : ''}
62
+ })();
63
+ `;
64
+ }
65
+ /** Whether each component script parses as a classic browser script, by its content (checked once per content). */
66
+ const parsed = new Map();
67
+ function parsesAsScript(file, source) {
68
+ const hash = (0, crypto_1.createHash)('sha256').update(source).digest('base64url');
69
+ const known = parsed.get(file);
70
+ if (known?.hash === hash)
71
+ return known.ok;
72
+ let ok = true;
73
+ try {
74
+ // Compiled only, never run. Wrapped as the bundle wraps it, so the check matches what the browser parses.
75
+ new vm_1.default.Script(`try {\n${source}\n} catch (err) {}`, { filename: file });
76
+ }
77
+ catch {
78
+ ok = false;
79
+ console.warn(`[formio-uag] The component script ${file} is not a browser script (an ES module, or a syntax error); it is left out of uag.use.js and portal.js.`);
80
+ }
81
+ parsed.set(file, { hash, ok });
82
+ return ok;
83
+ }
84
+ /** Serve one file: public, revalidated every time (no-cache), with an ETag from its content and the manifest. */
85
+ function serve(req, res, type, body) {
86
+ const etag = `"${(0, crypto_1.createHash)('sha256').update(body).digest('base64url')}"`;
87
+ res.set({ 'Content-Type': `${type}; charset=utf-8`, 'Cache-Control': 'no-cache', ETag: etag, 'Access-Control-Allow-Origin': '*' });
88
+ if (req.headers['if-none-match'] === etag)
89
+ return res.status(304).end();
90
+ res.status(200).send(body);
91
+ }
92
+ /**
93
+ * `@formio/uag-js` from the UAG's own installation, at stable URLs (never a
94
+ * version in the path: the portal remembers its one-time prompt by URL):
95
+ *
96
+ * - `/uag-js/uag.use.js`: the bundle, registering itself with this UAG's URL and the manifest;
97
+ * - `/uag-js/portal.js`: the same in portal mode (the component only), then `?chain=` (https only);
98
+ * - `/uag-js/portal.css`: the stylesheet, after `@import url(<chain>)` when `?chain=` is given;
99
+ * - `/uag-js/uag-js.css`: the stylesheet.
100
+ *
101
+ * The modules' components come after: each script after the registration in
102
+ * `uag.use.js` and `portal.js`, each stylesheet after `uag-js.css` (and in
103
+ * `portal.css`), and each on its own at `/uag-js/components/<type>.js|css`.
104
+ * They are read on every request, as the bundle is, so the ETag follows them.
105
+ */
106
+ function uagJsRouter(registry, components = () => []) {
107
+ const router = (0, express_1.Router)();
108
+ const manifest = () => {
109
+ const plugins = registry();
110
+ // Every plugin the modules' components may read (document, converter, module, and types
111
+ // modules define): not the UAG's own engines, tools and actions, which /plugins lists.
112
+ return plugins ? plugins.manifest().filter((plugin) => !['engine', 'tool', 'action'].includes(plugin.type)) : [];
113
+ };
114
+ // Each component's script in its own try/catch, so one that fails when it runs does not stop the others.
115
+ // A script that does not parse would break the whole bundle, which try/catch cannot stop: it is left out.
116
+ const scripts = () => components().map((component) => {
117
+ const label = `The ${component.type} component of the ${component.module} module`;
118
+ const source = fs_1.default.readFileSync(component.scriptPath, 'utf8');
119
+ if (!parsesAsScript(component.scriptPath, source)) {
120
+ return `\n;/* ${component.module}: ${component.type} */\nconsole.error(${JSON.stringify(`[uag-js] ${label} is not a browser script (an ES module, or a syntax error), so it was left out.`)});\n`;
121
+ }
122
+ return `\n;/* ${component.module}: ${component.type} */\ntry {\n${source}\n} catch (err) { console.error(${JSON.stringify(`[uag-js] ${label} could not load:`)}, err); }\n`;
123
+ }).join('');
124
+ const styles = () => components().filter((component) => component.stylePath).map((component) => `\n/* ${component.module}: ${component.type} */\n${fs_1.default.readFileSync(component.stylePath, 'utf8')}`).join('');
125
+ const component = (type) => components().find((candidate) => candidate.type === type);
126
+ router.get('/uag-js/components/:file', (req, res) => {
127
+ const match = /^([a-z][a-z0-9]*)\.(js|css)$/.exec(String(req.params.file));
128
+ const found = match ? component(match[1]) : undefined;
129
+ const file = found && (match[2] === 'js' ? found.scriptPath : found.stylePath);
130
+ if (!file)
131
+ return res.status(404).type('text/plain').send('No module of this UAG has that component.');
132
+ serve(req, res, match[2] === 'js' ? 'application/javascript' : 'text/css', fs_1.default.readFileSync(file, 'utf8'));
133
+ });
134
+ const missing = (res) => res.status(404).type('text/plain').send('@formio/uag-js is not installed with this UAG.');
135
+ router.get('/uag-js/uag.use.js', (req, res) => {
136
+ if (!uagJsDirectory())
137
+ return missing(res);
138
+ serve(req, res, 'application/javascript', read('index.js') + registration(manifest(), { portal: false }) + scripts());
139
+ });
140
+ router.get('/uag-js/portal.js', (req, res) => {
141
+ if (!uagJsDirectory())
142
+ return missing(res);
143
+ serve(req, res, 'application/javascript', read('index.js') + registration(manifest(), { portal: true, chain: chainOf(req, ['https:']) }) + scripts());
144
+ });
145
+ router.get('/uag-js/portal.css', (req, res) => {
146
+ if (!uagJsDirectory())
147
+ return missing(res);
148
+ const chain = chainOf(req, ['https:', 'http:']);
149
+ serve(req, res, 'text/css', (chain ? `@import url(${JSON.stringify(chain)});\n` : '') + read('uag-js.css') + styles());
150
+ });
151
+ router.get('/uag-js/uag-js.css', (req, res) => {
152
+ if (!uagJsDirectory())
153
+ return missing(res);
154
+ serve(req, res, 'text/css', read('uag-js.css') + styles());
155
+ });
156
+ return router;
157
+ }
package/lib/formflow.d.ts CHANGED
@@ -1,11 +1,15 @@
1
- import { Formflow } from '@formio/appserver-core';
1
+ import { Formflow } from '@formio/appserver';
2
2
  /**
3
- * Agentic Formflow from @formio/appserver-core, imported directly rather than
4
- * through @formio/appserver, under the names the UAG uses. Internal to the UAG:
3
+ * Agentic Formflow from @formio/appserver-core, through @formio/appserver (see
4
+ * assertSingleCore), under the names the UAG uses. Internal to the UAG:
5
5
  * applications reach Formflow as `Formflow` from this package's exports.
6
6
  */
7
- export declare const EngineConfigError: typeof Formflow.EngineConfigError, EngineRegistry: typeof Formflow.EngineRegistry, FormflowError: typeof Formflow.FormflowError, FormflowService: typeof Formflow.FormflowService, RateLimitError: typeof Formflow.RateLimitError, ScopedTokenError: typeof Formflow.ScopedTokenError, bearerOf: typeof Formflow.bearerOf, engineRegistry: typeof Formflow.engineRegistry, formflow: typeof Formflow.formflow, formflowAuthenticate: typeof Formflow.formflowAuthenticate, identityOf: typeof Formflow.identityOf, issueScopedToken: typeof Formflow.issueScopedToken, isScopedToken: typeof Formflow.isScopedToken, readScopedToken: typeof Formflow.readScopedToken, resolveTaskName: typeof Formflow.resolveTaskName, resultEvent: typeof Formflow.resultEvent, warnLegacy: typeof Formflow.warnLegacy, withoutOwnedValues: typeof Formflow.withoutOwnedValues, stubAgentEngine: typeof Formflow.stubAgentEngine, stubDecisionEngine: typeof Formflow.stubDecisionEngine, toolsForScope: typeof Formflow.toolsForScope, EngineError: typeof Formflow.EngineError, formatReport: typeof Formflow.formatReport;
7
+ export declare const EngineConfigError: typeof Formflow.EngineConfigError, EngineRegistry: typeof Formflow.EngineRegistry, FormflowError: typeof Formflow.FormflowError, FormflowService: typeof Formflow.FormflowService, RateLimitError: typeof Formflow.RateLimitError, ScopedTokenError: typeof Formflow.ScopedTokenError, bearerOf: typeof Formflow.bearerOf, engineRegistry: typeof Formflow.engineRegistry, formflow: typeof Formflow.formflow, formflowAuthenticate: typeof Formflow.formflowAuthenticate, identityOf: typeof Formflow.identityOf, issueScopedToken: typeof Formflow.issueScopedToken, isScopedToken: typeof Formflow.isScopedToken, readScopedToken: typeof Formflow.readScopedToken, resultEvent: typeof Formflow.resultEvent, withoutOwnedValues: typeof Formflow.withoutOwnedValues, callerKey: typeof Formflow.callerKey, stubAgentEngine: typeof Formflow.stubAgentEngine, stubDecisionEngine: typeof Formflow.stubDecisionEngine, toolsForScope: typeof Formflow.toolsForScope, EngineError: typeof Formflow.EngineError, formatReport: typeof Formflow.formatReport, flagOn: typeof Formflow.flagOn, flagOff: typeof Formflow.flagOff, ruleWithConstraints: typeof Formflow.ruleWithConstraints, describeBinary: typeof Formflow.describeBinary, isBinaryComponent: typeof Formflow.isBinaryComponent, fileMeta: typeof Formflow.fileMeta, plainTextReader: Formflow.FormflowFileReader, useDatabase: typeof Formflow.useDatabase, startEngines: typeof Formflow.startEngines, CHAT_INSTRUCTIONS: string, CHAT_REPLY: string, CHAT_CONFIRMED: string, CHAT_CHECK_TOOL: Formflow.ToolDefinition, LEASE_MS: number, RENEW_MS: number, MAX_ATTEMPTS: number;
8
8
  export type AgentEngine = Formflow.AgentEngine;
9
+ export type TaskRunStatus = Formflow.TaskRunStatus;
10
+ export type CoreTaskRunRecord<Id> = Formflow.TaskRunRecord<Id>;
11
+ export type CoreNewTaskRun<Id> = Formflow.NewTaskRun<Id>;
12
+ export type CoreTaskRunStore<Id> = Formflow.TaskRunStore<Id>;
9
13
  export type CallerIdentity = Formflow.CallerIdentity;
10
14
  export type DecisionEngine = Formflow.DecisionEngine;
11
15
  export type DecisionRequest = Formflow.DecisionRequest;
@@ -25,6 +29,9 @@ export type FormflowRoutingConfig = Formflow.FormflowRoutingConfig;
25
29
  export type FormflowService = Formflow.FormflowService;
26
30
  export type FormflowTask = Formflow.FormflowTask;
27
31
  export type FormflowTool = Formflow.FormflowTool;
32
+ export type FormflowFileReader = Formflow.FormflowFileReader;
33
+ export type FormflowFileMeta = Formflow.FormflowFileMeta;
34
+ export type FormflowFileContent = Formflow.FormflowFileContent;
28
35
  export type OperationContext = Formflow.OperationContext;
29
36
  export type OperationResult = Formflow.OperationResult;
30
37
  export type ScopedTokenClaims = Formflow.ScopedTokenClaims;
package/lib/formflow.js CHANGED
@@ -1,14 +1,14 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.formatReport = exports.EngineError = exports.toolsForScope = exports.stubDecisionEngine = exports.stubAgentEngine = exports.withoutOwnedValues = exports.warnLegacy = exports.resultEvent = exports.resolveTaskName = exports.readScopedToken = exports.isScopedToken = exports.issueScopedToken = exports.identityOf = exports.formflowAuthenticate = exports.formflow = exports.engineRegistry = exports.bearerOf = exports.ScopedTokenError = exports.RateLimitError = exports.FormflowService = exports.FormflowError = exports.EngineRegistry = exports.EngineConfigError = void 0;
3
+ exports.MAX_ATTEMPTS = exports.RENEW_MS = exports.LEASE_MS = exports.CHAT_CHECK_TOOL = exports.CHAT_CONFIRMED = exports.CHAT_REPLY = exports.CHAT_INSTRUCTIONS = exports.startEngines = exports.useDatabase = exports.plainTextReader = exports.fileMeta = exports.isBinaryComponent = exports.describeBinary = exports.ruleWithConstraints = exports.flagOff = exports.flagOn = exports.formatReport = exports.EngineError = exports.toolsForScope = exports.stubDecisionEngine = exports.stubAgentEngine = exports.callerKey = exports.withoutOwnedValues = exports.resultEvent = exports.readScopedToken = exports.isScopedToken = exports.issueScopedToken = exports.identityOf = exports.formflowAuthenticate = exports.formflow = exports.engineRegistry = exports.bearerOf = exports.ScopedTokenError = exports.RateLimitError = exports.FormflowService = exports.FormflowError = exports.EngineRegistry = exports.EngineConfigError = void 0;
4
4
  exports.callerIdentity = callerIdentity;
5
- const appserver_core_1 = require("@formio/appserver-core");
5
+ const appserver_1 = require("@formio/appserver");
6
6
  /**
7
- * Agentic Formflow from @formio/appserver-core, imported directly rather than
8
- * through @formio/appserver, under the names the UAG uses. Internal to the UAG:
7
+ * Agentic Formflow from @formio/appserver-core, through @formio/appserver (see
8
+ * assertSingleCore), under the names the UAG uses. Internal to the UAG:
9
9
  * applications reach Formflow as `Formflow` from this package's exports.
10
10
  */
11
- ({ EngineConfigError: exports.EngineConfigError, EngineRegistry: exports.EngineRegistry, FormflowError: exports.FormflowError, FormflowService: exports.FormflowService, RateLimitError: exports.RateLimitError, ScopedTokenError: exports.ScopedTokenError, bearerOf: exports.bearerOf, engineRegistry: exports.engineRegistry, formflow: exports.formflow, formflowAuthenticate: exports.formflowAuthenticate, identityOf: exports.identityOf, issueScopedToken: exports.issueScopedToken, isScopedToken: exports.isScopedToken, readScopedToken: exports.readScopedToken, resolveTaskName: exports.resolveTaskName, resultEvent: exports.resultEvent, warnLegacy: exports.warnLegacy, withoutOwnedValues: exports.withoutOwnedValues, stubAgentEngine: exports.stubAgentEngine, stubDecisionEngine: exports.stubDecisionEngine, toolsForScope: exports.toolsForScope, EngineError: exports.EngineError, formatReport: exports.formatReport } = appserver_core_1.Formflow);
11
+ ({ EngineConfigError: exports.EngineConfigError, EngineRegistry: exports.EngineRegistry, FormflowError: exports.FormflowError, FormflowService: exports.FormflowService, RateLimitError: exports.RateLimitError, ScopedTokenError: exports.ScopedTokenError, bearerOf: exports.bearerOf, engineRegistry: exports.engineRegistry, formflow: exports.formflow, formflowAuthenticate: exports.formflowAuthenticate, identityOf: exports.identityOf, issueScopedToken: exports.issueScopedToken, isScopedToken: exports.isScopedToken, readScopedToken: exports.readScopedToken, resultEvent: exports.resultEvent, withoutOwnedValues: exports.withoutOwnedValues, callerKey: exports.callerKey, stubAgentEngine: exports.stubAgentEngine, stubDecisionEngine: exports.stubDecisionEngine, toolsForScope: exports.toolsForScope, EngineError: exports.EngineError, formatReport: exports.formatReport, flagOn: exports.flagOn, flagOff: exports.flagOff, ruleWithConstraints: exports.ruleWithConstraints, describeBinary: exports.describeBinary, isBinaryComponent: exports.isBinaryComponent, fileMeta: exports.fileMeta, plainTextReader: exports.plainTextReader, useDatabase: exports.useDatabase, startEngines: exports.startEngines, CHAT_INSTRUCTIONS: exports.CHAT_INSTRUCTIONS, CHAT_REPLY: exports.CHAT_REPLY, CHAT_CONFIRMED: exports.CHAT_CONFIRMED, CHAT_CHECK_TOOL: exports.CHAT_CHECK_TOOL, LEASE_MS: exports.LEASE_MS, RENEW_MS: exports.RENEW_MS, MAX_ATTEMPTS: exports.MAX_ATTEMPTS } = appserver_1.Formflow);
12
12
  /**
13
13
  * Who an operation's research acts as: core's `identityOf`, and for a caller
14
14
  * signed in with a Form.io token (`x-jwt-token`), that token, so a scoped token