@uipath/maestro-builder-sdk 6.8.6 → 6.9.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/check.d.ts CHANGED
@@ -164,3 +164,22 @@ export declare const SCHEDULE_PATTERN: string;
164
164
  * and day names, and the Quartz specials `L W LW ? * #`.
165
165
  */
166
166
  export declare const SCHEDULE_CRON_PATTERN: string;
167
+ /**
168
+ * The phrase in `text` that tells a model to answer in JSON TEXT, or `undefined`.
169
+ *
170
+ * `check` uses it for `INLINE_AGENT_PROMPT_JSON_TEXT`, passing the step's `returns`
171
+ * keys so the brace form only counts a brace that spells one of them. Without
172
+ * `returnKeys` (the example and doc sweeps) any quoted identifier key counts.
173
+ * `{{input.*}}` placeholders are removed first, so an input named `json` never
174
+ * matches.
175
+ *
176
+ * @example
177
+ * ```ts
178
+ * jsonTextDirective('Return ONLY a JSON object with keys "subject" and "body".'); // 'Return ONLY a JSON'
179
+ * jsonTextDirective('Return ONLY {"category","priority"}.', ['category']); // '{"category",'
180
+ * jsonTextDirective('Return a result conforming to the output schema.'); // undefined
181
+ * jsonTextDirective('Do not return JSON.'); // undefined
182
+ * jsonTextDirective('Answer the question using the JSON data below.'); // undefined
183
+ * ```
184
+ */
185
+ export declare function jsonTextDirective(text: string, returnKeys?: readonly string[]): string | undefined;
package/dist/check.js CHANGED
@@ -3154,6 +3154,106 @@ function checkAgentRead(name, field, rest, spec, step, diags) {
3154
3154
  ...(nearest ? { suggestion: `out('${name}', '${nearest}')` } : {}),
3155
3155
  });
3156
3156
  }
