@uipath/packager-tool-flow 1.201.0-preview.133 → 1.202.0-preview.134
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/agent-input-encoding-utils.d.ts +44 -0
- package/dist/agents.d.ts +3 -1
- package/dist/flow-tool.d.ts +9 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +156 -27
- package/dist/inline-agent-utils.d.ts +68 -0
- package/package.json +8 -8
- package/src/agent-input-encoding-utils.ts +104 -0
- package/src/agents.ts +52 -3
- package/src/flow-tool.ts +53 -0
- package/src/index.ts +10 -0
- package/src/inline-agent-utils.ts +147 -1
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Encoding-agnostic readers for the inline-agent input encoding (MST-12020).
|
|
3
|
+
*
|
|
4
|
+
* flow-workbench writes inline-agent input names in one of two schemes, gated by its
|
|
5
|
+
* `services.agent-storage.nested-agent-input-schema` feature flag:
|
|
6
|
+
* - FLAT (shipped default): one top-level `inputSchema` property per input, `__`-joined
|
|
7
|
+
* (`script1__output__query`), prompts read `{{input.script1__output__query}}`.
|
|
8
|
+
* - NESTED (flag on): a nested object tree whose auto-created container nodes carry
|
|
9
|
+
* `x-uipath-container: true`, prompts read `{{input.script1.output.query}}` (the runtime
|
|
10
|
+
* template engine treats `.` as navigation).
|
|
11
|
+
*
|
|
12
|
+
* The CLI must read BOTH — an agent.json is whatever the authoring canvas last wrote — so these
|
|
13
|
+
* helpers detect per file instead of consuming any flag. Detection is strictly the per-node
|
|
14
|
+
* container marker: an object property WITHOUT the marker is a user-declared whole-object input
|
|
15
|
+
* under either encoding and must be treated as one leaf, never unfolded.
|
|
16
|
+
*
|
|
17
|
+
* TODO: replace with flow-workbench's canonical helpers (`collectInlineAgentInputsFromAgent`,
|
|
18
|
+
* `decodeFlatName`, `getContainerChildProps`) once they are re-exported from
|
|
19
|
+
* `@uipath/flow-converter` — that package is the single source of truth for the encoding.
|
|
20
|
+
*/
|
|
21
|
+
/** Marker flow-workbench stamps on auto-created container nodes of a NESTED inputSchema. */
|
|
22
|
+
export declare const SCHEMA_CONTAINER_MARKER = "x-uipath-container";
|
|
23
|
+
/** One bindable input leaf of an agent `inputSchema`, in both name forms. */
|
|
24
|
+
export interface AgentInputLeaf {
|
|
25
|
+
/** Flat `__`-joined name — what a flat prompt token / flat JobArgument uses. */
|
|
26
|
+
flatKey: string;
|
|
27
|
+
/** Canonical dotted path — what a nested prompt token navigates. */
|
|
28
|
+
path: string;
|
|
29
|
+
/** Declared `type` of the leaf node, when present. */
|
|
30
|
+
type?: string;
|
|
31
|
+
}
|
|
32
|
+
/** Canonical dotted form of an input name from either encoding (`a__b` and `a.b` → `a.b`). */
|
|
33
|
+
export declare function canonicalAgentInputPath(name: string): string;
|
|
34
|
+
/**
|
|
35
|
+
* Enumerate the bindable input leaves of an agent `inputSchema`, descending MARKED containers
|
|
36
|
+
* (nested encoding) and passing flat / declared-object top-level keys through as single leaves.
|
|
37
|
+
* Returns `[]` for an absent/empty/malformed schema.
|
|
38
|
+
*/
|
|
39
|
+
export declare function collectAgentInputLeaves(inputSchema: unknown): AgentInputLeaf[];
|
|
40
|
+
/**
|
|
41
|
+
* Source binding expression for one input leaf — the inverse of flow-workbench's encode:
|
|
42
|
+
* a `metadata`-rooted path binds `$metadata.*`; everything else binds `$vars.*`.
|
|
43
|
+
*/
|
|
44
|
+
export declare function leafToBinding(leaf: AgentInputLeaf): string;
|
package/dist/agents.d.ts
CHANGED
|
@@ -8,7 +8,9 @@ export interface InlineAgentContract {
|
|
|
8
8
|
}
|
|
9
9
|
/**
|
|
10
10
|
* Build the execution contract for one inline agent with the same migration,
|
|
11
|
-
* validation, and binding pipeline used by normal solution packaging.
|
|
11
|
+
* validation, and binding pipeline used by normal solution packaging. The
|
|
12
|
+
* returned `agentJson` is healed to the flat input encoding (MST-12020) — see
|
|
13
|
+
* {@link healAgentInputEncoding}.
|
|
12
14
|
*/
|
|
13
15
|
export declare function buildInlineAgentContract(config: {
|
|
14
16
|
fileSystem: IFileSystem;
|
package/dist/flow-tool.d.ts
CHANGED
|
@@ -37,6 +37,15 @@ export declare class FlowTool extends ProjectTool {
|
|
|
37
37
|
* `buildVoiceAgentDefinitions` reports it.
|
|
38
38
|
*/
|
|
39
39
|
private resolveVoiceAgents;
|
|
40
|
+
/**
|
|
41
|
+
* Reconcile each inline-agent node's `agentInputVariables` from its
|
|
42
|
+
* agent's `inputSchema` before conversion. Reads the schema from the
|
|
43
|
+
* already-built agent (`builtAgents`) first, falling back to an
|
|
44
|
+
* `agent.json` copied under `content/<source>/`. Mutates `workflow.nodes`
|
|
45
|
+
* in place; best-effort, so a missing/unreadable agent leaves the node
|
|
46
|
+
* as-is.
|
|
47
|
+
*/
|
|
48
|
+
private hydrateInlineAgentInputs;
|
|
40
49
|
/** Read an `agent.json` copied in without going through `buildAgent`. */
|
|
41
50
|
private readCopiedAgentJson;
|
|
42
51
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
export { type AgentInputLeaf, canonicalAgentInputPath, collectAgentInputLeaves, leafToBinding, SCHEMA_CONTAINER_MARKER, } from "./agent-input-encoding-utils.js";
|
|
1
2
|
export { buildInlineAgentContract, type InlineAgentContract, } from "./agents.js";
|
|
2
3
|
export { replaceExporterVersion } from "./exporter-version.js";
|
|
3
4
|
export { createFlowRefResolver, type FlowIoLogger, migrateRawWorkflow, readFlowWorkflow, resolveAndMigrateWorkflow, resolveWorkflowRefs, } from "./flow-io.js";
|
|
4
5
|
export { FlowTool } from "./flow-tool.js";
|
|
5
6
|
export { FlowToolFactory } from "./flow-tool-factory.js";
|
|
6
|
-
export { isInlineAgentNodeType, readInlineAgentSource, setPublishIntentOnInlineAgents, } from "./inline-agent-utils.js";
|
|
7
|
+
export { type AgentInputVariable, hydrateInlineAgentInputVariables, isInlineAgentNodeType, readInlineAgentSource, reconcileInlineAgentInputVariables, setPublishIntentOnInlineAgents, } from "./inline-agent-utils.js";
|
|
7
8
|
export { operateRuntimeOptions } from "./operate-runtime-options.js";
|
|
8
9
|
export { assertVoiceAgentDefinitionsEmbedded, type VoiceAgentFlowNode, voiceConvertOptions, } from "./voice-agent-definitions.js";
|
package/dist/index.js
CHANGED
|
@@ -32,7 +32,7 @@ import {
|
|
|
32
32
|
// package.json
|
|
33
33
|
var package_default = {
|
|
34
34
|
name: "@uipath/packager-tool-flow",
|
|
35
|
-
version: "1.
|
|
35
|
+
version: "1.202.0-preview.134",
|
|
36
36
|
description: "UiPath Flow tool implementation",
|
|
37
37
|
type: "module",
|
|
38
38
|
exports: {
|
|
@@ -75,17 +75,18 @@ var package_default = {
|
|
|
75
75
|
license: "ISC",
|
|
76
76
|
peerDependencies: {
|
|
77
77
|
"@uipath/filesystem": "workspace:*",
|
|
78
|
-
"@uipath/flow-converter": "
|
|
79
|
-
"@uipath/flow-core": "
|
|
80
|
-
"@uipath/flow-migrations": "
|
|
81
|
-
"@uipath/flow-schema": "
|
|
78
|
+
"@uipath/flow-converter": "0.55.0-develop",
|
|
79
|
+
"@uipath/flow-core": "0.96.0-develop",
|
|
80
|
+
"@uipath/flow-migrations": "0.45.0-develop",
|
|
81
|
+
"@uipath/flow-schema": "0.57.4-develop",
|
|
82
82
|
"@uipath/solutionpackager-tool-core": "workspace:*",
|
|
83
83
|
"@uipath/tool-agent": "^2.0.0"
|
|
84
84
|
},
|
|
85
85
|
devDependencies: {
|
|
86
86
|
"@types/node": "^25.5.2",
|
|
87
87
|
"@uipath/filesystem": "workspace:*",
|
|
88
|
-
"@uipath/flow-
|
|
88
|
+
"@uipath/flow-converter": "0.55.0-develop",
|
|
89
|
+
"@uipath/flow-core": "0.96.0-develop",
|
|
89
90
|
"@uipath/solutionpackager-tool-core": "workspace:*",
|
|
90
91
|
"@uipath/tool-agent": "^2.0.0",
|
|
91
92
|
"@vitest/browser": "^4.1.6",
|
|
@@ -98,6 +99,10 @@ var package_default = {
|
|
|
98
99
|
};
|
|
99
100
|
|
|
100
101
|
// src/agents.ts
|
|
102
|
+
import {
|
|
103
|
+
healResourceInputEncoding,
|
|
104
|
+
normalizeAgentInputEncoding
|
|
105
|
+
} from "@uipath/flow-converter";
|
|
101
106
|
import { Path as Path2 } from "@uipath/solutionpackager-tool-core";
|
|
102
107
|
import { buildAgent } from "@uipath/tool-agent/build";
|
|
103
108
|
|
|
@@ -139,13 +144,19 @@ async function buildInlineAgentContract(config) {
|
|
|
139
144
|
options: { writeBindings: false }
|
|
140
145
|
});
|
|
141
146
|
return {
|
|
142
|
-
agentJson: built.agent,
|
|
147
|
+
agentJson: healAgentInputEncoding(built.agent),
|
|
143
148
|
bindingsJson: {
|
|
144
149
|
version: built.bindings.version,
|
|
145
150
|
resources: built.bindings.resources
|
|
146
151
|
}
|
|
147
152
|
};
|
|
148
153
|
}
|
|
154
|
+
function healAgentInputEncoding(agent) {
|
|
155
|
+
const normalized = normalizeAgentInputEncoding(agent);
|
|
156
|
+
const rawResources = normalized.resources;
|
|
157
|
+
const resources = Array.isArray(rawResources) ? rawResources.map((resource) => resource && typeof resource === "object" && !Array.isArray(resource) ? healResourceInputEncoding(resource) : resource) : rawResources;
|
|
158
|
+
return { ...normalized, resources };
|
|
159
|
+
}
|
|
149
160
|
var processAgents = async (fileSystem, logger, projectPath, contentFolder) => {
|
|
150
161
|
const agentResults = [];
|
|
151
162
|
const builtAgents = new Map;
|
|
@@ -168,8 +179,13 @@ var processAgents = async (fileSystem, logger, projectPath, contentFolder) => {
|
|
|
168
179
|
outputPath: outputDir,
|
|
169
180
|
logger
|
|
170
181
|
});
|
|
182
|
+
let agentJson = result.agent;
|
|
183
|
+
if (agentJson) {
|
|
184
|
+
agentJson = healAgentInputEncoding(agentJson);
|
|
185
|
+
await fileSystem.writeFile(Path2.join(outputDir, "agent.json"), JSON.stringify(agentJson, null, 2));
|
|
186
|
+
}
|
|
171
187
|
agentResults.push(result);
|
|
172
|
-
builtAgents.set(entry,
|
|
188
|
+
builtAgents.set(entry, agentJson);
|
|
173
189
|
}
|
|
174
190
|
const agentBindingResources = agentResults.flatMap(toBindingsResources);
|
|
175
191
|
if (agentBindingResources.length > 0) {
|
|
@@ -434,7 +450,57 @@ async function readUtf8OrNull(fs, filePath) {
|
|
|
434
450
|
}
|
|
435
451
|
|
|
436
452
|
// src/inline-agent-utils.ts
|
|
437
|
-
import { isAgentNodeType } from "@uipath/flow-schema";
|
|
453
|
+
import { isAgentNodeType, isVoiceAgentNodeType } from "@uipath/flow-schema";
|
|
454
|
+
|
|
455
|
+
// src/agent-input-encoding-utils.ts
|
|
456
|
+
var SCHEMA_CONTAINER_MARKER = "x-uipath-container";
|
|
457
|
+
var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
458
|
+
function containerChildren(prop) {
|
|
459
|
+
if (!isRecord(prop))
|
|
460
|
+
return;
|
|
461
|
+
if (prop[SCHEMA_CONTAINER_MARKER] !== true)
|
|
462
|
+
return;
|
|
463
|
+
const children = prop.properties;
|
|
464
|
+
return isRecord(children) ? children : undefined;
|
|
465
|
+
}
|
|
466
|
+
function canonicalAgentInputPath(name) {
|
|
467
|
+
return name.split("__").join(".");
|
|
468
|
+
}
|
|
469
|
+
function collectAgentInputLeaves(inputSchema) {
|
|
470
|
+
const properties = isRecord(inputSchema) ? inputSchema.properties : undefined;
|
|
471
|
+
if (!isRecord(properties))
|
|
472
|
+
return [];
|
|
473
|
+
const leaves = [];
|
|
474
|
+
const walk = (prop, segments) => {
|
|
475
|
+
const children = containerChildren(prop);
|
|
476
|
+
if (!children) {
|
|
477
|
+
const flatKey = segments.join("__");
|
|
478
|
+
leaves.push({
|
|
479
|
+
flatKey,
|
|
480
|
+
path: canonicalAgentInputPath(segments.join(".")),
|
|
481
|
+
type: isRecord(prop) && typeof prop.type === "string" ? prop.type : undefined
|
|
482
|
+
});
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
for (const [key, child] of Object.entries(children)) {
|
|
486
|
+
walk(child, [...segments, key]);
|
|
487
|
+
}
|
|
488
|
+
};
|
|
489
|
+
for (const [key, prop] of Object.entries(properties)) {
|
|
490
|
+
walk(prop, [key]);
|
|
491
|
+
}
|
|
492
|
+
return leaves;
|
|
493
|
+
}
|
|
494
|
+
function leafToBinding(leaf) {
|
|
495
|
+
const segments = leaf.path.split(".");
|
|
496
|
+
if (segments[0] === "metadata") {
|
|
497
|
+
const rest = segments.slice(1).join(".");
|
|
498
|
+
return rest ? `=$metadata.${rest}` : "=$metadata";
|
|
499
|
+
}
|
|
500
|
+
return `=$vars.${leaf.path}`;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// src/inline-agent-utils.ts
|
|
438
504
|
function readInlineAgentSource(node) {
|
|
439
505
|
const candidates = [
|
|
440
506
|
node.inputs?.source,
|
|
@@ -452,6 +518,55 @@ function readInlineAgentSource(node) {
|
|
|
452
518
|
function isInlineAgentNodeType(type) {
|
|
453
519
|
return isAgentNodeType(type);
|
|
454
520
|
}
|
|
521
|
+
function reconcileInlineAgentInputVariables(existing, inputSchema) {
|
|
522
|
+
const leaves = collectAgentInputLeaves(inputSchema);
|
|
523
|
+
if (leaves.length === 0) {
|
|
524
|
+
return { variables: existing, added: 0 };
|
|
525
|
+
}
|
|
526
|
+
const existingPaths = new Set(existing.map((e) => e.id).filter((id) => typeof id === "string").map(canonicalAgentInputPath));
|
|
527
|
+
const additions = leaves.filter((leaf) => !existingPaths.has(leaf.path)).map((leaf) => {
|
|
528
|
+
const binding = leafToBinding(leaf);
|
|
529
|
+
return {
|
|
530
|
+
id: leaf.flatKey,
|
|
531
|
+
type: leaf.type ?? "string",
|
|
532
|
+
binding,
|
|
533
|
+
description: `Bound from ${binding.slice(1)}`
|
|
534
|
+
};
|
|
535
|
+
});
|
|
536
|
+
if (additions.length === 0) {
|
|
537
|
+
return { variables: existing, added: 0 };
|
|
538
|
+
}
|
|
539
|
+
return { variables: [...existing, ...additions], added: additions.length };
|
|
540
|
+
}
|
|
541
|
+
async function hydrateInlineAgentInputVariables(nodes, resolveInputSchema, onReconcile, onUnresolved) {
|
|
542
|
+
for (const node of nodes) {
|
|
543
|
+
if (!isInlineAgentNodeType(node.type))
|
|
544
|
+
continue;
|
|
545
|
+
const source = readInlineAgentSource(node);
|
|
546
|
+
if (!source)
|
|
547
|
+
continue;
|
|
548
|
+
const reportable = !isVoiceAgentNodeType(node.type);
|
|
549
|
+
if (/[/\\.]/.test(source)) {
|
|
550
|
+
if (reportable)
|
|
551
|
+
onUnresolved?.(source, "invalid-source");
|
|
552
|
+
continue;
|
|
553
|
+
}
|
|
554
|
+
const inputSchema = await resolveInputSchema(source);
|
|
555
|
+
if (inputSchema === undefined) {
|
|
556
|
+
if (reportable)
|
|
557
|
+
onUnresolved?.(source, "schema-unavailable");
|
|
558
|
+
continue;
|
|
559
|
+
}
|
|
560
|
+
const existing = Array.isArray(node.inputs?.agentInputVariables) ? node.inputs.agentInputVariables : [];
|
|
561
|
+
const { variables, added } = reconcileInlineAgentInputVariables(existing, inputSchema);
|
|
562
|
+
if (added === 0)
|
|
563
|
+
continue;
|
|
564
|
+
if (!node.inputs)
|
|
565
|
+
node.inputs = {};
|
|
566
|
+
node.inputs.agentInputVariables = variables;
|
|
567
|
+
onReconcile?.(source, added);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
455
570
|
function setPublishIntentOnInlineAgents(fileFormat) {
|
|
456
571
|
const ff = fileFormat;
|
|
457
572
|
for (const def of ff.definitions ?? []) {
|
|
@@ -471,7 +586,7 @@ function operateRuntimeOptions(nodes) {
|
|
|
471
586
|
}
|
|
472
587
|
|
|
473
588
|
// src/voice-agent-definitions.ts
|
|
474
|
-
import { isVoiceAgentNodeType } from "@uipath/flow-schema";
|
|
589
|
+
import { isVoiceAgentNodeType as isVoiceAgentNodeType2 } from "@uipath/flow-schema";
|
|
475
590
|
function nodeLabel(node) {
|
|
476
591
|
const label = node.display?.label;
|
|
477
592
|
return typeof label === "string" && label.length > 0 ? label : node.id;
|
|
@@ -479,7 +594,7 @@ function nodeLabel(node) {
|
|
|
479
594
|
function collectVoiceAgentNodes(nodes) {
|
|
480
595
|
const result = [];
|
|
481
596
|
for (const node of nodes) {
|
|
482
|
-
if (!
|
|
597
|
+
if (!isVoiceAgentNodeType2(node.type))
|
|
483
598
|
continue;
|
|
484
599
|
const source = readInlineAgentSource(node);
|
|
485
600
|
if (!source) {
|
|
@@ -510,7 +625,7 @@ function assertNoSubflowVoiceAgents(flowJson) {
|
|
|
510
625
|
const workflow = JSON.parse(flowJson);
|
|
511
626
|
for (const [subflowId, entry] of Object.entries(workflow.subflows ?? {})) {
|
|
512
627
|
for (const node of entry.nodes ?? []) {
|
|
513
|
-
if (!
|
|
628
|
+
if (!isVoiceAgentNodeType2(node.type))
|
|
514
629
|
continue;
|
|
515
630
|
throw new Error(`Voice agent node "${nodeLabel(node)}" is inside subflow "${subflowId}". ` + "Voice agent nodes are not supported inside subflows — move it to the top-level flow.");
|
|
516
631
|
}
|
|
@@ -653,6 +768,7 @@ class FlowTool extends ProjectTool {
|
|
|
653
768
|
this.logger.warn(`Could not load ${flowFile} as a workflow — skipping artifact generation: ${message}`);
|
|
654
769
|
return;
|
|
655
770
|
}
|
|
771
|
+
await this.hydrateInlineAgentInputs(workflow, builtAgents, contentFolder);
|
|
656
772
|
const bpmnFileName = flowFile.replace(/\.flow$/, ".bpmn");
|
|
657
773
|
const bpmnPath = Path3.join(contentFolder, bpmnFileName);
|
|
658
774
|
const fileFormat = inMemoryWorkflowToFileFormat(workflow);
|
|
@@ -745,6 +861,13 @@ class FlowTool extends ProjectTool {
|
|
|
745
861
|
}
|
|
746
862
|
return voiceAgents;
|
|
747
863
|
}
|
|
864
|
+
async hydrateInlineAgentInputs(workflow, builtAgents, contentFolder) {
|
|
865
|
+
const nodes = workflow.nodes ?? [];
|
|
866
|
+
await hydrateInlineAgentInputVariables(nodes, async (source) => {
|
|
867
|
+
const agent = builtAgents.get(source) ?? await this.readCopiedAgentJson(contentFolder, source);
|
|
868
|
+
return agent?.inputSchema;
|
|
869
|
+
}, (source, added) => this.logger.info(`Reconciled ${added} inline-agent input(s) for "${source}" from agent.json inputSchema`), (source, reason) => this.logger.warn(reason === "invalid-source" ? `Inline agent "${source}" is not a bare project-id folder, so its inputs were not reconciled and packaging skips it — the agent would ship with no bound inputs.` : `Could not read an inputSchema for inline agent "${source}", so its inputs were not reconciled — the agent would ship with no bound inputs. Check that ${source}/agent.json exists and parses.`));
|
|
870
|
+
}
|
|
748
871
|
async readCopiedAgentJson(contentFolder, source) {
|
|
749
872
|
const agentJsonPath = Path3.join(contentFolder, source, "agent.json");
|
|
750
873
|
if (!await this.fileSystem.exists(agentJsonPath)) {
|
|
@@ -838,21 +961,27 @@ class FlowToolFactory {
|
|
|
838
961
|
// src/index.ts
|
|
839
962
|
toolsFactoryRepository.registerProjectToolFactory(new FlowToolFactory);
|
|
840
963
|
export {
|
|
841
|
-
|
|
842
|
-
setPublishIntentOnInlineAgents,
|
|
843
|
-
resolveWorkflowRefs,
|
|
844
|
-
resolveAndMigrateWorkflow,
|
|
845
|
-
replaceExporterVersion,
|
|
846
|
-
readInlineAgentSource,
|
|
847
|
-
readFlowWorkflow,
|
|
848
|
-
operateRuntimeOptions,
|
|
849
|
-
migrateRawWorkflow,
|
|
850
|
-
isInlineAgentNodeType,
|
|
851
|
-
createFlowRefResolver,
|
|
852
|
-
buildInlineAgentContract,
|
|
853
|
-
assertVoiceAgentDefinitionsEmbedded,
|
|
964
|
+
FlowTool,
|
|
854
965
|
FlowToolFactory,
|
|
855
|
-
|
|
966
|
+
SCHEMA_CONTAINER_MARKER,
|
|
967
|
+
assertVoiceAgentDefinitionsEmbedded,
|
|
968
|
+
buildInlineAgentContract,
|
|
969
|
+
canonicalAgentInputPath,
|
|
970
|
+
collectAgentInputLeaves,
|
|
971
|
+
createFlowRefResolver,
|
|
972
|
+
hydrateInlineAgentInputVariables,
|
|
973
|
+
isInlineAgentNodeType,
|
|
974
|
+
leafToBinding,
|
|
975
|
+
migrateRawWorkflow,
|
|
976
|
+
operateRuntimeOptions,
|
|
977
|
+
readFlowWorkflow,
|
|
978
|
+
readInlineAgentSource,
|
|
979
|
+
reconcileInlineAgentInputVariables,
|
|
980
|
+
replaceExporterVersion,
|
|
981
|
+
resolveAndMigrateWorkflow,
|
|
982
|
+
resolveWorkflowRefs,
|
|
983
|
+
setPublishIntentOnInlineAgents,
|
|
984
|
+
voiceConvertOptions
|
|
856
985
|
};
|
|
857
986
|
|
|
858
|
-
//# debugId=
|
|
987
|
+
//# debugId=A6C01CA5DD3E27EB64756E2164756E21
|
|
@@ -10,6 +10,73 @@ export declare function readInlineAgentSource(node: {
|
|
|
10
10
|
}): string | undefined;
|
|
11
11
|
/** True for a known inline-agent node type. */
|
|
12
12
|
export declare function isInlineAgentNodeType(type: string | undefined): boolean;
|
|
13
|
+
/** One flow-node `agentInputVariables[]` entry. */
|
|
14
|
+
export interface AgentInputVariable {
|
|
15
|
+
id?: string;
|
|
16
|
+
type?: string;
|
|
17
|
+
binding?: string;
|
|
18
|
+
description?: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Reconcile a node's existing `agentInputVariables` against an agent's
|
|
22
|
+
* `inputSchema` — the input contract the runtime actually binds.
|
|
23
|
+
*
|
|
24
|
+
* Adds a derived entry for every input leaf the node is missing and preserves
|
|
25
|
+
* every entry the node already declares, so a custom or divergent binding is
|
|
26
|
+
* never clobbered. Leaves come from {@link collectAgentInputLeaves}, so a
|
|
27
|
+
* nested schema descends its marked containers rather than being read as one
|
|
28
|
+
* opaque top-level key.
|
|
29
|
+
*
|
|
30
|
+
* Comparison is on the canonical dotted path, so a node that declares the
|
|
31
|
+
* nested spelling (`a.b`) is not re-added under the flat one (`a__b`). The
|
|
32
|
+
* derived id is always the FLAT spelling: the packager heals the shipped
|
|
33
|
+
* agent.json prompts and schema to the flat encoding, and the converter emits
|
|
34
|
+
* each id verbatim as a JobArgument name, so flat ids are what match the
|
|
35
|
+
* healed `{{input.<flat>}}` tokens for both schema encodings.
|
|
36
|
+
*
|
|
37
|
+
* Returns the reconciled list plus how many entries were added; when nothing
|
|
38
|
+
* is missing (or the schema is empty) returns the original array unchanged so
|
|
39
|
+
* callers can skip a spurious mutation.
|
|
40
|
+
*/
|
|
41
|
+
export declare function reconcileInlineAgentInputVariables(existing: AgentInputVariable[], inputSchema: unknown): {
|
|
42
|
+
variables: AgentInputVariable[];
|
|
43
|
+
added: number;
|
|
44
|
+
};
|
|
45
|
+
/** Minimal shape of a flow node needed to hydrate inline-agent inputs. */
|
|
46
|
+
interface InlineAgentNodeLike {
|
|
47
|
+
type?: string;
|
|
48
|
+
inputs?: Record<string, unknown>;
|
|
49
|
+
model?: Record<string, unknown>;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Reconcile every inline-agent node's `agentInputVariables` against its
|
|
53
|
+
* agent's `inputSchema` before BPMN conversion. Mutates `nodes` in place.
|
|
54
|
+
*
|
|
55
|
+
* Studio Web keeps the input binding in the agent's `inputSchema` and rebuilds
|
|
56
|
+
* the node's `agentInputVariables` from it in memory on load; the `.flow` it
|
|
57
|
+
* writes carries an empty `agentInputVariables`. The converter derives the
|
|
58
|
+
* agent's `JobArguments` from `agentInputVariables` and never reads
|
|
59
|
+
* `agent.json`, so an SW-authored flow would otherwise convert to an agent with
|
|
60
|
+
* `JobArguments = {"input":""}` — works in SW, drops every bound input on
|
|
61
|
+
* `flow pack`, `solution pack` and `debug`. Mirroring SW here fills the gap.
|
|
62
|
+
*
|
|
63
|
+
* `resolveInputSchema` returns the agent's `inputSchema` for a given `source`,
|
|
64
|
+
* or `undefined` when it cannot be resolved. Best-effort by design: an
|
|
65
|
+
* unresolvable agent leaves its node as-is rather than failing the build.
|
|
66
|
+
*
|
|
67
|
+
* `onUnresolved` fires for a node this function could NOT hydrate, so callers
|
|
68
|
+
* can say so. Without it the empty-`JobArguments` bug this exists to fix comes
|
|
69
|
+
* back silently: the node keeps `agentInputVariables: []`, the converter emits
|
|
70
|
+
* the `{"input":""}` floor, and nothing in the output explains why. It does not
|
|
71
|
+
* fire for a node that needed nothing — an agent declaring no inputs, or one
|
|
72
|
+
* whose bindings the node already covers, is not a problem.
|
|
73
|
+
*
|
|
74
|
+
* Voice agents are hydrated but never reported: their inputs are authored on
|
|
75
|
+
* the node (`callContext`, explicit `agentInputVariables`) rather than bound
|
|
76
|
+
* from a schema, so an unresolved schema is the normal case and packaging
|
|
77
|
+
* already warns separately about a voice agent's embedded definition.
|
|
78
|
+
*/
|
|
79
|
+
export declare function hydrateInlineAgentInputVariables(nodes: InlineAgentNodeLike[], resolveInputSchema: (source: string) => Promise<unknown>, onReconcile?: (source: string, added: number) => void, onUnresolved?: (source: string, reason: "invalid-source" | "schema-unavailable") => void): Promise<void>;
|
|
13
80
|
/**
|
|
14
81
|
* Set `__packageIntent = "Publish"` on inline agent definitions in a
|
|
15
82
|
* file-format workflow so the BPMN converter emits "content/" prefixed
|
|
@@ -18,3 +85,4 @@ export declare function isInlineAgentNodeType(type: string | undefined): boolean
|
|
|
18
85
|
* Mutates `fileFormat` in place.
|
|
19
86
|
*/
|
|
20
87
|
export declare function setPublishIntentOnInlineAgents(fileFormat: unknown): void;
|
|
88
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uipath/packager-tool-flow",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.202.0-preview.134",
|
|
4
4
|
"description": "UiPath Flow tool implementation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -25,13 +25,13 @@
|
|
|
25
25
|
"author": "",
|
|
26
26
|
"license": "ISC",
|
|
27
27
|
"peerDependencies": {
|
|
28
|
-
"@uipath/filesystem": "1.
|
|
29
|
-
"@uipath/flow-converter": "
|
|
30
|
-
"@uipath/flow-core": "
|
|
31
|
-
"@uipath/flow-migrations": "
|
|
32
|
-
"@uipath/flow-schema": "
|
|
33
|
-
"@uipath/solutionpackager-tool-core": "1.
|
|
28
|
+
"@uipath/filesystem": "1.202.0",
|
|
29
|
+
"@uipath/flow-converter": "0.55.0-develop",
|
|
30
|
+
"@uipath/flow-core": "0.96.0-develop",
|
|
31
|
+
"@uipath/flow-migrations": "0.45.0-develop",
|
|
32
|
+
"@uipath/flow-schema": "0.57.4-develop",
|
|
33
|
+
"@uipath/solutionpackager-tool-core": "1.202.0",
|
|
34
34
|
"@uipath/tool-agent": "^2.0.0"
|
|
35
35
|
},
|
|
36
|
-
"gitHead": "
|
|
36
|
+
"gitHead": "a335728adbdb02f28308e4f55d8936d0b150444b"
|
|
37
37
|
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Encoding-agnostic readers for the inline-agent input encoding (MST-12020).
|
|
3
|
+
*
|
|
4
|
+
* flow-workbench writes inline-agent input names in one of two schemes, gated by its
|
|
5
|
+
* `services.agent-storage.nested-agent-input-schema` feature flag:
|
|
6
|
+
* - FLAT (shipped default): one top-level `inputSchema` property per input, `__`-joined
|
|
7
|
+
* (`script1__output__query`), prompts read `{{input.script1__output__query}}`.
|
|
8
|
+
* - NESTED (flag on): a nested object tree whose auto-created container nodes carry
|
|
9
|
+
* `x-uipath-container: true`, prompts read `{{input.script1.output.query}}` (the runtime
|
|
10
|
+
* template engine treats `.` as navigation).
|
|
11
|
+
*
|
|
12
|
+
* The CLI must read BOTH — an agent.json is whatever the authoring canvas last wrote — so these
|
|
13
|
+
* helpers detect per file instead of consuming any flag. Detection is strictly the per-node
|
|
14
|
+
* container marker: an object property WITHOUT the marker is a user-declared whole-object input
|
|
15
|
+
* under either encoding and must be treated as one leaf, never unfolded.
|
|
16
|
+
*
|
|
17
|
+
* TODO: replace with flow-workbench's canonical helpers (`collectInlineAgentInputsFromAgent`,
|
|
18
|
+
* `decodeFlatName`, `getContainerChildProps`) once they are re-exported from
|
|
19
|
+
* `@uipath/flow-converter` — that package is the single source of truth for the encoding.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/** Marker flow-workbench stamps on auto-created container nodes of a NESTED inputSchema. */
|
|
23
|
+
export const SCHEMA_CONTAINER_MARKER = "x-uipath-container";
|
|
24
|
+
|
|
25
|
+
/** One bindable input leaf of an agent `inputSchema`, in both name forms. */
|
|
26
|
+
export interface AgentInputLeaf {
|
|
27
|
+
/** Flat `__`-joined name — what a flat prompt token / flat JobArgument uses. */
|
|
28
|
+
flatKey: string;
|
|
29
|
+
/** Canonical dotted path — what a nested prompt token navigates. */
|
|
30
|
+
path: string;
|
|
31
|
+
/** Declared `type` of the leaf node, when present. */
|
|
32
|
+
type?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
36
|
+
typeof value === "object" && value !== null && !Array.isArray(value);
|
|
37
|
+
|
|
38
|
+
/** Children of a MARKED container node; `undefined` for leaves and declared whole-object inputs. */
|
|
39
|
+
function containerChildren(prop: unknown): Record<string, unknown> | undefined {
|
|
40
|
+
if (!isRecord(prop)) return undefined;
|
|
41
|
+
if (prop[SCHEMA_CONTAINER_MARKER] !== true) return undefined;
|
|
42
|
+
const children = prop.properties;
|
|
43
|
+
return isRecord(children) ? children : undefined;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Canonical dotted form of an input name from either encoding (`a__b` and `a.b` → `a.b`). */
|
|
47
|
+
export function canonicalAgentInputPath(name: string): string {
|
|
48
|
+
return name.split("__").join(".");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Enumerate the bindable input leaves of an agent `inputSchema`, descending MARKED containers
|
|
53
|
+
* (nested encoding) and passing flat / declared-object top-level keys through as single leaves.
|
|
54
|
+
* Returns `[]` for an absent/empty/malformed schema.
|
|
55
|
+
*/
|
|
56
|
+
export function collectAgentInputLeaves(
|
|
57
|
+
inputSchema: unknown,
|
|
58
|
+
): AgentInputLeaf[] {
|
|
59
|
+
const properties = isRecord(inputSchema)
|
|
60
|
+
? inputSchema.properties
|
|
61
|
+
: undefined;
|
|
62
|
+
if (!isRecord(properties)) return [];
|
|
63
|
+
|
|
64
|
+
const leaves: AgentInputLeaf[] = [];
|
|
65
|
+
const walk = (prop: unknown, segments: string[]): void => {
|
|
66
|
+
const children = containerChildren(prop);
|
|
67
|
+
if (!children) {
|
|
68
|
+
// Flat keys already carry their whole path in one segment; keep the
|
|
69
|
+
// stored spelling for flatKey and canonicalize for the dotted path.
|
|
70
|
+
const flatKey = segments.join("__");
|
|
71
|
+
leaves.push({
|
|
72
|
+
flatKey,
|
|
73
|
+
path: canonicalAgentInputPath(segments.join(".")),
|
|
74
|
+
type:
|
|
75
|
+
isRecord(prop) && typeof prop.type === "string"
|
|
76
|
+
? prop.type
|
|
77
|
+
: undefined,
|
|
78
|
+
});
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
for (const [key, child] of Object.entries(children)) {
|
|
82
|
+
walk(child, [...segments, key]);
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
for (const [key, prop] of Object.entries(properties)) {
|
|
86
|
+
walk(prop, [key]);
|
|
87
|
+
}
|
|
88
|
+
return leaves;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Source binding expression for one input leaf — the inverse of flow-workbench's encode:
|
|
93
|
+
* a `metadata`-rooted path binds `$metadata.*`; everything else binds `$vars.*`.
|
|
94
|
+
*/
|
|
95
|
+
export function leafToBinding(leaf: AgentInputLeaf): string {
|
|
96
|
+
const segments = leaf.path.split(".");
|
|
97
|
+
if (segments[0] === "metadata") {
|
|
98
|
+
// A whole-object `metadata` leaf (unmarked object) has no trailing
|
|
99
|
+
// segments — bind the root `$metadata`, not an invalid `$metadata.`.
|
|
100
|
+
const rest = segments.slice(1).join(".");
|
|
101
|
+
return rest ? `=$metadata.${rest}` : "=$metadata";
|
|
102
|
+
}
|
|
103
|
+
return `=$vars.${leaf.path}`;
|
|
104
|
+
}
|
package/src/agents.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
healResourceInputEncoding,
|
|
3
|
+
normalizeAgentInputEncoding,
|
|
4
|
+
} from "@uipath/flow-converter";
|
|
1
5
|
import type {
|
|
2
6
|
IFileSystem,
|
|
3
7
|
IToolLogger,
|
|
@@ -16,7 +20,9 @@ export interface InlineAgentContract {
|
|
|
16
20
|
|
|
17
21
|
/**
|
|
18
22
|
* Build the execution contract for one inline agent with the same migration,
|
|
19
|
-
* validation, and binding pipeline used by normal solution packaging.
|
|
23
|
+
* validation, and binding pipeline used by normal solution packaging. The
|
|
24
|
+
* returned `agentJson` is healed to the flat input encoding (MST-12020) — see
|
|
25
|
+
* {@link healAgentInputEncoding}.
|
|
20
26
|
*/
|
|
21
27
|
export async function buildInlineAgentContract(config: {
|
|
22
28
|
fileSystem: IFileSystem;
|
|
@@ -30,7 +36,9 @@ export async function buildInlineAgentContract(config: {
|
|
|
30
36
|
});
|
|
31
37
|
|
|
32
38
|
return {
|
|
33
|
-
agentJson:
|
|
39
|
+
agentJson: healAgentInputEncoding(
|
|
40
|
+
built.agent as Record<string, unknown>,
|
|
41
|
+
),
|
|
34
42
|
bindingsJson: {
|
|
35
43
|
version: built.bindings.version,
|
|
36
44
|
resources: built.bindings.resources as unknown as Record<
|
|
@@ -41,6 +49,37 @@ export async function buildInlineAgentContract(config: {
|
|
|
41
49
|
};
|
|
42
50
|
}
|
|
43
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Normalize a built inline-agent contract to the FLAT input encoding (MST-12020) —
|
|
54
|
+
* the one heal every packaging path shares.
|
|
55
|
+
*
|
|
56
|
+
* The sidecar `agent.json` is whatever the authoring canvas last wrote: flat `__`
|
|
57
|
+
* names, or — behind flow-workbench's `nested-agent-input-schema` flag — a nested
|
|
58
|
+
* `x-uipath-container` tree with dotted prompt tokens. Flattens both halves: prompts
|
|
59
|
+
* + `inputSchema` via the agent heal, and each embedded resource's `argumentPath`
|
|
60
|
+
* via the resource heal. Applied by `buildInlineAgentContract` (flow-tool's
|
|
61
|
+
* `flow pack`/`publish`) and by `processAgents` (solution packaging). Defaults to
|
|
62
|
+
* 'flat', idempotent.
|
|
63
|
+
*/
|
|
64
|
+
function healAgentInputEncoding(
|
|
65
|
+
agent: Record<string, unknown>,
|
|
66
|
+
): Record<string, unknown> {
|
|
67
|
+
const normalized = normalizeAgentInputEncoding(agent);
|
|
68
|
+
const rawResources = normalized.resources;
|
|
69
|
+
const resources = Array.isArray(rawResources)
|
|
70
|
+
? rawResources.map((resource) =>
|
|
71
|
+
resource &&
|
|
72
|
+
typeof resource === "object" &&
|
|
73
|
+
!Array.isArray(resource)
|
|
74
|
+
? healResourceInputEncoding(
|
|
75
|
+
resource as Record<string, unknown>,
|
|
76
|
+
)
|
|
77
|
+
: resource,
|
|
78
|
+
)
|
|
79
|
+
: rawResources;
|
|
80
|
+
return { ...normalized, resources };
|
|
81
|
+
}
|
|
82
|
+
|
|
44
83
|
/**
|
|
45
84
|
* Build every root-level agent directory into the content folder and merge
|
|
46
85
|
* their binding resources. Returns each built agent.json keyed by its
|
|
@@ -77,8 +116,18 @@ export const processAgents = async (
|
|
|
77
116
|
outputPath: outputDir,
|
|
78
117
|
logger,
|
|
79
118
|
});
|
|
119
|
+
// buildAgent wrote the sidecar's encoding verbatim; normalize the deploy
|
|
120
|
+
// artifact to flat (MST-12020), matching `flow pack` / `publish`.
|
|
121
|
+
let agentJson = result.agent as Record<string, unknown>;
|
|
122
|
+
if (agentJson) {
|
|
123
|
+
agentJson = healAgentInputEncoding(agentJson);
|
|
124
|
+
await fileSystem.writeFile(
|
|
125
|
+
Path.join(outputDir, "agent.json"),
|
|
126
|
+
JSON.stringify(agentJson, null, 2),
|
|
127
|
+
);
|
|
128
|
+
}
|
|
80
129
|
agentResults.push(result);
|
|
81
|
-
builtAgents.set(entry,
|
|
130
|
+
builtAgents.set(entry, agentJson);
|
|
82
131
|
}
|
|
83
132
|
|
|
84
133
|
const agentBindingResources = agentResults.flatMap(toBindingsResources);
|
package/src/flow-tool.ts
CHANGED
|
@@ -40,6 +40,7 @@ import { ensureProcessBindings } from "./ensure-process-bindings.js";
|
|
|
40
40
|
import { replaceExporterVersion } from "./exporter-version.js";
|
|
41
41
|
import { readFlowWorkflow } from "./flow-io.js";
|
|
42
42
|
import {
|
|
43
|
+
hydrateInlineAgentInputVariables,
|
|
43
44
|
isInlineAgentNodeType,
|
|
44
45
|
readInlineAgentSource,
|
|
45
46
|
setPublishIntentOnInlineAgents,
|
|
@@ -273,6 +274,19 @@ export class FlowTool extends ProjectTool {
|
|
|
273
274
|
return;
|
|
274
275
|
}
|
|
275
276
|
|
|
277
|
+
// Reconcile each inline agent's `agentInputVariables` from its
|
|
278
|
+
// agent.json `inputSchema` before conversion. Studio-Web-authored
|
|
279
|
+
// flows write the node with `agentInputVariables: []` (SW assembles
|
|
280
|
+
// them in memory), and the converter derives `JobArguments` from that
|
|
281
|
+
// node array — so without this the agent packs with
|
|
282
|
+
// `JobArguments = {"input":""}` and every bound input is dropped
|
|
283
|
+
// (UV-15890 / UV-15979). Mirrors the debug path.
|
|
284
|
+
await this.hydrateInlineAgentInputs(
|
|
285
|
+
workflow,
|
|
286
|
+
builtAgents,
|
|
287
|
+
contentFolder,
|
|
288
|
+
);
|
|
289
|
+
|
|
276
290
|
// Convert .flow to BPMN from the resolved file format, so any $ref
|
|
277
291
|
// content is inlined before the converter sees it.
|
|
278
292
|
// For pack/publish, set __packageIntent = "Publish" on inline agent
|
|
@@ -459,6 +473,45 @@ export class FlowTool extends ProjectTool {
|
|
|
459
473
|
return voiceAgents;
|
|
460
474
|
}
|
|
461
475
|
|
|
476
|
+
/**
|
|
477
|
+
* Reconcile each inline-agent node's `agentInputVariables` from its
|
|
478
|
+
* agent's `inputSchema` before conversion. Reads the schema from the
|
|
479
|
+
* already-built agent (`builtAgents`) first, falling back to an
|
|
480
|
+
* `agent.json` copied under `content/<source>/`. Mutates `workflow.nodes`
|
|
481
|
+
* in place; best-effort, so a missing/unreadable agent leaves the node
|
|
482
|
+
* as-is.
|
|
483
|
+
*/
|
|
484
|
+
private async hydrateInlineAgentInputs(
|
|
485
|
+
workflow: Workflow,
|
|
486
|
+
builtAgents: ReadonlyMap<string, Record<string, unknown>>,
|
|
487
|
+
contentFolder: string,
|
|
488
|
+
): Promise<void> {
|
|
489
|
+
const nodes = (workflow.nodes ?? []) as Array<{
|
|
490
|
+
type?: string;
|
|
491
|
+
inputs?: Record<string, unknown>;
|
|
492
|
+
model?: Record<string, unknown>;
|
|
493
|
+
}>;
|
|
494
|
+
await hydrateInlineAgentInputVariables(
|
|
495
|
+
nodes,
|
|
496
|
+
async (source) => {
|
|
497
|
+
const agent =
|
|
498
|
+
builtAgents.get(source) ??
|
|
499
|
+
(await this.readCopiedAgentJson(contentFolder, source));
|
|
500
|
+
return agent?.inputSchema;
|
|
501
|
+
},
|
|
502
|
+
(source, added) =>
|
|
503
|
+
this.logger.info(
|
|
504
|
+
`Reconciled ${added} inline-agent input(s) for "${source}" from agent.json inputSchema`,
|
|
505
|
+
),
|
|
506
|
+
(source, reason) =>
|
|
507
|
+
this.logger.warn(
|
|
508
|
+
reason === "invalid-source"
|
|
509
|
+
? `Inline agent "${source}" is not a bare project-id folder, so its inputs were not reconciled and packaging skips it — the agent would ship with no bound inputs.`
|
|
510
|
+
: `Could not read an inputSchema for inline agent "${source}", so its inputs were not reconciled — the agent would ship with no bound inputs. Check that ${source}/agent.json exists and parses.`,
|
|
511
|
+
),
|
|
512
|
+
);
|
|
513
|
+
}
|
|
514
|
+
|
|
462
515
|
/** Read an `agent.json` copied in without going through `buildAgent`. */
|
|
463
516
|
private async readCopiedAgentJson(
|
|
464
517
|
contentFolder: string,
|
package/src/index.ts
CHANGED
|
@@ -8,6 +8,13 @@
|
|
|
8
8
|
import { toolsFactoryRepository } from "@uipath/solutionpackager-tool-core";
|
|
9
9
|
import { FlowToolFactory } from "./flow-tool-factory.js";
|
|
10
10
|
|
|
11
|
+
export {
|
|
12
|
+
type AgentInputLeaf,
|
|
13
|
+
canonicalAgentInputPath,
|
|
14
|
+
collectAgentInputLeaves,
|
|
15
|
+
leafToBinding,
|
|
16
|
+
SCHEMA_CONTAINER_MARKER,
|
|
17
|
+
} from "./agent-input-encoding-utils.js";
|
|
11
18
|
export {
|
|
12
19
|
buildInlineAgentContract,
|
|
13
20
|
type InlineAgentContract,
|
|
@@ -24,8 +31,11 @@ export {
|
|
|
24
31
|
export { FlowTool } from "./flow-tool.js";
|
|
25
32
|
export { FlowToolFactory } from "./flow-tool-factory.js";
|
|
26
33
|
export {
|
|
34
|
+
type AgentInputVariable,
|
|
35
|
+
hydrateInlineAgentInputVariables,
|
|
27
36
|
isInlineAgentNodeType,
|
|
28
37
|
readInlineAgentSource,
|
|
38
|
+
reconcileInlineAgentInputVariables,
|
|
29
39
|
setPublishIntentOnInlineAgents,
|
|
30
40
|
} from "./inline-agent-utils.js";
|
|
31
41
|
export { operateRuntimeOptions } from "./operate-runtime-options.js";
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import { isAgentNodeType } from "@uipath/flow-schema";
|
|
1
|
+
import { isAgentNodeType, isVoiceAgentNodeType } from "@uipath/flow-schema";
|
|
2
|
+
import {
|
|
3
|
+
canonicalAgentInputPath,
|
|
4
|
+
collectAgentInputLeaves,
|
|
5
|
+
leafToBinding,
|
|
6
|
+
} from "./agent-input-encoding-utils.js";
|
|
2
7
|
|
|
3
8
|
/**
|
|
4
9
|
* Resolve the inline-agent's projectId UUID from a flow node. Falls back to
|
|
@@ -29,6 +34,147 @@ export function isInlineAgentNodeType(type: string | undefined): boolean {
|
|
|
29
34
|
return isAgentNodeType(type);
|
|
30
35
|
}
|
|
31
36
|
|
|
37
|
+
/** One flow-node `agentInputVariables[]` entry. */
|
|
38
|
+
export interface AgentInputVariable {
|
|
39
|
+
id?: string;
|
|
40
|
+
type?: string;
|
|
41
|
+
binding?: string;
|
|
42
|
+
description?: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Reconcile a node's existing `agentInputVariables` against an agent's
|
|
47
|
+
* `inputSchema` — the input contract the runtime actually binds.
|
|
48
|
+
*
|
|
49
|
+
* Adds a derived entry for every input leaf the node is missing and preserves
|
|
50
|
+
* every entry the node already declares, so a custom or divergent binding is
|
|
51
|
+
* never clobbered. Leaves come from {@link collectAgentInputLeaves}, so a
|
|
52
|
+
* nested schema descends its marked containers rather than being read as one
|
|
53
|
+
* opaque top-level key.
|
|
54
|
+
*
|
|
55
|
+
* Comparison is on the canonical dotted path, so a node that declares the
|
|
56
|
+
* nested spelling (`a.b`) is not re-added under the flat one (`a__b`). The
|
|
57
|
+
* derived id is always the FLAT spelling: the packager heals the shipped
|
|
58
|
+
* agent.json prompts and schema to the flat encoding, and the converter emits
|
|
59
|
+
* each id verbatim as a JobArgument name, so flat ids are what match the
|
|
60
|
+
* healed `{{input.<flat>}}` tokens for both schema encodings.
|
|
61
|
+
*
|
|
62
|
+
* Returns the reconciled list plus how many entries were added; when nothing
|
|
63
|
+
* is missing (or the schema is empty) returns the original array unchanged so
|
|
64
|
+
* callers can skip a spurious mutation.
|
|
65
|
+
*/
|
|
66
|
+
export function reconcileInlineAgentInputVariables(
|
|
67
|
+
existing: AgentInputVariable[],
|
|
68
|
+
inputSchema: unknown,
|
|
69
|
+
): { variables: AgentInputVariable[]; added: number } {
|
|
70
|
+
const leaves = collectAgentInputLeaves(inputSchema);
|
|
71
|
+
if (leaves.length === 0) {
|
|
72
|
+
return { variables: existing, added: 0 };
|
|
73
|
+
}
|
|
74
|
+
const existingPaths = new Set(
|
|
75
|
+
existing
|
|
76
|
+
.map((e) => e.id)
|
|
77
|
+
.filter((id): id is string => typeof id === "string")
|
|
78
|
+
.map(canonicalAgentInputPath),
|
|
79
|
+
);
|
|
80
|
+
const additions = leaves
|
|
81
|
+
.filter((leaf) => !existingPaths.has(leaf.path))
|
|
82
|
+
.map((leaf) => {
|
|
83
|
+
const binding = leafToBinding(leaf);
|
|
84
|
+
return {
|
|
85
|
+
id: leaf.flatKey,
|
|
86
|
+
type: leaf.type ?? "string",
|
|
87
|
+
binding,
|
|
88
|
+
// binding is "=$vars.X" / "=$metadata.X"; drop the "="
|
|
89
|
+
description: `Bound from ${binding.slice(1)}`,
|
|
90
|
+
};
|
|
91
|
+
});
|
|
92
|
+
if (additions.length === 0) {
|
|
93
|
+
return { variables: existing, added: 0 };
|
|
94
|
+
}
|
|
95
|
+
return { variables: [...existing, ...additions], added: additions.length };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Minimal shape of a flow node needed to hydrate inline-agent inputs. */
|
|
99
|
+
interface InlineAgentNodeLike {
|
|
100
|
+
type?: string;
|
|
101
|
+
inputs?: Record<string, unknown>;
|
|
102
|
+
model?: Record<string, unknown>;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Reconcile every inline-agent node's `agentInputVariables` against its
|
|
107
|
+
* agent's `inputSchema` before BPMN conversion. Mutates `nodes` in place.
|
|
108
|
+
*
|
|
109
|
+
* Studio Web keeps the input binding in the agent's `inputSchema` and rebuilds
|
|
110
|
+
* the node's `agentInputVariables` from it in memory on load; the `.flow` it
|
|
111
|
+
* writes carries an empty `agentInputVariables`. The converter derives the
|
|
112
|
+
* agent's `JobArguments` from `agentInputVariables` and never reads
|
|
113
|
+
* `agent.json`, so an SW-authored flow would otherwise convert to an agent with
|
|
114
|
+
* `JobArguments = {"input":""}` — works in SW, drops every bound input on
|
|
115
|
+
* `flow pack`, `solution pack` and `debug`. Mirroring SW here fills the gap.
|
|
116
|
+
*
|
|
117
|
+
* `resolveInputSchema` returns the agent's `inputSchema` for a given `source`,
|
|
118
|
+
* or `undefined` when it cannot be resolved. Best-effort by design: an
|
|
119
|
+
* unresolvable agent leaves its node as-is rather than failing the build.
|
|
120
|
+
*
|
|
121
|
+
* `onUnresolved` fires for a node this function could NOT hydrate, so callers
|
|
122
|
+
* can say so. Without it the empty-`JobArguments` bug this exists to fix comes
|
|
123
|
+
* back silently: the node keeps `agentInputVariables: []`, the converter emits
|
|
124
|
+
* the `{"input":""}` floor, and nothing in the output explains why. It does not
|
|
125
|
+
* fire for a node that needed nothing — an agent declaring no inputs, or one
|
|
126
|
+
* whose bindings the node already covers, is not a problem.
|
|
127
|
+
*
|
|
128
|
+
* Voice agents are hydrated but never reported: their inputs are authored on
|
|
129
|
+
* the node (`callContext`, explicit `agentInputVariables`) rather than bound
|
|
130
|
+
* from a schema, so an unresolved schema is the normal case and packaging
|
|
131
|
+
* already warns separately about a voice agent's embedded definition.
|
|
132
|
+
*/
|
|
133
|
+
export async function hydrateInlineAgentInputVariables(
|
|
134
|
+
nodes: InlineAgentNodeLike[],
|
|
135
|
+
resolveInputSchema: (source: string) => Promise<unknown>,
|
|
136
|
+
onReconcile?: (source: string, added: number) => void,
|
|
137
|
+
onUnresolved?: (
|
|
138
|
+
source: string,
|
|
139
|
+
reason: "invalid-source" | "schema-unavailable",
|
|
140
|
+
) => void,
|
|
141
|
+
): Promise<void> {
|
|
142
|
+
for (const node of nodes) {
|
|
143
|
+
if (!isInlineAgentNodeType(node.type)) continue;
|
|
144
|
+
const source = readInlineAgentSource(node);
|
|
145
|
+
if (!source) continue;
|
|
146
|
+
// Same guard as inline-agent packaging: a `source` is a bare UUID
|
|
147
|
+
// folder name. Skip anything with `/`, `\`, or `.` so a traversing or
|
|
148
|
+
// divergent source cannot read an agent.json outside the packageable
|
|
149
|
+
// folder — packaging skips these too, so reconciling them would mutate
|
|
150
|
+
// inputs for an agent that will not ship.
|
|
151
|
+
const reportable = !isVoiceAgentNodeType(node.type);
|
|
152
|
+
if (/[/\\.]/.test(source)) {
|
|
153
|
+
if (reportable) onUnresolved?.(source, "invalid-source");
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const inputSchema = await resolveInputSchema(source);
|
|
158
|
+
if (inputSchema === undefined) {
|
|
159
|
+
if (reportable) onUnresolved?.(source, "schema-unavailable");
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const existing = Array.isArray(node.inputs?.agentInputVariables)
|
|
164
|
+
? (node.inputs.agentInputVariables as AgentInputVariable[])
|
|
165
|
+
: [];
|
|
166
|
+
const { variables, added } = reconcileInlineAgentInputVariables(
|
|
167
|
+
existing,
|
|
168
|
+
inputSchema,
|
|
169
|
+
);
|
|
170
|
+
if (added === 0) continue;
|
|
171
|
+
|
|
172
|
+
if (!node.inputs) node.inputs = {};
|
|
173
|
+
node.inputs.agentInputVariables = variables;
|
|
174
|
+
onReconcile?.(source, added);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
32
178
|
/**
|
|
33
179
|
* Set `__packageIntent = "Publish"` on inline agent definitions in a
|
|
34
180
|
* file-format workflow so the BPMN converter emits "content/" prefixed
|