@uipath/maestro-builder-sdk 6.21.2 → 6.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api-index.md +138 -138
- package/dist/api-members.md +407 -407
- package/dist/check.js +43 -331
- package/dist/core/actions.d.ts +0 -287
- package/dist/core/actions.js +0 -95
- package/dist/core/expr.d.ts +1 -1
- package/dist/core/expr.js +2 -2
- package/dist/core/nodes/node-type.d.ts +15 -0
- package/dist/core/step-ports.js +7 -23
- package/dist/core/typed-raw-node.js +0 -4
- package/dist/decompile.js +44 -115
- package/dist/flow-node-registry.js +3 -1
- package/dist/flow-node-type.d.ts +59 -0
- package/dist/flow-sdk.d.ts +2 -0
- package/dist/flow-sdk.js +2 -0
- package/dist/flow-step-exits.d.ts +12 -0
- package/dist/flow-step-exits.js +30 -0
- package/dist/nodes/ai-patterns/flow/factories.d.ts +83 -0
- package/dist/nodes/ai-patterns/flow/factories.js +86 -0
- package/dist/nodes/ai-patterns/flow/family.d.ts +3 -0
- package/dist/nodes/ai-patterns/flow/family.js +8 -0
- package/dist/nodes/ai-patterns/flow/index.d.ts +3 -0
- package/dist/nodes/ai-patterns/flow/index.js +3 -0
- package/dist/nodes/ai-patterns/flow/node-type.d.ts +11 -0
- package/dist/nodes/ai-patterns/flow/node-type.js +106 -0
- package/dist/nodes/ai-patterns/flow/spec.d.ts +75 -0
- package/dist/nodes/ai-patterns/flow/spec.js +1 -0
- package/dist/nodes/http/flow/check.d.ts +58 -0
- package/dist/nodes/http/flow/check.js +248 -0
- package/dist/nodes/http/flow/factories.d.ts +14 -0
- package/dist/nodes/http/flow/factories.js +20 -0
- package/dist/nodes/http/flow/family.d.ts +8 -0
- package/dist/nodes/http/flow/family.js +13 -0
- package/dist/nodes/http/flow/index.d.ts +3 -0
- package/dist/nodes/http/flow/index.js +3 -0
- package/dist/nodes/http/flow/node-type.d.ts +7 -0
- package/dist/nodes/http/flow/node-type.js +411 -0
- package/dist/nodes/http/flow/spec.d.ts +151 -0
- package/dist/nodes/http/flow/spec.js +1 -0
- package/dist/serialize.d.ts +2 -2
- package/dist/serialize.js +20 -316
- package/package.json +2 -2
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The two AI-pattern node types: `uipath.pattern.deep-rag` (Summarize) and
|
|
3
|
+
* `uipath.pattern.batch-transform`, each bundled at 1.0. Moved from
|
|
4
|
+
* serialize.ts / decompile.ts in Phase 4 of docs/NODE_TYPE_ARCHITECTURE_DESIGN.md;
|
|
5
|
+
* serialized output and printed source unchanged.
|
|
6
|
+
*/
|
|
7
|
+
import { Expr } from '../../../core/expr.js';
|
|
8
|
+
/** A value as the author wrote it: an expression back to an Expr, a literal string as itself, any other JSON as is. */
|
|
9
|
+
function lifted(raw, ctx) {
|
|
10
|
+
const text = Array.isArray(raw) ? undefined : ctx.sourceText(raw);
|
|
11
|
+
if (text === undefined)
|
|
12
|
+
return raw;
|
|
13
|
+
return text.startsWith('=js:') ? ctx.expr(text) : text;
|
|
14
|
+
}
|
|
15
|
+
/** A spec value as source: an Expr as the helper an author writes, `undefined` as itself, anything else as data. */
|
|
16
|
+
function printed(v, ctx) {
|
|
17
|
+
if (v === undefined)
|
|
18
|
+
return 'undefined';
|
|
19
|
+
return v instanceof Expr ? ctx.value(v) : ctx.json(v);
|
|
20
|
+
}
|
|
21
|
+
// Neither node carries a node-level `model`: both are `bpmn:ServiceTask`s whose
|
|
22
|
+
// `serviceType` the platform resolves from `definitions[]`, and every deployed
|
|
23
|
+
// instance of either carries none — v1's own structural checker fails a flow
|
|
24
|
+
// that has one. The resource model stays on the DEFINITION only, as it does for
|
|
25
|
+
// the published families. Per call (`model: 'omit'`), so a `rawNode` of either
|
|
26
|
+
// type keeps what it has always written.
|
|
27
|
+
const summarizeType = {
|
|
28
|
+
kind: 'summarize', typeName: 'uipath.pattern.deep-rag', version: '1.0', factory: 'summarize', definition: 'bundled',
|
|
29
|
+
rawNode: { factory: 'summarize()' },
|
|
30
|
+
// Each field is written under the NODE's own name, so the surface and the
|
|
31
|
+
// emitted file read alike.
|
|
32
|
+
serialize: ({ inputs }, ctx) => ({
|
|
33
|
+
model: 'omit',
|
|
34
|
+
inputs: {
|
|
35
|
+
attachment: ctx.render(inputs.attachment),
|
|
36
|
+
prompt: ctx.render(inputs.prompt),
|
|
37
|
+
returnCitations: inputs.returnCitations ?? false,
|
|
38
|
+
},
|
|
39
|
+
port: 'output',
|
|
40
|
+
}),
|
|
41
|
+
deserialize: (node, ctx) => {
|
|
42
|
+
const i = (node.inputs ?? {});
|
|
43
|
+
return {
|
|
44
|
+
kind: 'summarize',
|
|
45
|
+
inputs: {
|
|
46
|
+
attachment: lifted(i.attachment, ctx),
|
|
47
|
+
prompt: lifted(i.prompt, ctx),
|
|
48
|
+
// The flag defaults to false in the emitted node; only `true` was authored.
|
|
49
|
+
...(ctx.plainValue(i.returnCitations) === true ? { returnCitations: true } : {}),
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
},
|
|
53
|
+
print: ({ inputs }, ctx) => ({
|
|
54
|
+
factory: 'summarize',
|
|
55
|
+
options: [
|
|
56
|
+
['attachment', printed(inputs.attachment, ctx)],
|
|
57
|
+
['prompt', printed(inputs.prompt, ctx)],
|
|
58
|
+
['returnCitations', inputs.returnCitations === true ? 'true' : undefined],
|
|
59
|
+
],
|
|
60
|
+
}),
|
|
61
|
+
// Deliberately none: references in the prompt, the attachment and the columns are not checked.
|
|
62
|
+
expressions: () => [],
|
|
63
|
+
output: () => ({ kind: 'opaque' }),
|
|
64
|
+
};
|
|
65
|
+
const batchTransformType = {
|
|
66
|
+
kind: 'batchTransform', typeName: 'uipath.pattern.batch-transform', version: '1.0', factory: 'batchTransform',
|
|
67
|
+
definition: 'bundled',
|
|
68
|
+
rawNode: { factory: 'batchTransform()' },
|
|
69
|
+
serialize: ({ inputs }, ctx) => ({
|
|
70
|
+
model: 'omit',
|
|
71
|
+
inputs: {
|
|
72
|
+
attachment: ctx.render(inputs.attachment),
|
|
73
|
+
prompt: ctx.render(inputs.prompt),
|
|
74
|
+
enableWebSearchGrounding: inputs.enableWebSearchGrounding ?? false,
|
|
75
|
+
// Rendered per column, so an expression in a description renders like every other input.
|
|
76
|
+
outputColumns: inputs.outputColumns.map((c) => Object.fromEntries(Object.entries(c).map(([k, v]) => [k, ctx.render(v)]))),
|
|
77
|
+
},
|
|
78
|
+
port: 'output',
|
|
79
|
+
}),
|
|
80
|
+
deserialize: (node, ctx) => {
|
|
81
|
+
const i = (node.inputs ?? {});
|
|
82
|
+
const cols = Array.isArray(i.outputColumns) ? i.outputColumns : [];
|
|
83
|
+
return {
|
|
84
|
+
kind: 'batchTransform',
|
|
85
|
+
inputs: {
|
|
86
|
+
attachment: lifted(i.attachment, ctx),
|
|
87
|
+
prompt: lifted(i.prompt, ctx),
|
|
88
|
+
outputColumns: cols.map((c) => ({ name: lifted(c.name, ctx), description: lifted(c.description, ctx) })),
|
|
89
|
+
...(ctx.plainValue(i.enableWebSearchGrounding) === true ? { enableWebSearchGrounding: true } : {}),
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
},
|
|
93
|
+
print: ({ inputs }, ctx) => ({
|
|
94
|
+
factory: 'batchTransform',
|
|
95
|
+
options: [
|
|
96
|
+
['attachment', printed(inputs.attachment, ctx)],
|
|
97
|
+
['prompt', printed(inputs.prompt, ctx)],
|
|
98
|
+
['outputColumns', `[${inputs.outputColumns.map((c) => `{ name: ${printed(c.name, ctx)}, description: ${printed(c.description, ctx)} }`).join(', ')}]`],
|
|
99
|
+
['enableWebSearchGrounding', inputs.enableWebSearchGrounding === true ? 'true' : undefined],
|
|
100
|
+
],
|
|
101
|
+
}),
|
|
102
|
+
// Deliberately none, as for Summarize.
|
|
103
|
+
expressions: () => [],
|
|
104
|
+
output: () => ({ kind: 'opaque' }),
|
|
105
|
+
};
|
|
106
|
+
export const aiPatternNodeTypes = [summarizeType, batchTransformType];
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The AI-pattern family's authoring types (`uipath.pattern.deep-rag`,
|
|
3
|
+
* `uipath.pattern.batch-transform`), and their kinds' entries in
|
|
4
|
+
* `FlowActionSpecs`. Moved from core/actions.ts in Phase 4 of
|
|
5
|
+
* docs/NODE_TYPE_ARCHITECTURE_DESIGN.md.
|
|
6
|
+
*/
|
|
7
|
+
import type { Expr } from '../../../core/expr.js';
|
|
8
|
+
export interface SummarizeInputs {
|
|
9
|
+
/**
|
|
10
|
+
* The document to summarize — a whole **Flow Attachment** object
|
|
11
|
+
* (`{ ID, FullName, MimeType, Metadata }`), not a path, id or URL.
|
|
12
|
+
*
|
|
13
|
+
* In practice that is a `file`-typed flow input:
|
|
14
|
+
*
|
|
15
|
+
* ```ts
|
|
16
|
+
* flow('SummarizeDemo')
|
|
17
|
+
* .input({ documentFile: types.file })
|
|
18
|
+
* .step('summarizeContract', summarize({ attachment: input('documentFile'), … }))
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* `out('start', 'documentFile')` is the same value written the way the CANVAS
|
|
22
|
+
* writes it (`$vars.start.output.documentFile`, resolved through the input's
|
|
23
|
+
* `triggerNodeId` binding). Both forms run — measured on the real service —
|
|
24
|
+
* so prefer whichever the task asks for; `out('start', …)` is what a
|
|
25
|
+
* designer-made flow looks like.
|
|
26
|
+
*
|
|
27
|
+
* The service reads the file's CONTENT, and refuses one it cannot treat as a
|
|
28
|
+
* document: a PDF summarizes, a plain `.txt` comes back
|
|
29
|
+
* `[460005] … "No valid files to use for Summarization."`
|
|
30
|
+
*/
|
|
31
|
+
attachment: Expr;
|
|
32
|
+
/** What the summary should cover, e.g. `'Write a 5-bullet executive summary…'`. */
|
|
33
|
+
prompt: string | Expr;
|
|
34
|
+
/**
|
|
35
|
+
* Ask the service for per-claim citations. Each is
|
|
36
|
+
* `{ Ordinal, PageNumber, Source, Reference }` and lands in
|
|
37
|
+
* `out('<step>', 'content.Citations')`. Defaults to `false`.
|
|
38
|
+
*/
|
|
39
|
+
returnCitations?: boolean;
|
|
40
|
+
}
|
|
41
|
+
/** One column Batch Transform appends to the CSV. */
|
|
42
|
+
export interface OutputColumn {
|
|
43
|
+
/** The new column's header, e.g. `'Category'`. */
|
|
44
|
+
name: string;
|
|
45
|
+
/** What the model should put in it, e.g. `'One of: Utility, Software, Travel, Other'`. */
|
|
46
|
+
description: string;
|
|
47
|
+
}
|
|
48
|
+
export interface BatchTransformInputs {
|
|
49
|
+
/**
|
|
50
|
+
* The CSV to enrich — a whole **Flow Attachment** object, same shape and same
|
|
51
|
+
* two ways of referencing it as {@link SummarizeInputs.attachment}.
|
|
52
|
+
*/
|
|
53
|
+
attachment: Expr;
|
|
54
|
+
/** How to process each row, e.g. `'Classify each row and write a one-line summary.'`. */
|
|
55
|
+
prompt: string | Expr;
|
|
56
|
+
/**
|
|
57
|
+
* The columns to add, in order. The node's own field name — an array of
|
|
58
|
+
* `{ name, description }`, one per new column.
|
|
59
|
+
*/
|
|
60
|
+
outputColumns: OutputColumn[];
|
|
61
|
+
/** Let the model use web search while transforming. Defaults to `false`. */
|
|
62
|
+
enableWebSearchGrounding?: boolean;
|
|
63
|
+
}
|
|
64
|
+
declare module '../../../core/actions.js' {
|
|
65
|
+
interface FlowActionSpecs {
|
|
66
|
+
/** The spec {@link summarize} builds. */
|
|
67
|
+
summarize: {
|
|
68
|
+
inputs: SummarizeInputs;
|
|
69
|
+
};
|
|
70
|
+
/** The spec {@link batchTransform} builds. */
|
|
71
|
+
batchTransform: {
|
|
72
|
+
inputs: BatchTransformInputs;
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The HTTP family's rules. Moved from check.ts in Phase 4 of
|
|
3
|
+
* docs/NODE_TYPE_ARCHITECTURE_DESIGN.md; codes and messages unchanged.
|
|
4
|
+
*/
|
|
5
|
+
import type { DiagnosticCatalog } from '../../../core/nodes/family.js';
|
|
6
|
+
import type { OutputPathRead } from '../../../core/nodes/node-type.js';
|
|
7
|
+
import type { FlowCheckContext } from '../../../flow-node-type.js';
|
|
8
|
+
import type { HttpInputs } from './spec.js';
|
|
9
|
+
export declare const HTTP_DIAGNOSTICS: DiagnosticCatalog;
|
|
10
|
+
/**
|
|
11
|
+
* The two http settings every deployed request node carries.
|
|
12
|
+
*
|
|
13
|
+
* Both definitions declare the defaults (`timeout: 'PT15M'`, `retryCount: 0`), so
|
|
14
|
+
* omitting them is safe and is what ~99% of deployed nodes do. What is NOT safe
|
|
15
|
+
* is writing them in a form the platform cannot read: an unparseable duration is
|
|
16
|
+
* not a shorter timeout, it is a value the request node fails to configure with,
|
|
17
|
+
* and nothing downstream of `check` would say so in the author's own vocabulary.
|
|
18
|
+
*/
|
|
19
|
+
export declare function checkHttp(inputs: HttpInputs, ctx: FlowCheckContext): void;
|
|
20
|
+
/**
|
|
21
|
+
* Every node-local rule of an `http()` step, in the order check.ts ran them: the
|
|
22
|
+
* request settings, a managed step's binding labels, then the response branches.
|
|
23
|
+
*/
|
|
24
|
+
export declare function checkHttpStep(inputs: HttpInputs, ctx: FlowCheckContext): void;
|
|
25
|
+
/**
|
|
26
|
+
* A read of an `http()` step's result.
|
|
27
|
+
*
|
|
28
|
+
* The response is an envelope — `{ body, statusCode, headers }` — and `returns`
|
|
29
|
+
* declares the parsed JSON body's top-level fields, so a declared field is read
|
|
30
|
+
* at `output.body.<field>` (`out('<step>', 'body.<field>')`). That nesting is the
|
|
31
|
+
* one thing this family does not share with the process families, and it is
|
|
32
|
+
* exactly where flow-builder-sdk#850's run went wrong: a branch kept reading
|
|
33
|
+
* `output.currentDayForecast…` (the Azure Maps connector's path) after the step
|
|
34
|
+
* became an Open-Meteo `http()`, passed `check` and `validate`, and faulted at
|
|
35
|
+
* `flow debug` as `[400302]`.
|
|
36
|
+
*/
|
|
37
|
+
export declare function checkHttpRead({ node: name, field, rest, reader: step }: OutputPathRead, spec: HttpInputs | undefined, report: (code: string, message: string, suggestion?: string) => void): void;
|
|
38
|
+
/**
|
|
39
|
+
* `.onError` after a plain `core.action.http` node. Two separate facts about a
|
|
40
|
+
* PLAIN http step, both measured, both fatal to a handler there:
|
|
41
|
+
*
|
|
42
|
+
* 1. a 4xx/5xx does not FAIL the node — it completes carrying `statusCode`,
|
|
43
|
+
* so the handler only ever runs on a transport failure (DNS, refused,
|
|
44
|
+
* timeout);
|
|
45
|
+
* 2. when it does run there is nothing to read. `core.action.http@1.0.0` and
|
|
46
|
+
* the `@1.3` that `uip maestro flow migrate` upgrades it to both publish
|
|
47
|
+
* `<step>.error = true` and leave `<step>.output` null — no envelope
|
|
48
|
+
* anywhere, unlike every other family. `validate` still says Valid.
|
|
49
|
+
*
|
|
50
|
+
* So this stays an error, and says the second part too: an author who reads
|
|
51
|
+
* the old message ("use the managed node if statuses must route") could
|
|
52
|
+
* reasonably conclude a transport-failure handler is fine as it is. check.ts
|
|
53
|
+
* reports the same refusal for a custom node built on the plain definition.
|
|
54
|
+
*/
|
|
55
|
+
export declare function plainHttpOnErrorMessage(stepName: string): {
|
|
56
|
+
message: string;
|
|
57
|
+
suggestion: string;
|
|
58
|
+
};
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import { nearestName } from '../../../core/edit-distance.js';
|
|
2
|
+
export const HTTP_DIAGNOSTICS = {
|
|
3
|
+
HTTP_V1_RETIRED: { severity: 'warning', summary: 'the plain node is retired on the tenant registry' },
|
|
4
|
+
HTTP_BINDING_REQUIRES_MANAGED: { severity: 'error', summary: 'connection settings on the standalone node' },
|
|
5
|
+
HTTP_MANAGED_BINDING_INCOMPLETE: { severity: 'error', summary: 'a managed connection without both connection and folder' },
|
|
6
|
+
HTTP_TARGET_CONNECTOR_EMPTY: { severity: 'error', summary: 'an empty targetConnector' },
|
|
7
|
+
HTTP_TARGET_CONNECTOR_REQUIRES_BINDING: { severity: 'error', summary: 'targetConnector without a connection and folder' },
|
|
8
|
+
HTTP_TIMEOUT_NOT_DURATION: { severity: 'error', summary: 'timeout is not an ISO-8601 duration' },
|
|
9
|
+
HTTP_RETRY_COUNT_INVALID: { severity: 'error', summary: 'retryCount is not a non-negative integer' },
|
|
10
|
+
HTTP_RETRY_COUNT_HIGH: { severity: 'warning', summary: 'retryCount above what a local replay exercises' },
|
|
11
|
+
HTTP_BRANCH_NAME: { severity: 'error', summary: 'a branch name that is not port-safe' },
|
|
12
|
+
HTTP_BRANCH_DUP: { severity: 'error', summary: 'a branch declared twice' },
|
|
13
|
+
HTTP_BRANCH_CONST: { severity: 'error', summary: 'a branch condition that is a constant' },
|
|
14
|
+
HTTP_READ_NOT_OUTPUT: { severity: 'error', summary: 'a read of the step outside `output`' },
|
|
15
|
+
HTTP_READ_UNKNOWN_FIELD: { severity: 'error', summary: 'a read outside the response envelope or the declared body' },
|
|
16
|
+
HTTP_READ_WITHOUT_RETURNS: { severity: 'error', summary: 'a body field read with no `returns`' },
|
|
17
|
+
HTTP_ONERROR_V1: { severity: 'error', summary: '.onError() on the plain node' },
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* An ISO-8601 DURATION, which is what both http definitions declare `timeout` to
|
|
21
|
+
* be (`PT15M` by default) and what all 808 deployed http nodes carry.
|
|
22
|
+
*
|
|
23
|
+
* Deliberately the whole grammar rather than the `PT<n>[HMS]` subset the corpus
|
|
24
|
+
* happens to use: a too-narrow fence would refuse `P1D` or `PT1H30M`, which the
|
|
25
|
+
* platform parses perfectly well. What it does refuse is the family of things
|
|
26
|
+
* that are NOT durations at all and would reach the platform as an unparseable
|
|
27
|
+
* string — `'30s'`, `'30'`, `'PT'`, `'15 minutes'`.
|
|
28
|
+
*/
|
|
29
|
+
const ISO8601_DURATION = /^P(?!$)(\d+(?:\.\d+)?Y)?(\d+(?:\.\d+)?M)?(\d+(?:\.\d+)?W)?(\d+(?:\.\d+)?D)?(T(?!$)(\d+(?:\.\d+)?H)?(\d+(?:\.\d+)?M)?(\d+(?:\.\d+)?S)?)?$/;
|
|
30
|
+
/**
|
|
31
|
+
* Where `check` stops treating a retry count as plainly reasonable. The
|
|
32
|
+
* platform's own ceiling is NOT measured, and the deployed corpus's largest
|
|
33
|
+
* author-set value is 3 — so above this we WARN. A too-narrow fence is fatal
|
|
34
|
+
* (it refuses something that works); a too-wide one costs nothing.
|
|
35
|
+
*/
|
|
36
|
+
const RETRY_COUNT_SOFT_MAX = 5;
|
|
37
|
+
/**
|
|
38
|
+
* The two http settings every deployed request node carries.
|
|
39
|
+
*
|
|
40
|
+
* Both definitions declare the defaults (`timeout: 'PT15M'`, `retryCount: 0`), so
|
|
41
|
+
* omitting them is safe and is what ~99% of deployed nodes do. What is NOT safe
|
|
42
|
+
* is writing them in a form the platform cannot read: an unparseable duration is
|
|
43
|
+
* not a shorter timeout, it is a value the request node fails to configure with,
|
|
44
|
+
* and nothing downstream of `check` would say so in the author's own vocabulary.
|
|
45
|
+
*/
|
|
46
|
+
export function checkHttp(inputs, ctx) {
|
|
47
|
+
const step = ctx.step;
|
|
48
|
+
// `core.action.http` is retired on the platform side: after `registry pull
|
|
49
|
+
// --force` the tenant serves only `core.action.http.v2` (2.3), and a
|
|
50
|
+
// `registry get core.action.http` — bare or at the `@1.3` that `flow migrate`
|
|
51
|
+
// upgrades it to — answers "Node not found". It still compiles, validates and
|
|
52
|
+
// runs, which is why this is a warning and not an error: existing flows are
|
|
53
|
+
// not broken and `decompile` must keep round-tripping them.
|
|
54
|
+
//
|
|
55
|
+
// Moving to the managed node is not a rename. A plain step keeps a non-2xx on
|
|
56
|
+
// the SUCCESS path (`out(step,'statusCode')`); the managed one fails on it, so
|
|
57
|
+
// a status branch becomes an error handler reading the envelope's `status`.
|
|
58
|
+
//
|
|
59
|
+
// It is not a free upgrade either, and for a connection-less URL it is usually
|
|
60
|
+
// the wrong move (flow-builder-sdk#898). With no `connection` the managed node
|
|
61
|
+
// still goes through Integration Service: serialize emits the `uipath-uipath-http`
|
|
62
|
+
// connector on `ImplicitConnection`, so the request leaves from IS's SHARED
|
|
63
|
+
// egress. Measured on slack-weather-pipeline (3/3 runs): agents took the old
|
|
64
|
+
// `managed: true` hint, and Open-Meteo answered that egress 429 "Daily API
|
|
65
|
+
// request limit exceeded" while direct calls from the same box got 200. The
|
|
66
|
+
// plain node goes direct. So the hint only steers toward `managed: true` when
|
|
67
|
+
// the step was given connection settings; the two other real reasons to move
|
|
68
|
+
// (a binding, an `.onError()`) already fire as errors with that suggestion
|
|
69
|
+
// (HTTP_BINDING_REQUIRES_MANAGED, HTTP_ONERROR_V1).
|
|
70
|
+
if (inputs && !inputs.managed) {
|
|
71
|
+
const asked = inputs;
|
|
72
|
+
const wantsConnection = asked.connection !== undefined || asked.folder !== undefined
|
|
73
|
+
|| asked.targetConnector !== undefined;
|
|
74
|
+
if (wantsConnection) {
|
|
75
|
+
ctx.report('HTTP_V1_RETIRED', `Step "${step}" emits core.action.http, which the tenant registry no longer serves (only `
|
|
76
|
+
+ `core.action.http.v2). It sets Integration Service connection settings, which only the managed node `
|
|
77
|
+
+ `can use. Moving to managed: true changes behaviour rather than just the node type: a non-2xx stops `
|
|
78
|
+
+ `arriving on the success path with statusCode and fails the step instead, so a status branch becomes `
|
|
79
|
+
+ `an .onError() handler reading err('${step}', 'status').`, `http({ url: …, managed: true, connection, folder })`);
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
ctx.report('HTTP_V1_RETIRED', `Step "${step}" emits core.action.http, which the tenant registry no longer serves (only `
|
|
83
|
+
+ `core.action.http.v2). It still compiles, validates and runs, and it calls the URL directly. For a URL `
|
|
84
|
+
+ `that needs no Integration Service connection, keep managed: false. managed: true is not a drop-in `
|
|
85
|
+
+ `replacement: even without a connection it sends the request through Integration Service's shared `
|
|
86
|
+
+ `egress (rate limits there are shared, and public APIs can answer 429 where a direct call succeeds), `
|
|
87
|
+
+ `and a non-2xx fails the step instead of arriving with statusCode. Switch only if the call needs an `
|
|
88
|
+
+ `Integration Service connection or an .onError() handler.`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
const connection = inputs?.connection;
|
|
92
|
+
const folder = inputs?.folder;
|
|
93
|
+
const targetConnector = inputs?.targetConnector;
|
|
94
|
+
const bindingConfigured = connection !== undefined || folder !== undefined;
|
|
95
|
+
const targetConfigured = targetConnector !== undefined;
|
|
96
|
+
const nonEmpty = (value) => typeof value === 'string' && value.trim().length > 0;
|
|
97
|
+
if (!inputs?.managed && (bindingConfigured || targetConfigured)) {
|
|
98
|
+
ctx.report('HTTP_BINDING_REQUIRES_MANAGED', `Step "${step}" supplies Integration Service connection settings to the standalone HTTP node. `
|
|
99
|
+
+ `Only the managed node can use connection, folder, or targetConnector.`, `managed: true`);
|
|
100
|
+
}
|
|
101
|
+
else if (inputs?.managed && bindingConfigured && (!nonEmpty(connection) || !nonEmpty(folder))) {
|
|
102
|
+
ctx.report('HTTP_MANAGED_BINDING_INCOMPLETE', `Managed HTTP step "${step}" must name both a non-empty \`connection\` and \`folder\`. The platform `
|
|
103
|
+
+ `needs the pair to resolve the Integration Service connection at runtime.`, `connection: 'http', folder: 'shared'`);
|
|
104
|
+
}
|
|
105
|
+
if (inputs?.managed && targetConfigured && !nonEmpty(targetConnector)) {
|
|
106
|
+
ctx.report('HTTP_TARGET_CONNECTOR_EMPTY', `Managed HTTP step "${step}" sets an empty \`targetConnector\`; name its Integration Service connector key.`, `targetConnector: 'uipath-salesforce-slack'`);
|
|
107
|
+
}
|
|
108
|
+
else if (inputs?.managed && targetConfigured && !bindingConfigured) {
|
|
109
|
+
ctx.report('HTTP_TARGET_CONNECTOR_REQUIRES_BINDING', `Managed HTTP step "${step}" sets \`targetConnector\` without a connection and folder. `
|
|
110
|
+
+ `A connector-specific fallback needs the binding pair whose authentication it reuses.`, `connection: 'slack', folder: 'shared'`);
|
|
111
|
+
}
|
|
112
|
+
const timeout = inputs?.timeout;
|
|
113
|
+
if (timeout !== undefined) {
|
|
114
|
+
if (typeof timeout !== 'string' || !ISO8601_DURATION.test(timeout.trim())) {
|
|
115
|
+
ctx.report('HTTP_TIMEOUT_NOT_DURATION', `Step "${step}" sets timeout to ${JSON.stringify(timeout)}, which is not an ISO-8601 duration. The `
|
|
116
|
+
+ `platform reads this field as a duration string (both http definitions default it to "PT15M"), so a `
|
|
117
|
+
+ `value it cannot parse configures no timeout at all.`, `timeout: 'PT30S' // 30 seconds — also 'PT1M', 'PT15M', 'PT1H'`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const retryCount = inputs?.retryCount;
|
|
121
|
+
if (retryCount !== undefined) {
|
|
122
|
+
if (typeof retryCount !== 'number' || !Number.isInteger(retryCount) || retryCount < 0) {
|
|
123
|
+
ctx.report('HTTP_RETRY_COUNT_INVALID', `Step "${step}" sets retryCount to ${JSON.stringify(retryCount)}. It is a count of RETRIES after the `
|
|
124
|
+
+ `first attempt, so it must be a non-negative whole number — 0 (the definitions' default) means "try `
|
|
125
|
+
+ `once", 2 means up to three attempts.`, `retryCount: 2`);
|
|
126
|
+
}
|
|
127
|
+
else if (retryCount > RETRY_COUNT_SOFT_MAX) {
|
|
128
|
+
ctx.report('HTTP_RETRY_COUNT_HIGH', `Step "${step}" asks for ${retryCount} retries. The local runtime clamps at ${RETRY_COUNT_SOFT_MAX} and `
|
|
129
|
+
+ `the largest value in the deployed corpus is 3, so a local replay will not exercise what you wrote. `
|
|
130
|
+
+ `The platform's own ceiling is not measured — this is a warning, not a refusal.`, `retryCount: ${RETRY_COUNT_SOFT_MAX}`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Every node-local rule of an `http()` step, in the order check.ts ran them: the
|
|
136
|
+
* request settings, a managed step's binding labels, then the response branches.
|
|
137
|
+
*/
|
|
138
|
+
export function checkHttpStep(inputs, ctx) {
|
|
139
|
+
const step = ctx.step;
|
|
140
|
+
checkHttp(inputs, ctx);
|
|
141
|
+
// Only a complete pair: an empty half is HTTP_MANAGED_BINDING_INCOMPLETE, which
|
|
142
|
+
// serialize refuses before it resolves either name.
|
|
143
|
+
const pair = inputs;
|
|
144
|
+
const filled = (v) => typeof v === 'string' && v.trim() !== '';
|
|
145
|
+
if (pair?.managed && filled(pair.connection) && filled(pair.folder)) {
|
|
146
|
+
const target = pair.targetConnector;
|
|
147
|
+
ctx.bindingLabels(`managed http "${step}"`, pair,
|
|
148
|
+
// The connector its CONNECTION belongs to: the target, not the wrapper.
|
|
149
|
+
{ id: step, connector: typeof target === 'string' && target !== '' ? target : 'uipath-uipath-http' });
|
|
150
|
+
}
|
|
151
|
+
const seen = new Set();
|
|
152
|
+
for (const b of inputs?.branches ?? []) {
|
|
153
|
+
if (!/^[A-Za-z0-9_-]+$/.test(b.name)) {
|
|
154
|
+
ctx.report('HTTP_BRANCH_NAME', `HTTP branch ${JSON.stringify(b.name)} on "${step}" is not port-safe — use letters, digits, _ or - (it becomes the branch-<name> port).`);
|
|
155
|
+
}
|
|
156
|
+
if (seen.has(b.name)) {
|
|
157
|
+
ctx.report('HTTP_BRANCH_DUP', `HTTP branch ${JSON.stringify(b.name)} on "${step}" is declared twice.`);
|
|
158
|
+
}
|
|
159
|
+
seen.add(b.name);
|
|
160
|
+
if (b.condition.literal) {
|
|
161
|
+
ctx.report('HTTP_BRANCH_CONST', `HTTP branch ${JSON.stringify(b.name)} on "${step}" tests a constant — it would fire always or never. Reference the response, e.g. $vars.${step}.output.statusCode.`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* The fields every HTTP response carries at the top of `output`, whatever the
|
|
167
|
+
* API answered. `core.action.http`'s definition declares all three; the author's
|
|
168
|
+
* `returns` describes what is INSIDE `body`, never beside it.
|
|
169
|
+
*/
|
|
170
|
+
const HTTP_ENVELOPE_FIELDS = ['body', 'statusCode', 'headers'];
|
|
171
|
+
/**
|
|
172
|
+
* A read of an `http()` step's result.
|
|
173
|
+
*
|
|
174
|
+
* The response is an envelope — `{ body, statusCode, headers }` — and `returns`
|
|
175
|
+
* declares the parsed JSON body's top-level fields, so a declared field is read
|
|
176
|
+
* at `output.body.<field>` (`out('<step>', 'body.<field>')`). That nesting is the
|
|
177
|
+
* one thing this family does not share with the process families, and it is
|
|
178
|
+
* exactly where flow-builder-sdk#850's run went wrong: a branch kept reading
|
|
179
|
+
* `output.currentDayForecast…` (the Azure Maps connector's path) after the step
|
|
180
|
+
* became an Open-Meteo `http()`, passed `check` and `validate`, and faulted at
|
|
181
|
+
* `flow debug` as `[400302]`.
|
|
182
|
+
*/
|
|
183
|
+
export function checkHttpRead({ node: name, field, rest, reader: step }, spec, report) {
|
|
184
|
+
if (field === undefined)
|
|
185
|
+
return; // `out('fetch')` — the whole response.
|
|
186
|
+
if (field === 'error')
|
|
187
|
+
return; // the failure envelope (managed http's error port).
|
|
188
|
+
if (field !== 'output') {
|
|
189
|
+
report('HTTP_READ_NOT_OUTPUT', `HTTP step "${name}" publishes its response under "output", but "$vars.${name}.${field}" `
|
|
190
|
+
+ `(read in "${step}") asks for "${field}", which is undefined at run time.`, `out('${name}', 'body.<field>')`);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
const [top, inner] = rest.replace(/^\./, '').split('.');
|
|
194
|
+
if (!top)
|
|
195
|
+
return; // `$vars.<name>.output` — the whole envelope.
|
|
196
|
+
const declared = Object.keys(spec?.returns ?? {});
|
|
197
|
+
if (!HTTP_ENVELOPE_FIELDS.includes(top)) {
|
|
198
|
+
// The commonest form: a declared body field read one level too high.
|
|
199
|
+
const hoisted = declared.includes(top) ? top : undefined;
|
|
200
|
+
const nearest = hoisted ?? nearestName(top, declared);
|
|
201
|
+
report('HTTP_READ_UNKNOWN_FIELD', `"$vars.${name}.output.${top}" (read in "${step}") is not a field of HTTP step "${name}"'s `
|
|
202
|
+
+ `response. An HTTP response publishes ${HTTP_ENVELOPE_FIELDS.map((f) => `"${f}"`).join(', ')}; `
|
|
203
|
+
+ `the fields \`returns\` declares live under "body"`
|
|
204
|
+
+ (hoisted ? `, so "${top}" is read as "$vars.${name}.output.body.${top}".` : '.')
|
|
205
|
+
+ ` A read outside that shape resolves to undefined and faults a gateway at run time.`, nearest !== undefined ? `out('${name}', 'body.${nearest}')` : undefined);
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
if (top !== 'body' || !inner)
|
|
209
|
+
return; // `statusCode`, `headers`, or the whole body.
|
|
210
|
+
if (declared.length === 0) {
|
|
211
|
+
report('HTTP_READ_WITHOUT_RETURNS', `"$vars.${name}.output.body.${inner}" (read in "${step}") reads a field of HTTP step "${name}"'s `
|
|
212
|
+
+ `response body, but the step declares no \`returns\` — so the node carries no body schema and nothing `
|
|
213
|
+
+ `can tell a real field from a typo. Only the API being called knows its body; declare the fields you read.`, `returns: { ${inner}: 'string' }`);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
if (declared.includes(inner))
|
|
217
|
+
return;
|
|
218
|
+
const nearest = nearestName(inner, declared);
|
|
219
|
+
report('HTTP_READ_UNKNOWN_FIELD', `HTTP step "${name}" declares no body field "${inner}" (read as "$vars.${name}.output.body.${inner}" `
|
|
220
|
+
+ `in "${step}"). Its \`returns\` names: ${declared.join(', ')}. A read outside that set resolves to undefined.`, nearest !== undefined ? `out('${name}', 'body.${nearest}')` : undefined);
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* `.onError` after a plain `core.action.http` node. Two separate facts about a
|
|
224
|
+
* PLAIN http step, both measured, both fatal to a handler there:
|
|
225
|
+
*
|
|
226
|
+
* 1. a 4xx/5xx does not FAIL the node — it completes carrying `statusCode`,
|
|
227
|
+
* so the handler only ever runs on a transport failure (DNS, refused,
|
|
228
|
+
* timeout);
|
|
229
|
+
* 2. when it does run there is nothing to read. `core.action.http@1.0.0` and
|
|
230
|
+
* the `@1.3` that `uip maestro flow migrate` upgrades it to both publish
|
|
231
|
+
* `<step>.error = true` and leave `<step>.output` null — no envelope
|
|
232
|
+
* anywhere, unlike every other family. `validate` still says Valid.
|
|
233
|
+
*
|
|
234
|
+
* So this stays an error, and says the second part too: an author who reads
|
|
235
|
+
* the old message ("use the managed node if statuses must route") could
|
|
236
|
+
* reasonably conclude a transport-failure handler is fine as it is. check.ts
|
|
237
|
+
* reports the same refusal for a custom node built on the plain definition.
|
|
238
|
+
*/
|
|
239
|
+
export function plainHttpOnErrorMessage(stepName) {
|
|
240
|
+
return {
|
|
241
|
+
message: `Step "${stepName}" handles errors on a plain http() request. A 4xx/5xx does NOT fail that node — it `
|
|
242
|
+
+ `completes with out('${stepName}', 'statusCode') set — so the handler runs only on a transport failure, `
|
|
243
|
+
+ `and when it does the node publishes no failure envelope at all (measured: "<step>.error" is the boolean `
|
|
244
|
+
+ `true and "<step>.output" is null, in 1.0.0 and in the 1.3 flow migrate produces), so err() has nothing `
|
|
245
|
+
+ `to read. The managed node is the one whose failures route AND carry the envelope.`,
|
|
246
|
+
suggestion: `http({ url: …, managed: true })`,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The HTTP factory, `http()`. Moved from core/actions.ts in Phase 4 of
|
|
3
|
+
* docs/NODE_TYPE_ARCHITECTURE_DESIGN.md; the package entry re-exports it.
|
|
4
|
+
*/
|
|
5
|
+
import { type ActionSpec } from '../../../core/actions.js';
|
|
6
|
+
import type { HttpInputs } from './spec.js';
|
|
7
|
+
/**
|
|
8
|
+
* Declare an HTTP request action, explicitly choosing standalone or managed.
|
|
9
|
+
*
|
|
10
|
+
* @param inputs - The request: `url`, `method`, `body`, the response fields you
|
|
11
|
+
* read back via `returns`, and the `managed` node choice.
|
|
12
|
+
* @returns An action spec to pass to `.step(name, …)`.
|
|
13
|
+
*/
|
|
14
|
+
export declare function http(inputs: HttpInputs): ActionSpec;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The HTTP factory, `http()`. Moved from core/actions.ts in Phase 4 of
|
|
3
|
+
* docs/NODE_TYPE_ARCHITECTURE_DESIGN.md; the package entry re-exports it.
|
|
4
|
+
*/
|
|
5
|
+
import { builtinActionSpec } from '../../../core/actions.js';
|
|
6
|
+
/**
|
|
7
|
+
* Declare an HTTP request action, explicitly choosing standalone or managed.
|
|
8
|
+
*
|
|
9
|
+
* @param inputs - The request: `url`, `method`, `body`, the response fields you
|
|
10
|
+
* read back via `returns`, and the `managed` node choice.
|
|
11
|
+
* @returns An action spec to pass to `.step(name, …)`.
|
|
12
|
+
*/
|
|
13
|
+
export function http(inputs) {
|
|
14
|
+
if (typeof inputs.managed !== 'boolean') {
|
|
15
|
+
throw new Error('http() requires managed: true or managed: false because they emit different platform nodes. '
|
|
16
|
+
+ 'Use true for Managed HTTP Request/core.action.http.v2 or when non-2xx statuses must reach .onError(); '
|
|
17
|
+
+ 'use false for the standalone core.action.http node.');
|
|
18
|
+
}
|
|
19
|
+
return builtinActionSpec({ kind: 'http', inputs });
|
|
20
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** The HTTP node family, as flow-node-registry.ts lists it: `core.action.http` and `core.action.http.v2`. */
|
|
2
|
+
import type { FlowNodeFamily } from '../../../flow-node-type.js';
|
|
3
|
+
export declare const httpFlowFamily: FlowNodeFamily;
|
|
4
|
+
/**
|
|
5
|
+
* The plain node's `.onError` refusal, for check.ts: a custom node built on the
|
|
6
|
+
* `core.action.http` definition gets the same answer as an `http()` step.
|
|
7
|
+
*/
|
|
8
|
+
export { plainHttpOnErrorMessage } from './check.js';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { HTTP_DIAGNOSTICS } from './check.js';
|
|
2
|
+
import { httpNodeTypes } from './node-type.js';
|
|
3
|
+
export const httpFlowFamily = {
|
|
4
|
+
id: 'http',
|
|
5
|
+
title: 'HTTP',
|
|
6
|
+
types: httpNodeTypes,
|
|
7
|
+
diagnostics: HTTP_DIAGNOSTICS,
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* The plain node's `.onError` refusal, for check.ts: a custom node built on the
|
|
11
|
+
* `core.action.http` definition gets the same answer as an `http()` step.
|
|
12
|
+
*/
|
|
13
|
+
export { plainHttpOnErrorMessage } from './check.js';
|