3157
+ // ─── an inline agent's prompt asking for JSON text ───────────────────────────────
3158
+ //
3159
+ // The phrase table behind `INLINE_AGENT_PROMPT_JSON_TEXT`. It is kept narrow on
3160
+ // purpose, because the rule is an error. Three shapes count, and each skips a
3161
+ // negation anywhere earlier in the same clause ("Do not return JSON", "Do not wrap
3162
+ // the answer in JSON", "Return the items, not as JSON"):
3163
+ //
3164
+ // - DIRECT: an output verb, then only words from a closed qualifier list, then
3165
+ // `json` — "Return ONLY a JSON object", "Return only the typed JSON contract".
3166
+ // - VIA: an output verb, up to two words, then `as` / `in` / `with`, qualifiers,
3167
+ // `json` — "Format your answer as JSON", "Respond with valid JSON".
3168
+ // - BRACE: a quoted-key brace whose key is one of the step's own `returns` keys —
3169
+ // `Return ONLY {"category","priority"}` next to `returns: { category, priority }`.
3170
+ //
3171
+ // A word outside the qualifier list ends a DIRECT match, so JSON as DATA does not
3172
+ // count in the DIRECT form: "Answer the question using the JSON data below", "Give
3173
+ // a summary of the JSON ticket", "Return the invoice number from the JSON". The VIA
3174
+ // form allows two free words, so it can still fire on JSON as a topic ("Answer in
3175
+ // Spanish with JSON examples"); the tail skips the common data nouns after `json`
3176
+ // (keys, data, record, examples, input) and the JSON-family FORMATS a string field
3177
+ // legitimately holds (JSON Schema, JSON Patch, JSON-LD, JSON Lines, …). `write` is
3178
+ // not a verb here: "You help users write JSON" is a topic, not an answer shape.
3179
+ //
3180
+ // The skills repo's preview-doc gate (tests/tasks/uipath-maestro-flow/_shared/
3181
+ // test_preview_inline_agent_prompts.py) ports this table to Python and loads a
3182
+ // byte-identical copy of tests/fixtures/inline-agent-prompt-vectors.json, so
3183
+ // change the table, the fixture and the port together.
3184
+ const JSON_TEXT_VERBS = 'return|respond|reply|output|answer|produce|emit|format|give|provide';
3185
+ /** Words that are only ever qualifiers of the answer's shape, never the thing it is about. */
3186
+ const JSON_TEXT_QUALIFIERS = [
3187
+ 'only', 'just', 'exactly', 'strictly', 'solely', 'simply', 'a', 'an', 'one', 'single', 'the',
3188
+ 'your', 'its', 'this', 'that', 'valid', 'raw', 'pure', 'plain', 'strict', 'compact', 'minified',
3189
+ 'well-formed', 'properly', 'formatted', 'typed', 'declared', 'structured', 'following', 'same',
3190
+ 'final', 'back', 'proper', 'correct', 'machine-readable', 'parseable', 'parsable',
3191
+ ];
3192
+ /** A negation anywhere earlier in the same clause, or anywhere in the match's gap. */
3193
+ const JSON_TEXT_NEGATIONS = new Set(['not', 'never', 'no', 'dont', 'without', 'rather', 'instead', 'avoid']);
3194
+ const isJsonTextNegation = (w) => JSON_TEXT_NEGATIONS.has(w) || w.endsWith("n't") || w.endsWith('n’t');
3195
+ const JSON_TEXT_WORD = "[A-Za-z][\\w'’-]*";
3196
+ /** Words are separated by spaces, tabs or a comma — never a newline or a full stop. */
3197
+ const JSON_TEXT_SEP = '[ \\t]*,?[ \\t]+';
3198
+ const JSON_TEXT_QUAL = `(?:${[...JSON_TEXT_QUALIFIERS].sort((a, b) => b.length - a.length).join('|')})\\b`;
3199
+ // `(?<![-\w])` skips `--output json`; `(?!\.\w)` skips `JSON.parse` / `JSON.stringify`;
3200
+ // `(?!-(?:ld|rpc)\b)` skips JSON-LD / JSON-RPC (`\b` matches before the hyphen);
3201
+ // the last lookahead skips "the JSON below" / "the JSON provided" / "the JSON you
3202
+ // get", data nouns ("the JSON keys", "JSON examples") and JSON-family formats
3203
+ // ("JSON Schema", "JSON Patch", "the JSON path", "a JSON Web Token").
3204
+ const JSON_TEXT_TAIL = `json\\b(?!\\.\\w)(?!-(?:ld|rpc)\\b)(?![ \\t]+(?:below|above|provided|given|you`
3205
+ + `|keys?|data|records?|examples?|inputs?`
3206
+ + `|schemas?|patch|path|pointer|lines|rpc|web[ \\t]+tokens?)\\b)`;
3207
+ const JSON_TEXT_VERB = `(?<![-\\w])(?:${JSON_TEXT_VERBS})\\b`;
3208
+ const JSON_TEXT_DIRECT = new RegExp(`${JSON_TEXT_VERB}((?:${JSON_TEXT_SEP}${JSON_TEXT_QUAL}){0,5})${JSON_TEXT_SEP}${JSON_TEXT_TAIL}`, 'gi');
3209
+ const JSON_TEXT_VIA = new RegExp(`${JSON_TEXT_VERB}((?:${JSON_TEXT_SEP}${JSON_TEXT_WORD}){0,2}?)${JSON_TEXT_SEP}(?:as|in|with)`
3210
+ + `((?:${JSON_TEXT_SEP}${JSON_TEXT_QUAL}){0,4})${JSON_TEXT_SEP}${JSON_TEXT_TAIL}`, 'gi');
3211
+ const escapeRegExp = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
3212
+ /**
3213
+ * The phrase in `text` that tells a model to answer in JSON TEXT, or `undefined`.
3214
+ *
3215
+ * `check` uses it for `INLINE_AGENT_PROMPT_JSON_TEXT`, passing the step's `returns`
3216
+ * keys so the brace form only counts a brace that spells one of them. Without
3217
+ * `returnKeys` (the example and doc sweeps) any quoted identifier key counts.
3218
+ * `{{input.*}}` placeholders are removed first, so an input named `json` never
3219
+ * matches.
3220
+ *
3221
+ * @example
3222
+ * ```ts
3223
+ * jsonTextDirective('Return ONLY a JSON object with keys "subject" and "body".'); // 'Return ONLY a JSON'
3224
+ * jsonTextDirective('Return ONLY {"category","priority"}.', ['category']); // '{"category",'
3225
+ * jsonTextDirective('Return a result conforming to the output schema.'); // undefined
3226
+ * jsonTextDirective('Do not return JSON.'); // undefined
3227
+ * jsonTextDirective('Answer the question using the JSON data below.'); // undefined
3228
+ * ```
3229
+ */
3230
+ export function jsonTextDirective(text, returnKeys) {
3231
+ if (typeof text !== 'string' || text === '')
3232
+ return undefined;
3233
+ // A placeholder becomes a hard break (`·` is neither a word nor a gap), and a
3234
+ // quoted or back-ticked `JSON` is read as the bare word.
3235
+ const t = text
3236
+ .replace(/\{\{\s*input\.[^}]*\}\}/g, ' · ')
3237
+ .replace(/[`"'“”](json)[`"'“”]/gi, ' $1 ');
3238
+ const words = (s) => (s.match(new RegExp(JSON_TEXT_WORD, 'g')) ?? []).map((w) => w.toLowerCase());
3239
+ const keys = returnKeys?.length ? returnKeys.map(escapeRegExp).join('|') : '[A-Za-z_]\\w*';
3240
+ const brace = new RegExp(`\\{[ \\t]*["'](?:${keys})["'][ \\t]*[,:}]`, 'g');
3241
+ for (const rule of [JSON_TEXT_DIRECT, JSON_TEXT_VIA, brace]) {
3242
+ rule.lastIndex = 0;
3243
+ for (const m of t.matchAll(rule)) {
3244
+ if (m.slice(1).some((g) => words(g ?? '').some(isJsonTextNegation)))
3245
+ continue;
3246
+ // Any negation earlier in the same clause: "Do not wrap the answer in JSON",
3247
+ // "No need to return JSON". A comma ends a clause, so "Do not guess, return
3248
+ // ONLY a JSON object" still counts.
3249
+ const clause = t.slice(0, m.index).split(/[.;:!?\n,]/).pop() ?? '';
3250
+ if (words(clause).some(isJsonTextNegation))
3251
+ continue;
3252
+ return m[0].trim();
3253
+ }
3254
+ }
3255
+ return undefined;
3256
+ }
3157
3257
  /**
3158
3258
  * Check an `inlineAgent(...)` call.
3159
3259
  *
@@ -3162,7 +3262,7 @@ function checkAgentRead(name, field, rest, spec, step, diags) {
3162
3262
  * rules can be certain rather than best-effort — the definition's own `required`
3163
3263
  * list is checkable, and so is whether the prompts and the declared variables agree.
3164
3264
  *
3165
- * Two rules exist because the PLATFORM does something with what you wrote:
3265
+ * Three rules exist because the PLATFORM does something with what you wrote:
3166
3266
  *
3167
3267
  * - `INLINE_AGENT_SCAFFOLD_MODEL` — the model the platform's own scaffold ships is
3168
3268
  * stale, and v1's smoke check for this node type fails a flow that keeps it. A
@@ -3174,6 +3274,11 @@ function checkAgentRead(name, field, rest, spec, step, diags) {
3174
3274
  * input the prompts never mention is pruned deployed while arriving locally: a
3175
3275
  * disagreement between rungs and the platform, which is the one class that has to
3176
3276
  * be said out loud rather than left to a run.
3277
+ * - `INLINE_AGENT_PROMPT_JSON_TEXT` — the platform returns `returns` as a typed
3278
+ * object: it becomes the `agent.json` `outputSchema`, and the agent runtime ends
3279
+ * a run with a structured call that fills exactly those fields. A prompt that
3280
+ * ALSO asks for JSON text makes the model pack its whole answer, serialized, into
3281
+ * one string field. See {@link jsonTextDirective}.
3177
3282
  */
3178
3283
  function checkInlineAgent(step, inputs, diags) {
3179
3284
  const at = (code, message, suggestion) => {
@@ -3241,6 +3346,40 @@ function checkInlineAgent(step, inputs, diags) {
3241
3346
  + `definition's default, \`{ content: 'string' }\`, which is the scaffold shape. Declare the fields the `
3242
3347
  + `agent must return, or drop the key and read \`content\`.`);
3243
3348
  }
3349
+ // A prompt that asks for JSON TEXT while `returns` declares the fields. The
3350
+ // platform already returns those fields as a typed object — `returns` is the
3351
+ // agent.json `outputSchema`, and the runtime ends a run with a structured call
3352
+ // that fills exactly those keys — so the extra directive does not shape the
3353
+ // answer, it gets serialized INTO it: on 2026-09-23 a writer declaring
3354
+ // `{ subject, body }` came back with `body` = '{"subject":…,"body":…}' in 3 of 3
3355
+ // debug runs, and the flow mailed the JSON. `uip maestro flow validate` accepts
3356
+ // the prompt (it never reads prompt meaning), so this is where it is said. An
3357
+ // ERROR for the same reason `INLINE_AGENT_UNDECLARED_PLACEHOLDER` is one: the
3358
+ // platform accepts the shape and the run still delivers the wrong value, and a
3359
+ // warning is printed past while compile writes the .flow anyway. Only when
3360
+ // `returns` is declared: with the `{ content }` default, JSON text in `content`
3361
+ // may be exactly what the author reads back.
3362
+ const returnKeys = Object.keys(inputs?.returns ?? {});
3363
+ if (returnKeys.length) {
3364
+ for (const field of ['systemPrompt', 'userPrompt']) {
3365
+ const phrase = jsonTextDirective(String(inputs?.[field] ?? ''), returnKeys);
3366
+ if (phrase === undefined)
3367
+ continue;
3368
+ const rewrite = `Return a result conforming to the output schema. `
3369
+ + returnKeys.map((k) => `${k}: <what it holds>.`).join(' ');
3370
+ at('INLINE_AGENT_PROMPT_JSON_TEXT', `Inline agent step "${step}"'s \`${field}\` tells the model to answer in JSON text ("${phrase}"), `
3371
+ + `but \`returns\` already declares the answer: ${returnKeys.map((k) => `\`${k}\``).join(', ')}. `
3372
+ + `The platform returns those fields as a typed object — \`returns\` becomes the agent.json `
3373
+ + `\`outputSchema\`, and the agent runtime ends each run with a structured call that fills exactly `
3374
+ + `those keys — so \`out('${step}', '${returnKeys[0]}')\` reads a value, not text to parse. Asking `
3375
+ + `for JSON as well makes the model pack its whole answer, serialized, into one string field (for `
3376
+ + `example a \`body\` that reads '{"subject":…,"body":…}'). Replace only that JSON instruction (keep the rest `
3377
+ + `of the prompt) with one line per field, in the platform's own wording: "${rewrite}" Declare a field \`'object'\` or `
3378
+ + `\`'array'\` if it should carry structure. \`uip maestro flow validate\` accepts this prompt, so `
3379
+ + `nothing later catches it. If this is a false positive (the prompt only mentions JSON as data), `
3380
+ + `compile with --no-check.`);
3381
+ }
3382
+ }
3244
3383
  // The prompts-vs-variables agreement, in both directions. Both prompts are
