@uipath/maestro-builder-sdk 6.42.0 → 6.44.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 +104 -103
- package/dist/api-members.md +319 -317
- package/dist/check.d.ts +0 -19
- package/dist/check.js +140 -1042
- package/dist/compile-cli.js +2 -1
- package/dist/core/actions.d.ts +10 -713
- package/dist/core/actions.js +0 -193
- package/dist/core/agent-sidecar.d.ts +186 -0
- package/dist/core/agent-sidecar.js +409 -0
- package/dist/core/agent-tool-resources.d.ts +62 -0
- package/dist/core/agent-tool-resources.js +81 -0
- package/dist/{config.js → core/connector-configuration.js} +2 -2
- package/dist/core/form-field-type.d.ts +7 -0
- package/dist/core/form-field-type.js +12 -2
- package/dist/core/node-id-placeholder.d.ts +8 -0
- package/dist/core/node-id-placeholder.js +10 -0
- package/dist/core/ts-literal.d.ts +6 -0
- package/dist/core/ts-literal.js +15 -0
- package/dist/core/typed-raw-node.js +0 -5
- package/dist/decompile.js +239 -857
- package/dist/flow-expr-check.js +5 -0
- package/dist/flow-node-registry.js +2 -1
- package/dist/flow-node-type.d.ts +139 -6
- package/dist/flow-sdk.d.ts +41 -5
- package/dist/flow-sdk.js +15 -1
- package/dist/flow-step-variables.d.ts +4 -0
- package/dist/flow-step-variables.js +22 -0
- package/dist/loop-version.d.ts +23 -6
- package/dist/loop-version.js +37 -15
- package/dist/nodes/agent/flow/check.d.ts +88 -0
- package/dist/nodes/agent/flow/check.js +873 -0
- package/dist/nodes/agent/flow/factories.d.ts +134 -0
- package/dist/nodes/agent/flow/factories.js +143 -0
- package/dist/nodes/agent/flow/family.d.ts +3 -0
- package/dist/nodes/agent/flow/family.js +8 -0
- package/dist/nodes/agent/flow/index.d.ts +3 -0
- package/dist/nodes/agent/flow/index.js +3 -0
- package/dist/nodes/agent/flow/inline-agent-lift.d.ts +44 -0
- package/dist/nodes/agent/flow/inline-agent-lift.js +572 -0
- package/dist/nodes/agent/flow/inline-agent.d.ts +15 -0
- package/dist/nodes/agent/flow/inline-agent.js +526 -0
- package/dist/nodes/agent/flow/node-type.d.ts +20 -0
- package/dist/nodes/agent/flow/node-type.js +422 -0
- package/dist/nodes/agent/flow/resources.d.ts +502 -0
- package/dist/nodes/agent/flow/resources.js +1409 -0
- package/dist/nodes/agent/flow/spec.d.ts +448 -0
- package/dist/nodes/agent/flow/spec.js +1 -0
- package/dist/nodes/hitl/flow/check.js +15 -0
- package/dist/nodes/hitl/flow/node-type.js +18 -3
- package/dist/nodes/process/flow/check.d.ts +40 -2
- package/dist/nodes/process/flow/check.js +156 -0
- package/dist/nodes/process/flow/factories.d.ts +58 -3
- package/dist/nodes/process/flow/factories.js +59 -2
- package/dist/nodes/process/flow/node-type.d.ts +4 -1
- package/dist/nodes/process/flow/node-type.js +198 -12
- package/dist/nodes/process/flow/spec.d.ts +112 -5
- package/dist/serialize.d.ts +9 -54
- package/dist/serialize.js +102 -2777
- package/package.json +2 -2
- /package/dist/{config.d.ts → core/connector-configuration.d.ts} +0 -0
- /package/dist/{ixp-definition.json → core/ixp-definition.json} +0 -0
package/dist/serialize.js
CHANGED
|
@@ -6,12 +6,11 @@
|
|
|
6
6
|
* resolved from the connector library (entry + v1def sidecar), matching what
|
|
7
7
|
* the platform emits, so the result passes `uip maestro flow validate`.
|
|
8
8
|
*/
|
|
9
|
-
import ixpDefinition from './ixp-definition.json' with { type: 'json' };
|
|
9
|
+
import ixpDefinition from './core/ixp-definition.json' with { type: 'json' };
|
|
10
10
|
import { BUNDLED_DEFINITIONS, bundledVersions } from './flow-definitions.js';
|
|
11
|
-
import { LOOP_DEFAULT_VERSION, bodyUsesBreak, loopHasInnerHandles, loopVersion, loopVersionRefusal } from './loop-version.js';
|
|
12
|
-
import { Expr, toExpr, SCHEDULE_PRESETS, parseIxpProjectType,
|
|
11
|
+
import { LOOP_DEFAULT_VERSION, bodyUsesBreak, loopDeclaredInputs, loopHasInnerHandles, loopVersion, loopVersionRefusal } from './loop-version.js';
|
|
12
|
+
import { Expr, toExpr, SCHEDULE_PRESETS, parseIxpProjectType, flatNameSegmentsOf, parallelFork } from './flow-sdk.js';
|
|
13
13
|
import { FlowAction, FlowTrigger } from './core/node-classes.js';
|
|
14
|
-
import { CONTEXT_INDEX_DEFAULTS, CONTEXT_INDEX_DEFAULT_FILE_EXTENSION, contextIndexType } from './core/context-index.js';
|
|
15
14
|
import { connectorRawNodeRefusal } from './core/connector-raw-node.js';
|
|
16
15
|
import { rawNodeInputProblem, registeredRawType } from './raw-node-advice.js';
|
|
17
16
|
import { planContributionBindings } from './core/contribution-bindings.js';
|
|
@@ -20,25 +19,23 @@ import { FLOW_NODE_TYPES } from './flow-node-registry.js';
|
|
|
20
19
|
import { stepExits } from './flow-step-exits.js';
|
|
21
20
|
import { resolveVersionCarried } from './core/nodes/versioned.js';
|
|
22
21
|
import { indexCarriedDefinitions } from './core/carried-definitions.js';
|
|
23
|
-
import { formFieldJsonSchema, formFieldSettingsWire, wireFormFieldType } from './core/form-field-type.js';
|
|
24
22
|
import { ConnectorResolveError } from './core/connector-resolve-error.js';
|
|
25
23
|
import { synthesizedDefinitionRefusal } from './core/synthesized-definition.js';
|
|
26
24
|
import { connectionConnectors } from './bindings-v2.js';
|
|
27
25
|
import { BindingRefusal, GUID_RE, bindingLabelRefusal, bindingMissingMessage, missingBindingLabels } from './core/binding-messages.js';
|
|
28
26
|
import { ByoaRefusal, byoaUnverified } from './core/byoa.js';
|
|
29
27
|
import { genericEventObjectRefusal } from './core/event-checks.js';
|
|
30
|
-
import { OUTCOME_HANDLE_NODE_TYPES, assertHitlExitInvariant, hitlExitViolation
|
|
28
|
+
import { OUTCOME_HANDLE_NODE_TYPES, assertHitlExitInvariant, hitlExitViolation } from './core/hitl-routing.js';
|
|
31
29
|
import { STICKY_NOTE_TYPE, STICKY_NOTE_TYPE_VERSION } from './core/definitionless-node-types.js';
|
|
32
30
|
import { placeholderPosition } from './node-placement.js';
|
|
33
31
|
import { hasExits } from './core/step-ports.js';
|
|
34
32
|
import { stopsAtEnd } from './flow-step-tree.js';
|
|
35
33
|
import { assertNodeEnvelope } from './core/node-envelope.js';
|
|
36
|
-
import { buildConfiguration } from './
|
|
37
|
-
import { fnv1aX4Uuid,
|
|
38
|
-
import { derivedContextSource, derivedEscalationSource, derivedToolSource, } from './core/agent-resource-identity.js';
|
|
34
|
+
import { buildConfiguration } from './core/connector-configuration.js';
|
|
35
|
+
import { fnv1aX4Uuid, stableId } from './core/stable-id.js';
|
|
39
36
|
import { readEventFilter, eventFilterProblem, eventFilterJmes, eventFilterTreeLeaf, jmesString, jmesStringProblem } from './core/event-filters.js';
|
|
40
37
|
import { FLOW_EXPR_DIALECT } from './core/connector-checks.js';
|
|
41
|
-
import { buildConnectorInputs, connectorInputErrors as coreConnectorInputErrors,
|
|
38
|
+
import { buildConnectorInputs, connectorInputErrors as coreConnectorInputErrors, transportHttpMethod } from './core/connector-inputs.js';
|
|
42
39
|
/**
|
|
43
40
|
* What `compile` would refuse these connector inputs with, or `undefined` when it
|
|
44
41
|
* would accept them — Flow's binding of the shared validator.
|
|
@@ -62,14 +59,14 @@ import { outputBackingGlobal, planOutputBacking } from './flow-output-backing.js
|
|
|
62
59
|
import { mapJsCode } from './core/js-code-spans.js';
|
|
63
60
|
import { contributionPort } from './core/definition-handles.js';
|
|
64
61
|
import { prepareCommand } from './core/cli-spelling.js';
|
|
65
|
-
import {
|
|
66
|
-
import { processResourceBindings, processResourceLocation } from './core/process-resource.js';
|
|
62
|
+
import { processResourceBindings } from './core/process-resource.js';
|
|
67
63
|
import { types } from './core/expr.js';
|
|
68
64
|
import { SUBFLOW_START_LABEL } from './core/subflow-start.js';
|
|
69
|
-
import { ownEntry } from './core/own-entry.js';
|
|
70
65
|
import { endContentKey } from './flow-end-content.js';
|
|
71
66
|
import { terminateStepClashMessage, terminateTwiceMessage } from './flow-terminate-names.js';
|
|
72
67
|
import { jsonClone } from './core/json-clone.js';
|
|
68
|
+
import { subNodeId } from './core/node-id-placeholder.js';
|
|
69
|
+
import { JOB_ATTACHMENT_REF, jobAttachmentDefinition } from './core/agent-sidecar.js';
|
|
73
70
|
// core-definitions.json is an object keyed by "<nodeType>@<version>".
|
|
74
71
|
const DEFS = BUNDLED_DEFINITIONS;
|
|
75
72
|
// FIRST occurrence wins: with multiple bundled versions of one family (HITL
|
|
@@ -86,10 +83,6 @@ const NODE_TYPE = {
|
|
|
86
83
|
formTrigger: 'core.trigger.form',
|
|
87
84
|
conversationTrigger: 'core.trigger.conversation',
|
|
88
85
|
voiceTrigger: 'core.trigger.voice',
|
|
89
|
-
voiceAgent: 'uipath.agent.voice',
|
|
90
|
-
conversationalAgent: 'uipath.agent.conversational',
|
|
91
|
-
createOutgoingCall: 'uipath.conversational.voice.create-outgoing-call',
|
|
92
|
-
endCall: 'uipath.conversational.voice.end-call',
|
|
93
86
|
// The two other spellings of the SAME human task, both resolvable on this
|
|
94
87
|
// tenant. `hitl()` emits the base type unless `variant` names one of these;
|
|
95
88
|
// all three share the model (`bpmn:UserTask` / `Actions.HITL`), the single
|
|
@@ -126,1951 +119,7 @@ const SUBFLOW_OUTPUT = {
|
|
|
126
119
|
source: '=result.response',
|
|
127
120
|
var: 'output',
|
|
128
121
|
};
|
|
129
|
-
/** The published-agent node type family. The agent's release key is the suffix. */
|
|
130
|
-
const AGENT_PREFIX = 'uipath.core.agent.';
|
|
131
122
|
/** The inline (autonomous) agent's node type — fixed, not per-instance. */
|
|
132
|
-
const INLINE_AGENT_TYPE = 'uipath.agent.autonomous';
|
|
133
|
-
/**
|
|
134
|
-
* Build the definition for one published agent — coded or low-code, which are the
|
|
135
|
-
* SAME node.
|
|
136
|
-
*
|
|
137
|
-
* Unbundlable for rpa's, api-workflow's and agentic-process's reason: the node type
|
|
138
|
-
* carries a tenant's release key and the platform synthesizes the contract from
|
|
139
|
-
* whatever somebody published there. The constants below are transcribed from what
|
|
140
|
-
* the platform's oracles AGREE on, and this family is the first in the wave with
|
|
141
|
-
* BOTH a rich registry answer and a large deployed corpus, so the agreement is
|
|
142
|
-
* checkable rather than asserted:
|
|
143
|
-
*
|
|
144
|
-
* - `uip maestro flow registry get uipath.core.agent.<key>` for ALL NINE agents on
|
|
145
|
-
* the tenant (2026-07-29): `category: 'agent'`, `sortOrder: 505` (510 since flow-core's
|
|
146
|
-
* renumber: the 54 runs from 09-01 below carry 510; `resourceNodeSortOrder`, #1045),
|
|
147
|
-
* `supportsErrorHandling: true`, `version: '1.0.0'`, the `bpmn:ServiceTask` /
|
|
148
|
-
* `Orchestrator.StartAgentJob` / `v2` model, `section: 'Published'`,
|
|
149
|
-
* `resourceSubType: 'Agent'`, `orchestratorType: 'agent'`,
|
|
150
|
-
* `debug.runtime: 'bpmnEngine'`, `runtimeConstraints.exclude: ['api-function']`,
|
|
151
|
-
* and the `<bindings.name>` / `<bindings.folderPath>` context placeholders;
|
|
152
|
-
* - **61 deployed, debug-green agent instances** across 61 dated
|
|
153
|
-
* `skill-flow-{coded,lowcode}-agent` artifacts (05-18 → 07-29): `sortOrder: 505`
|
|
154
|
-
* in 61/61, the `<folderPath>.<name>` resourceKey in 58/61 (the 3 exceptions are
|
|
155
|
-
* the in-solution shape — a bare key and an EMPTY folderPath — which 404'd at
|
|
156
|
-
* job start in those runs; that is the shape of `location: 'in-solution'`,
|
|
157
|
-
* built by `processResourceLocation`, and it starts the job when the sibling is
|
|
158
|
-
* in the solution: 54 v1 `skill-flow-coded-agent` runs, 09-01 → 10-08, carry
|
|
159
|
-
* `isInSolution: true`, 45 score 1.0 and none of the other 9 fails on a
|
|
160
|
-
* job-start 404, #1012), and the descriptor
|
|
161
|
-
* ARRAY `bindings.values` in 59/61 (2 early runs used the object form);
|
|
162
|
-
* - flow-v1's own generator, which is where those come from:
|
|
163
|
-
* `node-manifest-builder.ts::createModel` hard-codes `bpmn:ServiceTask` for every
|
|
164
|
-
* published-resource node and derives `orchestratorType` as
|
|
165
|
-
* `resourceType.toLowerCase()`.
|
|
166
|
-
*
|
|
167
|
-
* `shared/sample-flows/agent.flow` is explicitly NOT an oracle here: dated
|
|
168
|
-
* 2026-05-12, it disagrees with both of the above in five places at once
|
|
169
|
-
* (`sortOrder: 530`, a bare-GUID resourceKey, an object-shaped `bindings.values`,
|
|
170
|
-
* and `=result.response` / `=result.Error` output sources). PR 11's rule — date a
|
|
171
|
-
* bundled sample before trusting the layer that copied it — and this is its second
|
|
172
|
-
* instance on that file.
|
|
173
|
-
*
|
|
174
|
-
* `outputs.output.source` is `=this`, which is what the definition declares. The
|
|
175
|
-
* corpus is split (`=result.response` 45, `=this` 14, both deploying green), and the
|
|
176
|
-
* split has a mechanical explanation: flow-v1's `applyOrchestratorActivityOutputs`
|
|
177
|
-
* APPENDS `{name:'output', source:'=this'}` when a node declares no `output`, so
|
|
178
|
-
* `=this` is what the platform writes for itself. We emit that one.
|
|
179
|
-
*
|
|
180
|
-
* The ONLY field that differs between a coded and a low-code agent is
|
|
181
|
-
* `display.icon` — which is why one factory serves both.
|
|
182
|
-
*/
|
|
183
|
-
function agentDef(spec) {
|
|
184
|
-
// One location slot with the other process resources: the in-solution
|
|
185
|
-
// model carries `projectId`, `projectName` AND `isInSolution: true`, as
|
|
186
|
-
// flow-core's `createModel` writes for every sibling-project resource.
|
|
187
|
-
const loc = processResourceLocation(spec);
|
|
188
|
-
const { inSolution, resourceKey } = loc;
|
|
189
|
-
const properties = {};
|
|
190
|
-
for (const [k, v] of Object.entries(spec.inputs)) {
|
|
191
|
-
properties[k] = { type: typeof v === 'number' ? 'number' : typeof v === 'boolean' ? 'boolean' : 'string' };
|
|
192
|
-
}
|
|
193
|
-
return {
|
|
194
|
-
nodeType: `${AGENT_PREFIX}${spec.key}`,
|
|
195
|
-
version: '1.0.0',
|
|
196
|
-
category: 'agent',
|
|
197
|
-
runtimeConstraints: { exclude: ['api-function'] },
|
|
198
|
-
description: loc.description,
|
|
199
|
-
tags: loc.tags,
|
|
200
|
-
sortOrder: resourceNodeSortOrder('agent'),
|
|
201
|
-
supportsErrorHandling: true,
|
|
202
|
-
display: {
|
|
203
|
-
label: spec.name,
|
|
204
|
-
icon: spec.flavour === 'coded' ? 'coded-agent' : 'autonomous-agent',
|
|
205
|
-
},
|
|
206
|
-
handleConfiguration: [
|
|
207
|
-
{ position: 'left', handles: [{ id: 'input', type: 'target', handleType: 'input' }] },
|
|
208
|
-
{ position: 'right', handles: [{ id: 'output', type: 'source', handleType: 'output' }] },
|
|
209
|
-
],
|
|
210
|
-
...loc.definitionExtras,
|
|
211
|
-
model: {
|
|
212
|
-
type: 'bpmn:ServiceTask',
|
|
213
|
-
serviceType: 'Orchestrator.StartAgentJob',
|
|
214
|
-
version: 'v2',
|
|
215
|
-
...(inSolution ? {} : { section: 'Published' }),
|
|
216
|
-
bindings: {
|
|
217
|
-
resource: 'process',
|
|
218
|
-
resourceSubType: 'Agent',
|
|
219
|
-
resourceKey,
|
|
220
|
-
orchestratorType: 'agent',
|
|
221
|
-
values: [
|
|
222
|
-
{ name: 'name', propertyAttribute: 'name', default: spec.name },
|
|
223
|
-
{ name: 'folderPath', propertyAttribute: 'folderPath', default: loc.folderPath },
|
|
224
|
-
],
|
|
225
|
-
},
|
|
226
|
-
...loc.modelExtras,
|
|
227
|
-
context: [
|
|
228
|
-
{ name: 'name', type: 'string', value: '<bindings.name>' },
|
|
229
|
-
{ name: 'folderPath', type: 'string', value: '<bindings.folderPath>' },
|
|
230
|
-
{ name: '_label', type: 'string', value: spec.name },
|
|
231
|
-
],
|
|
232
|
-
},
|
|
233
|
-
inputDefinition: Object.keys(properties).length ? { type: 'object', properties } : {},
|
|
234
|
-
inputDefaults: inSolution
|
|
235
|
-
? Object.fromEntries(Object.keys(properties).map((name) => [name, '']))
|
|
236
|
-
: {},
|
|
237
|
-
outputDefinition: {
|
|
238
|
-
output: {
|
|
239
|
-
type: 'object',
|
|
240
|
-
source: '=this',
|
|
241
|
-
var: 'output',
|
|
242
|
-
// The agent's output fields as the author declared them — the same shape
|
|
243
|
-
// `registry get` returns when it can reach the deployed agent (nested under
|
|
244
|
-
// `schema`, which is the spelling gen-tests reads). Without it a downstream
|
|
245
|
-
// read is unresolvable, rightly: nothing offline could tell a real field from a
|
|
246
|
-
// typo, and this is also what fixtures the node offline.
|
|
247
|
-
...(spec.returns && Object.keys(spec.returns).length
|
|
248
|
-
? {
|
|
249
|
-
schema: {
|
|
250
|
-
type: 'object',
|
|
251
|
-
properties: Object.fromEntries(Object.entries(spec.returns).map(([k, t]) => [k, { type: t }])),
|
|
252
|
-
},
|
|
253
|
-
}
|
|
254
|
-
: {}),
|
|
255
|
-
},
|
|
256
|
-
error: {
|
|
257
|
-
type: 'object',
|
|
258
|
-
description: 'Error information if the node fails',
|
|
259
|
-
source: '=Error',
|
|
260
|
-
var: 'error',
|
|
261
|
-
},
|
|
262
|
-
},
|
|
263
|
-
debug: { runtime: 'bpmnEngine' },
|
|
264
|
-
};
|
|
265
|
-
}
|
|
266
|
-
/**
|
|
267
|
-
* The definition for ONE Context Grounding index resource, transcribed from the
|
|
268
|
-
* platform's own registry entry (`uip maestro flow registry get
|
|
269
|
-
* uipath.agent.resource.context.index.<slug>.<uuid>`, read 2026-07-31 against the
|
|
270
|
-
* tenant's "Billing Dispute SOP Index"), and confirmed against the `.flow` v1
|
|
271
|
-
* emits for the same index.
|
|
272
|
-
*
|
|
273
|
-
* Per-INSTANCE, unlike `inlineAgentDef`: the index's identity is in the node type,
|
|
274
|
-
* so every tenant index is its own type — the same shape as a published agent's
|
|
275
|
-
* release key. What is constant is everything else, and this builds that from the
|
|
276
|
-
* one thing an author supplies (name + uuid).
|
|
277
|
-
*
|
|
278
|
-
* Two details are load-bearing rather than decorative:
|
|
279
|
-
*
|
|
280
|
-
* * `handleConfiguration` declares a single TARGET handle `input` whose
|
|
281
|
-
* `allowedSources` are the two agent types' `context` handles, with
|
|
282
|
-
* `maxConnections: 1`. That is the mirror of the agent's own
|
|
283
|
-
* `allowedTargets: [{ nodeType: 'uipath.agent.resource.context.*' }]`, and it is
|
|
284
|
-
* what makes the designer draw the join.
|
|
285
|
-
* * `model: { source: true }` and nothing else. A resource node has no
|
|
286
|
-
* `bpmn:` type, no serviceType and no bindings — it is not a step, it is an
|
|
287
|
-
* artifact hanging off one. The v1 artifact carries no `bindings[]` entry for it
|
|
288
|
-
* at all.
|
|
289
|
-
*/
|
|
290
|
-
function contextIndexDef(spec) {
|
|
291
|
-
return {
|
|
292
|
-
nodeType: contextIndexType(spec),
|
|
293
|
-
version: '1.0.0',
|
|
294
|
-
category: 'agent',
|
|
295
|
-
tags: ['agent-context', 'index'],
|
|
296
|
-
description: `Context grounding index "${spec.name}".`,
|
|
297
|
-
sortOrder: 505,
|
|
298
|
-
display: { label: spec.name, description: `Context grounding index "${spec.name}".`, icon: 'file-text', shape: 'circle' },
|
|
299
|
-
handleConfiguration: [
|
|
300
|
-
{
|
|
301
|
-
position: 'top',
|
|
302
|
-
handles: [{
|
|
303
|
-
id: 'input', type: 'target', handleType: 'artifact',
|
|
304
|
-
constraints: {
|
|
305
|
-
maxConnections: 1,
|
|
306
|
-
allowedSources: [
|
|
307
|
-
{ nodeType: 'uipath.agent.autonomous', handleId: 'context' },
|
|
308
|
-
{ nodeType: 'uipath.agent.conversational', handleId: 'context' },
|
|
309
|
-
],
|
|
310
|
-
validationMessage: "Only Agent's context handle can connect here",
|
|
311
|
-
},
|
|
312
|
-
}],
|
|
313
|
-
},
|
|
314
|
-
],
|
|
315
|
-
model: { source: true },
|
|
316
|
-
// The platform's own `inputDefinition.properties`, transcribed. Carried so the
|
|
317
|
-
// node's inputs are CHECKABLE — without an input schema nothing can validate
|
|
318
|
-
// this node's inputs at all, and a misspelled `resultCount` reaches the tenant
|
|
319
|
-
// unremarked.
|
|
320
|
-
//
|
|
321
|
-
// The registry's `allOf` conditionals (which retrieval modes require a query,
|
|
322
|
-
// and which of the query object's three modes is live) are deliberately NOT
|
|
323
|
-
// copied: they are the designer FORM's logic, and the serializer already emits
|
|
324
|
-
// a well-formed `query` for the mode it sets.
|
|
325
|
-
inputDefinition: {
|
|
326
|
-
type: 'object',
|
|
327
|
-
properties: {
|
|
328
|
-
indexId: { type: 'string' },
|
|
329
|
-
indexName: { type: 'string' },
|
|
330
|
-
folderKey: { type: 'string' },
|
|
331
|
-
folderPath: { type: 'string' },
|
|
332
|
-
retrievalMode: { type: 'string' },
|
|
333
|
-
query: { type: 'object' },
|
|
334
|
-
threshold: { type: 'number', minimum: 0, maximum: 1 },
|
|
335
|
-
resultCount: { type: 'number', minimum: 1, maximum: 40 },
|
|
336
|
-
fileExtension: { type: 'string' },
|
|
337
|
-
folderPathPrefix: { type: 'object' },
|
|
338
|
-
citations: { type: 'string' },
|
|
339
|
-
outputColumns: { type: 'array' },
|
|
340
|
-
webSearchGrounding: { type: 'object', properties: { value: { type: 'string' } } },
|
|
341
|
-
},
|
|
342
|
-
},
|
|
343
|
-
};
|
|
344
|
-
}
|
|
345
|
-
/**
|
|
346
|
-
* Render an index's retrieval query into the platform's MODE-TAGGED object.
|
|
347
|
-
*
|
|
348
|
-
* The index definition's own schema (`inputDefinition.allOf`) says `query` is a
|
|
349
|
-
* `oneOf` over three shapes, each identified by `mode`: `prompt` → `promptValue`,
|
|
350
|
-
* `text-builder` → `textValue`, `variable` → `argumentPath`. All four fields are
|
|
351
|
-
* present in the platform's `inputDefaults`; only the one the mode names is read.
|
|
352
|
-
*
|
|
353
|
-
* A plain string is a `prompt` (what a designer types); anything expression-shaped
|
|
354
|
-
* is `text-builder`, whose `textValue` the runtime templates — the same slot the
|
|
355
|
-
* `folderPathPrefix` field uses, and the reason that field ships with
|
|
356
|
-
* `mode: 'text-builder'` by default.
|
|
357
|
-
*/
|
|
358
|
-
function renderContextQuery(query, rename) {
|
|
359
|
-
const base = { mode: 'prompt', textValue: '', promptValue: '', argumentPath: '' };
|
|
360
|
-
if (query === undefined || query === null)
|
|
361
|
-
return base;
|
|
362
|
-
const rendered = renderValue(query, rename);
|
|
363
|
-
const asText = typeof rendered === 'string' ? rendered : JSON.stringify(rendered);
|
|
364
|
-
if (typeof rendered === 'string' && rendered.startsWith('=js:')) {
|
|
365
|
-
return { ...base, mode: 'text-builder', textValue: asText };
|
|
366
|
-
}
|
|
367
|
-
return { ...base, promptValue: asText };
|
|
368
|
-
}
|
|
369
|
-
/**
|
|
370
|
-
* The `resources/<id>/resource.json` an inline agent's context index needs.
|
|
371
|
-
*
|
|
372
|
-
* The node is only half of a grounded agent, the same split as the agent itself
|
|
373
|
-
* (see `inlineAgentJson`). The platform resolves an agent's capabilities from
|
|
374
|
-
* `<source>/resources/<resource-id>/resource.json`, and `uip agent refresh` turns
|
|
375
|
-
* those into the `bindings_v2.json` entries `uip solution resources refresh`
|
|
376
|
-
* syncs. Keyed by the node's `inputs.source`, which is what joins the two halves.
|
|
377
|
-
*
|
|
378
|
-
* On the CLI path the file is the only copy: refresh strips the node and
|
|
379
|
-
* re-derives it from here, so a `.flow` with the node and no file validates clean
|
|
380
|
-
* and leaves the agent ungrounded. (Studio Web is the other direction — its
|
|
381
|
-
* `mapContextResource` builds this file FROM the canvas node.)
|
|
382
|
-
*
|
|
383
|
-
* Shape and enums are the platform's `currentResourceStorageSchema`
|
|
384
|
-
* (`@uipath/agents-storage-schemas`): `description`, `folderPath`, `indexName` and
|
|
385
|
-
* all six `settings` keys are required, `fileExtension` is `{value}` rather than a
|
|
386
|
-
* bare string, and the retrieval mode pins which extensions it accepts — hence the
|
|
387
|
-
* per-mode default here against the node's flat `'All'`. An unparseable resource
|
|
388
|
-
* is worse than none: `uip agent refresh` falls back to `resources: []` and drops
|
|
389
|
-
* every other binding with it.
|
|
390
|
-
*/
|
|
391
|
-
function contextResourceJson(cx, spec) {
|
|
392
|
-
const mode = cx.retrievalMode ?? CONTEXT_INDEX_DEFAULTS.retrievalMode;
|
|
393
|
-
return {
|
|
394
|
-
$resourceType: 'context',
|
|
395
|
-
id: spec.id,
|
|
396
|
-
// Null rather than absent: optional in the schema, but every deployed
|
|
397
|
-
// resource carries it, and a designer round-trip writes it back.
|
|
398
|
-
referenceKey: null,
|
|
399
|
-
name: cx.name,
|
|
400
|
-
description: `Context grounding index "${cx.name}".`,
|
|
401
|
-
contextType: 'index',
|
|
402
|
-
// Required by the schema, and load-bearing: this file carries no `indexId`,
|
|
403
|
-
// so `indexName` + `folderPath` is the whole locator, and `createIndexBinding`
|
|
404
|
-
// leaves the folder off the binding when it is empty. `check` warns when the
|
|
405
|
-
// author omits it — an index outside the deployment folder is then unresolvable.
|
|
406
|
-
folderPath: cx.folderPath ?? '',
|
|
407
|
-
indexName: cx.name,
|
|
408
|
-
settings: {
|
|
409
|
-
retrievalMode: mode,
|
|
410
|
-
// The resource's `query` is VARIANT-tagged where the node's is MODE-tagged,
|
|
411
|
-
// and the two vocabularies do not overlap. `prompt` — what a designer types
|
|
412
|
-
// as guidance — is the model-invoked `dynamic`, and its text is the
|
|
413
|
-
// `description` the model reads when deciding what to search for.
|
|
414
|
-
// `text-builder` — an expression the runtime templates — is the pre-fetched
|
|
415
|
-
// `static`, and its text is the `value`. Confirmed behaviourally: switching
|
|
416
|
-
// the SDK's `query` from a string to an expression turned the same agent's
|
|
417
|
-
// retrieval from a tool call into a pre-fetch.
|
|
418
|
-
query: spec.query.mode === 'text-builder'
|
|
419
|
-
? { variant: 'static', value: String(spec.query.textValue ?? '') }
|
|
420
|
-
: { variant: 'dynamic', description: String(spec.query.promptValue ?? '') },
|
|
421
|
-
// `value: ''`, as the canvas writes it (flow-workbench `mapContextQueryToWire`
|
|
422
|
-
// always sets the value), so a canvas index's resource round-trips unchanged.
|
|
423
|
-
folderPathPrefix: { variant: 'static', value: '' },
|
|
424
|
-
threshold: cx.threshold ?? CONTEXT_INDEX_DEFAULTS.threshold,
|
|
425
|
-
resultCount: cx.resultCount ?? CONTEXT_INDEX_DEFAULTS.resultCount,
|
|
426
|
-
fileExtension: { value: cx.fileExtension ?? ownEntry(CONTEXT_INDEX_DEFAULT_FILE_EXTENSION, mode) ?? 'All' },
|
|
427
|
-
// Mode-specific keys the schema REQUIRES of those two modes and rejects
|
|
428
|
-
// nowhere else, carrying the node's own constants through the enum's
|
|
429
|
-
// spelling (the node says `enabled`; the resource says `Enabled`/`Inline`).
|
|
430
|
-
...(mode === 'deeprag' ? { citationMode: { value: 'Inline' } } : {}),
|
|
431
|
-
...(mode === 'batchtransform'
|
|
432
|
-
? { webSearchGrounding: { value: 'Enabled' }, outputColumns: [] }
|
|
433
|
-
: {}),
|
|
434
|
-
},
|
|
435
|
-
};
|
|
436
|
-
}
|
|
437
|
-
/** A `fieldSchema` object with every declared property marked required. */
|
|
438
|
-
function requireAll(schema) {
|
|
439
|
-
const names = Object.keys(schema.properties);
|
|
440
|
-
return names.length === 0 ? schema : { ...schema, required: names };
|
|
441
|
-
}
|
|
442
|
-
/**
|
|
443
|
-
* The resource `type` each deployed-resource tool kind carries in the agent
|
|
444
|
-
* project, which is NOT its node-type namespace: `maestro` mints
|
|
445
|
-
* `…tool.processorchestration.*` and its resource type is `processOrchestration`.
|
|
446
|
-
* Read off the platform's `currentResourceStorageSchema`; flow-workbench's
|
|
447
|
-
* `CANVAS_TO_STORAGE_TOOL_TYPE` maps them the same way.
|
|
448
|
-
*/
|
|
449
|
-
const TOOL_RESOURCE_TYPE = {
|
|
450
|
-
process: 'process', api: 'api', maestro: 'processOrchestration',
|
|
451
|
-
flow: 'flow', agent: 'agent', function: 'function',
|
|
452
|
-
};
|
|
453
|
-
/** The resource `type` of a connector tool's file ({@link connectorToolResourceJson}). */
|
|
454
|
-
const CONNECTOR_TOOL_RESOURCE_TYPE = 'integration';
|
|
455
|
-
/**
|
|
456
|
-
* The resource `type` of every platform built-in tool. Shared with tools the
|
|
457
|
-
* DESIGNER writes and the SDK cannot (`load-attachments`, `create-file`, …), so
|
|
458
|
-
* on its own it says nothing about who owns a file: `properties.toolType` does.
|
|
459
|
-
*/
|
|
460
|
-
const INTERNAL_TOOL_RESOURCE_TYPE = 'internal';
|
|
461
|
-
/**
|
|
462
|
-
* Where an agent schema property points to declare "this is a FILE":
|
|
463
|
-
* `{ $ref: JOB_ATTACHMENT_REF }`, with {@link jobAttachmentDefinition} under the
|
|
464
|
-
* same schema's `definitions['job-attachment']`.
|
|
465
|
-
*/
|
|
466
|
-
const JOB_ATTACHMENT_REF = '#/definitions/job-attachment';
|
|
467
|
-
/**
|
|
468
|
-
* The canonical `job-attachment` definition an agent schema's file field
|
|
469
|
-
* `$ref`s into — the uipath-agents skill's `agent-definition.md` § File
|
|
470
|
-
* Attachments ("Schema is fixed — copy verbatim"), and the same block the
|
|
471
|
-
* platform's own analyze-attachments tool contract carries.
|
|
472
|
-
*
|
|
473
|
-
* It tells the agent runtime that an input is an Orchestrator attachment, so a
|
|
474
|
-
* file tool (Analyze Files) can resolve the attachment's `ID`.
|
|
475
|
-
*
|
|
476
|
-
* A function, not a shared object: each schema gets its own copy, so an edit
|
|
477
|
-
* to one emitted file cannot reach another.
|
|
478
|
-
*
|
|
479
|
-
* (The case serializer keeps its own copy in `case/entry-points.ts`: that one
|
|
480
|
-
* mirrors the platform's `entry-points.json` packager byte for byte, and its
|
|
481
|
-
* `MimeType` description differs.)
|
|
482
|
-
*/
|
|
483
|
-
function jobAttachmentDefinition() {
|
|
484
|
-
return {
|
|
485
|
-
type: 'object',
|
|
486
|
-
properties: {
|
|
487
|
-
ID: { type: 'string', description: 'Orchestrator attachment key' },
|
|
488
|
-
FullName: { type: 'string', description: 'File name' },
|
|
489
|
-
MimeType: { type: 'string', description: 'MIME type, e.g. "application/pdf", "image/png"' },
|
|
490
|
-
Metadata: {
|
|
491
|
-
type: 'object',
|
|
492
|
-
description: 'Dictionary<string, string> of metadata',
|
|
493
|
-
additionalProperties: { type: 'string' },
|
|
494
|
-
},
|
|
495
|
-
},
|
|
496
|
-
required: ['ID'],
|
|
497
|
-
'x-uipath-resource-kind': 'JobAttachment',
|
|
498
|
-
};
|
|
499
|
-
}
|
|
500
|
-
/** The `properties.toolType` of the built-in HTTP-request tool's file (#935). */
|
|
501
|
-
const HTTP_REQUEST_TOOL_TYPE = 'http-request';
|
|
502
|
-
/**
|
|
503
|
-
* The `resources/<id>/resource.json` a DEPLOYED-RESOURCE tool needs — the tool
|
|
504
|
-
* half of what `contextResourceJson` does for an index, and inert for the same
|
|
505
|
-
* reason (see that function).
|
|
506
|
-
*
|
|
507
|
-
* Shape from a real agent project (`WebResearchBriefingAgent`'s `CountSources`,
|
|
508
|
-
* a `kind: 'process'` tool), cross-checked against the platform schema: this
|
|
509
|
-
* family carries `argumentProperties` and `referenceKey` but no `iconUrl`, and
|
|
510
|
-
* `exampleCalls` is present-and-empty. `location: 'solution'` and an empty
|
|
511
|
-
* `properties.folderPath` are the inline-in-flow pair Studio Web's
|
|
512
|
-
* `mapToolResource` writes and uipcli's `agent validate` enforces — the folder
|
|
513
|
-
* itself lives in the flow's top-level `bindings[]`, which
|
|
514
|
-
* `processResourceBindings` already writes for this tool.
|
|
515
|
-
*
|
|
516
|
-
* Returns `undefined` for every OTHER kind. Which kinds get a file is measured,
|
|
517
|
-
* because `uip agent refresh` strips each resource node and re-derives it FROM
|
|
518
|
-
* the file: where the node is `source`-only the round-trip is lossless, and where
|
|
519
|
-
* the node carries the configuration the strip loses it and the rebuild cannot
|
|
520
|
-
* put it back, so writing the file turns a valid flow INVALID.
|
|
521
|
-
*
|
|
522
|
-
* * `ixp`, `clientside` — measured to fail exactly that way (`ixp` leaves the
|
|
523
|
-
* flow failing validate; `clientside` drops the four inputs its node needs).
|
|
524
|
-
* * `connector` — has its own writer, {@link connectorToolResourceJson}: its
|
|
525
|
-
* node carries the configuration in `inputs.detail`, which the strip keeps,
|
|
526
|
-
* and the file is the one `uip agent refresh` itself derives from it.
|
|
527
|
-
* * `mcp`, `a2a` — need the server's `availableTools` and the remote agent's
|
|
528
|
-
* card, which are tenant reads.
|
|
529
|
-
* * `builtin` — has its own writer, {@link builtinToolResourceJson}: its
|
|
530
|
-
* `toolType` is a fixed per-tool constant, so the file is derivable offline.
|
|
531
|
-
* * `httpRequest` — has its own writer, {@link httpRequestToolResourceJson}:
|
|
532
|
-
* the file carries the request config as static `argumentProperties` and the
|
|
533
|
-
* model-facing `inputSchema`, both derivable from the node (#935).
|
|
534
|
-
*
|
|
535
|
-
* Which `type`/`toolType` values compile writes — and so owns and may prune —
|
|
536
|
-
* is {@link compileOwnsResource}, read off the same tables these writers use.
|
|
537
|
-
*/
|
|
538
|
-
function toolResourceJson(tr, spec) {
|
|
539
|
-
const common = {
|
|
540
|
-
$resourceType: 'tool',
|
|
541
|
-
id: spec.id,
|
|
542
|
-
name: tr.name,
|
|
543
|
-
// The runtime hands this to the MODEL as the tool's description, so an empty
|
|
544
|
-
// one is a tool the model has to guess at from its name alone.
|
|
545
|
-
description: tr.description ?? '',
|
|
546
|
-
isEnabled: true,
|
|
547
|
-
// `required` matters here where it does not on the agent's own contract: the
|
|
548
|
-
// model reads this schema to build the call, and an argument the process needs
|
|
549
|
-
// but the schema calls optional fails at run time with nothing said at compile
|
|
550
|
-
// time. The corpus fixture lists every declared argument.
|
|
551
|
-
inputSchema: requireAll(fieldSchema(tr.inputs)),
|
|
552
|
-
outputSchema: fieldSchema(tr.returns),
|
|
553
|
-
// No policies: `inlineAgent`'s tool refs carry none, and the field is
|
|
554
|
-
// required-but-emptyable.
|
|
555
|
-
guardrail: { policies: [] },
|
|
556
|
-
settings: {},
|
|
557
|
-
argumentProperties: {},
|
|
558
|
-
};
|
|
559
|
-
const resourceType = ownEntry(TOOL_RESOURCE_TYPE, tr.kind);
|
|
560
|
-
if (resourceType !== undefined) {
|
|
561
|
-
return {
|
|
562
|
-
...common,
|
|
563
|
-
type: resourceType,
|
|
564
|
-
location: 'solution',
|
|
565
|
-
// The deployed resource's own key: what joins this tool to the release the
|
|
566
|
-
// solution declares.
|
|
567
|
-
referenceKey: tr.key,
|
|
568
|
-
// `folderPath` is EMPTY on purpose, and the tenant folder is not lost: for
|
|
569
|
-
// an inline-in-flow agent the folder lives in the flow's own top-level
|
|
570
|
-
// `bindings[]`, which `processResourceBindings` already writes for this
|
|
571
|
-
// tool. uipcli's `agent validate` says so where it enforces the pair —
|
|
572
|
-
// *"Inline-in-flow agents store folder info in the flow's top-level
|
|
573
|
-
// bindings[] rather than in the tool resource — Studio Web emits an empty
|
|
574
|
-
// folderPath here"* — and every `inlineAgent` tool is inline by definition.
|
|
575
|
-
properties: { processName: tr.name, folderPath: '', exampleCalls: [] },
|
|
576
|
-
};
|
|
577
|
-
}
|
|
578
|
-
return undefined;
|
|
579
|
-
}
|
|
580
|
-
/**
|
|
581
|
-
* The `properties.toolType` each platform built-in tool carries in the agent
|
|
582
|
-
* project. Neither the node type nor the label: `analyzefiles` is the
|
|
583
|
-
* designer's "Analyze Files" and its resource is `analyze-attachments`;
|
|
584
|
-
* `summarize` is the designer's DeepRAG node (`manifest_node_deepRag_display_label:
|
|
585
|
-
* "Summarize"` in the vendored workbench strings) and its resource is `deep-rag`.
|
|
586
|
-
* Read off the platform's `builtInToolPropertiesSchemaV40` discriminated union
|
|
587
|
-
* (vendored `flow-to-bpmn.mjs`), which also lists `load-attachments` and
|
|
588
|
-
* `http-request`, and the uipath-agents skill's built-in-tools reference, which
|
|
589
|
-
* owns the file's body. The runtime ignores an unknown toolType silently, so
|
|
590
|
-
* this table is the whole contract — one that is wrong selects no tool at all.
|
|
591
|
-
*/
|
|
592
|
-
const BUILTIN_TOOL_TYPE = {
|
|
593
|
-
analyzefiles: 'analyze-attachments',
|
|
594
|
-
summarize: 'deep-rag',
|
|
595
|
-
batchtransform: 'batch-transform',
|
|
596
|
-
};
|
|
597
|
-
/** `inline` → `Inline`: the node's enum spelling to the resource schema's. */
|
|
598
|
-
const enumValue = (v) => {
|
|
599
|
-
const s = String(v ?? '');
|
|
600
|
-
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
601
|
-
};
|
|
602
|
-
/**
|
|
603
|
-
* The `resources/<id>/resource.json` of a platform BUILT-IN tool (#920) — the
|
|
604
|
-
* file the runtime builds the agent's tool list from. Without it the
|
|
605
|
-
* `tool.builtin.*` node is inert, the same failure #806 fixed for deployed
|
|
606
|
-
* resources: the agent runs with no built-in tool and the model never learns
|
|
607
|
-
* it exists.
|
|
608
|
-
*
|
|
609
|
-
* Derived from the node's own inputs (`tdef.inputDefaults` with the author's
|
|
610
|
-
* values over them), because those are what the file must agree with:
|
|
611
|
-
* `uip agent refresh --inline-in-flow` shell-ifies every resource node down to
|
|
612
|
-
* `{source, detail, itemsDescription}` and Studio Web re-hydrates the rest FROM
|
|
613
|
-
* this file, so a value only on the node does not survive a refresh or a
|
|
614
|
-
* designer round-trip.
|
|
615
|
-
*
|
|
616
|
-
* Shape: the skill's per-tool walkthroughs (`analyze-attachments.md`,
|
|
617
|
-
* `deeprag/impl-json.md`, `batch-transform/impl-json.md`) checked against the
|
|
618
|
-
* platform's `builtInToolResourceStorageSchemaV40`. Two vocabularies differ
|
|
619
|
-
* from the node's, as they do for a context index (`contextResourceJson`): the
|
|
620
|
-
* resource `query` is VARIANT-tagged where the node's is MODE-tagged, and its
|
|
621
|
-
* enums are capitalized (`Inline`, `Enabled`). `analyze-attachments` declares
|
|
622
|
-
* the model-facing call contract (`attachments` + `analysisTask` → `analysis`);
|
|
623
|
-
* `deep-rag` and `batch-transform` declare empty schemas and carry their
|
|
624
|
-
* configuration in `properties.settings`.
|
|
625
|
-
*/
|
|
626
|
-
function builtinToolResourceJson(tool, label, inputs, spec) {
|
|
627
|
-
const toolType = ownEntry(BUILTIN_TOOL_TYPE, tool);
|
|
628
|
-
if (toolType === undefined)
|
|
629
|
-
return undefined;
|
|
630
|
-
const empty = { type: 'object', properties: {} };
|
|
631
|
-
const common = {
|
|
632
|
-
$resourceType: 'tool',
|
|
633
|
-
id: spec.id,
|
|
634
|
-
// Null, not absent: what identifies a built-in as NOT an external tool.
|
|
635
|
-
referenceKey: null,
|
|
636
|
-
name: label,
|
|
637
|
-
type: INTERNAL_TOOL_RESOURCE_TYPE,
|
|
638
|
-
description: String(inputs.description ?? ''),
|
|
639
|
-
isEnabled: true,
|
|
640
|
-
settings: {},
|
|
641
|
-
guardrail: { policies: [] },
|
|
642
|
-
argumentProperties: {},
|
|
643
|
-
};
|
|
644
|
-
const query = inputs.query?.mode === 'text-builder'
|
|
645
|
-
? { variant: 'static', value: String(inputs.query.textValue ?? '') }
|
|
646
|
-
: { variant: 'dynamic', description: String(inputs.query?.promptValue ?? '') };
|
|
647
|
-
if (toolType === 'analyze-attachments') {
|
|
648
|
-
return {
|
|
649
|
-
...common,
|
|
650
|
-
inputSchema: {
|
|
651
|
-
type: 'object',
|
|
652
|
-
properties: {
|
|
653
|
-
attachments: {
|
|
654
|
-
type: 'array',
|
|
655
|
-
items: { $ref: JOB_ATTACHMENT_REF },
|
|
656
|
-
description: String(inputs.attachmentsDescription ?? ''),
|
|
657
|
-
},
|
|
658
|
-
analysisTask: { type: 'string', description: String(inputs.analysisTaskDescription ?? '') },
|
|
659
|
-
},
|
|
660
|
-
required: ['attachments', 'analysisTask'],
|
|
661
|
-
definitions: { 'job-attachment': jobAttachmentDefinition() },
|
|
662
|
-
},
|
|
663
|
-
outputSchema: {
|
|
664
|
-
type: 'object',
|
|
665
|
-
properties: {
|
|
666
|
-
analysis: {
|
|
667
|
-
type: 'string',
|
|
668
|
-
description: 'Analysis result of the attachments (analysis, summary, extracted information, etc.)',
|
|
669
|
-
},
|
|
670
|
-
},
|
|
671
|
-
required: ['analysis'],
|
|
672
|
-
},
|
|
673
|
-
properties: { toolType, exampleCalls: [] },
|
|
674
|
-
};
|
|
675
|
-
}
|
|
676
|
-
if (toolType === 'deep-rag') {
|
|
677
|
-
return {
|
|
678
|
-
...common,
|
|
679
|
-
inputSchema: empty,
|
|
680
|
-
outputSchema: empty,
|
|
681
|
-
properties: {
|
|
682
|
-
toolType,
|
|
683
|
-
exampleCalls: [],
|
|
684
|
-
settings: {
|
|
685
|
-
contextType: 'attachments',
|
|
686
|
-
query,
|
|
687
|
-
folderPathPrefix: { variant: 'static' },
|
|
688
|
-
fileExtension: { value: String(inputs.fileExtension?.value ?? 'pdf') },
|
|
689
|
-
citationMode: { value: enumValue(inputs.citationMode?.value ?? 'inline') },
|
|
690
|
-
},
|
|
691
|
-
},
|
|
692
|
-
};
|
|
693
|
-
}
|
|
694
|
-
return {
|
|
695
|
-
...common,
|
|
696
|
-
inputSchema: empty,
|
|
697
|
-
outputSchema: empty,
|
|
698
|
-
properties: {
|
|
699
|
-
toolType,
|
|
700
|
-
exampleCalls: [],
|
|
701
|
-
settings: {
|
|
702
|
-
contextType: 'attachments',
|
|
703
|
-
query,
|
|
704
|
-
folderPathPrefix: { variant: 'static' },
|
|
705
|
-
outputColumns: Array.isArray(inputs.outputColumns) ? inputs.outputColumns : [],
|
|
706
|
-
webSearchGrounding: { value: enumValue(inputs.webSearchGrounding?.value ?? 'enabled') },
|
|
707
|
-
},
|
|
708
|
-
},
|
|
709
|
-
};
|
|
710
|
-
}
|
|
711
|
-
/**
|
|
712
|
-
* The six request fields of the HTTP-request tool, in the node's (and the
|
|
713
|
-
* designer's file's) order, with the JSON Schema each takes in the file's
|
|
714
|
-
* model-facing `inputSchema`. `headers`/`params` are name/value rows and
|
|
715
|
-
* `timeout` is a number — the same normalization the node's text-builder
|
|
716
|
-
* values already carry.
|
|
717
|
-
*/
|
|
718
|
-
const HTTP_REQUEST_FIELDS = [
|
|
719
|
-
['url', { type: 'string' }],
|
|
720
|
-
['method', { type: 'string' }],
|
|
721
|
-
['headers', { type: 'array', items: { type: 'object', properties: { name: { type: 'string' }, value: { type: 'string' } } } }],
|
|
722
|
-
['params', { type: 'array', items: { type: 'object', properties: { name: { type: 'string' }, value: { type: 'string' } } } }],
|
|
723
|
-
['body', { type: 'string' }],
|
|
724
|
-
['timeout', { type: 'number' }],
|
|
725
|
-
];
|
|
726
|
-
/**
|
|
727
|
-
* The `resources/<id>/resource.json` of the built-in HTTP-request tool (#935) —
|
|
728
|
-
* without it the deployed agent is never offered the tool (measured: one LLM
|
|
729
|
-
* call, no tool span), and `uip agent refresh --inline-in-flow` strips the node
|
|
730
|
-
* to `{source}`, so the request config is lost entirely.
|
|
731
|
-
*
|
|
732
|
-
* Derived from the node's own mode-tagged inputs, so the two halves cannot
|
|
733
|
-
* disagree. The rules are the ones Studio Web's own file follows (measured by
|
|
734
|
-
* saving the agent once in the designer; a file built by these rules is
|
|
735
|
-
* JSON-equal to it, and a debug run with only this file showed the tool spans):
|
|
736
|
-
*
|
|
737
|
-
* * `inputSchema` declares all six fields, each with the node's `promptValue`
|
|
738
|
-
* as its description — the text the model reads when it fills the field;
|
|
739
|
-
* * `argumentProperties` pins every field NOT in `prompt` mode as
|
|
740
|
-
* `$['<field>']: {variant: 'static', value: <textValue>}` — a fixed value the
|
|
741
|
-
* model does not get to choose. The SDK writes only `prompt` and
|
|
742
|
-
* `text-builder` fields; a `text-builder` value is the literal.
|
|
743
|
-
*/
|
|
744
|
-
function httpRequestToolResourceJson(label, inputs, spec) {
|
|
745
|
-
const properties = {};
|
|
746
|
-
const argumentProperties = {};
|
|
747
|
-
for (const [field, schema] of HTTP_REQUEST_FIELDS) {
|
|
748
|
-
const row = (inputs[field] ?? {});
|
|
749
|
-
properties[field] = { ...schema, description: String(row.promptValue ?? '') };
|
|
750
|
-
if (row.mode !== undefined && row.mode !== 'prompt') {
|
|
751
|
-
argumentProperties[`$['${field}']`] = { variant: 'static', value: row.textValue, isSensitive: false };
|
|
752
|
-
}
|
|
753
|
-
}
|
|
754
|
-
return {
|
|
755
|
-
$resourceType: 'tool',
|
|
756
|
-
id: spec.id,
|
|
757
|
-
canvasNodeId: spec.canvasNodeId,
|
|
758
|
-
name: label,
|
|
759
|
-
type: INTERNAL_TOOL_RESOURCE_TYPE,
|
|
760
|
-
description: String(inputs.description ?? ''),
|
|
761
|
-
isEnabled: true,
|
|
762
|
-
inputSchema: { type: 'object', properties, required: ['url'] },
|
|
763
|
-
outputSchema: { type: 'object', properties: {} },
|
|
764
|
-
settings: {},
|
|
765
|
-
guardrail: { policies: [] },
|
|
766
|
-
argumentProperties,
|
|
767
|
-
properties: { toolType: HTTP_REQUEST_TOOL_TYPE },
|
|
768
|
-
};
|
|
769
|
-
}
|
|
770
|
-
/**
|
|
771
|
-
* Every tool resource identity `compile` writes, read off the tables its
|
|
772
|
-
* writers use — so a tool kind cannot be written without being owned, or owned
|
|
773
|
-
* without being written (#935: the #920 built-in files were written as
|
|
774
|
-
* `internal` and never pruned, because ownership was a hand-kept list in
|
|
775
|
-
* `compile-cli.ts` that did not have them).
|
|
776
|
-
*
|
|
777
|
-
* * `types` — the resource `type`s compile owns OUTRIGHT: every deployed-resource
|
|
778
|
-
* kind's, and `integration` (connector tools, since #867).
|
|
779
|
-
* * `internalToolTypes` — `internal` is shared with designer-only built-ins
|
|
780
|
-
* (`load-attachments`, `create-file`, `jev-classifier`), so for it ownership
|
|
781
|
-
* is the `properties.toolType`, and only the ones compile writes.
|
|
782
|
-
*/
|
|
783
|
-
export const COMPILE_WRITTEN_TOOL_RESOURCES = {
|
|
784
|
-
types: new Set([...Object.values(TOOL_RESOURCE_TYPE), CONNECTOR_TOOL_RESOURCE_TYPE]),
|
|
785
|
-
internalToolTypes: new Set([...Object.values(BUILTIN_TOOL_TYPE), HTTP_REQUEST_TOOL_TYPE]),
|
|
786
|
-
};
|
|
787
|
-
/**
|
|
788
|
-
* Does `compile` own this `resources/<id>/resource.json` — would a compile have
|
|
789
|
-
* written it, so one this compile did not write is stale rather than someone
|
|
790
|
-
* else's? Everything it says no to belongs to the designer or to `uip agent
|
|
791
|
-
* refresh` (escalation, mcp, designer-only built-ins, the kinds the SDK cannot
|
|
792
|
-
* derive), and is never deleted.
|
|
793
|
-
*/
|
|
794
|
-
export function compileOwnsResource(body) {
|
|
795
|
-
const kind = body?.$resourceType;
|
|
796
|
-
if (kind === 'context')
|
|
797
|
-
return true;
|
|
798
|
-
if (kind !== 'tool')
|
|
799
|
-
return false;
|
|
800
|
-
const type = String(body?.type);
|
|
801
|
-
if (type === INTERNAL_TOOL_RESOURCE_TYPE) {
|
|
802
|
-
const toolType = body?.properties?.toolType;
|
|
803
|
-
return COMPILE_WRITTEN_TOOL_RESOURCES.internalToolTypes.has(String(toolType));
|
|
804
|
-
}
|
|
805
|
-
return COMPILE_WRITTEN_TOOL_RESOURCES.types.has(type);
|
|
806
|
-
}
|
|
807
|
-
/**
|
|
808
|
-
* The .NET type an Action Center app argument of each field type carries. Studio
|
|
809
|
-
* Web serializes the task payload through `inputSchemaDotnetTypeMapping`, which
|
|
810
|
-
* the platform schema REQUIRES on an `actionCenter` channel; it reads the raw
|
|
811
|
-
* type off the app's action schema, which the SDK does not fetch, so this is
|
|
812
|
-
* the inverse of flow-workbench's `dotNetTypeToJsonSchema` for the scalars and
|
|
813
|
-
* `System.Object` for the two composite types it has no single answer for.
|
|
814
|
-
*/
|
|
815
|
-
const DOTNET_TYPE = {
|
|
816
|
-
string: 'System.String', number: 'System.Double', integer: 'System.Int32',
|
|
817
|
-
boolean: 'System.Boolean', object: 'System.Object', array: 'System.Object',
|
|
818
|
-
};
|
|
819
|
-
/**
|
|
820
|
-
* The folder an app escalation names: the author's `folderPath`, or
|
|
821
|
-
* `solution_folder` when they gave none — an app that ships in this solution.
|
|
822
|
-
*
|
|
823
|
-
* `solution_folder` is the platform's own spelling for that case: it is the
|
|
824
|
-
* `Folder` that `uip solution resources list` reports for a solution-internal
|
|
825
|
-
* app, and what the agents skill tells authors to copy into
|
|
826
|
-
* `channels[].properties.folderName`. The SDK used to write `""`, which no
|
|
827
|
-
* product writer produces (flow-builder-sdk#967). Measured, the two are the
|
|
828
|
-
* same everywhere it counts: the agent tool's binding generator drops both
|
|
829
|
-
* from the App binding (`resolveFolderPathBindingValue`), `uip agent refresh`
|
|
830
|
-
* plus `uip solution resources refresh` derive byte-identical files from
|
|
831
|
-
* either, and a live debug resolves the app in the debug folder for both. So
|
|
832
|
-
* this changes the vocabulary, not the behaviour.
|
|
833
|
-
*/
|
|
834
|
-
function escalationAppFolder(app) {
|
|
835
|
-
return app.folderPath ? app.folderPath : 'solution_folder';
|
|
836
|
-
}
|
|
837
|
-
/**
|
|
838
|
-
* The `resources/<id>/resource.json` an inline agent's ESCALATION needs — the
|
|
839
|
-
* escalation half of what `toolResourceJson` does for a tool, and inert without
|
|
840
|
-
* it for the same reason (#921). Measured 2026-10-05 on alpha with `uip maestro
|
|
841
|
-
* flow debug`: the same agent, told to escalate every claim, answered *"could
|
|
842
|
-
* not escalate: no ExpenseEscalation tool available"* with the node alone, and
|
|
843
|
-
* created the Action Center task with this file beside it.
|
|
844
|
-
*
|
|
845
|
-
* Shape: the uipath-agents skill's agent-level escalation resource (it owns the
|
|
846
|
-
* body), checked field by field against the platform's
|
|
847
|
-
* `currentResourceStorageSchema` — which REQUIRES `description` (top level and
|
|
848
|
-
* channel), `isAgentMemoryEnabled`, `governanceProperties`, `properties`,
|
|
849
|
-
* `inputSchemaDotnetTypeMapping` and `channels[].properties.appVersion`.
|
|
850
|
-
*
|
|
851
|
-
* Unlike a tool, an escalation node CARRIES its configuration, and `uip agent
|
|
852
|
-
* refresh --inline-in-flow` strips it to `source` either way. Writing the file
|
|
853
|
-
* does not change that; it is what makes refresh keep the escalation at all
|
|
854
|
-
* (`Resources: 1` and the App binding in `bindings_v2.json`, against
|
|
855
|
-
* `Resources: 0` without it). The compiled `.flow` keeps the node's full inputs,
|
|
856
|
-
* so `flow validate` is unaffected.
|
|
857
|
-
*/
|
|
858
|
-
function escalationResourceJson(esc, spec) {
|
|
859
|
-
const outcomeMapping = esc.outcomes && esc.outcomes.length
|
|
860
|
-
? Object.fromEntries(esc.outcomes.map((o) => [o, 'continue']))
|
|
861
|
-
: {};
|
|
862
|
-
const common = {
|
|
863
|
-
id: spec.channelId,
|
|
864
|
-
name: 'Channel',
|
|
865
|
-
description: esc.description ?? '',
|
|
866
|
-
outcomeMapping,
|
|
867
|
-
recipients: (esc.recipients ?? []).map((v) => ({ type: 3, value: v })),
|
|
868
|
-
taskTitle: esc.taskTitle ?? '',
|
|
869
|
-
taskTitleV2: {
|
|
870
|
-
type: 'textBuilder',
|
|
871
|
-
tokens: esc.taskTitle ? [{ type: 'simpleText', rawString: esc.taskTitle }] : [],
|
|
872
|
-
},
|
|
873
|
-
labels: esc.labels ?? [],
|
|
874
|
-
argumentProperties: {},
|
|
875
|
-
};
|
|
876
|
-
let channel;
|
|
877
|
-
if (spec.quickFormSchema !== undefined) {
|
|
878
|
-
// The INLINE form: the node's own `schema` (fields + outcomes) is the
|
|
879
|
-
// channel's, and its contract is read off the field directions — what the
|
|
880
|
-
// reviewer is shown (`input`/`inOut`) and what comes back (`output`/`inOut`).
|
|
881
|
-
const fields = esc.fields ?? [];
|
|
882
|
-
const props = (dirs) => Object.fromEntries(fields
|
|
883
|
-
.filter((f) => dirs.includes(f.direction))
|
|
884
|
-
.map((f) => [f.id, formFieldJsonSchema(f.type) ?? { type: 'string' }]));
|
|
885
|
-
channel = {
|
|
886
|
-
...common,
|
|
887
|
-
type: 'actionCenterQuickForm',
|
|
888
|
-
inputSchema: { type: 'object', properties: props(['input', 'inOut']) },
|
|
889
|
-
outputSchema: { type: 'object', properties: props(['output', 'inOut']) },
|
|
890
|
-
properties: {
|
|
891
|
-
schema: spec.quickFormSchema,
|
|
892
|
-
isActionableMessageEnabled: false,
|
|
893
|
-
actionableMessageMetaData: null,
|
|
894
|
-
},
|
|
895
|
-
};
|
|
896
|
-
}
|
|
897
|
-
else {
|
|
898
|
-
const app = esc.app;
|
|
899
|
-
const mapping = (fields) => Object.fromEntries(Object.entries(fields ?? {}).map(([k, t]) => [k, ownEntry(DOTNET_TYPE, t) ?? 'System.Object']));
|
|
900
|
-
channel = {
|
|
901
|
-
...common,
|
|
902
|
-
type: 'actionCenter',
|
|
903
|
-
inputSchema: fieldSchema(app.inputs),
|
|
904
|
-
outputSchema: fieldSchema(app.outputs),
|
|
905
|
-
inputSchemaDotnetTypeMapping: mapping(app.inputs),
|
|
906
|
-
outputSchemaDotnetTypeMapping: mapping(app.outputs),
|
|
907
|
-
properties: {
|
|
908
|
-
// The app's key and name are what `uip agent refresh` turns into the
|
|
909
|
-
// App binding (`name` + `folderPath`) that `uip solution resources
|
|
910
|
-
// refresh` imports; `folderName` is the app's Orchestrator folder.
|
|
911
|
-
resourceKey: app.key,
|
|
912
|
-
appName: app.name,
|
|
913
|
-
folderName: escalationAppFolder(app),
|
|
914
|
-
// The app's deploy version (its action schema's `version`). The SDK does
|
|
915
|
-
// not read the action schema, so this is the platform's own default — the
|
|
916
|
-
// storage migration that introduced the field writes `1` when absent.
|
|
917
|
-
appVersion: app.version ?? 1,
|
|
918
|
-
isActionableMessageEnabled: false,
|
|
919
|
-
actionableMessageMetaData: null,
|
|
920
|
-
},
|
|
921
|
-
};
|
|
922
|
-
}
|
|
923
|
-
return {
|
|
924
|
-
$resourceType: 'escalation',
|
|
925
|
-
id: spec.id,
|
|
926
|
-
name: esc.name,
|
|
927
|
-
description: esc.description ?? '',
|
|
928
|
-
escalationType: 0,
|
|
929
|
-
isAgentMemoryEnabled: false,
|
|
930
|
-
properties: {},
|
|
931
|
-
governanceProperties: { isEscalatedAtRuntime: false },
|
|
932
|
-
isEnabled: true,
|
|
933
|
-
channels: [channel],
|
|
934
|
-
};
|
|
935
|
-
}
|
|
936
|
-
/**
|
|
937
|
-
* The one TARGET handle every tool resource declares: `input`, artifact-typed,
|
|
938
|
-
* fed by the two agent types' `tool` handles. Transcribed from `registry get`
|
|
939
|
-
* (identical across `tool.builtin.*`, `tool.connector.*` and every
|
|
940
|
-
* per-instance kind, 2026-07-31) — the mirror of the agent's own
|
|
941
|
-
* `allowedTargets: [{ nodeType: 'uipath.agent.resource.tool.*' }]`.
|
|
942
|
-
*/
|
|
943
|
-
function toolInputHandle() {
|
|
944
|
-
return {
|
|
945
|
-
position: 'top',
|
|
946
|
-
handles: [{
|
|
947
|
-
id: 'input', type: 'target', handleType: 'artifact', isDefaultForType: true,
|
|
948
|
-
constraints: {
|
|
949
|
-
maxConnections: 1,
|
|
950
|
-
allowedSources: [
|
|
951
|
-
{ nodeType: 'uipath.agent.autonomous', handleId: 'tool' },
|
|
952
|
-
{ nodeType: 'uipath.agent.conversational', handleId: 'tool' },
|
|
953
|
-
],
|
|
954
|
-
validationMessage: "Only Agent's tool handle can connect here",
|
|
955
|
-
},
|
|
956
|
-
}],
|
|
957
|
-
};
|
|
958
|
-
}
|
|
959
|
-
/** A `{ name: type }` field map as the JSON-schema object the platform stores. */
|
|
960
|
-
function fieldSchema(fields) {
|
|
961
|
-
return {
|
|
962
|
-
type: 'object',
|
|
963
|
-
properties: Object.fromEntries(Object.entries(fields ?? {}).map(([k, t]) => [k, { type: t }])),
|
|
964
|
-
};
|
|
965
|
-
}
|
|
966
|
-
/**
|
|
967
|
-
* The definitions for the three platform BUILT-IN tools, transcribed from
|
|
968
|
-
* `uip maestro flow registry get uipath.agent.resource.tool.builtin.<name>`
|
|
969
|
-
* (2026-07-31, version 1.1 all three). These are fixed types — no tenant
|
|
970
|
-
* resource behind them — so unlike every other tool kind the def CAN be
|
|
971
|
-
* bundled.
|
|
972
|
-
*
|
|
973
|
-
* One wrinkle carried faithfully rather than smoothed over: the three defs
|
|
974
|
-
* disagree about where the node's `source` identity lives. `analyzefiles`
|
|
975
|
-
* declares `model.source: true` (the resource-family convention);
|
|
976
|
-
* `summarize`/`batchtransform` have NO model and declare `source` as an
|
|
977
|
-
* ordinary input instead. Both spellings satisfy the platform's stable-UUID
|
|
978
|
-
* requirement, so each def ships the registry's own shape.
|
|
979
|
-
*/
|
|
980
|
-
const BUILTIN_TOOL_DEFS = {
|
|
981
|
-
analyzefiles: {
|
|
982
|
-
nodeType: 'uipath.agent.resource.tool.builtin.analyzefiles',
|
|
983
|
-
version: '1.1',
|
|
984
|
-
category: 'agent.tool.builtin',
|
|
985
|
-
tags: ['agent-tool', 'builtin', 'analyze', 'files', 'attachments'],
|
|
986
|
-
description: 'Extract and analyze content from files',
|
|
987
|
-
sortOrder: 48,
|
|
988
|
-
display: {
|
|
989
|
-
label: 'Analyze Files',
|
|
990
|
-
description: 'Analyze one or more files with an LLM to extract, synthesize, or answer queries about their content.',
|
|
991
|
-
icon: 'paperclip', shape: 'circle',
|
|
992
|
-
},
|
|
993
|
-
handleConfiguration: [toolInputHandle()],
|
|
994
|
-
model: { source: true },
|
|
995
|
-
inputDefinition: {
|
|
996
|
-
type: 'object',
|
|
997
|
-
properties: {
|
|
998
|
-
description: { type: 'string' },
|
|
999
|
-
attachmentsDescription: { type: 'string' },
|
|
1000
|
-
itemsDescription: {
|
|
1001
|
-
type: 'array',
|
|
1002
|
-
items: { type: 'object', properties: { label: { type: 'string' }, value: { type: 'string' } } },
|
|
1003
|
-
},
|
|
1004
|
-
analysisTaskDescription: { type: 'string' },
|
|
1005
|
-
exampleCalls: { type: 'array' },
|
|
1006
|
-
},
|
|
1007
|
-
},
|
|
1008
|
-
inputDefaults: {
|
|
1009
|
-
description: 'Analyze one or more files with an LLM to extract, synthesize, or answer queries about their content.',
|
|
1010
|
-
attachmentsDescription: 'Array of files, documents, images, or other attachments to process',
|
|
1011
|
-
itemsDescription: [],
|
|
1012
|
-
analysisTaskDescription: "The task, question, or instruction for processing the files (e.g., 'summarize this document', 'extract key points', 'what is in this image')",
|
|
1013
|
-
exampleCalls: [],
|
|
1014
|
-
},
|
|
1015
|
-
},
|
|
1016
|
-
summarize: {
|
|
1017
|
-
nodeType: 'uipath.agent.resource.tool.builtin.summarize',
|
|
1018
|
-
version: '1.1',
|
|
1019
|
-
category: 'agent.tool.builtin',
|
|
1020
|
-
tags: ['agent-tool', 'builtin', 'summarize', 'document', 'synthesis', 'citations'],
|
|
1021
|
-
description: 'Synthesize across documents with citations',
|
|
1022
|
-
sortOrder: 48,
|
|
1023
|
-
display: {
|
|
1024
|
-
label: 'Summarize',
|
|
1025
|
-
description: 'Get comprehensive synthesis across up to 1,000 pages with citations.',
|
|
1026
|
-
icon: 'database', shape: 'circle',
|
|
1027
|
-
},
|
|
1028
|
-
handleConfiguration: [toolInputHandle()],
|
|
1029
|
-
inputDefinition: {
|
|
1030
|
-
type: 'object',
|
|
1031
|
-
properties: {
|
|
1032
|
-
description: { type: 'string' },
|
|
1033
|
-
source: { type: 'string' },
|
|
1034
|
-
query: { type: 'object' },
|
|
1035
|
-
fileExtension: { type: 'object', properties: { value: { type: 'string' } } },
|
|
1036
|
-
citationMode: { type: 'object', properties: { value: { type: 'string' } } },
|
|
1037
|
-
},
|
|
1038
|
-
},
|
|
1039
|
-
inputDefaults: {
|
|
1040
|
-
description: 'Get comprehensive synthesis across up to 1,000 pages with citations.',
|
|
1041
|
-
source: '',
|
|
1042
|
-
query: { mode: 'prompt', textValue: '', promptValue: '', argumentPath: '' },
|
|
1043
|
-
fileExtension: { value: 'pdf' },
|
|
1044
|
-
citationMode: { value: 'inline' },
|
|
1045
|
-
},
|
|
1046
|
-
},
|
|
1047
|
-
batchtransform: {
|
|
1048
|
-
nodeType: 'uipath.agent.resource.tool.builtin.batchtransform',
|
|
1049
|
-
version: '1.1',
|
|
1050
|
-
category: 'agent.tool.builtin',
|
|
1051
|
-
tags: ['agent-tool', 'builtin', 'batch-transform', 'csv', 'transform'],
|
|
1052
|
-
description: 'Process and transform CSV files',
|
|
1053
|
-
sortOrder: 48,
|
|
1054
|
-
display: {
|
|
1055
|
-
label: 'Batch Transform',
|
|
1056
|
-
description: 'Process and transform CSV files.',
|
|
1057
|
-
icon: 'table', shape: 'circle',
|
|
1058
|
-
},
|
|
1059
|
-
handleConfiguration: [toolInputHandle()],
|
|
1060
|
-
inputDefinition: {
|
|
1061
|
-
type: 'object',
|
|
1062
|
-
properties: {
|
|
1063
|
-
description: { type: 'string' },
|
|
1064
|
-
source: { type: 'string' },
|
|
1065
|
-
query: { type: 'object' },
|
|
1066
|
-
outputColumns: { type: 'array' },
|
|
1067
|
-
webSearchGrounding: { type: 'object', properties: { value: { type: 'string' } } },
|
|
1068
|
-
},
|
|
1069
|
-
},
|
|
1070
|
-
inputDefaults: {
|
|
1071
|
-
description: 'Process and transform CSV files.',
|
|
1072
|
-
source: '',
|
|
1073
|
-
query: { mode: 'prompt', textValue: '', promptValue: '', argumentPath: '' },
|
|
1074
|
-
outputColumns: [],
|
|
1075
|
-
webSearchGrounding: { value: 'enabled' },
|
|
1076
|
-
},
|
|
1077
|
-
},
|
|
1078
|
-
};
|
|
1079
|
-
/**
|
|
1080
|
-
* The definition for one CONNECTOR-operation tool, derived from the same
|
|
1081
|
-
* curated-library entry `connector(…)` resolves — measured equivalence, not
|
|
1082
|
-
* hope: the library's `.v1def.json` `model` (bindings + the
|
|
1083
|
-
* connectorKey/objectName/method context) is byte-identical to what
|
|
1084
|
-
* `registry get uipath.agent.resource.tool.connector.<key>.<op>` returns for
|
|
1085
|
-
* the same operation, minus `source: true` (checked 2026-07-31 against
|
|
1086
|
-
* `uipath-uipath-airdk/web-search`). What the tool era changes is the frame:
|
|
1087
|
-
* the node type moves one namespace over, the category gains the
|
|
1088
|
-
* `agent.tool.` prefix over the v1def's own (which carries the connector's
|
|
1089
|
-
* numeric id — `connector.196640` → `agent.tool.connector.196640`), the
|
|
1090
|
-
* handle becomes the tool artifact handle, and `sortOrder` is the registry's
|
|
1091
|
-
* 565.
|
|
1092
|
-
*/
|
|
1093
|
-
function connectorToolDef(rc) {
|
|
1094
|
-
const v1 = rc.v1def ?? {};
|
|
1095
|
-
const suffix = String(rc.nodeType).replace(/^uipath\.connector\./, '');
|
|
1096
|
-
return {
|
|
1097
|
-
nodeType: `uipath.agent.resource.tool.connector.${suffix}`,
|
|
1098
|
-
version: v1.version ?? '1.0.0',
|
|
1099
|
-
category: `agent.tool.${v1.category ?? 'connector'}`,
|
|
1100
|
-
tags: ['agent-tool', 'connector', 'activity'],
|
|
1101
|
-
sortOrder: 565,
|
|
1102
|
-
display: { ...jsonClone(v1.display ?? {}), shape: 'circle' },
|
|
1103
|
-
handleConfiguration: [toolInputHandle()],
|
|
1104
|
-
model: { ...jsonClone(v1.model ?? {}), source: true },
|
|
1105
|
-
inputDefinition: { type: 'object', properties: {} },
|
|
1106
|
-
inputDefaults: {},
|
|
1107
|
-
};
|
|
1108
|
-
}
|
|
1109
|
-
/** The folder key a connection with no folder binding carries in its resource.json. */
|
|
1110
|
-
const NO_FOLDER_KEY = '00000000-0000-0000-0000-000000000000';
|
|
1111
|
-
/**
|
|
1112
|
-
* The library's operation description without the `(<Connector Name>) ` prefix
|
|
1113
|
-
* the v1def's `display.description` carries — the model reads this text, and
|
|
1114
|
-
* the designer's own resource.json has the bare sentence.
|
|
1115
|
-
*/
|
|
1116
|
-
function connectorToolDescription(rc) {
|
|
1117
|
-
const text = String(rc.v1def?.display?.description ?? rc.entry?.display?.description ?? '');
|
|
1118
|
-
const prefix = `(${rc.connectorName}) `;
|
|
1119
|
-
return rc.connectorName && text.startsWith(prefix) ? text.slice(prefix.length) : text;
|
|
1120
|
-
}
|
|
1121
|
-
/**
|
|
1122
|
-
* An agent-decided value: `{{prompt: "<what the model is told>"}}`, byte for byte
|
|
1123
|
-
* what the product writes — `uip maestro flow node configure` (UiPath/cli
|
|
1124
|
-
* `connector-service.ts`, `` `{{prompt: "${f.description || f.displayName || f.name}"}}` ``)
|
|
1125
|
-
* and the designer's `PROMPT_WRAPPER` (@uipath/maestro-sdk) both splice the text RAW.
|
|
1126
|
-
* The chip format has no escape: the product's reader (`PROMPT_UNWRAPPER`) slices
|
|
1127
|
-
* off the fixed `{{prompt:` / `}}` frame and strips one pair of outer quotes, so
|
|
1128
|
-
* any text — `"`, `\`, a newline, a `}}` — round-trips. Escaping it here made the
|
|
1129
|
-
* model read literal backslashes. FB011 registers this as the one raw chip splice.
|
|
1130
|
-
*/
|
|
1131
|
-
function promptChip(f) {
|
|
1132
|
-
const text = f.description || f.displayName || f.name;
|
|
1133
|
-
return `{{prompt: "${text}"}}`;
|
|
1134
|
-
}
|
|
1135
|
-
const isPromptChip = (v) => typeof v === 'string' && v.startsWith('{{prompt');
|
|
1136
|
-
/** The text inside `{{prompt: "…"}}`, read the way the product reads it (maestro-sdk PROMPT_UNWRAPPER); `''` when `value` is not a chip. */
|
|
1137
|
-
function chipText(value) {
|
|
1138
|
-
if (!(value.startsWith('{{prompt:') && value.endsWith('}}')))
|
|
1139
|
-
return '';
|
|
1140
|
-
const inner = value.slice(9, -2).trim();
|
|
1141
|
-
const quoted = (inner.startsWith('"') && inner.endsWith('"')) || (inner.startsWith("'") && inner.endsWith("'"));
|
|
1142
|
-
return quoted ? inner.slice(1, -1) : inner;
|
|
1143
|
-
}
|
|
1144
|
-
/** A field's enum as `{ name, value }` rows, the platform's spelling; `undefined` when it has none. */
|
|
1145
|
-
function enumRows(raw) {
|
|
1146
|
-
if (!Array.isArray(raw) || raw.length === 0)
|
|
1147
|
-
return undefined;
|
|
1148
|
-
return raw.map((e) => (typeof e === 'string'
|
|
1149
|
-
? { name: e, value: e }
|
|
1150
|
-
: { name: String(e?.name ?? e?.value ?? ''), value: String(e?.value ?? e?.name ?? '') }));
|
|
1151
|
-
}
|
|
1152
|
-
/**
|
|
1153
|
-
* The `inputs.detail` of a connector-tool node — the same key set a
|
|
1154
|
-
* `connector()` step's node carries, with the request buckets filled the way
|
|
1155
|
-
* `uip maestro flow node configure` fills them for a `tool.connector.*` node
|
|
1156
|
-
* (uipcli `buildAgentToolParamsFromMetadata`): every BODY field is
|
|
1157
|
-
* agent-decided (`{{prompt: "<description>"}}`) except a single-value enum,
|
|
1158
|
-
* which is its one constant (the model would otherwise guess — `google` for
|
|
1159
|
-
* `GoogleCustomSearch`); a QUERY/PATH parameter with a declared default sends
|
|
1160
|
-
* it, and otherwise is agent-decided too.
|
|
1161
|
-
*/
|
|
1162
|
-
function connectorToolDetail(rc, conn, folder, seed) {
|
|
1163
|
-
const params = Array.isArray(rc.entry?.operation?.parameters) ? rc.entry.operation.parameters : [];
|
|
1164
|
-
const buckets = { body: {}, query: {}, path: {} };
|
|
1165
|
-
for (const f of rc.fields ?? []) {
|
|
1166
|
-
const loc = f.location ?? 'body';
|
|
1167
|
-
if (loc === 'body') {
|
|
1168
|
-
const only = enumRows(f.enum);
|
|
1169
|
-
buckets.body[f.name] = only && only.length === 1 ? only[0].value : promptChip(f);
|
|
1170
|
-
}
|
|
1171
|
-
else if (loc === 'query' || loc === 'path') {
|
|
1172
|
-
const dflt = params.find((p) => p?.name === f.name)?.defaultValue;
|
|
1173
|
-
buckets[loc][f.name] = dflt !== undefined && dflt !== null && dflt !== '' ? dflt : promptChip(f);
|
|
1174
|
-
}
|
|
1175
|
-
}
|
|
1176
|
-
const parts = rc.multipartParts ?? [];
|
|
1177
|
-
return {
|
|
1178
|
-
connector: rc.connectorKey,
|
|
1179
|
-
method: transportHttpMethod(rc.httpMethod),
|
|
1180
|
-
endpoint: rc.endpoint,
|
|
1181
|
-
...(rc.objectName ? { objectName: rc.objectName } : {}),
|
|
1182
|
-
inputMetadata: parts.length
|
|
1183
|
-
? { type: 'multipart', multipart: { bodyFieldName: multipartBodyFieldName(parts) } }
|
|
1184
|
-
: {},
|
|
1185
|
-
errorState: { hasError: false },
|
|
1186
|
-
telemetryData: { connectorKey: rc.connectorKey, connectorName: rc.connectorName },
|
|
1187
|
-
configuration: buildConfiguration(rc.entry, rc.version, {}, seed),
|
|
1188
|
-
...(rc.activityTypeId ? { uiPathActivityTypeId: rc.activityTypeId } : {}),
|
|
1189
|
-
...(conn ? { connectionId: conn, connectionResourceId: conn } : {}),
|
|
1190
|
-
...(folder ? { connectionFolderKey: folder } : {}),
|
|
1191
|
-
bodyParameters: buckets.body,
|
|
1192
|
-
...(Object.keys(buckets.path).length ? { pathParameters: buckets.path } : {}),
|
|
1193
|
-
...(Object.keys(buckets.query).length ? { queryParameters: buckets.query } : {}),
|
|
1194
|
-
...(parts.length ? { multipartParameters: parts.map((p) => ({ name: p.name, dataType: p.dataType })) } : {}),
|
|
1195
|
-
};
|
|
1196
|
-
}
|
|
1197
|
-
/** Walk (creating) the object chain for a dotted field name; returns the parent schema and the leaf key. */
|
|
1198
|
-
function nestedSlot(root, name) {
|
|
1199
|
-
const segments = name.split('.');
|
|
1200
|
-
let parent = root;
|
|
1201
|
-
for (const seg of segments.slice(0, -1)) {
|
|
1202
|
-
let child = parent.properties[seg];
|
|
1203
|
-
if (!child || child.type !== 'object') {
|
|
1204
|
-
child = { type: 'object', properties: {} };
|
|
1205
|
-
parent.properties[seg] = child;
|
|
1206
|
-
}
|
|
1207
|
-
parent = child;
|
|
1208
|
-
}
|
|
1209
|
-
return { parent, leaf: segments[segments.length - 1] };
|
|
1210
|
-
}
|
|
1211
|
-
/**
|
|
1212
|
-
* The `resources/<id>/resource.json` of a CONNECTOR tool (#867) — the file the
|
|
1213
|
-
* agent runtime registers the tool from, and the one that puts the connection
|
|
1214
|
-
* under the solution's Resources.
|
|
1215
|
-
*
|
|
1216
|
-
* The shape is uipcli's `mapConnectorToolToResource` (the writer behind `uip
|
|
1217
|
-
* agent refresh --inline-in-flow`, itself ported from flow-workbench's
|
|
1218
|
-
* `agent-connector-tool` mapper), in its metadata-enriched form: field types,
|
|
1219
|
-
* enums, `required` and the response fields come from the curated library,
|
|
1220
|
-
* which carries the same declarations that command fetches from Integration
|
|
1221
|
-
* Service. Where that writer and a Studio Web export differ, this follows the
|
|
1222
|
-
* writer: `enum`/`oneOf` only on agent-decided fields (a static field with an
|
|
1223
|
-
* enum breaks the runtime's model build), no `additionalProperties`, and
|
|
1224
|
-
* array-of-object response fields (`results[*]`) left out.
|
|
1225
|
-
*
|
|
1226
|
-
* The values come from the node's own `detail` — the same source refresh reads —
|
|
1227
|
-
* so a later refresh rewrites this file to itself.
|
|
1228
|
-
*/
|
|
1229
|
-
function connectorToolResourceJson(ctx, id) {
|
|
1230
|
-
const { rc, detail } = ctx;
|
|
1231
|
-
const valueOf = (raw) => raw !== null && typeof raw === 'object' ? JSON.stringify(raw) : String(raw ?? '');
|
|
1232
|
-
const bucketOf = { body: 'bodyParameters', query: 'queryParameters', path: 'pathParameters' };
|
|
1233
|
-
const fields = [];
|
|
1234
|
-
for (const f of rc.fields ?? []) {
|
|
1235
|
-
const loc = f.location ?? 'body';
|
|
1236
|
-
if (loc === 'multipart')
|
|
1237
|
-
continue;
|
|
1238
|
-
const value = valueOf(detail[bucketOf[loc]]?.[f.name]);
|
|
1239
|
-
fields.push({
|
|
1240
|
-
name: f.name,
|
|
1241
|
-
fieldLocation: loc,
|
|
1242
|
-
type: f.type || 'string',
|
|
1243
|
-
...(loc === 'body' && enumRows(f.enum)
|
|
1244
|
-
? { enumValues: enumRows(f.enum) } : {}),
|
|
1245
|
-
required: f.required === true,
|
|
1246
|
-
description: f.description ?? '',
|
|
1247
|
-
...(f.displayName ? { displayName: f.displayName } : {}),
|
|
1248
|
-
value,
|
|
1249
|
-
isPrompt: isPromptChip(value),
|
|
1250
|
-
});
|
|
1251
|
-
}
|
|
1252
|
-
for (const part of (detail.multipartParameters ?? [])) {
|
|
1253
|
-
fields.push({
|
|
1254
|
-
name: part.name, fieldLocation: 'multipart', type: part.dataType || 'string',
|
|
1255
|
-
required: false, description: '', value: '{{prompt}}', isPrompt: true,
|
|
1256
|
-
});
|
|
1257
|
-
}
|
|
1258
|
-
const inputSchema = { type: 'object', properties: {} };
|
|
1259
|
-
for (const f of fields) {
|
|
1260
|
-
if (f.fieldLocation === 'multipart')
|
|
1261
|
-
continue;
|
|
1262
|
-
const { parent, leaf } = nestedSlot(inputSchema, f.name);
|
|
1263
|
-
const prop = { type: f.type, title: f.displayName ?? leaf, description: f.description };
|
|
1264
|
-
if (f.isPrompt && f.enumValues) {
|
|
1265
|
-
prop.enum = f.enumValues.map((e) => e.value);
|
|
1266
|
-
prop.oneOf = f.enumValues.map((e) => ({ const: e.value, title: e.name }));
|
|
1267
|
-
}
|
|
1268
|
-
parent.properties[leaf] = prop;
|
|
1269
|
-
if (f.required)
|
|
1270
|
-
(parent.required ??= []).push(leaf);
|
|
1271
|
-
}
|
|
1272
|
-
const outputSchema = { type: 'object', properties: {}, definitions: {} };
|
|
1273
|
-
for (const f of (rc.entry?.outputSchema?.fields ?? [])) {
|
|
1274
|
-
if (typeof f?.name !== 'string' || f.name.includes('[*]'))
|
|
1275
|
-
continue;
|
|
1276
|
-
const { parent, leaf } = nestedSlot(outputSchema, f.name);
|
|
1277
|
-
parent.properties[leaf] = {
|
|
1278
|
-
type: f.type || 'string',
|
|
1279
|
-
title: f.displayName ?? leaf,
|
|
1280
|
-
...(f.description ? { description: f.description } : {}),
|
|
1281
|
-
};
|
|
1282
|
-
}
|
|
1283
|
-
const multipart = detail.inputMetadata?.type === 'multipart';
|
|
1284
|
-
return {
|
|
1285
|
-
$resourceType: 'tool',
|
|
1286
|
-
name: ctx.name,
|
|
1287
|
-
description: ctx.description,
|
|
1288
|
-
location: 'external',
|
|
1289
|
-
type: CONNECTOR_TOOL_RESOURCE_TYPE,
|
|
1290
|
-
inputSchema,
|
|
1291
|
-
outputSchema,
|
|
1292
|
-
settings: {},
|
|
1293
|
-
properties: {
|
|
1294
|
-
toolPath: rc.endpoint ?? '',
|
|
1295
|
-
objectName: rc.objectName ?? '',
|
|
1296
|
-
toolDisplayName: ctx.name,
|
|
1297
|
-
toolDescription: ctx.description,
|
|
1298
|
-
// The operation KEY (`GETBYID` for a get-by-id), not the transport verb:
|
|
1299
|
-
// the runtime sends it to Integration Service to pick the operation.
|
|
1300
|
-
method: rc.httpMethod,
|
|
1301
|
-
connection: {
|
|
1302
|
-
id: detail.connectionId ?? '',
|
|
1303
|
-
name: rc.connectorName,
|
|
1304
|
-
elementInstanceId: 0,
|
|
1305
|
-
apiBaseUri: '',
|
|
1306
|
-
state: 'enabled',
|
|
1307
|
-
isDefault: false,
|
|
1308
|
-
connector: { key: rc.connectorKey, name: rc.connectorName, image: '' },
|
|
1309
|
-
folder: { key: detail.connectionFolderKey ?? NO_FOLDER_KEY, path: '' },
|
|
1310
|
-
},
|
|
1311
|
-
parameters: fields.map((f, i) => ({
|
|
1312
|
-
name: f.name,
|
|
1313
|
-
displayName: f.displayName ?? f.name,
|
|
1314
|
-
type: f.type,
|
|
1315
|
-
fieldLocation: f.fieldLocation,
|
|
1316
|
-
value: f.value,
|
|
1317
|
-
// uipcli's fallback: an undescribed agent-decided field keeps the chip's own text.
|
|
1318
|
-
description: f.description || (f.isPrompt ? chipText(f.value) : ''),
|
|
1319
|
-
position: f.required ? 'primary' : 'secondary',
|
|
1320
|
-
sortOrder: i + 1,
|
|
1321
|
-
required: f.required,
|
|
1322
|
-
fieldVariant: f.isPrompt ? 'dynamic' : 'static',
|
|
1323
|
-
isCascading: false,
|
|
1324
|
-
dynamic: f.isPrompt,
|
|
1325
|
-
enumValues: f.enumValues ?? null,
|
|
1326
|
-
loadReferenceOptionsByDefault: null,
|
|
1327
|
-
dynamicBehavior: [],
|
|
1328
|
-
reference: null,
|
|
1329
|
-
})),
|
|
1330
|
-
bodyStructure: multipart
|
|
1331
|
-
? { contentType: 'multipart', jsonBodySection: detail.inputMetadata.multipart?.bodyFieldName ?? 'body' }
|
|
1332
|
-
: { contentType: 'json' },
|
|
1333
|
-
},
|
|
1334
|
-
guardrail: { policies: [] },
|
|
1335
|
-
iconUrl: ctx.iconUrl,
|
|
1336
|
-
id,
|
|
1337
|
-
isEnabled: true,
|
|
1338
|
-
};
|
|
1339
|
-
}
|
|
1340
|
-
/**
|
|
1341
|
-
* The tool kinds that wrap a DEPLOYED tenant resource, and everything that
|
|
1342
|
-
* differs between them. Transcribed from `registry get` over one live example
|
|
1343
|
-
* of each (2026-07-31): the five agree on the whole frame (`bpmn:ServiceTask`,
|
|
1344
|
-
* `version: 'v2'`, `section: 'Published'`, the `<bindings.name>` /
|
|
1345
|
-
* `<bindings.folderPath>` context, `source: true`, version `1.0.0`, the tool
|
|
1346
|
-
* artifact handle, `tags: ['agent-tool']`) and differ in exactly these rows.
|
|
1347
|
-
*/
|
|
1348
|
-
const PROCESS_TOOL_META = {
|
|
1349
|
-
process: { ns: 'process', serviceType: 'Orchestrator.StartJob', subType: 'Process', orchestratorType: 'process', category: 'agent.tool.rpa', icon: 'rpa', sortOrder: 550 },
|
|
1350
|
-
api: { ns: 'api', serviceType: 'Orchestrator.ExecuteApiWorkflowAsync', subType: 'Api', orchestratorType: 'api', category: 'agent.tool.api', icon: 'api', sortOrder: 555 },
|
|
1351
|
-
maestro: { ns: 'processorchestration', serviceType: 'Orchestrator.StartAgenticProcess', subType: 'ProcessOrchestration', orchestratorType: 'processorchestration', category: 'agent.tool.agentic-process', icon: 'agentic-process', sortOrder: 558 },
|
|
1352
|
-
flow: { ns: 'flow', serviceType: 'Orchestrator.StartFlowProcess', subType: 'Flow', orchestratorType: 'flow', category: 'agent.tool.flow', icon: 'flow-project', sortOrder: 559 },
|
|
1353
|
-
agent: { ns: 'agent', serviceType: 'Orchestrator.StartAgentJob', subType: 'Agent', orchestratorType: 'agent', category: 'agent.tool.agent', icon: 'autonomous-agent', sortOrder: 560 },
|
|
1354
|
-
// A published FUNCTION as an agent tool (Phase 5): same shape as its
|
|
1355
|
-
// siblings, with the function family's service type and binding subtype
|
|
1356
|
-
// (see `publishedFunctionDef`, transcribed from the platform's manifest).
|
|
1357
|
-
function: { ns: 'function', serviceType: 'Orchestrator.ExecuteFunctionAsync', subType: 'Function', orchestratorType: 'function', category: 'agent.tool.function', icon: 'function', sortOrder: 557 },
|
|
1358
|
-
};
|
|
1359
|
-
/**
|
|
1360
|
-
* The definition for one per-instance tool resource. Like `rpaWorkflowDef`
|
|
1361
|
-
* and its siblings — whose model half this shares verb for verb — the def
|
|
1362
|
-
* cannot be bundled: the node type carries the deployed resource's own key,
|
|
1363
|
-
* and the contract (`inputSchema`/`outputSchema` in `inputDefaults`, which is
|
|
1364
|
-
* where the registry's tenant-synthesized copy puts it) comes from the author
|
|
1365
|
-
* because offline nothing else can know it.
|
|
1366
|
-
*/
|
|
1367
|
-
function processToolDef(kind, spec) {
|
|
1368
|
-
const meta = PROCESS_TOOL_META[kind];
|
|
1369
|
-
const inputProps = Object.keys(spec.inputs ?? {});
|
|
1370
|
-
return {
|
|
1371
|
-
nodeType: `uipath.agent.resource.tool.${meta.ns}.${spec.key}`,
|
|
1372
|
-
version: '1.0.0',
|
|
1373
|
-
category: meta.category,
|
|
1374
|
-
tags: ['agent-tool'],
|
|
1375
|
-
sortOrder: meta.sortOrder,
|
|
1376
|
-
display: { label: spec.name, icon: meta.icon, shape: 'circle' },
|
|
1377
|
-
handleConfiguration: [toolInputHandle()],
|
|
1378
|
-
model: {
|
|
1379
|
-
type: 'bpmn:ServiceTask',
|
|
1380
|
-
serviceType: meta.serviceType,
|
|
1381
|
-
version: 'v2',
|
|
1382
|
-
section: 'Published',
|
|
1383
|
-
bindings: {
|
|
1384
|
-
resource: 'process',
|
|
1385
|
-
resourceSubType: meta.subType,
|
|
1386
|
-
resourceKey: `${spec.folderPath}.${spec.name}`,
|
|
1387
|
-
orchestratorType: meta.orchestratorType,
|
|
1388
|
-
values: [
|
|
1389
|
-
{ name: 'name', propertyAttribute: 'name', default: spec.name },
|
|
1390
|
-
{ name: 'folderPath', propertyAttribute: 'folderPath', default: spec.folderPath },
|
|
1391
|
-
],
|
|
1392
|
-
},
|
|
1393
|
-
context: [
|
|
1394
|
-
{ name: 'name', type: 'string', value: '<bindings.name>' },
|
|
1395
|
-
{ name: 'folderPath', type: 'string', value: '<bindings.folderPath>' },
|
|
1396
|
-
{ name: '_label', type: 'string', value: spec.name },
|
|
1397
|
-
],
|
|
1398
|
-
source: true,
|
|
1399
|
-
},
|
|
1400
|
-
// The registry's per-tenant copy declares the resource's arguments here
|
|
1401
|
-
// when it can read the deployed resource; offline the author's declaration
|
|
1402
|
-
// is the only source (`{}` when none — the registry's own empty shape).
|
|
1403
|
-
inputDefinition: inputProps.length ? fieldSchema(spec.inputs) : {},
|
|
1404
|
-
inputDefaults: {
|
|
1405
|
-
...Object.fromEntries(inputProps.map((k) => [k, null])),
|
|
1406
|
-
inputSchema: fieldSchema(spec.inputs),
|
|
1407
|
-
outputSchema: fieldSchema(spec.returns),
|
|
1408
|
-
},
|
|
1409
|
-
};
|
|
1410
|
-
}
|
|
1411
|
-
/**
|
|
1412
|
-
* The definition for one published IxP project AS A TOOL.
|
|
1413
|
-
*
|
|
1414
|
-
* The same registry family as `ixpExtractDef`'s one namespace over, and the
|
|
1415
|
-
* comparison that prices it: this def has **no `model` key at all** — which is
|
|
1416
|
-
* not special, it is the shape `tool.builtin.summarize` and
|
|
1417
|
-
* `tool.builtin.batchtransform` already carry. So an IxP tool needs no bindings
|
|
1418
|
-
* and no resource identity beyond the node type; the agent's own resource list
|
|
1419
|
-
* is what points at it. (G16 deferred this family as "`model: null`" — measured
|
|
1420
|
-
* again on 2026-07-31 with a fresh registry cache, the key is absent, and the
|
|
1421
|
-
* only genuinely new thing is the 6-field `inputDefaults` below.)
|
|
1422
|
-
*
|
|
1423
|
-
* Transcribed from `registry get uipath.agent.resource.tool.ixp.<slug>.<projectId>`
|
|
1424
|
-
* (2026-07-31, post-`pull --force`). Note the tail is the project's OWN id here,
|
|
1425
|
-
* where an extraction STEP's type carries the project VERSION id.
|
|
1426
|
-
*/
|
|
1427
|
-
function ixpToolDef(spec) {
|
|
1428
|
-
const slug = spec.name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
|
|
1429
|
-
const attachmentDescription = spec.attachment?.description ?? 'File to extract data from.';
|
|
1430
|
-
return {
|
|
1431
|
-
nodeType: `uipath.agent.resource.tool.ixp.${slug}.${spec.projectId}`,
|
|
1432
|
-
version: '1.0.0',
|
|
1433
|
-
category: 'agent.tool.ixp',
|
|
1434
|
-
tags: ['agent-tool', 'ixp', 'document-understanding'],
|
|
1435
|
-
description: '',
|
|
1436
|
-
sortOrder: 575,
|
|
1437
|
-
display: {
|
|
1438
|
-
label: spec.name,
|
|
1439
|
-
description: spec.description ?? '',
|
|
1440
|
-
icon: 'file-text',
|
|
1441
|
-
shape: 'circle',
|
|
1442
|
-
},
|
|
1443
|
-
handleConfiguration: [toolInputHandle()],
|
|
1444
|
-
form: {
|
|
1445
|
-
id: 'ixp-tool-form',
|
|
1446
|
-
title: 'IXP Tool',
|
|
1447
|
-
sections: [{
|
|
1448
|
-
id: 'ixp-settings',
|
|
1449
|
-
title: 'IXP Settings',
|
|
1450
|
-
collapsible: true,
|
|
1451
|
-
defaultExpanded: true,
|
|
1452
|
-
fields: [
|
|
1453
|
-
{ name: 'inputs.name', type: 'text', label: 'Name' },
|
|
1454
|
-
{ name: 'inputs.description', type: 'textarea', label: 'Description', placeholder: 'Enter tool description' },
|
|
1455
|
-
{
|
|
1456
|
-
name: 'inputs.versionTag', type: 'select', label: 'Model',
|
|
1457
|
-
placeholder: 'Select a version tag',
|
|
1458
|
-
dataSource: {
|
|
1459
|
-
type: 'fetch',
|
|
1460
|
-
url: `uipath://ixp/projects/${spec.projectId}/tags`,
|
|
1461
|
-
method: 'GET',
|
|
1462
|
-
transform: 'data.map(tag => ({ label: tag.name, value: tag.name }))',
|
|
1463
|
-
},
|
|
1464
|
-
},
|
|
1465
|
-
{
|
|
1466
|
-
name: 'inputs.attachmentConfig', type: 'custom', component: 'ixp-attachment-editor',
|
|
1467
|
-
label: '', componentProps: { defaultDescription: attachmentDescription },
|
|
1468
|
-
},
|
|
1469
|
-
],
|
|
1470
|
-
}],
|
|
1471
|
-
},
|
|
1472
|
-
// The tool namespace's OWN `inputDefinition` — the same 11-property
|
|
1473
|
-
// catalogue, but with `required: ["fileRef"]` and no `minLength`s, because a
|
|
1474
|
-
// tool has no model or folder to resolve at design time (measured: the
|
|
1475
|
-
// extractor's and the tool's copies are NOT equal, even post-pull) — plus
|
|
1476
|
-
// ONE deliberate widening, three keys.
|
|
1477
|
-
//
|
|
1478
|
-
// The registry's copy declares neither `name` nor `description`, and its own
|
|
1479
|
-
// `form` binds `inputs.name` and `inputs.description` as the tool's first two
|
|
1480
|
-
// fields, so a node that omits them is a tool with no name. `source` is the
|
|
1481
|
-
// third: the platform's validator requires a stable uuid there on every
|
|
1482
|
-
// agent-resource node (MST-9265), and its own model-less builtin tool defs
|
|
1483
|
-
// declare it as an input for exactly that reason. Widening is lesson 42's
|
|
1484
|
-
// other branch, taken here because — unlike the extractor's `description` —
|
|
1485
|
-
// these three are load-bearing rather than inert.
|
|
1486
|
-
inputDefinition: {
|
|
1487
|
-
...jsonClone(IXP_DEF.toolInputDefinition),
|
|
1488
|
-
properties: {
|
|
1489
|
-
...jsonClone(IXP_DEF.toolInputDefinition.properties),
|
|
1490
|
-
source: { type: 'string' },
|
|
1491
|
-
name: { type: 'string' },
|
|
1492
|
-
description: { type: 'string' },
|
|
1493
|
-
},
|
|
1494
|
-
},
|
|
1495
|
-
inputDefaults: {
|
|
1496
|
-
name: spec.name,
|
|
1497
|
-
description: spec.description ?? '',
|
|
1498
|
-
projectName: spec.name,
|
|
1499
|
-
projectId: spec.projectId,
|
|
1500
|
-
versionTag: spec.versionTag ?? '',
|
|
1501
|
-
attachmentConfig: {
|
|
1502
|
-
mode: spec.attachment?.mode ?? 'prompt',
|
|
1503
|
-
description: attachmentDescription,
|
|
1504
|
-
argumentPath: spec.attachment?.argumentPath ?? '',
|
|
1505
|
-
},
|
|
1506
|
-
},
|
|
1507
|
-
};
|
|
1508
|
-
}
|
|
1509
|
-
/**
|
|
1510
|
-
* The definition for an ESCALATION — the `coded-action-app` variant, which is
|
|
1511
|
-
* the one this tenant's registry serves. Transcribed from `registry get
|
|
1512
|
-
* uipath.agent.resource.escalation.coded-action-app` (2026-07-31, v1.1).
|
|
1513
|
-
*
|
|
1514
|
-
* Node-type note, because it is the one place this card had to choose between
|
|
1515
|
-
* eras: the deployed corpus (2026-05-22) carries the BARE
|
|
1516
|
-
* `uipath.agent.resource.escalation`, today's registry resolves only the
|
|
1517
|
-
* suffixed variants, and the agent definition's own handle accepts both
|
|
1518
|
-
* (`allowedTargets: [{escalation}, {escalation.*}]`). New emission follows
|
|
1519
|
-
* what the registry can still answer for.
|
|
1520
|
-
*/
|
|
1521
|
-
function escalationDef() {
|
|
1522
|
-
return {
|
|
1523
|
-
nodeType: 'uipath.agent.resource.escalation.coded-action-app',
|
|
1524
|
-
version: '1.1',
|
|
1525
|
-
category: 'agent',
|
|
1526
|
-
tags: ['agentic', 'ai', 'agent', 'escalation'],
|
|
1527
|
-
description: 'Escalate to a human for review or action',
|
|
1528
|
-
sortOrder: 5,
|
|
1529
|
-
display: {
|
|
1530
|
-
label: 'Action App Escalation',
|
|
1531
|
-
description: 'Escalation point for human intervention',
|
|
1532
|
-
icon: 'user-round', shape: 'circle',
|
|
1533
|
-
},
|
|
1534
|
-
handleConfiguration: [
|
|
1535
|
-
{
|
|
1536
|
-
position: 'bottom',
|
|
1537
|
-
handles: [{
|
|
1538
|
-
id: 'input', type: 'target', handleType: 'artifact',
|
|
1539
|
-
constraints: {
|
|
1540
|
-
maxConnections: 1,
|
|
1541
|
-
allowedSources: [
|
|
1542
|
-
{ nodeType: 'uipath.agent.autonomous', handleId: 'escalation' },
|
|
1543
|
-
{ nodeType: 'uipath.agent.conversational', handleId: 'escalation' },
|
|
1544
|
-
],
|
|
1545
|
-
validationMessage: "Only Agent's escalation handle can connect here",
|
|
1546
|
-
},
|
|
1547
|
-
}],
|
|
1548
|
-
},
|
|
1549
|
-
],
|
|
1550
|
-
model: { source: true },
|
|
1551
|
-
// ONE deliberate widening over the registry's copy, documented rather than
|
|
1552
|
-
// silent: the registry's inputDefinition declares only {name, description,
|
|
1553
|
-
// type} while its own inputDefaults — and every deployed escalation node in
|
|
1554
|
-
// the corpus — carry six more keys (app, recipients, outcomeMapping,
|
|
1555
|
-
// _additionalProps, _notifications, _appInputs). A def that under-declares
|
|
1556
|
-
// what the node carries makes a validator flag OUR OWN emission — it sets
|
|
1557
|
-
// inputs its type does not declare (measured: v1's deployed escalation
|
|
1558
|
-
// artifact draws the same six warnings). Declaring them keeps
|
|
1559
|
-
// the node checkable, which is the same reason CX's context def carries a
|
|
1560
|
-
// full schema (its comment: "without it a misspelled field reaches the
|
|
1561
|
-
// tenant unremarked").
|
|
1562
|
-
inputDefinition: {
|
|
1563
|
-
type: 'object',
|
|
1564
|
-
properties: {
|
|
1565
|
-
name: { type: 'string', minLength: 1 },
|
|
1566
|
-
description: { type: 'string' },
|
|
1567
|
-
type: { type: 'string' },
|
|
1568
|
-
app: { type: ['object', 'null'] },
|
|
1569
|
-
recipients: { type: 'array' },
|
|
1570
|
-
outcomeMapping: { type: ['object', 'null'] },
|
|
1571
|
-
_additionalProps: { type: 'object' },
|
|
1572
|
-
_notifications: { type: 'boolean' },
|
|
1573
|
-
_appInputs: { type: ['object', 'null'] },
|
|
1574
|
-
},
|
|
1575
|
-
required: ['name'],
|
|
1576
|
-
},
|
|
1577
|
-
inputDefaults: {
|
|
1578
|
-
name: 'Escalation',
|
|
1579
|
-
description: '',
|
|
1580
|
-
type: 'app-task',
|
|
1581
|
-
app: null,
|
|
1582
|
-
recipients: [],
|
|
1583
|
-
outcomeMapping: null,
|
|
1584
|
-
_additionalProps: { taskTitle: '', priority: 'medium', labels: [] },
|
|
1585
|
-
_notifications: false,
|
|
1586
|
-
_appInputs: null,
|
|
1587
|
-
},
|
|
1588
|
-
};
|
|
1589
|
-
}
|
|
1590
|
-
/**
|
|
1591
|
-
* The definition for ONE MCP-server tool resource, mirrored from the platform's
|
|
1592
|
-
* own mapper (`flow-core` `transformMcpServer`, read from the workbench dist —
|
|
1593
|
-
* the registry serves the family per instance, tenant-side). The node type is
|
|
1594
|
-
* minted from the server's name and key with the mapper's own slug rule.
|
|
1595
|
-
*/
|
|
1596
|
-
function mcpToolDef(spec) {
|
|
1597
|
-
const slug = (v) => v.replace(/[^a-zA-Z0-9.]+/g, '-').toLowerCase();
|
|
1598
|
-
return {
|
|
1599
|
-
nodeType: `uipath.agent.resource.tool.mcp.${slug(spec.name)}.${slug(spec.key)}`,
|
|
1600
|
-
version: '1.0.0',
|
|
1601
|
-
category: 'agent.tool.mcp',
|
|
1602
|
-
tags: ['agent-tool', 'mcp', 'mcp-server'],
|
|
1603
|
-
description: spec.description ?? '',
|
|
1604
|
-
sortOrder: 585,
|
|
1605
|
-
display: { label: spec.name, description: spec.description ?? '', icon: 'mcp', shape: 'circle' },
|
|
1606
|
-
handleConfiguration: [toolInputHandle()],
|
|
1607
|
-
model: { source: true },
|
|
1608
|
-
inputDefaults: {
|
|
1609
|
-
name: spec.name,
|
|
1610
|
-
description: spec.description ?? '',
|
|
1611
|
-
referenceKey: spec.key,
|
|
1612
|
-
folderPath: spec.folderPath ?? '',
|
|
1613
|
-
folderKey: spec.folderKey ?? '',
|
|
1614
|
-
mcpType: spec.mcpType ?? '',
|
|
1615
|
-
serviceName: spec.serviceName ?? '',
|
|
1616
|
-
// The mapper's field name for the canonical server SLUG (historical).
|
|
1617
|
-
serverUrl: spec.slug ?? '',
|
|
1618
|
-
selectedTools: spec.selectedTools ?? [],
|
|
1619
|
-
toolCatalog: spec.selectedTools ?? [],
|
|
1620
|
-
discoveryMode: { type: 'cached', refreshSchemaBeforeCall: true },
|
|
1621
|
-
},
|
|
1622
|
-
};
|
|
1623
|
-
}
|
|
1624
|
-
/**
|
|
1625
|
-
* The definition for ONE remote-A2A tool resource, mirrored from
|
|
1626
|
-
* `transformRemoteA2aResource` — same provenance and slug rule as the MCP def.
|
|
1627
|
-
*/
|
|
1628
|
-
function a2aToolDef(spec) {
|
|
1629
|
-
const slug = (v) => v.replace(/[^a-zA-Z0-9.]+/g, '-').toLowerCase();
|
|
1630
|
-
return {
|
|
1631
|
-
nodeType: `uipath.agent.resource.tool.a2a.${slug(spec.name)}.${slug(spec.key)}`,
|
|
1632
|
-
version: '1.0.0',
|
|
1633
|
-
category: 'agent.tool.remote-a2a',
|
|
1634
|
-
tags: ['agent-tool', 'remote-a2a', 'a2a', 'agent'],
|
|
1635
|
-
description: spec.description ?? '',
|
|
1636
|
-
sortOrder: 590,
|
|
1637
|
-
display: { label: spec.name, description: spec.description ?? '', icon: 'a2a', shape: 'circle' },
|
|
1638
|
-
handleConfiguration: [toolInputHandle()],
|
|
1639
|
-
model: { source: true },
|
|
1640
|
-
inputDefaults: {
|
|
1641
|
-
name: spec.name,
|
|
1642
|
-
description: spec.description ?? '',
|
|
1643
|
-
slug: spec.slug,
|
|
1644
|
-
cachedAgentCard: spec.cachedAgentCard ?? null,
|
|
1645
|
-
referenceKey: spec.key,
|
|
1646
|
-
resourceType: spec.resourceType ?? null,
|
|
1647
|
-
folderPath: spec.folderPath ?? '',
|
|
1648
|
-
folderKey: spec.folderKey ?? '',
|
|
1649
|
-
resourceFolderPath: spec.resourceFolderPath ?? '',
|
|
1650
|
-
},
|
|
1651
|
-
outputDefinition: {},
|
|
1652
|
-
};
|
|
1653
|
-
}
|
|
1654
|
-
/**
|
|
1655
|
-
* The definition for ONE episodic-memory resource, mirrored from
|
|
1656
|
-
* `transformEpisodicMemory`. The memory's identity rides ON the
|
|
1657
|
-
* inputDefinition (`memoryId`/`folderKey`), verbatim — that is where the
|
|
1658
|
-
* platform's own mapper puts it.
|
|
1659
|
-
*/
|
|
1660
|
-
function memoryResourceDef(spec) {
|
|
1661
|
-
const slug = (v) => v.replace(/[^a-zA-Z0-9.]+/g, '-').toLowerCase();
|
|
1662
|
-
return {
|
|
1663
|
-
nodeType: `uipath.agent.resource.memory.${slug(spec.name)}.${slug(spec.id)}`,
|
|
1664
|
-
version: '1.0.0',
|
|
1665
|
-
category: 'agent',
|
|
1666
|
-
tags: ['agent-memory', 'memory'],
|
|
1667
|
-
description: spec.description ?? '',
|
|
1668
|
-
sortOrder: 15,
|
|
1669
|
-
display: { label: spec.name, description: spec.description ?? '', icon: 'database', shape: 'circle' },
|
|
1670
|
-
handleConfiguration: [{
|
|
1671
|
-
position: 'bottom',
|
|
1672
|
-
handles: [{
|
|
1673
|
-
id: 'input', type: 'target', handleType: 'artifact',
|
|
1674
|
-
constraints: {
|
|
1675
|
-
maxConnections: 1,
|
|
1676
|
-
allowedSources: [
|
|
1677
|
-
{ nodeType: 'uipath.agent.autonomous', handleId: 'memory' },
|
|
1678
|
-
{ nodeType: 'uipath.agent.conversational', handleId: 'memory' },
|
|
1679
|
-
],
|
|
1680
|
-
validationMessage: "Only Agent's memory handle can connect here",
|
|
1681
|
-
},
|
|
1682
|
-
}],
|
|
1683
|
-
}],
|
|
1684
|
-
model: { source: true },
|
|
1685
|
-
inputDefinition: {
|
|
1686
|
-
memoryId: spec.id,
|
|
1687
|
-
folderKey: spec.folderKey ?? '',
|
|
1688
|
-
type: 'object',
|
|
1689
|
-
properties: {
|
|
1690
|
-
name: { type: 'string' },
|
|
1691
|
-
description: { type: 'string' },
|
|
1692
|
-
dynamicFewShotLearning: { type: 'boolean' },
|
|
1693
|
-
semanticSimilarity: { type: 'number', minimum: 0, maximum: 1 },
|
|
1694
|
-
kValue: { type: 'number' },
|
|
1695
|
-
searchMode: { type: 'string' },
|
|
1696
|
-
},
|
|
1697
|
-
},
|
|
1698
|
-
inputDefaults: {
|
|
1699
|
-
name: spec.name,
|
|
1700
|
-
description: spec.description ?? '',
|
|
1701
|
-
dynamicFewShotLearning: true,
|
|
1702
|
-
semanticSimilarity: 0,
|
|
1703
|
-
kValue: 3,
|
|
1704
|
-
searchMode: 'hybrid',
|
|
1705
|
-
},
|
|
1706
|
-
};
|
|
1707
|
-
}
|
|
1708
|
-
/** Numeric dotted compare: '1.10' > '1.9'. */
|
|
1709
|
-
function compareVersions(a, b) {
|
|
1710
|
-
const pa = a.split('.').map(Number);
|
|
1711
|
-
const pb = b.split('.').map(Number);
|
|
1712
|
-
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
1713
|
-
const d = (pa[i] ?? 0) - (pb[i] ?? 0);
|
|
1714
|
-
if (d !== 0)
|
|
1715
|
-
return d;
|
|
1716
|
-
}
|
|
1717
|
-
return 0;
|
|
1718
|
-
}
|
|
1719
|
-
/**
|
|
1720
|
-
* The inline agent's bundled versions, oldest first, and the newest of them.
|
|
1721
|
-
* The newest bundled pair is the default, so there is no version literal in
|
|
1722
|
-
* this file: when the registry serves a newer version, `check:definitions`
|
|
1723
|
-
* reports the missing pair, `sync:definitions` adds it, and the default follows.
|
|
1724
|
-
*/
|
|
1725
|
-
const INLINE_AGENT_VERSIONS = Object.keys(DEFS)
|
|
1726
|
-
.filter((k) => k.startsWith(`${INLINE_AGENT_TYPE}@`))
|
|
1727
|
-
.map((k) => k.slice(INLINE_AGENT_TYPE.length + 1))
|
|
1728
|
-
.sort(compareVersions);
|
|
1729
|
-
const INLINE_AGENT_DEFAULT_VERSION = INLINE_AGENT_VERSIONS[INLINE_AGENT_VERSIONS.length - 1];
|
|
1730
|
-
/** True when a definition declares the agent's `memory` artifact handle. */
|
|
1731
|
-
function declaresMemoryHandle(def) {
|
|
1732
|
-
return (def?.handleConfiguration ?? []).some((g) => (g?.handles ?? []).some((h) => h?.id === 'memory'));
|
|
1733
|
-
}
|
|
1734
|
-
/**
|
|
1735
|
-
* The definition for the INLINE autonomous agent: the bundled pair for
|
|
1736
|
-
* `version` (the newest by default), plus an explicit overlay of the details
|
|
1737
|
-
* the registry payload does not carry.
|
|
1738
|
-
*
|
|
1739
|
-
* This is the one node type in the agent family that is the SAME on every
|
|
1740
|
-
* tenant — it carries its own configuration instead of referencing a resource —
|
|
1741
|
-
* so `uip maestro flow registry get uipath.agent.autonomous` is a direct oracle
|
|
1742
|
-
* and the bundle entry is the registry's definition as last synced
|
|
1743
|
-
* (`sync:definitions` writes it through unchanged; `check:definitions --deep`
|
|
1744
|
-
* reports content that has moved since). Versions and content both come from
|
|
1745
|
-
* there.
|
|
1746
|
-
*
|
|
1747
|
-
* The overlay, and nothing else:
|
|
1748
|
-
*
|
|
1749
|
-
* * `outputDefinition.output.source` is `=result.response`. The registry's
|
|
1750
|
-
* definition declares no `source`; the deployed corpus (29 `uipath.agent.autonomous` nodes, 05-18 → 07-29) has
|
|
1751
|
-
* `=result.response` in 27 of 27 that declare outputs, and the engine's own
|
|
1752
|
-
* inline dispatch returns `{ response: { …declared keys } }`. Without it the
|
|
1753
|
-
* node's `output` is the whole result envelope rather than the declared keys.
|
|
1754
|
-
* It is applied at every version; the registry payload has no `source`.
|
|
1755
|
-
* * The `memory` handle is kept only when the flow attaches memory. It is
|
|
1756
|
-
* LOCAL content (#431) on the bundled 1.4, absent from the registry's 1.4
|
|
1757
|
-
* (`check.ts` AGENT_MEMORY_NOT_AUTHORABLE), so an agent without memory gets
|
|
1758
|
-
* the registry's handle set.
|
|
1759
|
-
*
|
|
1760
|
-
* NO per-instance schema goes on the definition: `definitions[]` is keyed by
|
|
1761
|
-
* node TYPE, and every inline agent in a flow shares this one entry. A flow
|
|
1762
|
-
* with two agents (an analyst and a writer, say) therefore cannot have both
|
|
1763
|
-
* contracts here — whichever was emitted last would win, and every read against
|
|
1764
|
-
* the other resolved to the wrong shape (GA row 7's port: 25 undeclared-field
|
|
1765
|
-
* errors on a correct flow). The authored contract goes on the NODE
|
|
1766
|
-
* (`node.outputs.output.schema`, in the `inlineAgent` arm), where a reader that
|
|
1767
|
-
* refines per node picks it up.
|
|
1768
|
-
*
|
|
1769
|
-
* The bundled 1.2 is the pre-#956 default, kept so a flow this SDK compiled
|
|
1770
|
-
* before — and a decompile of one, which pins `{ version: '1.2' }` — still
|
|
1771
|
-
* compiles. The corpus also carries 1.1 and 1.3; `shared/sample-flows/
|
|
1772
|
-
* inline-agent.flow` says 1.0, which appears in none of the 29.
|
|
1773
|
-
*/
|
|
1774
|
-
function inlineAgentDef(version, withMemory, resolve = (t, v) => DEFS[`${t}@${v}`]) {
|
|
1775
|
-
const bundled = resolve(INLINE_AGENT_TYPE, version);
|
|
1776
|
-
if (!bundled)
|
|
1777
|
-
return undefined;
|
|
1778
|
-
const def = jsonClone(bundled);
|
|
1779
|
-
const output = def.outputDefinition?.output;
|
|
1780
|
-
if (output)
|
|
1781
|
-
output.source = '=result.response';
|
|
1782
|
-
if (!withMemory) {
|
|
1783
|
-
for (const group of def.handleConfiguration ?? []) {
|
|
1784
|
-
if (Array.isArray(group?.handles))
|
|
1785
|
-
group.handles = group.handles.filter((h) => h?.id !== 'memory');
|
|
1786
|
-
}
|
|
1787
|
-
def.handleConfiguration = (def.handleConfiguration ?? []).filter((g) => g?.handles?.length);
|
|
1788
|
-
}
|
|
1789
|
-
return def;
|
|
1790
|
-
}
|
|
1791
|
-
/**
|
|
1792
|
-
* Classify a RENDERED input value (the output of {@link renderValue}).
|
|
1793
|
-
*
|
|
1794
|
-
* A rendered value is an expression exactly when it starts with `=js:` — the
|
|
1795
|
-
* same test the rest of the serializer applies, so a literal string that
|
|
1796
|
-
* happens to begin with `=js:` is treated as one everywhere.
|
|
1797
|
-
*/
|
|
1798
|
-
function classifyAgentInput(rendered) {
|
|
1799
|
-
if (typeof rendered !== 'string' || !rendered.startsWith('=js:')) {
|
|
1800
|
-
return { kind: 'literal', text: String(rendered) };
|
|
1801
|
-
}
|
|
1802
|
-
const flatName = flatNameOf(rendered);
|
|
1803
|
-
return flatName === undefined
|
|
1804
|
-
? { kind: 'expression', binding: rendered }
|
|
1805
|
-
: { kind: 'ref', flatName, binding: rendered };
|
|
1806
|
-
}
|
|
1807
|
-
/**
|
|
1808
|
-
* The platform-facing plan for one inline agent's declared inputs.
|
|
1809
|
-
*
|
|
1810
|
-
* Built once and used by all three agent-facing sites (the node's
|
|
1811
|
-
* `agentInputVariables`, the sidecar's `inputSchema`, and both prompts), so they
|
|
1812
|
-
* cannot drift apart.
|
|
1813
|
-
*
|
|
1814
|
-
* `refs` is DEDUPED BY FLAT NAME and keeps author order. Two logical names bound
|
|
1815
|
-
* to the same reference are one JobArgument on the platform (v1 dedupes on a
|
|
1816
|
-
* seen-set, `:490-522`), so emitting two descriptors would claim an argument that
|
|
1817
|
-
* will not exist.
|
|
1818
|
-
*/
|
|
1819
|
-
function agentInputPlan(inputs, rename) {
|
|
1820
|
-
const byName = new Map();
|
|
1821
|
-
const refs = [];
|
|
1822
|
-
const seen = new Set();
|
|
1823
|
-
for (const [name, val] of Object.entries(inputs)) {
|
|
1824
|
-
const b = classifyAgentInput(renderValue(val, rename));
|
|
1825
|
-
byName.set(name, b);
|
|
1826
|
-
if (b.kind === 'ref' && !seen.has(b.flatName)) {
|
|
1827
|
-
seen.add(b.flatName);
|
|
1828
|
-
refs.push({ flatName: b.flatName, binding: b.binding });
|
|
1829
|
-
}
|
|
1830
|
-
}
|
|
1831
|
-
return { byName, refs };
|
|
1832
|
-
}
|
|
1833
|
-
/**
|
|
1834
|
-
* Render an agent prompt for the SIDECAR — the author's placeholders rewritten to
|
|
1835
|
-
* the names the runtime will actually have.
|
|
1836
|
-
*
|
|
1837
|
-
* The three cases mirror {@link AgentInputBinding}; the fourth (a placeholder
|
|
1838
|
-
* naming nothing `inputs` declares) is left ALONE rather than deleted, exactly as
|
|
1839
|
-
* the node's renderer leaves it, so it reaches the model as visible literal text
|
|
1840
|
-
* and `check`'s `INLINE_AGENT_UNDECLARED_PLACEHOLDER` can name it.
|
|
1841
|
-
*
|
|
1842
|
-
* `contentTokens` is NOT rewritten separately — {@link promptTokens} runs over the
|
|
1843
|
-
* rewritten string, so the designer's chips and the runtime's template can't
|
|
1844
|
-
* disagree about a name.
|
|
1845
|
-
*/
|
|
1846
|
-
function renderAgentSidecarPrompt(template, plan) {
|
|
1847
|
-
return template.replace(INPUT_PLACEHOLDER, (whole, name) => {
|
|
1848
|
-
const b = plan.get(name);
|
|
1849
|
-
if (!b)
|
|
1850
|
-
return whole;
|
|
1851
|
-
if (b.kind === 'literal')
|
|
1852
|
-
return b.text;
|
|
1853
|
-
if (b.kind === 'ref')
|
|
1854
|
-
return `{{input.${b.flatName}}}`;
|
|
1855
|
-
return whole;
|
|
1856
|
-
});
|
|
1857
|
-
}
|
|
1858
|
-
/**
|
|
1859
|
-
* The `version` every `agent.json` sidecar declares — the agent tool's
|
|
1860
|
-
* `AGENT_SCHEMA_VERSION` (uipath-cli `packages/agent-tool/src/templates/agent-template.ts`).
|
|
1861
|
-
*
|
|
1862
|
-
* It is not a free label. The agents backend reads an on-file `outputSchema`
|
|
1863
|
-
* only at `version >= 1.2.0` (UiPath/Agents#5705), and since uipath-cli#3753
|
|
1864
|
-
* `uip agent init` writes `1.2.0` and `uip agent validate` refuses anything else
|
|
1865
|
-
* with `AgentValidationOutdated` ("Agent version "1.1.0" is outdated; latest is
|
|
1866
|
-
* "1.2.0""). The bump is a version change only: `uip agent refresh` rewrites
|
|
1867
|
-
* the field and nothing else. All three writers below share this one constant
|
|
1868
|
-
* so they cannot fall behind separately (flow-builder-sdk#962).
|
|
1869
|
-
*/
|
|
1870
|
-
const AGENT_DEFINITION_VERSION = '1.2.0';
|
|
1871
|
-
/**
|
|
1872
|
-
* The `agent.json` an inline agent's `source` directory must hold.
|
|
1873
|
-
*
|
|
1874
|
-
* The node is only half of an inline agent. flow-v1's `handleInlineAgentJob` emits
|
|
1875
|
-
* `entryPoint: "<source>/agent.json"` into the BPMN context, and the runtime
|
|
1876
|
-
* resolves the agent's real configuration — model settings, the message list, the
|
|
1877
|
-
* input/output schemas — from that file relative to the project. A `.flow` with an
|
|
1878
|
-
* inline-agent node and no such file references a definition that is not there.
|
|
1879
|
-
*
|
|
1880
|
-
* Shape transcribed from deployed agents under `runs/` (e.g. the four DevCon
|
|
1881
|
-
* billing agents, `type: 'lowCode'`), which is also what
|
|
1882
|
-
* `uip agent init --inline-in-flow` scaffolds. The `version` is
|
|
1883
|
-
* {@link AGENT_DEFINITION_VERSION}, not the `1.1.0` those older agents carry. `contentTokens` mirrors `content` as
|
|
1884
|
-
* one `simpleText` run: the designer uses those tokens to render a prompt with
|
|
1885
|
-
* inline variable chips, and a single run is the faithful encoding of a plain
|
|
1886
|
-
* string.
|
|
1887
|
-
*/
|
|
1888
|
-
/**
|
|
1889
|
-
* The `agent.json` sidecar for a text CONVERSATIONAL agent.
|
|
1890
|
-
*
|
|
1891
|
-
* The inline agent's shape minus the argument plumbing: the turn arrives as the
|
|
1892
|
-
* conversation transcript, so there is no `inputSchema` keyed by flat names and
|
|
1893
|
-
* no `outputSchema` (the definition declares
|
|
1894
|
-
* `uipath__agent_response_messages`). `metadata.isConversational` is what tells
|
|
1895
|
-
* the platform's own storage layer which kind of agent this is.
|
|
1896
|
-
*/
|
|
1897
|
-
function conversationalAgentJson(spec) {
|
|
1898
|
-
return {
|
|
1899
|
-
version: AGENT_DEFINITION_VERSION,
|
|
1900
|
-
settings: {
|
|
1901
|
-
model: spec.model,
|
|
1902
|
-
// Required by the platform's agent.json schema, same as the inline
|
|
1903
|
-
// agent's — a debug run without both is refused before it starts.
|
|
1904
|
-
maxTokens: spec.maxTokenPerResponse ?? 16384,
|
|
1905
|
-
temperature: spec.temperature ?? 0,
|
|
1906
|
-
// `basic-v2` is the NON-conversational inline agent's engine (the other
|
|
1907
|
-
// `agent.json` writer in this file, 100 lines down), and it was copied in
|
|
1908
|
-
// with the rest of that shape. Every instance of THIS node is
|
|
1909
|
-
// conversational — `metadata.isConversational` below says so
|
|
1910
|
-
// unconditionally — and `voiceAgentJson`, the sibling conversational
|
|
1911
|
-
// family, already spells it the other way. Measured across the 2026-09-23
|
|
1912
|
-
// A/B run: every product-written `agent.json` with `isConversational: true`
|
|
1913
|
-
// carries `conversational-v1` (3 of 3) and every one with `false` carries
|
|
1914
|
-
// `basic-v2` (6 of 6). The SDK's own voice path agreed (2 of 2); only this
|
|
1915
|
-
// path disagreed, and its task graded the sidecar as not conversational.
|
|
1916
|
-
engine: 'conversational-v1',
|
|
1917
|
-
...(spec.maxIterations === undefined ? {} : { maxIterations: spec.maxIterations }),
|
|
1918
|
-
mode: 'standard',
|
|
1919
|
-
},
|
|
1920
|
-
projectId: spec.source,
|
|
1921
|
-
id: spec.source,
|
|
1922
|
-
name: spec.name,
|
|
1923
|
-
metadata: { storageVersion: '50.0.0', isConversational: true, showProjectCreationExperience: false },
|
|
1924
|
-
type: 'lowCode',
|
|
1925
|
-
guardrails: spec.guardrails ?? [],
|
|
1926
|
-
messages: [
|
|
1927
|
-
{ role: 'system', content: spec.systemPrompt, contentTokens: promptTokens(spec.systemPrompt) },
|
|
1928
|
-
],
|
|
1929
|
-
};
|
|
1930
|
-
}
|
|
1931
|
-
/**
|
|
1932
|
-
* The `agent.json` sidecar for a VOICE agent.
|
|
1933
|
-
*
|
|
1934
|
-
* The text agent's shape with voice-specific settings. The turn arrives as
|
|
1935
|
-
* audio, while optional flow context is delivered through `inputSchema` and
|
|
1936
|
-
* `{{input.…}}` system-prompt placeholders. There is no `outputSchema` (the
|
|
1937
|
-
* definition declares what comes back), and `settings.voice` carries the
|
|
1938
|
-
* audio configuration in the storage layer's own `StoredVoiceSettings` shape
|
|
1939
|
-
* (`model` / `maxTokens` / `temperature` / `persona`) — with the platform's
|
|
1940
|
-
* catalog defaults filled in, because its own writer treats a partial voice
|
|
1941
|
-
* block as incomplete.
|
|
1942
|
-
*/
|
|
1943
|
-
function voiceAgentJson(spec) {
|
|
1944
|
-
// The platform's catalog default (`DEFAULT_CONVERSATIONAL_VOICE_SETTINGS`):
|
|
1945
|
-
// the live Gemini model, its first persona, temperature 0.
|
|
1946
|
-
const DEFAULT_MODEL = 'gemini-3.1-flash-live-preview';
|
|
1947
|
-
const DEFAULT_MAX_TOKENS = 65536;
|
|
1948
|
-
const DEFAULT_PERSONA = 'Aoede';
|
|
1949
|
-
const v = spec.voice ?? {};
|
|
1950
|
-
return {
|
|
1951
|
-
version: AGENT_DEFINITION_VERSION,
|
|
1952
|
-
settings: {
|
|
1953
|
-
engine: 'conversational-v1',
|
|
1954
|
-
mode: 'voice',
|
|
1955
|
-
voice: {
|
|
1956
|
-
model: v.model ?? DEFAULT_MODEL,
|
|
1957
|
-
maxTokens: v.maxTokens ?? DEFAULT_MAX_TOKENS,
|
|
1958
|
-
temperature: v.temperature ?? 0,
|
|
1959
|
-
persona: v.persona ?? DEFAULT_PERSONA,
|
|
1960
|
-
},
|
|
1961
|
-
...(spec.maxIterations === undefined ? {} : { maxIterations: spec.maxIterations }),
|
|
1962
|
-
},
|
|
1963
|
-
projectId: spec.source,
|
|
1964
|
-
id: spec.source,
|
|
1965
|
-
name: spec.name,
|
|
1966
|
-
...(spec.plan.refs.length === 0 ? {} : {
|
|
1967
|
-
inputSchema: {
|
|
1968
|
-
type: 'object',
|
|
1969
|
-
properties: Object.fromEntries(spec.plan.refs.map((r) => [r.flatName, {}])),
|
|
1970
|
-
},
|
|
1971
|
-
}),
|
|
1972
|
-
metadata: { storageVersion: '50.0.0', isConversational: true, showProjectCreationExperience: false },
|
|
1973
|
-
type: 'lowCode',
|
|
1974
|
-
guardrails: [],
|
|
1975
|
-
messages: (() => {
|
|
1976
|
-
const system = renderAgentSidecarPrompt(spec.systemPrompt, spec.plan.byName);
|
|
1977
|
-
return [{ role: 'system', content: system, contentTokens: promptTokens(system) }];
|
|
1978
|
-
})(),
|
|
1979
|
-
};
|
|
1980
|
-
}
|
|
1981
|
-
function inlineAgentJson(spec) {
|
|
1982
|
-
return {
|
|
1983
|
-
version: AGENT_DEFINITION_VERSION,
|
|
1984
|
-
settings: {
|
|
1985
|
-
model: spec.model,
|
|
1986
|
-
// `maxTokens` and `temperature` are REQUIRED by the platform's own
|
|
1987
|
-
// agent.json schema, not optional as this used to treat them: a debug run of
|
|
1988
|
-
// an agent without both is refused before it starts —
|
|
1989
|
-
// settings.maxTokens → "expected number, received undefined"
|
|
1990
|
-
// settings.temperature → "expected number, received undefined"
|
|
1991
|
-
// (measured on the product runtime, GA row 7's port). Every SDK-authored
|
|
1992
|
-
// inline agent hit it, and no local rung could: an offline runner calls no
|
|
1993
|
-
// model and never reads agent.json, so the sidecar's platform contract had
|
|
1994
|
-
// never been exercised.
|
|
1995
|
-
//
|
|
1996
|
-
// The defaults are what `uip agent init --inline-in-flow` writes and what
|
|
1997
|
-
// v1's own reference agents for this scenario carry.
|
|
1998
|
-
maxTokens: spec.maxTokenPerResponse ?? 16384,
|
|
1999
|
-
temperature: spec.temperature ?? 0,
|
|
2000
|
-
engine: 'basic-v2',
|
|
2001
|
-
...(spec.maxIterations === undefined ? {} : { maxIterations: spec.maxIterations }),
|
|
2002
|
-
mode: spec.mode ?? 'standard',
|
|
2003
|
-
},
|
|
2004
|
-
// Also required, and also derived rather than authored: the agent's own
|
|
2005
|
-
// directory uuid is its `projectId` and `id` (v1's scaffold writes the same
|
|
2006
|
-
// value in all three places, and it is the node's `inputs.source`).
|
|
2007
|
-
projectId: spec.source,
|
|
2008
|
-
id: spec.source,
|
|
2009
|
-
name: spec.name,
|
|
2010
|
-
// Keyed by FLAT NAME, because that is what the runtime's JobArguments are
|
|
2011
|
-
// keyed by (see the dialect note above). Literal- and expression-bound
|
|
2012
|
-
// inputs are absent on purpose: neither has a JobArgument, so declaring one
|
|
2013
|
-
// would promise an argument that never arrives.
|
|
2014
|
-
//
|
|
2015
|
-
// Each property is left EMPTY here and typed once the scope closes — see
|
|
2016
|
-
// `typeAgentInputSchemas`, which also explains why a wrong type is fatal
|
|
2017
|
-
// rather than untidy.
|
|
2018
|
-
inputSchema: {
|
|
2019
|
-
type: 'object',
|
|
2020
|
-
properties: Object.fromEntries(spec.plan.refs.map((r) => [r.flatName, {}])),
|
|
2021
|
-
},
|
|
2022
|
-
outputSchema: {
|
|
2023
|
-
type: 'object',
|
|
2024
|
-
properties: Object.fromEntries(Object.entries(spec.returns).map(([k, t]) => [k, { type: t }])),
|
|
2025
|
-
required: Object.keys(spec.returns),
|
|
2026
|
-
},
|
|
2027
|
-
metadata: { storageVersion: '50.0.0', isConversational: false, showProjectCreationExperience: false },
|
|
2028
|
-
type: 'lowCode',
|
|
2029
|
-
guardrails: spec.guardrails ?? [],
|
|
2030
|
-
// `content` and `contentTokens` are the half the platform keeps EXACTLY as
|
|
2031
|
-
// authored (the CLI regenerates neither), so this is where the flatName
|
|
2032
|
-
// rewrite has to land. Tokens are derived from the rewritten string rather
|
|
2033
|
-
// than rewritten in parallel — the designer renders from the tokens and the
|
|
2034
|
-
// runtime templates the content, and the two must name the same thing.
|
|
2035
|
-
messages: (() => {
|
|
2036
|
-
const system = renderAgentSidecarPrompt(spec.systemPrompt, spec.plan.byName);
|
|
2037
|
-
const user = renderAgentSidecarPrompt(spec.userPrompt, spec.plan.byName);
|
|
2038
|
-
return [
|
|
2039
|
-
{ role: 'system', content: system, contentTokens: promptTokens(system) },
|
|
2040
|
-
{ role: 'user', content: user, contentTokens: promptTokens(user) },
|
|
2041
|
-
];
|
|
2042
|
-
})(),
|
|
2043
|
-
};
|
|
2044
|
-
}
|
|
2045
|
-
/** `{{input.<name>}}` — the agent-template placeholder, in both prompt directions. */
|
|
2046
|
-
const INPUT_PLACEHOLDER = /\{\{\s*input\.([A-Za-z_$][A-Za-z0-9_$]*)\s*\}\}/g;
|
|
2047
|
-
/**
|
|
2048
|
-
* Split an agent prompt into the `contentTokens` the designer renders it from.
|
|
2049
|
-
*
|
|
2050
|
-
* The designer shows a prompt as prose with inline variable CHIPS, and `contentTokens`
|
|
2051
|
-
* is that structure: alternating `simpleText` runs and `variable` entries whose
|
|
2052
|
-
* `rawString` is `input.<name>`. Transcribed from deployed agents under `runs/` (the
|
|
2053
|
-
* DevCon billing agents), where a prompt reading
|
|
2054
|
-
* `"…Customer Name: {{input.customerName}}\nInvoice Number: {{input.invoiceNumber}}"`
|
|
2055
|
-
* carries exactly that alternation.
|
|
2056
|
-
*
|
|
2057
|
-
* A prompt with no placeholders is one `simpleText` run, which is what a
|
|
2058
|
-
* variable-free prompt looks like there too.
|
|
2059
|
-
*/
|
|
2060
|
-
function promptTokens(content) {
|
|
2061
|
-
const tokens = [];
|
|
2062
|
-
let last = 0;
|
|
2063
|
-
for (const m of content.matchAll(INPUT_PLACEHOLDER)) {
|
|
2064
|
-
const at = m.index ?? 0;
|
|
2065
|
-
if (at > last)
|
|
2066
|
-
tokens.push({ type: 'simpleText', rawString: content.slice(last, at) });
|
|
2067
|
-
tokens.push({ type: 'variable', rawString: `input.${m[1]}` });
|
|
2068
|
-
last = at + m[0].length;
|
|
2069
|
-
}
|
|
2070
|
-
if (last < content.length)
|
|
2071
|
-
tokens.push({ type: 'simpleText', rawString: content.slice(last) });
|
|
2072
|
-
return tokens.length ? tokens : [{ type: 'simpleText', rawString: content }];
|
|
2073
|
-
}
|
|
2074
123
|
/**
|
|
2075
124
|
* The parts every `uipath.ixp.*` definition shares, bundled verbatim from
|
|
2076
125
|
* `uip maestro flow registry get` (2026-07-31, after `registry pull --force`).
|
|
@@ -2437,14 +486,6 @@ function scheduleInputs(inputs, nodeId) {
|
|
|
2437
486
|
...(isPreset ? {} : { timerValue: every }),
|
|
2438
487
|
};
|
|
2439
488
|
}
|
|
2440
|
-
/**
|
|
2441
|
-
* `Approve` → `approve`; a name that slugifies to nothing falls back to its index.
|
|
2442
|
-
* The same rule that names the `outcome-<id>` ports (`outcomeSlug`), so an id
|
|
2443
|
-
* this writes into `schema.outcomes` is always one a port can name.
|
|
2444
|
-
*/
|
|
2445
|
-
function outcomeId(name, index) {
|
|
2446
|
-
return outcomeSlug(name, index);
|
|
2447
|
-
}
|
|
2448
489
|
/** A required connector input left empty — the one draft code with no error class of its own. */
|
|
2449
490
|
export const CONNECTOR_INPUT_MISSING = 'CONNECTOR_INPUT_MISSING';
|
|
2450
491
|
/**
|
|
@@ -2508,10 +549,6 @@ function nodeVariables(nodes) {
|
|
|
2508
549
|
}
|
|
2509
550
|
return out;
|
|
2510
551
|
}
|
|
2511
|
-
/** Deep-substitute the `{nodeId}` placeholder used in definition model templates. */
|
|
2512
|
-
function subNodeId(obj, nodeId) {
|
|
2513
|
-
return JSON.parse(JSON.stringify(obj).split('{nodeId}').join(nodeId));
|
|
2514
|
-
}
|
|
2515
552
|
const NO_RENAME = { nodeIds: new Map(), errorEnvelopes: new Map() };
|
|
2516
553
|
/**
|
|
2517
554
|
* Append the flow's `.stickyNotes()` as real `stickyNote` nodes.
|
|
@@ -2806,37 +843,29 @@ function describeDeclared(bindings) {
|
|
|
2806
843
|
/**
|
|
2807
844
|
* The node type a typed action compiles to, when that type's definition is
|
|
2808
845
|
* bundled in `core-definitions.json`. `undefined` for a family whose definition
|
|
2809
|
-
* is built per tenant or per call (connector,
|
|
2810
|
-
*
|
|
2811
|
-
* does not lower by kind.
|
|
846
|
+
* is built per tenant or per call (connector, ixpExtract) and for a spec this
|
|
847
|
+
* SDK does not lower by kind.
|
|
2812
848
|
*
|
|
2813
849
|
* A registered kind (flow-node-registry.ts) is not listed: its type is the
|
|
2814
850
|
* registry's.
|
|
2815
851
|
*/
|
|
2816
852
|
export function bundledActionNodeType(spec) {
|
|
2817
853
|
switch (spec?.kind) {
|
|
2818
|
-
case '
|
|
2819
|
-
case 'subflow':
|
|
2820
|
-
case 'createOutgoingCall':
|
|
2821
|
-
case 'endCall':
|
|
2822
|
-
case 'voiceAgent':
|
|
2823
|
-
case 'conversationalAgent':
|
|
2824
|
-
return NODE_TYPE[spec.kind];
|
|
854
|
+
case 'subflow': return NODE_TYPE.subflow;
|
|
2825
855
|
default: return undefined;
|
|
2826
856
|
}
|
|
2827
857
|
}
|
|
2828
858
|
/**
|
|
2829
|
-
* The definition serialize synthesizes for a
|
|
2830
|
-
*
|
|
2831
|
-
*
|
|
2832
|
-
*
|
|
2833
|
-
*
|
|
2834
|
-
*
|
|
859
|
+
* The definition serialize synthesizes for a kind that has no bundled one and
|
|
860
|
+
* is not on the node-type registry: an `ixpExtract` step. Its node type carries
|
|
861
|
+
* a tenant's key, so the SDK builds the definition per call, transcribed from
|
|
862
|
+
* what `registry get` returns. `undefined` for every other kind (a bundled
|
|
863
|
+
* family, a connector, a container, a registered kind — the published process
|
|
864
|
+
* resources and the agent build theirs in nodes/process/flow/).
|
|
2835
865
|
*
|
|
2836
866
|
* The emit path builds its definition here, and `check` asks this same
|
|
2837
867
|
* definition whether the node has an error port (#1040), so the two cannot
|
|
2838
|
-
* disagree.
|
|
2839
|
-
* `uipath.core.function.<key>@1.0.0` with `supportsErrorHandling: true`.
|
|
868
|
+
* disagree.
|
|
2840
869
|
*
|
|
2841
870
|
* Throws what the family's builder throws for an unusable spec (an
|
|
2842
871
|
* `ixpExtract` project that is not a node type).
|
|
@@ -2844,39 +873,10 @@ export function bundledActionNodeType(spec) {
|
|
|
2844
873
|
export function perCallDefinition(spec) {
|
|
2845
874
|
const s = spec?.inputs ?? {};
|
|
2846
875
|
switch (spec?.kind) {
|
|
2847
|
-
case 'agent':
|
|
2848
|
-
return agentDef({
|
|
2849
|
-
key: s.key, name: s.name, folderPath: s.folderPath, location: s.location, projectId: s.projectId,
|
|
2850
|
-
inputs: s.inputs ?? {}, returns: s.returns, flavour: s.flavour,
|
|
2851
|
-
});
|
|
2852
876
|
case 'ixpExtract': return ixpExtractDef(s);
|
|
2853
877
|
default: return undefined;
|
|
2854
878
|
}
|
|
2855
879
|
}
|
|
2856
|
-
/**
|
|
2857
|
-
* The definition version serialize emits for a typed action step that is not
|
|
2858
|
-
* yet on the node-type registry: the author's `{ version }` when given,
|
|
2859
|
-
* otherwise the version the step's inputs select, otherwise `undefined` (the
|
|
2860
|
-
* pinned default). One such family picks a version itself: `inlineAgent`
|
|
2861
|
-
* takes the newest bundled version, not the first bundled entry.
|
|
2862
|
-
*
|
|
2863
|
-
* The emit paths read their version here, and `check` resolves the definition
|
|
2864
|
-
* it asks about with this same version (#1041), so the two cannot pick
|
|
2865
|
-
* different definitions. For instance `inlineAgent({ ... })` gives the newest
|
|
2866
|
-
* bundled version in both. A registered family (e.g. `dataFabricRead`, whose
|
|
2867
|
-
* `resultMode` selects `core.datafabric.read@1.4`) gets the same guarantee from
|
|
2868
|
-
* the registry's `resolveVersion`, which check and serialize both call.
|
|
2869
|
-
*/
|
|
2870
|
-
export function actionVersion(spec, explicit) {
|
|
2871
|
-
if (explicit !== undefined)
|
|
2872
|
-
return explicit;
|
|
2873
|
-
// Keyed by the node type `bundledActionNodeType` already picks, so the
|
|
2874
|
-
// version rule adds no second dispatch on the step's kind.
|
|
2875
|
-
const type = bundledActionNodeType(spec);
|
|
2876
|
-
if (type === INLINE_AGENT_TYPE)
|
|
2877
|
-
return INLINE_AGENT_DEFAULT_VERSION;
|
|
2878
|
-
return undefined;
|
|
2879
|
-
}
|
|
2880
880
|
/**
|
|
2881
881
|
* The bundled definition of `nodeType` that `makeNode` resolves: the author's
|
|
2882
882
|
* exact `{ version }` when given, otherwise the pinned default (the first
|
|
@@ -3656,6 +1656,12 @@ function stepNamesOf(steps, out = [], terminates) {
|
|
|
3656
1656
|
// End id is claimed as `end`); `stepToRef` has no `name` at all (it is one edge).
|
|
3657
1657
|
if ('name' in s && s.kind !== 'return')
|
|
3658
1658
|
out.push(s.name);
|
|
1659
|
+
// A `.parallel` with a fork node claims that node's name too (#1310).
|
|
1660
|
+
if (s.kind === 'parallel') {
|
|
1661
|
+
const fork = parallelFork(s.options);
|
|
1662
|
+
if (fork)
|
|
1663
|
+
out.push(fork.name);
|
|
1664
|
+
}
|
|
3659
1665
|
// How many `.terminate`s hold each name, so a clash gets the right remedy (#1239).
|
|
3660
1666
|
if (s.kind === 'terminate' && terminates)
|
|
3661
1667
|
terminates.set(s.name, (terminates.get(s.name) ?? 0) + 1);
|
|
@@ -3705,6 +1711,11 @@ function namedNodesOf(steps, out = { steps: [], ends: [] }, seenEnds = new Set()
|
|
|
3705
1711
|
}
|
|
3706
1712
|
else if ('name' in s)
|
|
3707
1713
|
out.steps.push(s.name);
|
|
1714
|
+
if (s.kind === 'parallel') {
|
|
1715
|
+
const fork = parallelFork(s.options);
|
|
1716
|
+
if (fork)
|
|
1717
|
+
out.steps.push(fork.name);
|
|
1718
|
+
}
|
|
3708
1719
|
if (s.kind === 'stepToList')
|
|
3709
1720
|
namedNodesOf(s.body, out, seenEnds);
|
|
3710
1721
|
else if (s.kind === 'branch') {
|
|
@@ -4404,19 +2415,6 @@ export function serialize(built, opts = {}) {
|
|
|
4404
2415
|
function successPort(type) {
|
|
4405
2416
|
if (type === NODE_TYPE.loop)
|
|
4406
2417
|
return 'success';
|
|
4407
|
-
// The inline agent's forward handle is `success`, not `output` — its definition
|
|
4408
|
-
// declares `success` (with `isDefaultForType`) + `error` on the right, and 29 of
|
|
4409
|
-
// 29 deployed instances wire the outgoing edge to `success`. The PUBLISHED agent
|
|
4410
|
-
// family is the opposite (`output` + `error`), which is a reminder that "the
|
|
4411
|
-
// agent family" is two node shapes, not one.
|
|
4412
|
-
if (type === INLINE_AGENT_TYPE)
|
|
4413
|
-
return 'success';
|
|
4414
|
-
// The voice family's own handles: the agent and both call actions declare
|
|
4415
|
-
// `success` on the right (the two service tasks unlike their text-only
|
|
4416
|
-
// conversational siblings, which declare `output`).
|
|
4417
|
-
if (type === NODE_TYPE.voiceAgent || type === NODE_TYPE.createOutgoingCall
|
|
4418
|
-
|| type === NODE_TYPE.endCall || type === NODE_TYPE.conversationalAgent)
|
|
4419
|
-
return 'success';
|
|
4420
2418
|
// Every extractor's definition declares `success` (+ `error`, gated on
|
|
4421
2419
|
// `errorHandlingEnabled`) on the right — so the forward edge is `success`,
|
|
4422
2420
|
// as it is in all 151 deployed IxP instances.
|
|
@@ -4494,6 +2492,16 @@ export function serialize(built, opts = {}) {
|
|
|
4494
2492
|
legacyCompletedNodes.add(id);
|
|
4495
2493
|
else if (o.trackOutcomes && OUTCOME_HANDLE_NODE_TYPES.has(o.typeName))
|
|
4496
2494
|
hitlNodes.set(id, node);
|
|
2495
|
+
for (const a of contribution.attach ?? []) {
|
|
2496
|
+
const def = a.definition;
|
|
2497
|
+
const rnode = makeNode(def.nodeType, a.id, a.label, { parentId: o.parentId, def });
|
|
2498
|
+
rnode.display = { ...rnode.display, shape: 'circle', ...(a.display ?? {}) };
|
|
2499
|
+
// The resource model lives on the DEFINITION only.
|
|
2500
|
+
delete rnode.model;
|
|
2501
|
+
rnode.inputs = { ...a.inputs };
|
|
2502
|
+
scope.nodes.push(rnode);
|
|
2503
|
+
addEdge(scope, { node: id, port: a.port }, a.id, 'input');
|
|
2504
|
+
}
|
|
4497
2505
|
for (const t of o.tails)
|
|
4498
2506
|
addEdge(scope, t, id);
|
|
4499
2507
|
if (contribution.fanOut) {
|
|
@@ -4552,7 +2560,9 @@ export function serialize(built, opts = {}) {
|
|
|
4552
2560
|
renameRefs: (js) => renameRefs(js, scope.rename),
|
|
4553
2561
|
stepNodeId: (name) => scope.stepNodeId.get(name) ?? name,
|
|
4554
2562
|
mint: (seed) => stableId(`${built.id}:${id}:${seed}`),
|
|
4555
|
-
|
|
2563
|
+
// Into the flow's bindings at once as well, so `ctx.bindings` shows a pair
|
|
2564
|
+
// to the step's next resource (two tools on one process share it).
|
|
2565
|
+
bind: (b) => { bound.push(b); resourceBindings.set(b.id, b); },
|
|
4556
2566
|
// At a carried version the node's own definition IS the carried one,
|
|
4557
2567
|
// whatever version the implementation asks for (it was written for a
|
|
4558
2568
|
// bundled one), so its defaults and outputs come from the file's entry.
|
|
@@ -4560,7 +2570,7 @@ export function serialize(built, opts = {}) {
|
|
|
4560
2570
|
? resolved.carried
|
|
4561
2571
|
: bundledDefinition(typeName, v) ?? carriedFor(typeName, v)),
|
|
4562
2572
|
bindings: resourceBindings,
|
|
4563
|
-
resolveBinding: (name, attribute, at) => resolveBinding(opts, name, attribute, at),
|
|
2573
|
+
resolveBinding: ((name, attribute, at, target) => resolveBinding(opts, name, attribute, at, target)),
|
|
4564
2574
|
bindConnection: (c) => {
|
|
4565
2575
|
const { entries } = connectionResourceBindings({
|
|
4566
2576
|
def: { model: c.model },
|
|
@@ -4570,7 +2580,15 @@ export function serialize(built, opts = {}) {
|
|
|
4570
2580
|
...(c.resourceKey !== undefined ? { resourceKey: c.resourceKey } : {}),
|
|
4571
2581
|
}, resourceBindings);
|
|
4572
2582
|
bound.push(...entries);
|
|
2583
|
+
for (const b of entries)
|
|
2584
|
+
resourceBindings.set(b.id, b);
|
|
4573
2585
|
},
|
|
2586
|
+
agentSource: (carried) => agentFolder(carried, step.name),
|
|
2587
|
+
sidecar: (file, explicitSource) => addSidecar(file, explicitSource),
|
|
2588
|
+
typeAgentInputs: (schema, refs) => { scope.agentSchemas.push({ schema: schema, refs: [...refs] }); },
|
|
2589
|
+
resourceNodeId: (explicit, seed, at) => resourceNodeId(scope, explicit, seed, at),
|
|
2590
|
+
emitted: (typeName, v) => emittedDefs.get(`${typeName}@${v}`),
|
|
2591
|
+
library: opts.library,
|
|
4574
2592
|
};
|
|
4575
2593
|
const bound = [];
|
|
4576
2594
|
const contribution = impl.serialize(spec, ctx);
|
|
@@ -4604,7 +2622,12 @@ export function serialize(built, opts = {}) {
|
|
|
4604
2622
|
+ `${JSON.stringify(nodeType)}, which is not a name under "${prefix}".`);
|
|
4605
2623
|
}
|
|
4606
2624
|
if (impl.definition === 'per-tenant') {
|
|
4607
|
-
|
|
2625
|
+
// At a version the flow carries, the carried definition answers: the
|
|
2626
|
+
// type's declaration speaks for the versions it registers, and an older
|
|
2627
|
+
// tenant definition (an inline agent at 1.1) may have no error handle.
|
|
2628
|
+
const port = resolved.carried !== undefined
|
|
2629
|
+
? definitionHasErrorPort(def ?? resolved.carried)
|
|
2630
|
+
: typeof impl.errorPort === 'function' ? impl.errorPort(spec) : impl.errorPort === true;
|
|
4608
2631
|
// The declaration answers for check, which has no definition; here the
|
|
4609
2632
|
// definition is in hand, and the two must agree (#1011).
|
|
4610
2633
|
if (port && def !== undefined && !definitionHasErrorPort(def)) {
|
|
@@ -4792,734 +2815,6 @@ export function serialize(built, opts = {}) {
|
|
|
4792
2815
|
addEdge(scope, t, id);
|
|
4793
2816
|
return [{ node: id, port: successPort(rc.nodeType) }];
|
|
4794
2817
|
}
|
|
4795
|
-
if (spec.kind === 'agent') {
|
|
4796
|
-
// A published agent is the FOURTH member of the process-resource family, and
|
|
4797
|
-
// it goes down the same path for the same reason the other three share one:
|
|
4798
|
-
// the node body, the binding pair and the model-stripping are identical, and
|
|
4799
|
-
// what differs is the definition it carries. Kept as its own branch only
|
|
4800
|
-
// because `inputs` is required here (the empty-input floor — see
|
|
4801
|
-
// `AgentInputs.inputs`) and because the emitted binding subtype is `Agent`.
|
|
4802
|
-
const s = spec.inputs;
|
|
4803
|
-
const def = perCallDefinition(spec);
|
|
4804
|
-
// The pair's key and folder come off the definition, as for rpa / agentic
|
|
4805
|
-
// steps: one place decides the in-solution shape.
|
|
4806
|
-
const pairFolder = def.model.bindings.values.find((v) => v.name === 'folderPath').default;
|
|
4807
|
-
const { entries } = processResourceBindings({ name: s.name, folderPath: pairFolder, resourceSubType: 'Agent', resourceKey: def.model.bindings.resourceKey }, resourceBindings);
|
|
4808
|
-
for (const b of entries)
|
|
4809
|
-
resourceBindings.set(b.id, b);
|
|
4810
|
-
const id = stepUid(scope, step.name);
|
|
4811
|
-
const node = makeNode(def.nodeType, id, step.name, { parentId, requestedVersion: step.options?.version, def });
|
|
4812
|
-
// The resource model stays on the DEFINITION only, as it does for the other
|
|
4813
|
-
// three published families — 58 of 61 deployed agent instances carry no
|
|
4814
|
-
// node-level `model` at all.
|
|
4815
|
-
delete node.model;
|
|
4816
|
-
// Expression inputs stay `=js:` strings here, published or in-solution, and
|
|
4817
|
-
// the format migration wraps them as `{ type: 'jsExpression', expression }`
|
|
4818
|
-
// — the shape flow-core writes for an in-solution agent (52 of 54 v1
|
|
4819
|
-
// expression inputs, 09-01 → 10-08; the other 2 are bare `=js:` strings,
|
|
4820
|
-
// none is a `{{ }}` template). A `{ type: 'literal',
|
|
4821
|
-
// expression: '{{ … }}' }` template used to be emitted instead (#1013): it
|
|
4822
|
-
// ran, but it is the same envelope as a literal string, so decompile read
|
|
4823
|
-
// it back as one.
|
|
4824
|
-
node.inputs = renderMap(s.inputs, scope.rename);
|
|
4825
|
-
scope.nodes.push(node);
|
|
4826
|
-
for (const t of tails)
|
|
4827
|
-
addEdge(scope, t, id);
|
|
4828
|
-
return [{ node: id, port: successPort(def.nodeType) }];
|
|
4829
|
-
}
|
|
4830
|
-
if (spec.kind === 'inlineAgent') {
|
|
4831
|
-
const s = spec.inputs;
|
|
4832
|
-
const returns = s.returns && Object.keys(s.returns).length
|
|
4833
|
-
? s.returns
|
|
4834
|
-
// The definition's own default. Deliberately kept rather than made
|
|
4835
|
-
// required: it is what the platform scaffolds, so a flow that reads
|
|
4836
|
-
// `content` is a real (if toy) flow. `check` says so.
|
|
4837
|
-
: { content: 'string' };
|
|
4838
|
-
// ONE default version for every call shape — the newest bundled pair (see
|
|
4839
|
-
// `inlineAgentDef`). `mode` and `memory` used to pick the version too (1.3
|
|
4840
|
-
// and 1.4), so a plain agent got a superseded 1.2 and each option moved
|
|
4841
|
-
// the node to another version as a side effect (#956). Both now live in
|
|
4842
|
-
// the sidecar and on edges; neither selects a definition. An explicit
|
|
4843
|
-
// `{ version }` (what decompile writes back) resolves exactly.
|
|
4844
|
-
const version = actionVersion(spec, step.options?.version);
|
|
4845
|
-
// `definitions[]` holds one entry per type@version, so a flow where any
|
|
4846
|
-
// agent attaches memory keeps the handle for all of them.
|
|
4847
|
-
const withMemory = s.memory !== undefined
|
|
4848
|
-
|| declaresMemoryHandle(emittedDefs.get(`${INLINE_AGENT_TYPE}@${version}`));
|
|
4849
|
-
const def = inlineAgentDef(version, withMemory, definitionFor);
|
|
4850
|
-
if (!def) {
|
|
4851
|
-
throw new Error(`"${step.name}" requests ${INLINE_AGENT_TYPE}@${version}, but this SDK resolves only `
|
|
4852
|
-
+ `${INLINE_AGENT_VERSIONS.map((v) => `${INLINE_AGENT_TYPE}@${v}`).join(', ')}. Versions are exact — `
|
|
4853
|
-
+ `omit { version } to use the default (${INLINE_AGENT_DEFAULT_VERSION}), request a version this SDK ships, `
|
|
4854
|
-
+ `or carry that version's definition with .definitions([...]).`);
|
|
4855
|
-
}
|
|
4856
|
-
if (s.memory !== undefined && !declaresMemoryHandle(def)) {
|
|
4857
|
-
throw new Error(`"${step.name}" attaches \`memory\`, but ${INLINE_AGENT_TYPE}@${version} declares no \`memory\` handle. `
|
|
4858
|
-
+ (version === INLINE_AGENT_DEFAULT_VERSION
|
|
4859
|
-
? `Pin a version that has it with inlineAgent({ ..., version: '<x.y>' }) `
|
|
4860
|
-
+ `(bundled with memory: ${INLINE_AGENT_VERSIONS.filter((v) => declaresMemoryHandle(DEFS[`${INLINE_AGENT_TYPE}@${v}`])).join(', ') || 'none'}), or drop \`memory\`.`
|
|
4861
|
-
: `Drop { version: '${version}' } to use ${INLINE_AGENT_DEFAULT_VERSION}, or drop \`memory\`.`));
|
|
4862
|
-
}
|
|
4863
|
-
const id = stepUid(scope, step.name);
|
|
4864
|
-
// `source` is the agent's directory name; see `agentFolder`.
|
|
4865
|
-
const source = agentFolder(s.source, step.name);
|
|
4866
|
-
const node = makeNode(def.nodeType, id, step.name, { parentId, requestedVersion: step.options?.version, def });
|
|
4867
|
-
// Unlike the published families the inline agent KEEPS no node-level model:
|
|
4868
|
-
// 29 of 29 deployed instances carry none (the def's `model.source: true` is
|
|
4869
|
-
// what tells the serializer the node has a `source` input).
|
|
4870
|
-
delete node.model;
|
|
4871
|
-
// The per-instance CONTRACT lands here, on the node, because the definition
|
|
4872
|
-
// is shared by every inline agent in the flow and cannot hold it (see
|
|
4873
|
-
// `inlineAgentDef`). `makeNode` copied the definition's generic `output`
|
|
4874
|
-
// descriptor; this adds the declared keys to that copy, which is the shape
|
|
4875
|
-
// an instance-refining reader picks up.
|
|
4876
|
-
if (Object.keys(returns).length && node.outputs?.output) {
|
|
4877
|
-
node.outputs.output.schema = {
|
|
4878
|
-
type: 'object',
|
|
4879
|
-
properties: Object.fromEntries(Object.entries(returns).map(([k, t]) => [k, { type: t }])),
|
|
4880
|
-
};
|
|
4881
|
-
}
|
|
4882
|
-
const nodeInputs = s.inputs ?? {};
|
|
4883
|
-
// What the PLATFORM will call each of these — computed once and shared by
|
|
4884
|
-
// the descriptor array below and the sidecar. See `agentInputPlan`.
|
|
4885
|
-
const plan = agentInputPlan(nodeInputs, scope.rename);
|
|
4886
|
-
// A SHELL node (#927): the prompts and the model live only in the
|
|
4887
|
-
// `<source>/agent.json` sidecar written below, never on the node. Studio
|
|
4888
|
-
// Web decides which copy wins by `hasEmbeddedAgentContent`
|
|
4889
|
-
// (@uipath/flow-schema): any `systemPrompt`/`userPrompt` string on the node
|
|
4890
|
-
// makes the `.flow` authoritative and the sidecar is skipped, so a node copy
|
|
4891
|
-
// shadowed the sidecar and lost the input tokens and the tool configuration
|
|
4892
|
-
// on the first save. With no prompt strings the converter also keeps the
|
|
4893
|
-
// `agentInputVariables` below as written (`canPruneAgentInputs` is false),
|
|
4894
|
-
// and `uip maestro flow debug` reconciles them against the sidecar's
|
|
4895
|
-
// `inputSchema`.
|
|
4896
|
-
//
|
|
4897
|
-
// The run settings (`mode`, `temperature`, `maxTokenPerResponse`,
|
|
4898
|
-
// `maxIterations`, `guardrails`) live in the sidecar too. The node keeps
|
|
4899
|
-
// exactly the keys Studio Web's own strip keeps (`toAgentShellInputs`,
|
|
4900
|
-
// @uipath/flow-schema): anything else is discarded on hydrate, and while
|
|
4901
|
-
// present it makes Studio Web "trust the .flow" when the sidecar folder is
|
|
4902
|
-
// missing (`flowNodeHasRealInputs`), flushing a prompt-less agent.json.
|
|
4903
|
-
// `modelMaxTokens` is written nowhere: the designer derives it from the
|
|
4904
|
-
// selected model.
|
|
4905
|
-
node.inputs = {
|
|
4906
|
-
source,
|
|
4907
|
-
// The two DESCRIPTOR ARRAYS. `agentInputVariables` entries carry the
|
|
4908
|
-
// binding expression; `agentOutputVariables` entries are name + type only,
|
|
4909
|
-
// and the runtime's structured final call fills exactly these keys.
|
|
4910
|
-
//
|
|
4911
|
-
// `id` is the FLAT NAME, not the author's — the name the runtime's
|
|
4912
|
-
// JobArguments are keyed by, and the key the CLI reconciles against the
|
|
4913
|
-
// sidecar's `inputSchema`. One entry per flatName, so two logical names
|
|
4914
|
-
// bound to the same reference claim one argument rather than two.
|
|
4915
|
-
agentInputVariables: plan.refs.map((r) => ({
|
|
4916
|
-
id: r.flatName,
|
|
4917
|
-
type: 'string',
|
|
4918
|
-
binding: r.binding,
|
|
4919
|
-
})),
|
|
4920
|
-
agentOutputVariables: Object.entries(returns).map(([k, t]) => ({ id: k, type: t })),
|
|
4921
|
-
};
|
|
4922
|
-
scope.nodes.push(node);
|
|
4923
|
-
// The other half of the artifact: the agent's own configuration, which the
|
|
4924
|
-
// platform resolves at `<source>/agent.json`. Collected here and written by
|
|
4925
|
-
// compile-cli beside the `.flow`.
|
|
4926
|
-
const agentJson = inlineAgentJson({
|
|
4927
|
-
model: s.model,
|
|
4928
|
-
// The AGENT's dialect: `{{input.x}}` placeholders, rewritten from the
|
|
4929
|
-
// author's logical names to the FLAT NAMES the runtime's JobArguments
|
|
4930
|
-
// are keyed by (board G-20). `inlineAgentJson` does the rewrite so the
|
|
4931
|
-
// schema keys, the message text and the tokens all come off one plan.
|
|
4932
|
-
systemPrompt: s.systemPrompt,
|
|
4933
|
-
userPrompt: s.userPrompt,
|
|
4934
|
-
plan,
|
|
4935
|
-
returns,
|
|
4936
|
-
temperature: s.temperature,
|
|
4937
|
-
maxTokenPerResponse: s.maxTokenPerResponse,
|
|
4938
|
-
maxIterations: s.maxIterations,
|
|
4939
|
-
mode: s.mode,
|
|
4940
|
-
guardrails: s.guardrails,
|
|
4941
|
-
source,
|
|
4942
|
-
name: step.name,
|
|
4943
|
-
});
|
|
4944
|
-
addSidecar({ path: `${source}/agent.json`, json: agentJson });
|
|
4945
|
-
// The schema's property TYPES need this scope's finished variables, which
|
|
4946
|
-
// do not exist yet — park them (the `properties` object is filled in
|
|
4947
|
-
// place when the scope closes). See `typeAgentInputSchemas`.
|
|
4948
|
-
scope.agentSchemas.push({ schema: agentJson.inputSchema, refs: plan.refs });
|
|
4949
|
-
// GROUNDING — one resource node per context index, joined to the agent's own
|
|
4950
|
-
// `context` handle. An ARTIFACT, not a step: it takes no incoming control
|
|
4951
|
-
// flow, produces no output, and never reaches the End node. The edge runs OUT
|
|
4952
|
-
// of the agent (source port `context`) INTO the resource's `input` port,
|
|
4953
|
-
// which is the direction both definitions declare.
|
|
4954
|
-
//
|
|
4955
|
-
// Emitted after the agent node so the resource sits beside it in the
|
|
4956
|
-
// designer's default layout rather than in front of it.
|
|
4957
|
-
const contexts = s.context === undefined ? []
|
|
4958
|
-
: Array.isArray(s.context) ? s.context : [s.context];
|
|
4959
|
-
for (const cx of contexts) {
|
|
4960
|
-
// `check`'s AGENT_CONTEXT_NO_ID, again for `--no-check`: a blank id builds
|
|
4961
|
-
// the type `…context.index.<slug>.`, which no tenant serves, and nothing
|
|
4962
|
-
// after this point would say so.
|
|
4963
|
-
if (typeof cx?.id !== 'string' || !GUID_RE.test(cx.id)) {
|
|
4964
|
-
throw Object.assign(new Error(`inline agent "${step.name}": context index "${cx?.name ?? ''}" needs its index id (a GUID) as \`id\`; `
|
|
4965
|
-
+ `got ${JSON.stringify(cx?.id)}. \`uip solution resources list --kind Index --source remote --search "<name>"\` returns it as \`Key\`.`), { code: 'AGENT_CONTEXT_NO_ID' });
|
|
4966
|
-
}
|
|
4967
|
-
const cdef = contextIndexDef({ name: cx.name, id: cx.id });
|
|
4968
|
-
// `<step>Context`, deduped by `uid` for a second index on the same agent,
|
|
4969
|
-
// unless the author placed it (`nodeId`, which decompile writes for a
|
|
4970
|
-
// node the canvas named). Not slugged from the index NAME: an id is the
|
|
4971
|
-
// author's handle on the node and a 40-character one helps nobody.
|
|
4972
|
-
const cid = resourceNodeId(scope, cx.nodeId, `${step.name}Context`, `inline agent "${step.name}": context index "${cx.name}"`);
|
|
4973
|
-
const cnode = makeNode(cdef.nodeType, cid, cx.label ?? cx.name, { parentId, def: cdef });
|
|
4974
|
-
// A resource is drawn as a circle, and `makeNode` copies only the icon.
|
|
4975
|
-
cnode.display = { ...cnode.display, shape: 'circle' };
|
|
4976
|
-
// Same rule as the agent's: the resource `model` lives on the DEFINITION.
|
|
4977
|
-
// What the NODE carries is the index's own configuration, and the platform's
|
|
4978
|
-
// `inputDefaults` are the shape a designer-placed node has — so an omitted
|
|
4979
|
-
// option emits the designer's value rather than nothing.
|
|
4980
|
-
delete cnode.model;
|
|
4981
|
-
// ONE identity, spent twice: the node's `inputs.source` and the sidecar
|
|
4982
|
-
// resource's `id` are the same uuid, because that is what joins them. The
|
|
4983
|
-
// platform validator says so in its own refusal text — *"the resource id
|
|
4984
|
-
// from the parent inline agent's `resources[]`"*.
|
|
4985
|
-
const contextResourceId = cx.source ?? derivedContextSource(built.id, step.name, cx.id);
|
|
4986
|
-
const contextQuery = renderContextQuery(cx.query, scope.rename);
|
|
4987
|
-
cnode.inputs = {
|
|
4988
|
-
// REQUIRED, and the platform's validator is what says so — measured
|
|
4989
|
-
// 2026-07-31, omitting it: *"Node type
|
|
4990
|
-
// uipath.agent.resource.context.index.… requires a stable UUID at
|
|
4991
|
-
// inputs.source … Set \"inputs\": { \"source\": \"<uuid>\" } on the
|
|
4992
|
-
// node"* (MST-9265). It is the resource's own identity inside the
|
|
4993
|
-
// project, so it is derived from flow + step + index rather than random,
|
|
4994
|
-
// exactly like the agent's `source`: a recompile must not be a diff.
|
|
4995
|
-
// An author's `source` (a canvas directory decompile carried) wins.
|
|
4996
|
-
source: contextResourceId,
|
|
4997
|
-
indexId: cx.id,
|
|
4998
|
-
indexName: cx.name,
|
|
4999
|
-
...(cx.folderKey === undefined ? {} : { folderKey: cx.folderKey }),
|
|
5000
|
-
...(cx.folderPath === undefined ? {} : { folderPath: cx.folderPath }),
|
|
5001
|
-
retrievalMode: cx.retrievalMode ?? 'semantic',
|
|
5002
|
-
// The platform's `query` is a MODE-TAGGED object, not a bare string:
|
|
5003
|
-
// `{mode, textValue, promptValue, argumentPath}`, with the mode saying
|
|
5004
|
-
// which field is live. A plain string is the `prompt` mode; a flow
|
|
5005
|
-
// expression is `text-builder`, whose `textValue` the runtime templates.
|
|
5006
|
-
query: contextQuery,
|
|
5007
|
-
threshold: cx.threshold ?? 0,
|
|
5008
|
-
resultCount: cx.resultCount ?? 3,
|
|
5009
|
-
fileExtension: cx.fileExtension ?? 'All',
|
|
5010
|
-
folderPathPrefix: { mode: 'text-builder', textValue: '', promptValue: '', argumentPath: '' },
|
|
5011
|
-
citations: 'enabled',
|
|
5012
|
-
outputColumns: [],
|
|
5013
|
-
webSearchGrounding: { value: 'enabled' },
|
|
5014
|
-
};
|
|
5015
|
-
scope.nodes.push(cnode);
|
|
5016
|
-
addEdge(scope, { node: id, port: 'context' }, cid, 'input');
|
|
5017
|
-
// The other half — without it the node is inert: see
|
|
5018
|
-
// `contextResourceJson`.
|
|
5019
|
-
addSidecar({
|
|
5020
|
-
path: `${source}/resources/${contextResourceId}/resource.json`,
|
|
5021
|
-
json: contextResourceJson(cx, { id: contextResourceId, query: contextQuery }),
|
|
5022
|
-
}, cx.source !== undefined);
|
|
5023
|
-
}
|
|
5024
|
-
// TOOLS — one resource node per tool, joined to the agent's `tool` handle;
|
|
5025
|
-
// the same artifact shape as context (no incoming control flow, no output,
|
|
5026
|
-
// never reaches End — a graph walk drops it).
|
|
5027
|
-
//
|
|
5028
|
-
// Every tool node carries `inputs.source` — the platform's validator
|
|
5029
|
-
// requires "a stable UUID at inputs.source … the resource id from the
|
|
5030
|
-
// parent inline agent's resources[] for agent-tool resource nodes"
|
|
5031
|
-
// (MST-9265's own wording) — derived, so a recompile is not a diff.
|
|
5032
|
-
const toolRefs = s.tools === undefined ? []
|
|
5033
|
-
: Array.isArray(s.tools) ? s.tools : [s.tools];
|
|
5034
|
-
for (const tr of toolRefs) {
|
|
5035
|
-
let tdef;
|
|
5036
|
-
let label;
|
|
5037
|
-
let extraInputs = {};
|
|
5038
|
-
let connectorTool;
|
|
5039
|
-
if (tr.kind === 'builtin') {
|
|
5040
|
-
const def = ownEntry(BUILTIN_TOOL_DEFS, tr.tool);
|
|
5041
|
-
tdef = def === undefined ? undefined : jsonClone(def);
|
|
5042
|
-
if (!tdef)
|
|
5043
|
-
throw new Error(`inline agent "${step.name}": unknown builtin tool "${tr.tool}" (analyzefiles | summarize | batchtransform).`);
|
|
5044
|
-
label = tdef.display.label;
|
|
5045
|
-
// The designer-placed node carries the def's `inputDefaults` with the
|
|
5046
|
-
// author's values over them — CX's context rule, kept per builtin
|
|
5047
|
-
// (each def declares different fields, so overrides apply only where
|
|
5048
|
-
// the def has the field).
|
|
5049
|
-
extraInputs = jsonClone(tdef.inputDefaults);
|
|
5050
|
-
// summarize/batchtransform declare `source` among their defaults (as
|
|
5051
|
-
// an empty string); the derived uuid below must win, not be clobbered.
|
|
5052
|
-
delete extraInputs.source;
|
|
5053
|
-
if (tr.description !== undefined)
|
|
5054
|
-
extraInputs.description = tr.description;
|
|
5055
|
-
if ('query' in extraInputs && tr.query !== undefined) {
|
|
5056
|
-
extraInputs.query = renderContextQuery(tr.query, scope.rename);
|
|
5057
|
-
}
|
|
5058
|
-
if ('fileExtension' in extraInputs && tr.fileExtension !== undefined) {
|
|
5059
|
-
extraInputs.fileExtension = { value: tr.fileExtension };
|
|
5060
|
-
}
|
|
5061
|
-
if ('citationMode' in extraInputs && tr.citationMode !== undefined) {
|
|
5062
|
-
extraInputs.citationMode = { value: tr.citationMode };
|
|
5063
|
-
}
|
|
5064
|
-
}
|
|
5065
|
-
else if (tr.kind === 'ixp') {
|
|
5066
|
-
tdef = ixpToolDef(tr);
|
|
5067
|
-
label = tr.name;
|
|
5068
|
-
// The node carries the def's own `inputDefaults` (name / description /
|
|
5069
|
-
// project identity / attachment config) with the derived `source` over
|
|
5070
|
-
// them — the builtin-tool rule, not the process-tool one, because this
|
|
5071
|
-
// family's configuration lives on the NODE rather than in a binding:
|
|
5072
|
-
// its def declares no `model` at all, so there is no resource identity
|
|
5073
|
-
// to bind and nothing to add to `bindings[]`.
|
|
5074
|
-
extraInputs = jsonClone(tdef.inputDefaults);
|
|
5075
|
-
}
|
|
5076
|
-
else if (tr.kind === 'connector') {
|
|
5077
|
-
if (!opts.library)
|
|
5078
|
-
throw new Error(`inline agent "${step.name}"'s connector tool needs a library (pass --library / set $FLOW_SDK_LIBRARY_JSON).`);
|
|
5079
|
-
const rc = opts.library.resolve(tr.connector, tr.operation, tr.version, tr.object);
|
|
5080
|
-
// An entry `prepare` synthesized from Integration Service compiles in
|
|
5081
|
-
// BPMN only — for a tool as for a step: `connectorToolDef` reads the
|
|
5082
|
-
// same definition, and a synthesized one has no form or handles.
|
|
5083
|
-
const noDefinition = synthesizedDefinitionRefusal(rc);
|
|
5084
|
-
if (noDefinition)
|
|
5085
|
-
throw noDefinition;
|
|
5086
|
-
tdef = connectorToolDef(rc);
|
|
5087
|
-
label = tr.name ?? rc.v1def?.display?.label ?? tr.operation;
|
|
5088
|
-
// The connection rides the same three places it does for a
|
|
5089
|
-
// `connector()` step, because the platform reads it from the same
|
|
5090
|
-
// three (#867):
|
|
5091
|
-
// * the node's `inputs.detail` — what `uip maestro flow node configure`
|
|
5092
|
-
// writes on a `tool.connector.*` node, what `uip agent refresh
|
|
5093
|
-
// --inline-in-flow` builds the tool's resource.json FROM, and what
|
|
5094
|
-
// its shell-ify pass keeps ("where a connector tool's request config
|
|
5095
|
-
// lives"). It is also what `bindings_v2.json` is derived from.
|
|
5096
|
-
// * the top-level `bindings[]` pair the def's `model.context`
|
|
5097
|
-
// placeholders (`<bindings.… connection>` / `<bindings.FolderKey>`)
|
|
5098
|
-
// name.
|
|
5099
|
-
// * the agent's `resources/<id>/resource.json` (below).
|
|
5100
|
-
// Emitting none of them left the tool with no connection anywhere.
|
|
5101
|
-
const where = `inline agent "${step.name}"'s connector tool "${tr.connector}/${tr.operation}"`;
|
|
5102
|
-
const target = { key: tr.connector, action: tr.operation };
|
|
5103
|
-
const conn = resolveBinding(opts, tr.connection, 'connection', where, target);
|
|
5104
|
-
const folder = resolveBinding(opts, tr.folder, 'folder', where, target);
|
|
5105
|
-
const { entries: connBindings } = connectionResourceBindings({ def: rc.v1def, connectorKey: rc.connectorKey, connectionId: conn, folderKey: folder }, resourceBindings);
|
|
5106
|
-
for (const b of connBindings)
|
|
5107
|
-
resourceBindings.set(b.id, b);
|
|
5108
|
-
const detail = connectorToolDetail(rc, conn, folder, `${step.name}Tool`);
|
|
5109
|
-
extraInputs = { detail };
|
|
5110
|
-
connectorTool = {
|
|
5111
|
-
rc, detail, name: label,
|
|
5112
|
-
description: tr.description ?? connectorToolDescription(rc),
|
|
5113
|
-
iconUrl: String(tdef.display?.icon ?? ''),
|
|
5114
|
-
};
|
|
5115
|
-
}
|
|
5116
|
-
else if (tr.kind === 'mcp') {
|
|
5117
|
-
// Per-instance def minted like the ixp family's; the node carries the
|
|
5118
|
-
// def's own defaults (server identity + tool selection) — the
|
|
5119
|
-
// configuration lives on the node, not in a binding.
|
|
5120
|
-
tdef = mcpToolDef(tr);
|
|
5121
|
-
label = tr.name;
|
|
5122
|
-
extraInputs = jsonClone(tdef.inputDefaults);
|
|
5123
|
-
}
|
|
5124
|
-
else if (tr.kind === 'a2a') {
|
|
5125
|
-
tdef = a2aToolDef(tr);
|
|
5126
|
-
label = tr.name;
|
|
5127
|
-
extraInputs = jsonClone(tdef.inputDefaults);
|
|
5128
|
-
}
|
|
5129
|
-
else if (tr.kind === 'clientside') {
|
|
5130
|
-
// A static bundled def: the flow declares only the CONTRACT — the
|
|
5131
|
-
// calling application owns the implementation.
|
|
5132
|
-
tdef = jsonClone(DEFS['uipath.agent.resource.tool.clientside@1.0']);
|
|
5133
|
-
label = tr.name;
|
|
5134
|
-
extraInputs = {
|
|
5135
|
-
...jsonClone(tdef.inputDefaults),
|
|
5136
|
-
name: tr.name,
|
|
5137
|
-
...(tr.description !== undefined ? { description: tr.description } : {}),
|
|
5138
|
-
...(tr.inputs !== undefined ? { inputSchema: fieldSchema(tr.inputs) } : {}),
|
|
5139
|
-
...(tr.returns !== undefined ? { outputSchema: fieldSchema(tr.returns) } : {}),
|
|
5140
|
-
};
|
|
5141
|
-
}
|
|
5142
|
-
else if (tr.kind === 'httpRequest') {
|
|
5143
|
-
// Each field is a mode triple: a FIXED author value rides as
|
|
5144
|
-
// text-builder (headers/params as name/value rows, timeout numeric —
|
|
5145
|
-
// the storage layer's own normalization), an omitted one keeps the
|
|
5146
|
-
// definition's prompt-mode default so the MODEL fills it at call time.
|
|
5147
|
-
tdef = jsonClone(DEFS['uipath.agent.resource.tool.builtin.httprequest@1.0']);
|
|
5148
|
-
label = tr.name ?? tdef.display.label;
|
|
5149
|
-
extraInputs = jsonClone(tdef.inputDefaults);
|
|
5150
|
-
const fix = (field, value) => {
|
|
5151
|
-
const row = extraInputs[field];
|
|
5152
|
-
extraInputs[field] = { ...row, mode: 'text-builder', textValue: value };
|
|
5153
|
-
};
|
|
5154
|
-
if (tr.url !== undefined)
|
|
5155
|
-
fix('url', tr.url);
|
|
5156
|
-
if (tr.method !== undefined)
|
|
5157
|
-
fix('method', tr.method);
|
|
5158
|
-
if (tr.headers !== undefined)
|
|
5159
|
-
fix('headers', tr.headers);
|
|
5160
|
-
if (tr.params !== undefined)
|
|
5161
|
-
fix('params', tr.params);
|
|
5162
|
-
if (tr.body !== undefined)
|
|
5163
|
-
fix('body', tr.body);
|
|
5164
|
-
if (tr.timeout !== undefined)
|
|
5165
|
-
fix('timeout', tr.timeout);
|
|
5166
|
-
// Always set, falling back to the definition's own text: the resource
|
|
5167
|
-
// file hands this to the MODEL as the tool's description, and the node
|
|
5168
|
-
// must carry the same one (#935).
|
|
5169
|
-
extraInputs.description = tr.description ?? String(tdef.description ?? '');
|
|
5170
|
-
}
|
|
5171
|
-
else {
|
|
5172
|
-
const meta = ownEntry(PROCESS_TOOL_META, tr.kind);
|
|
5173
|
-
if (!meta)
|
|
5174
|
-
throw new Error(`inline agent "${step.name}": unknown tool kind "${tr.kind}".`);
|
|
5175
|
-
tdef = processToolDef(tr.kind, tr);
|
|
5176
|
-
label = tr.name;
|
|
5177
|
-
// The definition's `model.context` names `<bindings.name>` /
|
|
5178
|
-
// `<bindings.folderPath>` — the same pair every published-resource
|
|
5179
|
-
// STEP references — so the entries must exist at the top level
|
|
5180
|
-
// (v1's current authoring doc says so for process tools; a def
|
|
5181
|
-
// whose bindings resolve to nothing is a dangling reference).
|
|
5182
|
-
const { entries } = processResourceBindings({ name: tr.name, folderPath: tr.folderPath, resourceSubType: meta.subType }, resourceBindings);
|
|
5183
|
-
for (const b of entries)
|
|
5184
|
-
resourceBindings.set(b.id, b);
|
|
5185
|
-
// The NODE stays minimal — `source` only. Both witnesses agree: the
|
|
5186
|
-
// deployed corpus carries `inputs: {}` on every per-instance tool, and
|
|
5187
|
-
// v1's current authoring doc says "inputs.source only, no instance
|
|
5188
|
-
// model". The contract (inputSchema/outputSchema) lives on the
|
|
5189
|
-
// DEFINITION's inputDefaults, where the registry's tenant-synthesized
|
|
5190
|
-
// copy puts it — emitting it on the node too would set inputs the
|
|
5191
|
-
// def's own schema does not declare (our own output would be flagged).
|
|
5192
|
-
}
|
|
5193
|
-
const tid = resourceNodeId(scope, tr.nodeId, `${step.name}Tool`, `inline agent "${step.name}": ${tr.kind} tool "${label}"`);
|
|
5194
|
-
const tnode = makeNode(tdef.nodeType, tid, label, { parentId, def: tdef });
|
|
5195
|
-
tnode.display = { ...tnode.display, shape: 'circle' };
|
|
5196
|
-
// `uip agent refresh` reads the tool's description off the node's display
|
|
5197
|
-
// first, so the node carries the one the resource.json does.
|
|
5198
|
-
if (connectorTool)
|
|
5199
|
-
tnode.display.description = connectorTool.description;
|
|
5200
|
-
// The resource model lives on the DEFINITION only (CX's rule for
|
|
5201
|
-
// context, and what the deployed corpus carries for tools).
|
|
5202
|
-
delete tnode.model;
|
|
5203
|
-
const toolResourceId = tr.source ?? derivedToolSource(built.id, step.name, tr);
|
|
5204
|
-
tnode.inputs = {
|
|
5205
|
-
source: toolResourceId,
|
|
5206
|
-
...extraInputs,
|
|
5207
|
-
};
|
|
5208
|
-
scope.nodes.push(tnode);
|
|
5209
|
-
addEdge(scope, { node: id, port: 'tool' }, tid, 'input');
|
|
5210
|
-
// The other half, for the kinds whose file is DERIVABLE — see
|
|
5211
|
-
// `toolResourceJson` for which are not, and why each is left to the
|
|
5212
|
-
// designer rather than guessed.
|
|
5213
|
-
const toolResource = connectorTool
|
|
5214
|
-
? connectorToolResourceJson(connectorTool, toolResourceId)
|
|
5215
|
-
: tr.kind === 'builtin'
|
|
5216
|
-
? builtinToolResourceJson(tr.tool, label, tnode.inputs, { id: toolResourceId })
|
|
5217
|
-
: tr.kind === 'httpRequest'
|
|
5218
|
-
? httpRequestToolResourceJson(label, tnode.inputs, { id: toolResourceId, canvasNodeId: tid })
|
|
5219
|
-
: toolResourceJson(tr, { id: toolResourceId });
|
|
5220
|
-
if (toolResource !== undefined) {
|
|
5221
|
-
addSidecar({
|
|
5222
|
-
path: `${source}/resources/${toolResourceId}/resource.json`,
|
|
5223
|
-
json: toolResource,
|
|
5224
|
-
}, tr.source !== undefined);
|
|
5225
|
-
}
|
|
5226
|
-
}
|
|
5227
|
-
// ESCALATION — the agent's TOP handle; an Action Center app task a human
|
|
5228
|
-
// completes. The node's inputs are the platform's `inputDefaults` shape
|
|
5229
|
-
// with the author's values — the exact shape the deployed corpus carries.
|
|
5230
|
-
const escalations = s.escalation === undefined ? []
|
|
5231
|
-
: Array.isArray(s.escalation) ? s.escalation : [s.escalation];
|
|
5232
|
-
for (const esc of escalations) {
|
|
5233
|
-
if (esc.variant === 'quick-form') {
|
|
5234
|
-
// The INLINE-form escalation: the same schema rows a human task's
|
|
5235
|
-
// quick form carries, on the bundled `.quick-form` definition (the
|
|
5236
|
-
// tenant registry does not serve the family).
|
|
5237
|
-
const qdef = jsonClone(DEFS['uipath.agent.resource.escalation.quick-form@1.2']);
|
|
5238
|
-
const qid = resourceNodeId(scope, esc.nodeId, `${step.name}Escalation`, `inline agent "${step.name}": escalation "${esc.name}"`);
|
|
5239
|
-
const qnode = makeNode(qdef.nodeType, qid, esc.name, { parentId, def: qdef });
|
|
5240
|
-
qnode.display = { ...qnode.display, shape: 'circle' };
|
|
5241
|
-
delete qnode.model;
|
|
5242
|
-
const rows = (esc.fields ?? []).map((f) => {
|
|
5243
|
-
const required = f.direction === 'input' ? undefined : f.required;
|
|
5244
|
-
return {
|
|
5245
|
-
id: f.id,
|
|
5246
|
-
label: f.label ?? f.id,
|
|
5247
|
-
type: wireFormFieldType(f.type),
|
|
5248
|
-
direction: f.direction,
|
|
5249
|
-
...(typeof required === 'boolean' ? { required } : {}),
|
|
5250
|
-
// The field's settings (`controlType`, a picker's `options`,
|
|
5251
|
-
// `defaultValue`, `colSpan`, …), as the human task's rows carry
|
|
5252
|
-
// them (#1235, #1250).
|
|
5253
|
-
...formFieldSettingsWire(f),
|
|
5254
|
-
};
|
|
5255
|
-
});
|
|
5256
|
-
const qsource = esc.source ?? derivedEscalationSource(built.id, step.name, esc.name);
|
|
5257
|
-
qnode.inputs = {
|
|
5258
|
-
source: qsource,
|
|
5259
|
-
name: esc.name,
|
|
5260
|
-
description: esc.description ?? '',
|
|
5261
|
-
type: 'quick-form',
|
|
5262
|
-
schema: {
|
|
5263
|
-
// The runtime refuses a quick form without one (`AGENT_STARTUP.
|
|
5264
|
-
// INVALID_TOOL_CONFIG`: "has a quick form schema without a
|
|
5265
|
-
// schemaId", measured 2026-10-05) — the same stable id a human
|
|
5266
|
-
// task's form carries.
|
|
5267
|
-
schemaId: schemaId(built.id, qid),
|
|
5268
|
-
fields: rows,
|
|
5269
|
-
outcomes: (esc.outcomes ?? []).map((o, i) => ({
|
|
5270
|
-
id: outcomeId(o, i), name: o, type: 'string', isPrimary: i === 0, action: 'Continue',
|
|
5271
|
-
})),
|
|
5272
|
-
},
|
|
5273
|
-
recipients: (esc.recipients ?? []).map((v) => ({ type: 3, value: v })),
|
|
5274
|
-
outcomeMapping: esc.outcomes && esc.outcomes.length
|
|
5275
|
-
? Object.fromEntries(esc.outcomes.map((o) => [o, 'continue']))
|
|
5276
|
-
: null,
|
|
5277
|
-
_additionalProps: {
|
|
5278
|
-
taskTitle: esc.taskTitle ?? '',
|
|
5279
|
-
priority: esc.priority ?? 'medium',
|
|
5280
|
-
labels: esc.labels ?? [],
|
|
5281
|
-
},
|
|
5282
|
-
_notifications: false,
|
|
5283
|
-
};
|
|
5284
|
-
scope.nodes.push(qnode);
|
|
5285
|
-
addEdge(scope, { node: id, port: 'escalation' }, qid, 'input');
|
|
5286
|
-
// The other half — without it the escalation is inert: see
|
|
5287
|
-
// `escalationResourceJson`.
|
|
5288
|
-
addSidecar({
|
|
5289
|
-
path: `${source}/resources/${qsource}/resource.json`,
|
|
5290
|
-
json: escalationResourceJson(esc, {
|
|
5291
|
-
id: qsource,
|
|
5292
|
-
channelId: fnv1aX4Uuid(`${built.id}/${step.name}/escalation/${esc.name}/channel`),
|
|
5293
|
-
quickFormSchema: qnode.inputs.schema,
|
|
5294
|
-
}),
|
|
5295
|
-
}, esc.source !== undefined);
|
|
5296
|
-
continue;
|
|
5297
|
-
}
|
|
5298
|
-
const app = esc.app;
|
|
5299
|
-
const edef = escalationDef();
|
|
5300
|
-
const eid = resourceNodeId(scope, esc.nodeId, `${step.name}Escalation`, `inline agent "${step.name}": escalation "${esc.name}"`);
|
|
5301
|
-
const enode = makeNode(edef.nodeType, eid, esc.name, { parentId, def: edef });
|
|
5302
|
-
enode.display = { ...enode.display, shape: 'circle' };
|
|
5303
|
-
delete enode.model;
|
|
5304
|
-
const esource = esc.source ?? derivedEscalationSource(built.id, step.name, esc.name);
|
|
5305
|
-
enode.inputs = {
|
|
5306
|
-
source: esource,
|
|
5307
|
-
name: esc.name,
|
|
5308
|
-
description: esc.description ?? '',
|
|
5309
|
-
type: 'app-task',
|
|
5310
|
-
app: {
|
|
5311
|
-
key: app.key,
|
|
5312
|
-
name: app.name,
|
|
5313
|
-
appName: app.name,
|
|
5314
|
-
displayName: app.name,
|
|
5315
|
-
folderPath: escalationAppFolder(app),
|
|
5316
|
-
inputSchema: fieldSchema(app.inputs),
|
|
5317
|
-
outputSchema: fieldSchema(app.outputs),
|
|
5318
|
-
},
|
|
5319
|
-
// `{type: 3, value: <email>}` is the deployed corpus's recipient
|
|
5320
|
-
// shape (type 3 = email address).
|
|
5321
|
-
recipients: (esc.recipients ?? []).map((v) => ({ type: 3, value: v })),
|
|
5322
|
-
// Every deployed outcome maps to 'continue' — the agent resumes with
|
|
5323
|
-
// the reviewer's answer whichever outcome they pick.
|
|
5324
|
-
outcomeMapping: esc.outcomes && esc.outcomes.length
|
|
5325
|
-
? Object.fromEntries(esc.outcomes.map((o) => [o, 'continue']))
|
|
5326
|
-
: null,
|
|
5327
|
-
_additionalProps: {
|
|
5328
|
-
taskTitle: esc.taskTitle ?? '',
|
|
5329
|
-
priority: esc.priority ?? 'medium',
|
|
5330
|
-
labels: esc.labels ?? [],
|
|
5331
|
-
},
|
|
5332
|
-
_notifications: false,
|
|
5333
|
-
_appInputs: null,
|
|
5334
|
-
};
|
|
5335
|
-
scope.nodes.push(enode);
|
|
5336
|
-
addEdge(scope, { node: id, port: 'escalation' }, eid, 'input');
|
|
5337
|
-
addSidecar({
|
|
5338
|
-
path: `${source}/resources/${esource}/resource.json`,
|
|
5339
|
-
json: escalationResourceJson(esc, {
|
|
5340
|
-
id: esource,
|
|
5341
|
-
channelId: fnv1aX4Uuid(`${built.id}/${step.name}/escalation/${esc.name}/channel`),
|
|
5342
|
-
}),
|
|
5343
|
-
}, esc.source !== undefined);
|
|
5344
|
-
}
|
|
5345
|
-
// MEMORY — one episodic-memory resource on the agent's `memory` handle
|
|
5346
|
-
// (the 1.4 definition selected above). Same artifact shape as context.
|
|
5347
|
-
if (s.memory !== undefined) {
|
|
5348
|
-
const m = s.memory;
|
|
5349
|
-
const mdef = memoryResourceDef(m);
|
|
5350
|
-
const mid = uid(scope, `${step.name}Memory`);
|
|
5351
|
-
const mnode = makeNode(mdef.nodeType, mid, m.name, { parentId, def: mdef });
|
|
5352
|
-
mnode.display = { ...mnode.display, shape: 'circle' };
|
|
5353
|
-
delete mnode.model;
|
|
5354
|
-
mnode.inputs = {
|
|
5355
|
-
source: fnv1aX4Uuid(`${built.id}/${step.name}/memory/${m.id}`),
|
|
5356
|
-
name: m.name,
|
|
5357
|
-
description: m.description ?? '',
|
|
5358
|
-
dynamicFewShotLearning: m.dynamicFewShotLearning ?? true,
|
|
5359
|
-
semanticSimilarity: m.semanticSimilarity ?? 0,
|
|
5360
|
-
kValue: m.kValue ?? 3,
|
|
5361
|
-
searchMode: m.searchMode ?? 'hybrid',
|
|
5362
|
-
};
|
|
5363
|
-
scope.nodes.push(mnode);
|
|
5364
|
-
addEdge(scope, { node: id, port: 'memory' }, mid, 'input');
|
|
5365
|
-
// No `features/<id>/feature.json` until `check` stops refusing memory
|
|
5366
|
-
// (AGENT_MEMORY_NOT_AUTHORABLE); its shape maps cleanly off `AgentMemoryRef`
|
|
5367
|
-
// but cannot be exercised while the handle does not exist.
|
|
5368
|
-
}
|
|
5369
|
-
for (const t of tails)
|
|
5370
|
-
addEdge(scope, t, id);
|
|
5371
|
-
return [{ node: id, port: successPort(def.nodeType) }];
|
|
5372
|
-
}
|
|
5373
|
-
if (spec.kind === 'conversationalAgent') {
|
|
5374
|
-
// The text CONVERSATIONAL agent: the inline agent's cluster shape (a
|
|
5375
|
-
// minted `source` plus an `agent.json` sidecar) with the family's own
|
|
5376
|
-
// turn wiring. The per-turn bindings live in `conversationalAgentSettings`
|
|
5377
|
-
// — the platform's serializer reads that sub-object and turns it into the
|
|
5378
|
-
// engine's `fpsProperties`, so the SDK writes the designer's shape and
|
|
5379
|
-
// lets the platform do that translation.
|
|
5380
|
-
const s = spec.inputs;
|
|
5381
|
-
const id = stepUid(scope, step.name);
|
|
5382
|
-
const source = agentFolder(s.source, step.name);
|
|
5383
|
-
const node = makeNode(NODE_TYPE.conversationalAgent, id, step.name, { parentId, requestedVersion: step.options?.version });
|
|
5384
|
-
delete node.model;
|
|
5385
|
-
const st = s.settings ?? {};
|
|
5386
|
-
// The FOUR individual fields are the runtime's source of truth — the
|
|
5387
|
-
// platform's own editor says so ("they always reflect the current
|
|
5388
|
-
// configuration regardless of mode; `mode` and `context` exist only to
|
|
5389
|
-
// restore the editor UI"), and its validator refuses a node without
|
|
5390
|
-
// `conversationId` even when a context binding is present. So `'simple'`
|
|
5391
|
-
// mode DERIVES them from the context exactly as `deriveFieldsFromContext`
|
|
5392
|
-
// does, appending each field's own context path (note `latestExchangeId`,
|
|
5393
|
-
// which is not the field's own name).
|
|
5394
|
-
const rendered = (v) => renderValue(toExpr(v), scope.rename);
|
|
5395
|
-
const ctxExpr = st.context === undefined ? undefined : toExpr(st.context);
|
|
5396
|
-
const derive = (contextPath) => {
|
|
5397
|
-
if (ctxExpr === undefined || ctxExpr.literal)
|
|
5398
|
-
return undefined;
|
|
5399
|
-
return renderValue(new Expr(`${ctxExpr.js}.${contextPath}`), scope.rename);
|
|
5400
|
-
};
|
|
5401
|
-
const field = (authored, contextPath) => authored !== undefined ? rendered(authored) : derive(contextPath);
|
|
5402
|
-
const settings = {
|
|
5403
|
-
mode: st.mode ?? 'simple',
|
|
5404
|
-
...(st.context !== undefined ? { context: rendered(st.context) } : {}),
|
|
5405
|
-
};
|
|
5406
|
-
for (const [key, contextPath] of [
|
|
5407
|
-
['conversationId', 'conversationId'],
|
|
5408
|
-
['exchangeId', 'latestExchangeId'],
|
|
5409
|
-
['messages', 'messages'],
|
|
5410
|
-
['userSettings', 'userSettings'],
|
|
5411
|
-
]) {
|
|
5412
|
-
const v = field(st[key], contextPath);
|
|
5413
|
-
if (v !== undefined)
|
|
5414
|
-
settings[key] = v;
|
|
5415
|
-
}
|
|
5416
|
-
// A SHELL node, like the inline agent's (#927): exactly the keys
|
|
5417
|
-
// `toAgentShellInputs` keeps. The system prompt, the model and the run
|
|
5418
|
-
// settings live only in the sidecar (`metadata.isConversational` there is
|
|
5419
|
-
// the family flag). `endExchange` is written nowhere: the platform retired
|
|
5420
|
-
// it (the node's v1.7 migration drops it, and the converter always sends
|
|
5421
|
-
// `conversationalService.endExchange = ''`).
|
|
5422
|
-
node.inputs = {
|
|
5423
|
-
source,
|
|
5424
|
-
conversationalAgentSettings: settings,
|
|
5425
|
-
};
|
|
5426
|
-
scope.nodes.push(node);
|
|
5427
|
-
addSidecar({
|
|
5428
|
-
path: `${source}/agent.json`,
|
|
5429
|
-
json: conversationalAgentJson({
|
|
5430
|
-
model: s.model, systemPrompt: s.systemPrompt, temperature: s.temperature,
|
|
5431
|
-
maxTokenPerResponse: s.maxTokenPerResponse, maxIterations: s.maxIterations,
|
|
5432
|
-
guardrails: s.guardrails, source, name: step.name,
|
|
5433
|
-
}),
|
|
5434
|
-
});
|
|
5435
|
-
for (const t of tails)
|
|
5436
|
-
addEdge(scope, t, id);
|
|
5437
|
-
return [{ node: id, port: successPort(NODE_TYPE.conversationalAgent) }];
|
|
5438
|
-
}
|
|
5439
|
-
if (spec.kind === 'createOutgoingCall' || spec.kind === 'endCall') {
|
|
5440
|
-
// The two VOICE service tasks. Both keep the service model on the
|
|
5441
|
-
// DEFINITION (the family convention) and write the author's fields over
|
|
5442
|
-
// the definition's own empty defaults.
|
|
5443
|
-
const type = spec.kind === 'createOutgoingCall' ? NODE_TYPE.createOutgoingCall : NODE_TYPE.endCall;
|
|
5444
|
-
const id = stepUid(scope, step.name);
|
|
5445
|
-
const node = makeNode(type, id, step.name, { parentId, requestedVersion: step.options?.version });
|
|
5446
|
-
delete node.model;
|
|
5447
|
-
const defaults = jsonClone(defByType.get(type)?.inputDefaults ?? {});
|
|
5448
|
-
node.inputs = spec.kind === 'createOutgoingCall'
|
|
5449
|
-
? {
|
|
5450
|
-
...defaults,
|
|
5451
|
-
from: renderValue(toExpr(spec.inputs.from), scope.rename),
|
|
5452
|
-
to: typeof spec.inputs.to === 'string'
|
|
5453
|
-
? { type: 'literal', expression: spec.inputs.to, fieldType: 'string' }
|
|
5454
|
-
: renderValue(toExpr(spec.inputs.to), scope.rename),
|
|
5455
|
-
}
|
|
5456
|
-
: {
|
|
5457
|
-
...defaults,
|
|
5458
|
-
// The whole call-context OBJECT, not a scalar — bound from a
|
|
5459
|
-
// trigger or a create-call step.
|
|
5460
|
-
callContext: renderValue(toExpr(spec.inputs.callContext), scope.rename),
|
|
5461
|
-
};
|
|
5462
|
-
scope.nodes.push(node);
|
|
5463
|
-
for (const t of tails)
|
|
5464
|
-
addEdge(scope, t, id);
|
|
5465
|
-
return [{ node: id, port: successPort(type) }];
|
|
5466
|
-
}
|
|
5467
|
-
if (spec.kind === 'voiceAgent') {
|
|
5468
|
-
// A VOICE agent: the inline agent's cluster shape (a `source` uuid plus an
|
|
5469
|
-
// `agent.json` sidecar the platform resolves at `<source>/agent.json`),
|
|
5470
|
-
// with the voice family's own inputs. There is no prompt templating and
|
|
5471
|
-
// no declared `returns` — the turn comes from the live call, and the
|
|
5472
|
-
// definition declares what comes back.
|
|
5473
|
-
const s = spec.inputs;
|
|
5474
|
-
const id = stepUid(scope, step.name);
|
|
5475
|
-
const source = agentFolder(s.source, step.name);
|
|
5476
|
-
const node = makeNode(NODE_TYPE.voiceAgent, id, step.name, { parentId, requestedVersion: step.options?.version });
|
|
5477
|
-
// Same rule as the inline agent's: the resource model lives on the
|
|
5478
|
-
// definition (its `model.source: true` is what makes `source` an input).
|
|
5479
|
-
delete node.model;
|
|
5480
|
-
const nodeInputs = s.inputs ?? {};
|
|
5481
|
-
const plan = agentInputPlan(nodeInputs, scope.rename);
|
|
5482
|
-
// A SHELL node (#927): the system prompt and the `voice` settings live only
|
|
5483
|
-
// in the sidecar's `messages` and `settings.voice`. On a voice node either
|
|
5484
|
-
// one makes Studio Web treat the `.flow` as authoritative and skip the
|
|
5485
|
-
// sidecar (`hasEmbeddedAgentContent`). `uip maestro flow debug` builds the
|
|
5486
|
-
// voice agent from the sidecar (`packageInlineAgents`).
|
|
5487
|
-
// The node keeps exactly the keys `toAgentShellInputs` keeps;
|
|
5488
|
-
// `maxIterations` and `metadata.isConversational` are in the sidecar.
|
|
5489
|
-
node.inputs = {
|
|
5490
|
-
source,
|
|
5491
|
-
callContext: renderValue(toExpr(s.callContext), scope.rename),
|
|
5492
|
-
...(plan.refs.length === 0 ? {} : {
|
|
5493
|
-
agentInputVariables: plan.refs.map((r) => ({
|
|
5494
|
-
id: r.flatName,
|
|
5495
|
-
type: 'string',
|
|
5496
|
-
binding: r.binding,
|
|
5497
|
-
})),
|
|
5498
|
-
}),
|
|
5499
|
-
};
|
|
5500
|
-
scope.nodes.push(node);
|
|
5501
|
-
// The sidecar half, in the voice dialect: `settings.voice` carries the
|
|
5502
|
-
// audio configuration (the storage layer's own StoredVoiceSettings
|
|
5503
|
-
// shape), and the messages carry the system prompt alone.
|
|
5504
|
-
addSidecar({
|
|
5505
|
-
path: `${source}/agent.json`,
|
|
5506
|
-
json: voiceAgentJson({
|
|
5507
|
-
systemPrompt: s.systemPrompt,
|
|
5508
|
-
plan,
|
|
5509
|
-
voice: s.voice,
|
|
5510
|
-
maxIterations: s.maxIterations,
|
|
5511
|
-
source,
|
|
5512
|
-
name: step.name,
|
|
5513
|
-
}),
|
|
5514
|
-
});
|
|
5515
|
-
if (plan.refs.length > 0) {
|
|
5516
|
-
const agentJson = sidecars[sidecars.length - 1].json;
|
|
5517
|
-
scope.agentSchemas.push({ schema: agentJson.inputSchema, refs: plan.refs });
|
|
5518
|
-
}
|
|
5519
|
-
for (const t of tails)
|
|
5520
|
-
addEdge(scope, t, id);
|
|
5521
|
-
return [{ node: id, port: successPort(NODE_TYPE.voiceAgent) }];
|
|
5522
|
-
}
|
|
5523
2818
|
if (spec.kind === 'ixpExtract') {
|
|
5524
2819
|
const s = spec.inputs;
|
|
5525
2820
|
const def = perCallDefinition(spec); // ixpExtractDef: throws on an unparseable project
|
|
@@ -5943,6 +3238,29 @@ export function serialize(built, opts = {}) {
|
|
|
5943
3238
|
// one, hang the run with no incident. check reports both
|
|
5944
3239
|
// (HITL_FAN_OUT_INTO_JOIN, HITL_END_OUTCOME_IN_PARALLEL); this is the
|
|
5945
3240
|
// compile-side half, and it fails closed.
|
|
3241
|
+
// The fork, when the author names one (`{ fork }`, #1310): a merge node
|
|
3242
|
+
// with one incoming edge and one edge per arm — the gateway Studio Web
|
|
3243
|
+
// draws for a parallel split. The arms then leave from it instead of
|
|
3244
|
+
// from the step before.
|
|
3245
|
+
const fork = parallelFork(step.options);
|
|
3246
|
+
if (fork) {
|
|
3247
|
+
// The gateway waits for a token on EVERY incoming edge, so two tails
|
|
3248
|
+
// that are alternatives — a `.branch()`'s or `.switch()`'s arms, a
|
|
3249
|
+
// fan-out task's outcomes — must not both enter it: only one fires
|
|
3250
|
+
// and the arms never start. Fails closed, as the join does.
|
|
3251
|
+
if (tails.length > 1) {
|
|
3252
|
+
throw new Error(`.parallel("${step.name}"): its fork "${fork.name}" would receive ${tails.length} alternative edges `
|
|
3253
|
+
+ `(${tails.map((t) => `${t.node}@${t.port}`).join(', ')}), and only one of them fires while the gateway `
|
|
3254
|
+
+ `waits for all of them. Add a step before the .parallel so the paths meet there first, or drop { fork }.`);
|
|
3255
|
+
}
|
|
3256
|
+
const forkId = stepUid(scope, fork.name);
|
|
3257
|
+
const forkNode = makeNode(NODE_TYPE.merge, forkId, fork.label ?? fork.name, { parentId, requestedVersion: fork.version ?? step.options?.version });
|
|
3258
|
+
forkNode.inputs = {};
|
|
3259
|
+
scope.nodes.push(forkNode);
|
|
3260
|
+
for (const t of tails)
|
|
3261
|
+
addEdge(scope, t, forkId);
|
|
3262
|
+
tails = [{ node: forkId, port: successPort(NODE_TYPE.merge) }];
|
|
3263
|
+
}
|
|
5946
3264
|
const armTails = [];
|
|
5947
3265
|
const endsBefore = hitlEndExits.length;
|
|
5948
3266
|
for (const arm of step.arms) {
|
|
@@ -6008,23 +3326,30 @@ export function serialize(built, opts = {}) {
|
|
|
6008
3326
|
// An option or a .break() the pinned version's definition cannot express
|
|
6009
3327
|
// is a contradiction the author must resolve, not a silent drop; `check`
|
|
6010
3328
|
// reports the same refusal from the same helper (#1186).
|
|
6011
|
-
const
|
|
3329
|
+
const version = loopVersion(o);
|
|
3330
|
+
// The definition the loop compiles against decides its handles AND its
|
|
3331
|
+
// inputs: a carried `core.logic.loop@1.0` (a designer-saved file's own)
|
|
3332
|
+
// keeps its `output`/`loopBack` pair, where the bundled lookup alone
|
|
3333
|
+
// would have written 2.4's `start`/`continue` onto a 1.0 node (#1311),
|
|
3334
|
+
// and an option is written when that definition declares the input
|
|
3335
|
+
// (the carried 1.0 declares `completionCondition`; the bundled 1.0.0
|
|
3336
|
+
// does not) and refused when it does not.
|
|
3337
|
+
const loopDef = definitionFor(NODE_TYPE.loop, version);
|
|
3338
|
+
const refusal = loopVersionRefusal(step.name, o, usesBreak, loopDef);
|
|
6012
3339
|
if (refusal)
|
|
6013
3340
|
throw new Error(refusal);
|
|
6014
|
-
const
|
|
6015
|
-
const
|
|
3341
|
+
const v24 = loopHasInnerHandles(version, loopDef);
|
|
3342
|
+
const declared = loopDeclaredInputs(loopDef);
|
|
6016
3343
|
const id = stepUid(scope, step.name);
|
|
6017
3344
|
const node = makeNode(NODE_TYPE.loop, id, step.options?.label ?? step.label ?? step.name, { parentId, requestedVersion: version });
|
|
6018
|
-
node.inputs =
|
|
6019
|
-
|
|
6020
|
-
|
|
6021
|
-
|
|
6022
|
-
|
|
6023
|
-
|
|
6024
|
-
|
|
6025
|
-
|
|
6026
|
-
}
|
|
6027
|
-
: { collection: renderValue(step.collection, scope.rename), parallel: o.parallel ?? false };
|
|
3345
|
+
node.inputs = {
|
|
3346
|
+
collection: renderValue(step.collection, scope.rename),
|
|
3347
|
+
parallel: o.parallel ?? false,
|
|
3348
|
+
...(o.completionCondition !== undefined && (v24 || 'completionCondition' in declared)
|
|
3349
|
+
? { completionCondition: renameRefs(o.completionCondition.js, scope.rename) }
|
|
3350
|
+
: {}),
|
|
3351
|
+
...(v24 || 'breakEnabled' in declared ? { breakEnabled: (o.breakEnabled ?? false) || usesBreak } : {}),
|
|
3352
|
+
};
|
|
6028
3353
|
scope.nodes.push(node);
|
|
6029
3354
|
for (const t of tails)
|
|
6030
3355
|
addEdge(scope, t, id);
|