@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.
Files changed (42) hide show
  1. package/dist/api-index.md +138 -138
  2. package/dist/api-members.md +407 -407
  3. package/dist/check.js +43 -331
  4. package/dist/core/actions.d.ts +0 -287
  5. package/dist/core/actions.js +0 -95
  6. package/dist/core/expr.d.ts +1 -1
  7. package/dist/core/expr.js +2 -2
  8. package/dist/core/nodes/node-type.d.ts +15 -0
  9. package/dist/core/step-ports.js +7 -23
  10. package/dist/core/typed-raw-node.js +0 -4
  11. package/dist/decompile.js +44 -115
  12. package/dist/flow-node-registry.js +3 -1
  13. package/dist/flow-node-type.d.ts +59 -0
  14. package/dist/flow-sdk.d.ts +2 -0
  15. package/dist/flow-sdk.js +2 -0
  16. package/dist/flow-step-exits.d.ts +12 -0
  17. package/dist/flow-step-exits.js +30 -0
  18. package/dist/nodes/ai-patterns/flow/factories.d.ts +83 -0
  19. package/dist/nodes/ai-patterns/flow/factories.js +86 -0
  20. package/dist/nodes/ai-patterns/flow/family.d.ts +3 -0
  21. package/dist/nodes/ai-patterns/flow/family.js +8 -0
  22. package/dist/nodes/ai-patterns/flow/index.d.ts +3 -0
  23. package/dist/nodes/ai-patterns/flow/index.js +3 -0
  24. package/dist/nodes/ai-patterns/flow/node-type.d.ts +11 -0
  25. package/dist/nodes/ai-patterns/flow/node-type.js +106 -0
  26. package/dist/nodes/ai-patterns/flow/spec.d.ts +75 -0
  27. package/dist/nodes/ai-patterns/flow/spec.js +1 -0
  28. package/dist/nodes/http/flow/check.d.ts +58 -0
  29. package/dist/nodes/http/flow/check.js +248 -0
  30. package/dist/nodes/http/flow/factories.d.ts +14 -0
  31. package/dist/nodes/http/flow/factories.js +20 -0
  32. package/dist/nodes/http/flow/family.d.ts +8 -0
  33. package/dist/nodes/http/flow/family.js +13 -0
  34. package/dist/nodes/http/flow/index.d.ts +3 -0
  35. package/dist/nodes/http/flow/index.js +3 -0
  36. package/dist/nodes/http/flow/node-type.d.ts +7 -0
  37. package/dist/nodes/http/flow/node-type.js +411 -0
  38. package/dist/nodes/http/flow/spec.d.ts +151 -0
  39. package/dist/nodes/http/flow/spec.js +1 -0
  40. package/dist/serialize.d.ts +2 -2
  41. package/dist/serialize.js +20 -316
  42. 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';
@@ -0,0 +1,3 @@
1
+ /** The HTTP family's public surface, which the package entry re-exports. */
2
+ export * from './spec.js';
3
+ export * from './factories.js';
@@ -0,0 +1,3 @@
1
+ /** The HTTP family's public surface, which the package entry re-exports. */
2
+ export * from './spec.js';
3
+ export * from './factories.js';
@@ -0,0 +1,7 @@
1
+ import type { FlowVersionedNodeType } from '../../../flow-node-type.js';
2
+ import type { HttpInputs } from './spec.js';
3
+ export type HttpSpec = {
4
+ kind: 'http';
5
+ inputs: HttpInputs;
6
+ };
7
+ export declare const httpNodeTypes: FlowVersionedNodeType<HttpSpec>[];