3245
3384
  // searched, because flow-v1's pre-pass scans both.
3246
3385
  const prompts = `${String(inputs?.systemPrompt ?? '')}\n${String(inputs?.userPrompt ?? '')}`;
@@ -1376,16 +1376,18 @@ export interface InlineAgentInputs {
1376
1376
  */
1377
1377
  model: string;
1378
1378
  /**
1379
- * The agent's standing instructions — who it is and what it must return.
1380
- * Required by the definition (`minLength: 1`).
1379
+ * The agent's standing instructions — who it is, and what each `returns` field
1380
+ * should hold. Required by the definition (`minLength: 1`).
1381
1381
  *
1382
1382
  * **Write it as plain text, and reference a value as `{{input.<name>}}`** — the
1383
1383
  * agent's own template syntax, and the same names `inputs` declares:
1384
1384
  *
1385
1385
  * ```ts
1386
- * systemPrompt: 'You classify support email. Return ONLY {"category","priority"}.',
1386
+ * systemPrompt: 'You classify support email. Return a result conforming to the output '
1387
+ * + 'schema. category: billing | technical | account. priority: low | normal | high.',
1387
1388
  * userPrompt: 'Classify this email:\n{{input.body}}',
1388
1389
  * inputs: { body: input('body') },
1390
+ * returns: { category: 'string', priority: 'string' },
1389
1391
  * ```
1390
1392
  *
1391
1393
  * Why a placeholder rather than an interpolated expression: an inline agent is two
@@ -1404,11 +1406,24 @@ export interface InlineAgentInputs {
1404
1406
  * literal text reaches the model. See the inline-agent node reference (`website/reference/inline-agent.md`)
1405
1407
  * §"The three prompt DIALECTS".
1406
1408
  *
1407
- * If the agent has to answer in a shape the flow then reads, say so here. The
1408
- * `returns` keys are what a downstream `out(...)` reads, and the engine's own live
1409
- * path appends a "return ONLY a JSON object with exactly these keys" directive for
1410
- * the same reason — a system prompt that contradicts it is a fight the model may
1411
- * lose.
1409
+ * **The answer's shape is `returns`, not the prompt.** `returns` becomes the node's
1410
+ * `agentOutputVariables` and the `agent.json` `outputSchema` — what Studio Web's
1411
+ * Outputs editor writes — and the platform's agent runtime ends each run with a
1412
+ * built-in structured call (`end_execution`) that fills exactly those fields. So
1413
+ * `out('<step>', '<field>')` reads a typed value, not text to parse. Describe what
1414
+ * each field holds, in the product's own wording, as the example above does:
1415
+ * `Return a result conforming to the output schema. <field>: <how to fill it>.`
1416
+ *
1417
+ * Never ask for JSON TEXT as well ("Return ONLY a JSON object with keys …"). The
1418
+ * model then packs its whole answer, serialized, into ONE string field: a writer
1419
+ * declaring `{ subject, body }` came back on 2026-09-23 with
1420
+ * `body: '{"subject":"…","body":"Dear …"}'` in 3 of 3 debug runs, and the flow sent
1421
+ * that text on. A field that should carry structure is declared `'object'` or
1422
+ * `'array'` instead.
1423
+ *
1424
+ * @enforcedBy INLINE_AGENT_PROMPT_JSON_TEXT A prompt that asks for JSON text while
1425
+ * `returns` declares fields is refused; the platform accepts it and the run returns
1426
+ * the packed value.
1412
1427
  */
1413
1428
  systemPrompt: string;
1414
1429
  /**
@@ -1516,7 +1531,8 @@ export interface InlineAgentInputs {
1516
1531
  * ```ts
1517
1532
  * inlineAgent({
1518
1533
  * model: 'gpt-5.4',
1519
- * systemPrompt: 'You decide billing disputes from the SOPs. Return ONLY {"determination","rationale"}.',
1534
+ * systemPrompt: 'You decide billing disputes from the SOPs. Return a result conforming to the output '
1535
+ * + 'schema. determination: valid | invalid | needs_review. rationale: the SOP rule that decided it.',
1520
1536
  * userPrompt: 'Dispute: {{input.description}}',
1521
1537
  * inputs: { description: input('disputeDescription') },
1522
1538
  * returns: { determination: 'string', rationale: 'string' },
@@ -3069,8 +3085,8 @@ export declare function agent(inputs: AgentInputs): ActionSpec;
3069
3085
  * model: 'gpt-5.4',
3070
3086
  * systemPrompt:
3071
3087
  * 'You are a support triage assistant. Classify the email into exactly one ' +
3072
- * 'category (billing | technical | account) and one priority (low | normal | ' +
3073
- * 'high). Return ONLY a JSON object with keys "category" and "priority".',
3088
+ * 'category and one priority. Return a result conforming to the output schema. ' +
3089
+ * 'category: billing | technical | account. priority: low | normal | high.',
3074
3090
  * userPrompt: 'Classify this email:\n{{input.body}}',
3075
3091
  * inputs: { body: input('body') },
3076
3092
  * returns: { category: 'string', priority: 'string' },
@@ -850,8 +850,8 @@ export function agent(inputs) {
850
850
  * model: 'gpt-5.4',
851
851
  * systemPrompt:
852
852
  * 'You are a support triage assistant. Classify the email into exactly one ' +
853
- * 'category (billing | technical | account) and one priority (low | normal | ' +
854
- * 'high). Return ONLY a JSON object with keys "category" and "priority".',
853
+ * 'category and one priority. Return a result conforming to the output schema. ' +
854
+ * 'category: billing | technical | account. priority: low | normal | high.',
855
855
  * userPrompt: 'Classify this email:\n{{input.body}}',
856
856
  * inputs: { body: input('body') },
857
857
  * returns: { category: 'string', priority: 'string' },
package/dist/serialize.js CHANGED
@@ -4396,7 +4396,7 @@ export function serialize(built, opts = {}) {
4396
4396
  ...(s.guardrails === undefined ? {} : { guardrails: s.guardrails }),
4397
4397
  // The two DESCRIPTOR ARRAYS. `agentInputVariables` entries carry the
4398
4398
  // binding expression; `agentOutputVariables` entries are name + type only,
4399
- // and a runner coerces the model's completion to exactly these keys.
4399
+ // and the runtime's structured final call fills exactly these keys.
4400
4400
  //
4401
4401
  // `id` is the FLAT NAME, not the author's — the platform re-derives this
4402
4402
  // array from the node's prompts and keeps a pre-existing entry only when
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uipath/maestro-builder-sdk",
3
- "version": "6.8.6",
3
+ "version": "6.9.0",
4
4
  "description": "Build UiPath Flow, Case, and BPMN artifacts by writing TypeScript.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://docs.uipath.com/maestro",
@@ -87,5 +87,5 @@
87
87
  "@types/node": "^22.7.0",
88
88
  "esbuild": "^0.28.1"
89
89
  },
90
- "gitref": "503ff8140144536c938e0b0ad0356b6ccb41b9f9"
90
+ "gitref": "b7baf691c4126ec18eb3388d0d0a70b09301f24d"
91
91
  }