@uipath/maestro-builder-sdk 6.14.3 → 6.14.5
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 +58 -58
- package/dist/api-members.md +99 -96
- package/dist/bindings-v2.d.ts +26 -0
- package/dist/bindings-v2.js +111 -7
- package/dist/check.js +80 -9
- package/dist/compile-cli.js +14 -8
- package/dist/core/actions.d.ts +19 -0
- package/dist/core/binding-messages.d.ts +16 -1
- package/dist/core/binding-messages.js +21 -0
- package/dist/serialize.d.ts +28 -0
- package/dist/serialize.js +345 -12
- package/lib/README.md +1 -0
- package/lib/library-json.samples/index.json +9 -0
- package/lib/library-json.samples/uipath-uipath-airdk/web-search@1.0.0.json +118 -0
- package/lib/library-json.samples/uipath-uipath-airdk/web-search@1.0.0.v1def.json +258 -0
- package/package.json +2 -2
package/dist/serialize.js
CHANGED
|
@@ -15,6 +15,7 @@ import { FlowAction, FlowTrigger } from './core/node-classes.js';
|
|
|
15
15
|
import { connectorRawNodeRefusal } from './core/connector-raw-node.js';
|
|
16
16
|
import { dataFabricUnknownInputs } from './core/typed-raw-node.js';
|
|
17
17
|
import { isAnswerField } from './core/hitl-answers.js';
|
|
18
|
+
import { connectionConnectors } from './bindings-v2.js';
|
|
18
19
|
import { BindingRefusal, GUID_RE, bindingSelfNameMessage, bindingUndeclaredMessage, undeclaredBinding } from './core/binding-messages.js';
|
|
19
20
|
import { ByoaRefusal, byoaUnverified } from './core/byoa.js';
|
|
20
21
|
import { OUTCOME_HANDLE_NODE_TYPES, assertHitlExitInvariant, hitlExitModel, hitlExitViolation, outcomeSlug } from './core/hitl-routing.js';
|
|
@@ -979,8 +980,9 @@ const TOOL_RESOURCE_TYPE = {
|
|
|
979
980
|
*
|
|
980
981
|
* * `ixp`, `clientside` — measured to fail exactly that way (`ixp` leaves the
|
|
981
982
|
* flow failing validate; `clientside` drops the four inputs its node needs).
|
|
982
|
-
* * `connector` —
|
|
983
|
-
* node
|
|
983
|
+
* * `connector` — has its own writer, {@link connectorToolResourceJson}: its
|
|
984
|
+
* node carries the configuration in `inputs.detail`, which the strip keeps,
|
|
985
|
+
* and the file is the one `uip agent refresh` itself derives from it.
|
|
984
986
|
* * `mcp`, `a2a` — need the server's `availableTools` and the remote agent's
|
|
985
987
|
* card, which are tenant reads.
|
|
986
988
|
* * `builtin`, `httpRequest` — need a `properties.toolType` that maps
|
|
@@ -1201,6 +1203,225 @@ function connectorToolDef(rc) {
|
|
|
1201
1203
|
inputDefaults: {},
|
|
1202
1204
|
};
|
|
1203
1205
|
}
|
|
1206
|
+
/** The folder key a connection with no folder binding carries in its resource.json. */
|
|
1207
|
+
const NO_FOLDER_KEY = '00000000-0000-0000-0000-000000000000';
|
|
1208
|
+
/**
|
|
1209
|
+
* The library's operation description without the `(<Connector Name>) ` prefix
|
|
1210
|
+
* the v1def's `display.description` carries — the model reads this text, and
|
|
1211
|
+
* the designer's own resource.json has the bare sentence.
|
|
1212
|
+
*/
|
|
1213
|
+
function connectorToolDescription(rc) {
|
|
1214
|
+
const text = String(rc.v1def?.display?.description ?? rc.entry?.display?.description ?? '');
|
|
1215
|
+
const prefix = `(${rc.connectorName}) `;
|
|
1216
|
+
return rc.connectorName && text.startsWith(prefix) ? text.slice(prefix.length) : text;
|
|
1217
|
+
}
|
|
1218
|
+
/** An agent-decided value: `{{prompt: "<what the model is told>"}}`. */
|
|
1219
|
+
function promptChip(f) {
|
|
1220
|
+
const text = f.description || f.displayName || f.name;
|
|
1221
|
+
return `{{prompt: "${text.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"}}`;
|
|
1222
|
+
}
|
|
1223
|
+
const isPromptChip = (v) => typeof v === 'string' && v.startsWith('{{prompt');
|
|
1224
|
+
/** The text inside `{{prompt: "…"}}`, unescaped; `''` for a bare `{{prompt}}`. */
|
|
1225
|
+
function chipText(value) {
|
|
1226
|
+
const m = /\{\{\s*prompt:\s*"((?:[^"\\]|\\.)*)"/.exec(value);
|
|
1227
|
+
return m ? m[1].replace(/\\(.)/g, '$1') : '';
|
|
1228
|
+
}
|
|
1229
|
+
/** A field's enum as `{ name, value }` rows, the platform's spelling; `undefined` when it has none. */
|
|
1230
|
+
function enumRows(raw) {
|
|
1231
|
+
if (!Array.isArray(raw) || raw.length === 0)
|
|
1232
|
+
return undefined;
|
|
1233
|
+
return raw.map((e) => (typeof e === 'string'
|
|
1234
|
+
? { name: e, value: e }
|
|
1235
|
+
: { name: String(e?.name ?? e?.value ?? ''), value: String(e?.value ?? e?.name ?? '') }));
|
|
1236
|
+
}
|
|
1237
|
+
/**
|
|
1238
|
+
* The `inputs.detail` of a connector-tool node — the same key set a
|
|
1239
|
+
* `connector()` step's node carries, with the request buckets filled the way
|
|
1240
|
+
* `uip maestro flow node configure` fills them for a `tool.connector.*` node
|
|
1241
|
+
* (uipcli `buildAgentToolParamsFromMetadata`): every BODY field is
|
|
1242
|
+
* agent-decided (`{{prompt: "<description>"}}`) except a single-value enum,
|
|
1243
|
+
* which is its one constant (the model would otherwise guess — `google` for
|
|
1244
|
+
* `GoogleCustomSearch`); a QUERY/PATH parameter with a declared default sends
|
|
1245
|
+
* it, and otherwise is agent-decided too.
|
|
1246
|
+
*/
|
|
1247
|
+
function connectorToolDetail(rc, conn, folder, seed) {
|
|
1248
|
+
const params = Array.isArray(rc.entry?.operation?.parameters) ? rc.entry.operation.parameters : [];
|
|
1249
|
+
const buckets = { body: {}, query: {}, path: {} };
|
|
1250
|
+
for (const f of rc.fields ?? []) {
|
|
1251
|
+
const loc = f.location ?? 'body';
|
|
1252
|
+
if (loc === 'body') {
|
|
1253
|
+
const only = enumRows(f.enum);
|
|
1254
|
+
buckets.body[f.name] = only && only.length === 1 ? only[0].value : promptChip(f);
|
|
1255
|
+
}
|
|
1256
|
+
else if (loc === 'query' || loc === 'path') {
|
|
1257
|
+
const dflt = params.find((p) => p?.name === f.name)?.defaultValue;
|
|
1258
|
+
buckets[loc][f.name] = dflt !== undefined && dflt !== null && dflt !== '' ? dflt : promptChip(f);
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
const parts = rc.multipartParts ?? [];
|
|
1262
|
+
return {
|
|
1263
|
+
connector: rc.connectorKey,
|
|
1264
|
+
method: transportHttpMethod(rc.httpMethod),
|
|
1265
|
+
endpoint: rc.endpoint,
|
|
1266
|
+
...(rc.objectName ? { objectName: rc.objectName } : {}),
|
|
1267
|
+
inputMetadata: parts.length
|
|
1268
|
+
? { type: 'multipart', multipart: { bodyFieldName: multipartBodyFieldName(parts) } }
|
|
1269
|
+
: {},
|
|
1270
|
+
errorState: { hasError: false },
|
|
1271
|
+
telemetryData: { connectorKey: rc.connectorKey, connectorName: rc.connectorName },
|
|
1272
|
+
configuration: buildConfiguration(rc.entry, rc.version, {}, seed),
|
|
1273
|
+
...(rc.activityTypeId ? { uiPathActivityTypeId: rc.activityTypeId } : {}),
|
|
1274
|
+
...(conn ? { connectionId: conn, connectionResourceId: conn } : {}),
|
|
1275
|
+
...(folder ? { connectionFolderKey: folder } : {}),
|
|
1276
|
+
bodyParameters: buckets.body,
|
|
1277
|
+
...(Object.keys(buckets.path).length ? { pathParameters: buckets.path } : {}),
|
|
1278
|
+
...(Object.keys(buckets.query).length ? { queryParameters: buckets.query } : {}),
|
|
1279
|
+
...(parts.length ? { multipartParameters: parts.map((p) => ({ name: p.name, dataType: p.dataType })) } : {}),
|
|
1280
|
+
};
|
|
1281
|
+
}
|
|
1282
|
+
/** Walk (creating) the object chain for a dotted field name; returns the parent schema and the leaf key. */
|
|
1283
|
+
function nestedSlot(root, name) {
|
|
1284
|
+
const segments = name.split('.');
|
|
1285
|
+
let parent = root;
|
|
1286
|
+
for (const seg of segments.slice(0, -1)) {
|
|
1287
|
+
let child = parent.properties[seg];
|
|
1288
|
+
if (!child || child.type !== 'object') {
|
|
1289
|
+
child = { type: 'object', properties: {} };
|
|
1290
|
+
parent.properties[seg] = child;
|
|
1291
|
+
}
|
|
1292
|
+
parent = child;
|
|
1293
|
+
}
|
|
1294
|
+
return { parent, leaf: segments[segments.length - 1] };
|
|
1295
|
+
}
|
|
1296
|
+
/**
|
|
1297
|
+
* The `resources/<id>/resource.json` of a CONNECTOR tool (#867) — the file the
|
|
1298
|
+
* agent runtime registers the tool from, and the one that puts the connection
|
|
1299
|
+
* under the solution's Resources.
|
|
1300
|
+
*
|
|
1301
|
+
* The shape is uipcli's `mapConnectorToolToResource` (the writer behind `uip
|
|
1302
|
+
* agent refresh --inline-in-flow`, itself ported from flow-workbench's
|
|
1303
|
+
* `agent-connector-tool` mapper), in its metadata-enriched form: field types,
|
|
1304
|
+
* enums, `required` and the response fields come from the curated library,
|
|
1305
|
+
* which carries the same declarations that command fetches from Integration
|
|
1306
|
+
* Service. Where that writer and a Studio Web export differ, this follows the
|
|
1307
|
+
* writer: `enum`/`oneOf` only on agent-decided fields (a static field with an
|
|
1308
|
+
* enum breaks the runtime's model build), no `additionalProperties`, and
|
|
1309
|
+
* array-of-object response fields (`results[*]`) left out.
|
|
1310
|
+
*
|
|
1311
|
+
* The values come from the node's own `detail` — the same source refresh reads —
|
|
1312
|
+
* so a later refresh rewrites this file to itself.
|
|
1313
|
+
*/
|
|
1314
|
+
function connectorToolResourceJson(ctx, id) {
|
|
1315
|
+
const { rc, detail } = ctx;
|
|
1316
|
+
const valueOf = (raw) => raw !== null && typeof raw === 'object' ? JSON.stringify(raw) : String(raw ?? '');
|
|
1317
|
+
const bucketOf = { body: 'bodyParameters', query: 'queryParameters', path: 'pathParameters' };
|
|
1318
|
+
const fields = [];
|
|
1319
|
+
for (const f of rc.fields ?? []) {
|
|
1320
|
+
const loc = f.location ?? 'body';
|
|
1321
|
+
if (loc === 'multipart')
|
|
1322
|
+
continue;
|
|
1323
|
+
const value = valueOf(detail[bucketOf[loc]]?.[f.name]);
|
|
1324
|
+
fields.push({
|
|
1325
|
+
name: f.name,
|
|
1326
|
+
fieldLocation: loc,
|
|
1327
|
+
type: f.type || 'string',
|
|
1328
|
+
...(loc === 'body' && enumRows(f.enum)
|
|
1329
|
+
? { enumValues: enumRows(f.enum) } : {}),
|
|
1330
|
+
required: f.required === true,
|
|
1331
|
+
description: f.description ?? '',
|
|
1332
|
+
...(f.displayName ? { displayName: f.displayName } : {}),
|
|
1333
|
+
value,
|
|
1334
|
+
isPrompt: isPromptChip(value),
|
|
1335
|
+
});
|
|
1336
|
+
}
|
|
1337
|
+
for (const part of (detail.multipartParameters ?? [])) {
|
|
1338
|
+
fields.push({
|
|
1339
|
+
name: part.name, fieldLocation: 'multipart', type: part.dataType || 'string',
|
|
1340
|
+
required: false, description: '', value: '{{prompt}}', isPrompt: true,
|
|
1341
|
+
});
|
|
1342
|
+
}
|
|
1343
|
+
const inputSchema = { type: 'object', properties: {} };
|
|
1344
|
+
for (const f of fields) {
|
|
1345
|
+
if (f.fieldLocation === 'multipart')
|
|
1346
|
+
continue;
|
|
1347
|
+
const { parent, leaf } = nestedSlot(inputSchema, f.name);
|
|
1348
|
+
const prop = { type: f.type, title: f.displayName ?? leaf, description: f.description };
|
|
1349
|
+
if (f.isPrompt && f.enumValues) {
|
|
1350
|
+
prop.enum = f.enumValues.map((e) => e.value);
|
|
1351
|
+
prop.oneOf = f.enumValues.map((e) => ({ const: e.value, title: e.name }));
|
|
1352
|
+
}
|
|
1353
|
+
parent.properties[leaf] = prop;
|
|
1354
|
+
if (f.required)
|
|
1355
|
+
(parent.required ??= []).push(leaf);
|
|
1356
|
+
}
|
|
1357
|
+
const outputSchema = { type: 'object', properties: {}, definitions: {} };
|
|
1358
|
+
for (const f of (rc.entry?.outputSchema?.fields ?? [])) {
|
|
1359
|
+
if (typeof f?.name !== 'string' || f.name.includes('[*]'))
|
|
1360
|
+
continue;
|
|
1361
|
+
const { parent, leaf } = nestedSlot(outputSchema, f.name);
|
|
1362
|
+
parent.properties[leaf] = {
|
|
1363
|
+
type: f.type || 'string',
|
|
1364
|
+
title: f.displayName ?? leaf,
|
|
1365
|
+
...(f.description ? { description: f.description } : {}),
|
|
1366
|
+
};
|
|
1367
|
+
}
|
|
1368
|
+
const multipart = detail.inputMetadata?.type === 'multipart';
|
|
1369
|
+
return {
|
|
1370
|
+
$resourceType: 'tool',
|
|
1371
|
+
name: ctx.name,
|
|
1372
|
+
description: ctx.description,
|
|
1373
|
+
location: 'external',
|
|
1374
|
+
type: 'integration',
|
|
1375
|
+
inputSchema,
|
|
1376
|
+
outputSchema,
|
|
1377
|
+
settings: {},
|
|
1378
|
+
properties: {
|
|
1379
|
+
toolPath: rc.endpoint ?? '',
|
|
1380
|
+
objectName: rc.objectName ?? '',
|
|
1381
|
+
toolDisplayName: ctx.name,
|
|
1382
|
+
toolDescription: ctx.description,
|
|
1383
|
+
// The operation KEY (`GETBYID` for a get-by-id), not the transport verb:
|
|
1384
|
+
// the runtime sends it to Integration Service to pick the operation.
|
|
1385
|
+
method: rc.httpMethod,
|
|
1386
|
+
connection: {
|
|
1387
|
+
id: detail.connectionId ?? '',
|
|
1388
|
+
name: rc.connectorName,
|
|
1389
|
+
elementInstanceId: 0,
|
|
1390
|
+
apiBaseUri: '',
|
|
1391
|
+
state: 'enabled',
|
|
1392
|
+
isDefault: false,
|
|
1393
|
+
connector: { key: rc.connectorKey, name: rc.connectorName, image: '' },
|
|
1394
|
+
folder: { key: detail.connectionFolderKey ?? NO_FOLDER_KEY, path: '' },
|
|
1395
|
+
},
|
|
1396
|
+
parameters: fields.map((f, i) => ({
|
|
1397
|
+
name: f.name,
|
|
1398
|
+
displayName: f.displayName ?? f.name,
|
|
1399
|
+
type: f.type,
|
|
1400
|
+
fieldLocation: f.fieldLocation,
|
|
1401
|
+
value: f.value,
|
|
1402
|
+
// uipcli's fallback: an undescribed agent-decided field keeps the chip's own text.
|
|
1403
|
+
description: f.description || (f.isPrompt ? chipText(f.value) : ''),
|
|
1404
|
+
position: f.required ? 'primary' : 'secondary',
|
|
1405
|
+
sortOrder: i + 1,
|
|
1406
|
+
required: f.required,
|
|
1407
|
+
fieldVariant: f.isPrompt ? 'dynamic' : 'static',
|
|
1408
|
+
isCascading: false,
|
|
1409
|
+
dynamic: f.isPrompt,
|
|
1410
|
+
enumValues: f.enumValues ?? null,
|
|
1411
|
+
loadReferenceOptionsByDefault: null,
|
|
1412
|
+
dynamicBehavior: [],
|
|
1413
|
+
reference: null,
|
|
1414
|
+
})),
|
|
1415
|
+
bodyStructure: multipart
|
|
1416
|
+
? { contentType: 'multipart', jsonBodySection: detail.inputMetadata.multipart?.bodyFieldName ?? 'body' }
|
|
1417
|
+
: { contentType: 'json' },
|
|
1418
|
+
},
|
|
1419
|
+
guardrail: { policies: [] },
|
|
1420
|
+
iconUrl: ctx.iconUrl,
|
|
1421
|
+
id,
|
|
1422
|
+
isEnabled: true,
|
|
1423
|
+
};
|
|
1424
|
+
}
|
|
1204
1425
|
/**
|
|
1205
1426
|
* The tool kinds that wrap a DEPLOYED tenant resource, and everything that
|
|
1206
1427
|
* differs between them. Transcribed from `registry get` over one live example
|
|
@@ -2306,9 +2527,15 @@ function connectionResourceBindings(spec, existing) {
|
|
|
2306
2527
|
if (values.length === 0)
|
|
2307
2528
|
return { entries: [] };
|
|
2308
2529
|
const resourceKey = spec.resourceKey ?? spec.connectionId ?? '';
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2530
|
+
// Keyed on the NAME as well (flow-builder-sdk#865). The name is the definition's
|
|
2531
|
+
// own `<bindings.NAME>` placeholder, and two definitions sharing one connection
|
|
2532
|
+
// can spell it differently — managed HTTP says `uipath-uipath-http connection`,
|
|
2533
|
+
// the Slack connector `uipath-salesforce-slack connection`. Keyed on
|
|
2534
|
+
// resourceKey + attribute alone, whichever step came first claimed the
|
|
2535
|
+
// connection and the other node's placeholder never resolved: Studio showed
|
|
2536
|
+
// `invalid GUID <bindings.uipath-salesforce-slack connection>`.
|
|
2537
|
+
const have = (name, attr) => [...existing.values()].some((b) => b.resource === 'connection' && b.name === name && b.resourceKey === resourceKey
|
|
2538
|
+
&& b.propertyAttribute === attr);
|
|
2312
2539
|
const slug = spec.connectorKey.replace(/[^A-Za-z0-9]/g, '') || 'Connector';
|
|
2313
2540
|
let base = `b${slug.charAt(0).toUpperCase()}${slug.slice(1)}`;
|
|
2314
2541
|
for (let n = 2; existing.has(`${base}Connection`) || existing.has(`${base}FolderKey`); n++) {
|
|
@@ -2317,7 +2544,7 @@ function connectionResourceBindings(spec, existing) {
|
|
|
2317
2544
|
const entries = values.flatMap((v) => {
|
|
2318
2545
|
const name = typeof v.name === 'string' ? v.name : '';
|
|
2319
2546
|
const attr = v.propertyAttribute;
|
|
2320
|
-
if (!name || (attr !== 'ConnectionId' && attr !== 'FolderKey') || have(attr))
|
|
2547
|
+
if (!name || (attr !== 'ConnectionId' && attr !== 'FolderKey') || have(name, attr))
|
|
2321
2548
|
return [];
|
|
2322
2549
|
const isConnection = attr === 'ConnectionId';
|
|
2323
2550
|
const dflt = isConnection ? spec.connectionId : spec.folderKey;
|
|
@@ -4705,6 +4932,7 @@ export function serialize(built, opts = {}) {
|
|
|
4705
4932
|
let tdef;
|
|
4706
4933
|
let label;
|
|
4707
4934
|
let extraInputs = {};
|
|
4935
|
+
let connectorTool;
|
|
4708
4936
|
if (tr.kind === 'builtin') {
|
|
4709
4937
|
tdef = clone(BUILTIN_TOOL_DEFS[tr.tool]);
|
|
4710
4938
|
if (!tdef)
|
|
@@ -4747,10 +4975,33 @@ export function serialize(built, opts = {}) {
|
|
|
4747
4975
|
const rc = opts.library.resolve(tr.connector, tr.operation, tr.version, tr.object);
|
|
4748
4976
|
tdef = connectorToolDef(rc);
|
|
4749
4977
|
label = tr.name ?? rc.v1def?.display?.label ?? tr.operation;
|
|
4750
|
-
// The
|
|
4751
|
-
//
|
|
4752
|
-
//
|
|
4753
|
-
//
|
|
4978
|
+
// The connection rides the same three places it does for a
|
|
4979
|
+
// `connector()` step, because the platform reads it from the same
|
|
4980
|
+
// three (#867):
|
|
4981
|
+
// * the node's `inputs.detail` — what `uip maestro flow node configure`
|
|
4982
|
+
// writes on a `tool.connector.*` node, what `uip agent refresh
|
|
4983
|
+
// --inline-in-flow` builds the tool's resource.json FROM, and what
|
|
4984
|
+
// its shell-ify pass keeps ("where a connector tool's request config
|
|
4985
|
+
// lives"). It is also what `bindings_v2.json` is derived from.
|
|
4986
|
+
// * the top-level `bindings[]` pair the def's `model.context`
|
|
4987
|
+
// placeholders (`<bindings.… connection>` / `<bindings.FolderKey>`)
|
|
4988
|
+
// name.
|
|
4989
|
+
// * the agent's `resources/<id>/resource.json` (below).
|
|
4990
|
+
// Emitting none of them left the tool with no connection anywhere.
|
|
4991
|
+
const where = `inline agent "${step.name}"'s connector tool "${tr.connector}/${tr.operation}"`;
|
|
4992
|
+
const target = { key: tr.connector, action: tr.operation };
|
|
4993
|
+
const conn = resolveBinding(opts, tr.connection, 'connection', where, target);
|
|
4994
|
+
const folder = resolveBinding(opts, tr.folder, 'folder', where, target);
|
|
4995
|
+
const { entries: connBindings } = connectionResourceBindings({ def: rc.v1def, connectorKey: rc.connectorKey, connectionId: conn, folderKey: folder }, resourceBindings);
|
|
4996
|
+
for (const b of connBindings)
|
|
4997
|
+
resourceBindings.set(b.id, b);
|
|
4998
|
+
const detail = connectorToolDetail(rc, conn, folder, `${step.name}Tool`);
|
|
4999
|
+
extraInputs = { detail };
|
|
5000
|
+
connectorTool = {
|
|
5001
|
+
rc, detail, name: label,
|
|
5002
|
+
description: tr.description ?? connectorToolDescription(rc),
|
|
5003
|
+
iconUrl: String(tdef.display?.icon ?? ''),
|
|
5004
|
+
};
|
|
4754
5005
|
}
|
|
4755
5006
|
else if (tr.kind === 'mcp') {
|
|
4756
5007
|
// Per-instance def minted like the ixp family's; the node carries the
|
|
@@ -4830,6 +5081,10 @@ export function serialize(built, opts = {}) {
|
|
|
4830
5081
|
const tid = uid(scope, `${step.name}Tool`);
|
|
4831
5082
|
const tnode = makeNode(tdef.nodeType, tid, label, { parentId, def: tdef });
|
|
4832
5083
|
tnode.display = { ...tnode.display, shape: 'circle' };
|
|
5084
|
+
// `uip agent refresh` reads the tool's description off the node's display
|
|
5085
|
+
// first, so the node carries the one the resource.json does.
|
|
5086
|
+
if (connectorTool)
|
|
5087
|
+
tnode.display.description = connectorTool.description;
|
|
4833
5088
|
// The resource model lives on the DEFINITION only (CX's rule for
|
|
4834
5089
|
// context, and what the deployed corpus carries for tools).
|
|
4835
5090
|
delete tnode.model;
|
|
@@ -4848,8 +5103,10 @@ export function serialize(built, opts = {}) {
|
|
|
4848
5103
|
addEdge(scope, { node: id, port: 'tool' }, tid, 'input');
|
|
4849
5104
|
// The other half, for the kinds whose file is DERIVABLE — see
|
|
4850
5105
|
// `toolResourceJson` for which are not, and why each is left to the
|
|
4851
|
-
//
|
|
4852
|
-
const toolResource =
|
|
5106
|
+
// designer rather than guessed.
|
|
5107
|
+
const toolResource = connectorTool
|
|
5108
|
+
? connectorToolResourceJson(connectorTool, toolResourceId)
|
|
5109
|
+
: toolResourceJson(tr, { id: toolResourceId });
|
|
4853
5110
|
if (toolResource !== undefined) {
|
|
4854
5111
|
sidecars.push({
|
|
4855
5112
|
path: `${source}/resources/${toolResourceId}/resource.json`,
|
|
@@ -6280,8 +6537,84 @@ export function serialize(built, opts = {}) {
|
|
|
6280
6537
|
// Passing the target is what lets a version-conditional step exist at all —
|
|
6281
6538
|
// see migrate.ts's VERSION_CONDITIONAL ladder.
|
|
6282
6539
|
migrateToSchemaVersion(flow, schemaVersion);
|
|
6540
|
+
// Emitted-document invariants over connections, checked on the FINISHED file
|
|
6541
|
+
// so every family and every emission order is covered by one rule, not by
|
|
6542
|
+
// each call site remembering it (flow-builder-sdk#865/#866).
|
|
6543
|
+
assertBindingPlaceholdersResolve(flow);
|
|
6544
|
+
connectionConnectors(flow);
|
|
6283
6545
|
return flow;
|
|
6284
6546
|
}
|
|
6547
|
+
/**
|
|
6548
|
+
* Every `<bindings.NAME>` placeholder a node's definition carries in its
|
|
6549
|
+
* `model.context` must resolve to a `bindings[]` entry for THAT node.
|
|
6550
|
+
*
|
|
6551
|
+
* "Resolve" is Studio Web's rule, not a looser one: flow-v1
|
|
6552
|
+
* `matchContextBindings` + `getInstanceBindingKey`
|
|
6553
|
+
* (flow-schema/src/packaging/resolve-context.ts) take the entries whose
|
|
6554
|
+
* `resource` EQUALS the definition's `model.bindings.resource`, scope them to the
|
|
6555
|
+
* node's `inputs.detail.connectionResourceId` when that resource is `connection`
|
|
6556
|
+
* (or undeclared), and look the placeholder up there BY NAME. Any miss blocks
|
|
6557
|
+
* publish (MST-15010), and one that slips through reaches Studio as the literal
|
|
6558
|
+
* placeholder: `invalid GUID <bindings.uipath-salesforce-slack connection>`.
|
|
6559
|
+
* `uip maestro flow validate` does not check this.
|
|
6560
|
+
*
|
|
6561
|
+
* That is how #865 shipped — a per-call-site dedupe that let the first step on a
|
|
6562
|
+
* shared connection claim it — and UV-16774 before it (a `Connection` resource
|
|
6563
|
+
* spelling). Checking the finished document catches that class for every family
|
|
6564
|
+
* and every emission order, whichever call site produced it.
|
|
6565
|
+
*
|
|
6566
|
+
* It holds for real definitions: of the 5,102 in the local registry index
|
|
6567
|
+
* (2026-09-30), 4,999 reference a `<bindings.…>` name and every one of them
|
|
6568
|
+
* declares that name in `model.bindings.values`, which is what the emitters
|
|
6569
|
+
* close. Agent resource definitions are the one exemption (see below).
|
|
6570
|
+
*
|
|
6571
|
+
* @throws BindingRefusal `BINDING_PLACEHOLDER_UNRESOLVED`, naming each dangling
|
|
6572
|
+
* placeholder and the step that carries it.
|
|
6573
|
+
*/
|
|
6574
|
+
export function assertBindingPlaceholdersResolve(flow) {
|
|
6575
|
+
const bindings = Array.isArray(flow?.bindings) ? flow.bindings : [];
|
|
6576
|
+
const defs = new Map();
|
|
6577
|
+
for (const def of Array.isArray(flow?.definitions) ? flow.definitions : []) {
|
|
6578
|
+
defs.set(`${def?.nodeType}@${def?.version}`, def);
|
|
6579
|
+
}
|
|
6580
|
+
const nodes = [...(Array.isArray(flow?.nodes) ? flow.nodes : [])];
|
|
6581
|
+
for (const sub of Object.values(flow?.subflows ?? {})) {
|
|
6582
|
+
if (Array.isArray(sub?.nodes))
|
|
6583
|
+
nodes.push(...sub.nodes);
|
|
6584
|
+
}
|
|
6585
|
+
const dangling = [];
|
|
6586
|
+
for (const node of nodes) {
|
|
6587
|
+
// An agent's resource nodes (`uipath.agent.resource.tool.connector.…`) carry
|
|
6588
|
+
// the same placeholders, but they are resolved through the AGENT project —
|
|
6589
|
+
// `uip agent refresh` generates the connector tool's resource — and the
|
|
6590
|
+
// deployed corpus carries no flow `bindings[]` entry for them.
|
|
6591
|
+
if (typeof node?.type !== 'string' || node.type.startsWith('uipath.agent.resource.'))
|
|
6592
|
+
continue;
|
|
6593
|
+
const model = defs.get(`${node.type}@${node.typeVersion}`)?.model;
|
|
6594
|
+
if (!Array.isArray(model?.context))
|
|
6595
|
+
continue;
|
|
6596
|
+
const resource = model.bindings?.resource;
|
|
6597
|
+
const scope = resource === 'connection' || resource === undefined
|
|
6598
|
+
? node.inputs?.detail?.connectionResourceId ?? node.inputs?.connectionId ?? undefined
|
|
6599
|
+
: undefined;
|
|
6600
|
+
const candidates = resource === undefined ? bindings : bindings.filter((b) => b?.resource === resource);
|
|
6601
|
+
for (const row of model.context) {
|
|
6602
|
+
const name = typeof row?.value === 'string' ? /^<bindings\.(.+)>$/.exec(row.value)?.[1] : undefined;
|
|
6603
|
+
if (name === undefined)
|
|
6604
|
+
continue;
|
|
6605
|
+
if (candidates.some((b) => b?.name === name && (!scope || b.resourceKey === scope)))
|
|
6606
|
+
continue;
|
|
6607
|
+
dangling.push(`step "${node.display?.label ?? node.id}" (${node.type}): "${row.value}"`
|
|
6608
|
+
+ `${scope ? ` on connection ${scope}` : ''}`);
|
|
6609
|
+
}
|
|
6610
|
+
}
|
|
6611
|
+
if (dangling.length === 0)
|
|
6612
|
+
return;
|
|
6613
|
+
throw new BindingRefusal('BINDING_PLACEHOLDER_UNRESOLVED', `serialize: ${dangling.join('; ')} — no bindings[] entry of that name${dangling.length === 1 ? '' : ' (each)'} `
|
|
6614
|
+
+ `is emitted for the step, so Studio Web reads the placeholder itself as the value (`
|
|
6615
|
+
+ `"invalid GUID <bindings.…>") and refuses to publish. This is a serializer defect, not an authoring `
|
|
6616
|
+
+ `error; report it with the flow source.`);
|
|
6617
|
+
}
|
|
6285
6618
|
/**
|
|
6286
6619
|
* Validate a connector's activity inputs against its declared field list and
|
|
6287
6620
|
* return them ROUTED to the wire slots the platform reads. Catches the three
|
package/lib/README.md
CHANGED
|
@@ -90,6 +90,7 @@ points at the materialized `library-json/`. Until you unpack, only
|
|
|
90
90
|
| `uipath-atlassian-jira` | `create-issue` | `fields.project.key`, `fields.summary` |
|
|
91
91
|
| `uipath-salesforce-slack` | `send-message-to-user` | `channel`, `messageToSend` |
|
|
92
92
|
| `uipath-microsoft-outlook365` | `send-email` | `message.toRecipients` |
|
|
93
|
+
| `uipath-uipath-airdk` | `web-search` | `query` — the inline-agent connector-tool fixture (#867) |
|
|
93
94
|
|
|
94
95
|
## Refreshing the shipped library (needs an authenticated `uip`)
|
|
95
96
|
|
|
@@ -163,6 +163,15 @@
|
|
|
163
163
|
"objectName": "send_message_to_user_v2",
|
|
164
164
|
"path": "uipath-salesforce-slack/send-message-to-user@1.0.0.json"
|
|
165
165
|
},
|
|
166
|
+
{
|
|
167
|
+
"nodeType": "uipath.connector.uipath-uipath-airdk.web-search",
|
|
168
|
+
"version": "1.0.0",
|
|
169
|
+
"connectorKey": "uipath-uipath-airdk",
|
|
170
|
+
"label": "Web Search",
|
|
171
|
+
"activityType": "Curated",
|
|
172
|
+
"objectName": "v2::webSearch",
|
|
173
|
+
"path": "uipath-uipath-airdk/web-search@1.0.0.json"
|
|
174
|
+
},
|
|
166
175
|
{
|
|
167
176
|
"nodeType": "uipath.connector.uipath-uipath-dataservice.query-entity-records",
|
|
168
177
|
"version": "1.0.0",
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": "1",
|
|
3
|
+
"nodeType": "uipath.connector.uipath-uipath-airdk.web-search",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"category": "connector.196640",
|
|
6
|
+
"tags": [
|
|
7
|
+
"connector",
|
|
8
|
+
"activity"
|
|
9
|
+
],
|
|
10
|
+
"connector": {
|
|
11
|
+
"key": "uipath-uipath-airdk",
|
|
12
|
+
"name": "UiPath GenAI Activities"
|
|
13
|
+
},
|
|
14
|
+
"operation": {
|
|
15
|
+
"name": "Create",
|
|
16
|
+
"objectName": "v2::webSearch",
|
|
17
|
+
"objectDisplayName": "Web Search",
|
|
18
|
+
"httpMethod": "POST",
|
|
19
|
+
"path": "/v2/webSearch",
|
|
20
|
+
"subType": "standard",
|
|
21
|
+
"supportsStreaming": false,
|
|
22
|
+
"activityType": "Curated",
|
|
23
|
+
"pathTemplate": "/v2/webSearch"
|
|
24
|
+
},
|
|
25
|
+
"display": {
|
|
26
|
+
"label": "Web Search",
|
|
27
|
+
"description": "(UiPath GenAI Activities) Web search executes a search of the public domain using a natural language search query. The search results are available in the output. Web search offers near-real time search results, but certain time sensitive data like stock prices or weather will not consistently be current. There could be a few days lag. Be sure to pass in date/time and/or location as arguments in the query if required.",
|
|
28
|
+
"icon": "https://alpha.uipath.com/bc2ddac5-57bc-40e6-93fe-3b319b60ce36/studio_/typecache/icons/d93eefff0af32b964a4ce6fe3823de1d4c7ed71962c6f878520b57ebf8f4497a.svg",
|
|
29
|
+
"iconBackground": "linear-gradient(225deg, #FAFAFB 0%, #ECEDEF 100%)",
|
|
30
|
+
"iconBackgroundDark": "linear-gradient(225deg, #526069 0%, rgba(50, 60, 66, 0.6) 100%)",
|
|
31
|
+
"operationLabel": "Web Search"
|
|
32
|
+
},
|
|
33
|
+
"runtime": {
|
|
34
|
+
"bpmnType": "bpmn:SendTask",
|
|
35
|
+
"serviceType": "Intsvc.ActivityExecution",
|
|
36
|
+
"activityConfigurationVersion": "v1",
|
|
37
|
+
"requiresConnection": true,
|
|
38
|
+
"requiresFolderKey": true,
|
|
39
|
+
"requiresConnectionForSchema": false
|
|
40
|
+
},
|
|
41
|
+
"inputSchema": {
|
|
42
|
+
"fields": [
|
|
43
|
+
{
|
|
44
|
+
"name": "provider",
|
|
45
|
+
"type": "string",
|
|
46
|
+
"displayName": "Search Engine",
|
|
47
|
+
"description": "The search engine to use",
|
|
48
|
+
"enum": [
|
|
49
|
+
"GoogleCustomSearch"
|
|
50
|
+
],
|
|
51
|
+
"required": true
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"name": "query",
|
|
55
|
+
"type": "string",
|
|
56
|
+
"displayName": "Search",
|
|
57
|
+
"description": "The natural language query to search the web for",
|
|
58
|
+
"required": true
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"name": "num",
|
|
62
|
+
"type": "integer",
|
|
63
|
+
"displayName": "Number of results",
|
|
64
|
+
"description": "The number of results. Default to 10.",
|
|
65
|
+
"required": false
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"name": "start",
|
|
69
|
+
"type": "integer",
|
|
70
|
+
"displayName": "Start",
|
|
71
|
+
"description": "First result index (pagination).",
|
|
72
|
+
"required": false
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"name": "siteSearch",
|
|
76
|
+
"type": "string",
|
|
77
|
+
"displayName": "Site Search",
|
|
78
|
+
"description": "Restrict or exclude results for a domain.",
|
|
79
|
+
"required": false
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"name": "siteSearchFilter",
|
|
83
|
+
"type": "string",
|
|
84
|
+
"displayName": "Site Search Filter",
|
|
85
|
+
"description": "Either \"i\" (include) or \"e\" (exclude).",
|
|
86
|
+
"required": false
|
|
87
|
+
}
|
|
88
|
+
]
|
|
89
|
+
},
|
|
90
|
+
"outputSchema": {
|
|
91
|
+
"fields": [
|
|
92
|
+
{
|
|
93
|
+
"name": "formattedResults",
|
|
94
|
+
"type": "string",
|
|
95
|
+
"displayName": "FormattedResults",
|
|
96
|
+
"description": "The search results string containing title, URL, and snippet"
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"name": "results[*].title",
|
|
100
|
+
"type": "string",
|
|
101
|
+
"displayName": "Result title",
|
|
102
|
+
"description": "The title of a search result item."
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
"name": "results[*].snippet",
|
|
106
|
+
"type": "string",
|
|
107
|
+
"displayName": "Result snippet",
|
|
108
|
+
"description": "A brief summary or excerpt of a search result item."
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"name": "results[*].url",
|
|
112
|
+
"type": "string",
|
|
113
|
+
"displayName": "Result url",
|
|
114
|
+
"description": "The web address of the search result."
|
|
115
|
+
}
|
|
116
|
+
]
|
|
117
|
+
}
|
|
118
|
+
}
|