@uipath/llmgw-tool 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/tool.js +568 -118
  2. package/package.json +25 -31
package/dist/tool.js CHANGED
@@ -3,7 +3,8 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
3
3
  // package.json
4
4
  var package_default = {
5
5
  name: "@uipath/llmgw-tool",
6
- version: "1.1.0",
6
+ license: "MIT",
7
+ version: "1.2.0",
7
8
  description: "CLI plugin for UiPath AI Trust Layer Bring-Your-Own LLM connections.",
8
9
  private: false,
9
10
  repository: {
@@ -39,6 +40,7 @@ var package_default = {
39
40
  };
40
41
 
41
42
  // ../../filesystem/src/node.ts
43
+ import { randomUUID } from "node:crypto";
42
44
  import { existsSync } from "node:fs";
43
45
  import * as fs6 from "node:fs/promises";
44
46
  import * as os2 from "node:os";
@@ -655,6 +657,13 @@ defineLazyProperty(apps, "safari", () => detectPlatformBinary({
655
657
  var open_default = open;
656
658
 
657
659
  // ../../filesystem/src/node.ts
660
+ var LOCK_HEARTBEAT_MS = 5000;
661
+ var LOCK_STALE_MS = 15000;
662
+ var LOCK_MAX_WAIT_MS = 20000;
663
+ var LOCK_MAX_HOLD_MS = 60000;
664
+ var LOCK_RETRY_MIN_MS = 100;
665
+ var LOCK_RETRY_JITTER_MS = 200;
666
+
658
667
  class NodeFileSystem {
659
668
  path = {
660
669
  join: path2.join,
@@ -731,6 +740,90 @@ class NodeFileSystem {
731
740
  async mkdir(dirPath) {
732
741
  await fs6.mkdir(dirPath, { recursive: true });
733
742
  }
743
+ async acquireLock(lockPath) {
744
+ const canonicalPath = await this.canonicalizeLockTarget(lockPath);
745
+ const lockFile = `${canonicalPath}.lock`;
746
+ const ownerId = randomUUID();
747
+ const start = Date.now();
748
+ while (true) {
749
+ try {
750
+ await fs6.writeFile(lockFile, ownerId, { flag: "wx" });
751
+ return this.createLockRelease(lockFile, ownerId);
752
+ } catch (error) {
753
+ if (!this.hasErrnoCode(error, "EEXIST")) {
754
+ throw error;
755
+ }
756
+ const stats = await fs6.stat(lockFile).catch(() => null);
757
+ if (stats && Date.now() - stats.mtimeMs > LOCK_STALE_MS) {
758
+ const reclaimed = await fs6.rm(lockFile, { force: true }).then(() => true).catch(() => false);
759
+ if (reclaimed)
760
+ continue;
761
+ }
762
+ if (Date.now() - start > LOCK_MAX_WAIT_MS) {
763
+ throw new Error(`ELOCKED: timed out waiting for lock on ${canonicalPath}`);
764
+ }
765
+ await new Promise((resolve2) => setTimeout(resolve2, LOCK_RETRY_MIN_MS + Math.random() * LOCK_RETRY_JITTER_MS));
766
+ }
767
+ }
768
+ }
769
+ async canonicalizeLockTarget(lockPath) {
770
+ const absolute = path2.resolve(lockPath);
771
+ const fullReal = await fs6.realpath(absolute).catch(() => null);
772
+ if (fullReal)
773
+ return fullReal;
774
+ const parent = path2.dirname(absolute);
775
+ const base = path2.basename(absolute);
776
+ const canonicalParent = await fs6.realpath(parent).catch(() => parent);
777
+ return path2.join(canonicalParent, base);
778
+ }
779
+ createLockRelease(lockFile, ownerId) {
780
+ const heartbeatStart = Date.now();
781
+ let heartbeatTimer;
782
+ let stopped = false;
783
+ const stopHeartbeat = () => {
784
+ stopped = true;
785
+ if (heartbeatTimer)
786
+ clearTimeout(heartbeatTimer);
787
+ };
788
+ const scheduleNextHeartbeat = () => {
789
+ if (stopped)
790
+ return;
791
+ if (Date.now() - heartbeatStart >= LOCK_MAX_HOLD_MS) {
792
+ stopped = true;
793
+ return;
794
+ }
795
+ heartbeatTimer = setTimeout(() => {
796
+ runHeartbeat();
797
+ }, LOCK_HEARTBEAT_MS);
798
+ heartbeatTimer.unref?.();
799
+ };
800
+ const runHeartbeat = async () => {
801
+ if (stopped)
802
+ return;
803
+ const current = await fs6.readFile(lockFile, "utf-8").catch(() => null);
804
+ if (stopped)
805
+ return;
806
+ if (current !== ownerId) {
807
+ stopped = true;
808
+ return;
809
+ }
810
+ const now = Date.now() / 1000;
811
+ await fs6.utimes(lockFile, now, now).catch(() => {});
812
+ scheduleNextHeartbeat();
813
+ };
814
+ scheduleNextHeartbeat();
815
+ let released = false;
816
+ return async () => {
817
+ if (released)
818
+ return;
819
+ released = true;
820
+ stopHeartbeat();
821
+ const current = await fs6.readFile(lockFile, "utf-8").catch(() => null);
822
+ if (current === ownerId) {
823
+ await fs6.rm(lockFile, { force: true });
824
+ }
825
+ };
826
+ }
734
827
  async rm(filePath) {
735
828
  await fs6.rm(filePath, { recursive: true, force: true });
736
829
  }
@@ -776,7 +869,10 @@ class NodeFileSystem {
776
869
  }
777
870
  }
778
871
  isEnoent(error) {
779
- return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
872
+ return this.hasErrnoCode(error, "ENOENT");
873
+ }
874
+ hasErrnoCode(error, code) {
875
+ return typeof error === "object" && error !== null && "code" in error && error.code === code;
780
876
  }
781
877
  }
782
878
 
@@ -918,10 +1014,15 @@ async function extractErrorDetails(error, options) {
918
1014
  }
919
1015
  if (parsedBody?.errorCode && typeof parsedBody.errorCode === "string") {
920
1016
  context.errorCode = parsedBody.errorCode;
1017
+ } else if (parsedBody?.code && typeof parsedBody.code === "string") {
1018
+ context.errorCode = parsedBody.code;
921
1019
  }
922
1020
  if (parsedBody?.requestId && typeof parsedBody.requestId === "string") {
923
1021
  context.requestId = parsedBody.requestId;
924
1022
  }
1023
+ if (parsedBody?.traceId && typeof parsedBody.traceId === "string") {
1024
+ context.traceId = parsedBody.traceId;
1025
+ }
925
1026
  if (status === 429) {
926
1027
  const resp = response;
927
1028
  const headersObj = resp?.headers;
@@ -941,7 +1042,35 @@ async function extractErrorDetails(error, options) {
941
1042
  }
942
1043
  }
943
1044
  const hasContext = Object.keys(context).length > 0;
944
- return { result, message, details, ...hasContext ? { context } : {} };
1045
+ let parsedErrors;
1046
+ if (parsedBody?.errors && typeof parsedBody.errors === "object") {
1047
+ const errors = {};
1048
+ for (const [field, raw] of Object.entries(parsedBody.errors)) {
1049
+ if (Array.isArray(raw)) {
1050
+ const messages = raw.map((entry) => {
1051
+ if (typeof entry === "string")
1052
+ return entry;
1053
+ if (entry && typeof entry === "object" && typeof entry.message === "string") {
1054
+ return entry.message;
1055
+ }
1056
+ return String(entry);
1057
+ }).filter(Boolean);
1058
+ if (messages.length > 0)
1059
+ errors[field] = messages;
1060
+ } else if (typeof raw === "string") {
1061
+ errors[field] = [raw];
1062
+ }
1063
+ }
1064
+ if (Object.keys(errors).length > 0)
1065
+ parsedErrors = errors;
1066
+ }
1067
+ return {
1068
+ result,
1069
+ message,
1070
+ details,
1071
+ ...hasContext ? { context } : {},
1072
+ ...parsedErrors ? { parsedErrors } : {}
1073
+ };
945
1074
  }
946
1075
  async function extractErrorMessage(error, options) {
947
1076
  const { message } = await extractErrorDetails(error, options);
@@ -6072,6 +6201,60 @@ function escapeNonAscii(jsonText) {
6072
6201
  function needsAsciiSafeJson(sink) {
6073
6202
  return process.platform === "win32" && !sink.capabilities.isInteractive;
6074
6203
  }
6204
+ function isPlainRecord(value) {
6205
+ if (value === null || typeof value !== "object")
6206
+ return false;
6207
+ const prototype = Object.getPrototypeOf(value);
6208
+ return prototype === Object.prototype || prototype === null;
6209
+ }
6210
+ function toLowerCamelCaseKey(key) {
6211
+ if (!key)
6212
+ return key;
6213
+ if (/[_\-\s]/.test(key)) {
6214
+ const [firstPart, ...restParts] = key.split(/[_\-\s]+/).filter(Boolean);
6215
+ if (!firstPart)
6216
+ return key;
6217
+ return [
6218
+ toLowerCamelCaseSimpleKey(firstPart),
6219
+ ...restParts.map((part) => {
6220
+ const normalized = toLowerCamelCaseSimpleKey(part);
6221
+ return normalized.charAt(0).toUpperCase() + normalized.slice(1);
6222
+ })
6223
+ ].join("");
6224
+ }
6225
+ return toLowerCamelCaseSimpleKey(key);
6226
+ }
6227
+ function toLowerCamelCaseSimpleKey(key) {
6228
+ if (/^[A-Z0-9]+$/.test(key))
6229
+ return key.toLowerCase();
6230
+ return key.replace(/^[A-Z]+(?=[A-Z][a-z]|\d|$)|^[A-Z]/, (match) => match.toLowerCase());
6231
+ }
6232
+ function toPascalCaseKey(key) {
6233
+ const lowerCamelKey = toLowerCamelCaseKey(key);
6234
+ return lowerCamelKey ? lowerCamelKey.charAt(0).toUpperCase() + lowerCamelKey.slice(1) : lowerCamelKey;
6235
+ }
6236
+ function toPascalCaseData(value) {
6237
+ if (Array.isArray(value))
6238
+ return value.map(toPascalCaseData);
6239
+ if (!isPlainRecord(value))
6240
+ return value;
6241
+ const result = {};
6242
+ for (const [key, nestedValue] of Object.entries(value)) {
6243
+ result[toPascalCaseKey(key)] = toPascalCaseData(nestedValue);
6244
+ }
6245
+ return result;
6246
+ }
6247
+ function normalizeDataKeys(data) {
6248
+ return toPascalCaseData(data);
6249
+ }
6250
+ function normalizeOutputKeys(data) {
6251
+ const result = {};
6252
+ for (const [key, value] of Object.entries(data)) {
6253
+ const pascalKey = toPascalCaseKey(key);
6254
+ result[pascalKey] = pascalKey === "Data" ? value : toPascalCaseData(value);
6255
+ }
6256
+ return result;
6257
+ }
6075
6258
  function printOutput(data, format = "json", logFn, asciiSafe = false) {
6076
6259
  if (!data) {
6077
6260
  logFn("Empty response object. No data to display.");
@@ -6134,7 +6317,7 @@ function wrapText(text, width) {
6134
6317
  function printTable(data, logFn, externalLogValue) {
6135
6318
  if (data.length === 0)
6136
6319
  return;
6137
- const keys = Object.keys(data[0]).filter((key) => key !== "Code" && key !== "Log");
6320
+ const keys = Object.keys(data[0]).filter((key) => !["code", "log"].includes(key.toLowerCase()));
6138
6321
  const maxWidths = keys.map((key) => Math.max(key.length, ...data.map((item) => cellToString(item[key]).length)));
6139
6322
  const header = keys.map((key, i2) => key.padEnd(maxWidths[i2])).join(" | ");
6140
6323
  logFn(header);
@@ -6149,7 +6332,7 @@ function printTable(data, logFn, externalLogValue) {
6149
6332
  }
6150
6333
  }
6151
6334
  function printVerticalTable(data, logFn = console.log, externalLogValue) {
6152
- const keys = Object.keys(data).filter((key) => key !== "Code" && key !== "Log");
6335
+ const keys = Object.keys(data).filter((key) => !["code", "log"].includes(key.toLowerCase()));
6153
6336
  if (keys.length === 0)
6154
6337
  return;
6155
6338
  const maxKeyWidth = Math.max(...keys.map((key) => key.length));
@@ -6165,7 +6348,7 @@ function printVerticalTable(data, logFn = console.log, externalLogValue) {
6165
6348
  function printResizableTable(data, logFn = console.log, externalLogValue) {
6166
6349
  if (data.length === 0)
6167
6350
  return;
6168
- const keys = Object.keys(data[0]).filter((key) => key !== "Code" && key !== "Log");
6351
+ const keys = Object.keys(data[0]).filter((key) => !["code", "log"].includes(key.toLowerCase()));
6169
6352
  if (keys.length === 0)
6170
6353
  return;
6171
6354
  if (!process.stdout.isTTY) {
@@ -6241,8 +6424,26 @@ function printResizableTable(data, logFn = console.log, externalLogValue) {
6241
6424
  function toYaml(data) {
6242
6425
  return dump(data);
6243
6426
  }
6427
+ class FilterEvaluationError extends Error {
6428
+ __brand = "FilterEvaluationError";
6429
+ filter;
6430
+ instructions;
6431
+ result = RESULTS.ValidationError;
6432
+ constructor(filter, cause) {
6433
+ const underlying = cause instanceof Error ? cause.message : String(cause);
6434
+ super(`Filter '${filter}' failed to evaluate: ${underlying}`);
6435
+ this.name = "FilterEvaluationError";
6436
+ this.filter = filter;
6437
+ this.instructions = `The --output-filter expression '${filter}' failed at evaluation time. ` + "Note that --output-filter operates on the 'Data' field of the envelope, not the full object. " + "For example, on a list result use 'length(@)' instead of 'Data | length(@)'.";
6438
+ }
6439
+ }
6244
6440
  function applyFilter(data, filter) {
6245
- const result = search(data, filter);
6441
+ let result;
6442
+ try {
6443
+ result = search(data, filter);
6444
+ } catch (err) {
6445
+ throw new FilterEvaluationError(filter, err);
6446
+ }
6246
6447
  if (result == null)
6247
6448
  return [];
6248
6449
  if (Array.isArray(result)) {
@@ -6259,13 +6460,18 @@ function applyFilter(data, filter) {
6259
6460
  }
6260
6461
  var OutputFormatter;
6261
6462
  ((OutputFormatter) => {
6262
- function success(data) {
6463
+ function success(data, options) {
6263
6464
  data.Log ??= getLogFilePath() || undefined;
6465
+ const normalize = !options?.preserveDataKeys;
6466
+ if (normalize) {
6467
+ data.Data = normalizeDataKeys(data.Data);
6468
+ }
6264
6469
  const filter = getOutputFilter();
6265
6470
  if (filter) {
6266
- data.Data = applyFilter(data.Data, filter);
6471
+ const filtered = applyFilter(data.Data, filter);
6472
+ data.Data = normalize ? normalizeDataKeys(filtered) : filtered;
6267
6473
  }
6268
- logOutput(data, getOutputFormat());
6474
+ logOutput(normalizeOutputKeys(data), getOutputFormat());
6269
6475
  }
6270
6476
  OutputFormatter.success = success;
6271
6477
  function error(data) {
@@ -6275,7 +6481,7 @@ var OutputFormatter;
6275
6481
  result: data.Result,
6276
6482
  message: data.Message
6277
6483
  });
6278
- logOutput(data, getOutputFormat());
6484
+ logOutput(normalizeOutputKeys(data), getOutputFormat());
6279
6485
  }
6280
6486
  OutputFormatter.error = error;
6281
6487
  function emitList(code, items, opts) {
@@ -6296,13 +6502,14 @@ var OutputFormatter;
6296
6502
  function log(data) {
6297
6503
  const format = getOutputFormat();
6298
6504
  const sink = getOutputSink();
6505
+ const normalized = toPascalCaseData(data);
6299
6506
  if (format === "json") {
6300
- const json2 = JSON.stringify(data);
6507
+ const json2 = JSON.stringify(normalized);
6301
6508
  const safe = needsAsciiSafeJson(sink) ? escapeNonAscii(json2) : json2;
6302
6509
  sink.writeErr(`${safe}
6303
6510
  `);
6304
6511
  } else {
6305
- for (const [key, value] of Object.entries(data)) {
6512
+ for (const [key, value] of Object.entries(normalized)) {
6306
6513
  sink.writeErr(`${key}: ${value}
6307
6514
  `);
6308
6515
  }
@@ -6311,12 +6518,16 @@ var OutputFormatter;
6311
6518
  OutputFormatter.log = log;
6312
6519
  function formatToString(data) {
6313
6520
  const filter = getOutputFilter();
6314
- if (filter && "Data" in data && data.Data != null) {
6315
- data.Data = applyFilter(data.Data, filter);
6521
+ if ("Data" in data && data.Data != null) {
6522
+ data.Data = normalizeDataKeys(data.Data);
6523
+ if (filter) {
6524
+ data.Data = normalizeDataKeys(applyFilter(data.Data, filter));
6525
+ }
6316
6526
  }
6527
+ const output = normalizeOutputKeys(data);
6317
6528
  const lines = [];
6318
6529
  const sink = getOutputSink();
6319
- printOutput(data, getOutputFormat(), (msg) => {
6530
+ printOutput(output, getOutputFormat(), (msg) => {
6320
6531
  lines.push(msg);
6321
6532
  }, needsAsciiSafeJson(sink));
6322
6533
  return lines.join(`
@@ -7731,6 +7942,22 @@ JSONPath.prototype.vm = vm;
7731
7942
  import { Option } from "commander";
7732
7943
  // ../../common/src/option-validators.ts
7733
7944
  import { InvalidArgumentError } from "commander";
7945
+ // ../../common/src/polling/types.ts
7946
+ var PollOutcome = {
7947
+ Completed: "completed",
7948
+ Timeout: "timeout",
7949
+ Interrupted: "interrupted",
7950
+ Aborted: "aborted",
7951
+ Failed: "failed"
7952
+ };
7953
+
7954
+ // ../../common/src/polling/poll-failure-mapping.ts
7955
+ var REASON_BY_OUTCOME = {
7956
+ [PollOutcome.Timeout]: "poll_timeout",
7957
+ [PollOutcome.Failed]: "poll_failed",
7958
+ [PollOutcome.Interrupted]: "poll_failed",
7959
+ [PollOutcome.Aborted]: "poll_aborted"
7960
+ };
7734
7961
  // ../../common/src/polling/terminal-statuses.ts
7735
7962
  var TERMINAL_STATUSES = new Set([
7736
7963
  "completed",
@@ -7758,6 +7985,8 @@ var ScreenLogger;
7758
7985
  }
7759
7986
  ScreenLogger.progress = progress;
7760
7987
  })(ScreenLogger ||= {});
7988
+ // ../../common/src/sdk-user-agent.ts
7989
+ var sdkUserAgentHostToken = singleton("SdkUserAgentHostToken");
7761
7990
  // ../../common/src/tool-provider.ts
7762
7991
  var factorySlot = singleton("PackagerFactoryProvider");
7763
7992
  // ../../common/src/trackedAction.ts
@@ -7943,13 +8172,17 @@ Command2.prototype.trackedAction = function(context, fn, properties) {
7943
8172
  const [error] = await catchError(fn(...args));
7944
8173
  if (error) {
7945
8174
  errorMessage = error instanceof Error ? error.message : String(error);
7946
- logger.error(`[trackedAction] ${telemetryName} failed: ${errorMessage}`);
8175
+ logger.debug(`[trackedAction] ${telemetryName} failed: ${errorMessage}`);
8176
+ const typed = error;
8177
+ const customInstructions = typeof typed.instructions === "string" ? typed.instructions : undefined;
8178
+ const customResult = typeof typed.result === "string" && typed.result !== RESULTS.Success && Object.values(RESULTS).includes(typed.result) ? typed.result : undefined;
8179
+ const finalResult = customResult ?? RESULTS.Failure;
7947
8180
  OutputFormatter.error({
7948
- Result: RESULTS.Failure,
8181
+ Result: finalResult,
7949
8182
  Message: errorMessage,
7950
- Instructions: "An unexpected error occurred. Run with --log-level debug for details."
8183
+ Instructions: customInstructions ?? "An unexpected error occurred. Run with --log-level debug for details."
7951
8184
  });
7952
- context.exit(1);
8185
+ context.exit(EXIT_CODES[finalResult]);
7953
8186
  }
7954
8187
  const durationMs = performance.now() - startTime;
7955
8188
  const success = !error && (process.exitCode === undefined || process.exitCode === 0);
@@ -7993,6 +8226,7 @@ import path3 from "node:path";
7993
8226
  import { fileURLToPath as fileURLToPath2 } from "node:url";
7994
8227
  import childProcess32 from "node:child_process";
7995
8228
  import fs52, { constants as fsConstants22 } from "node:fs/promises";
8229
+ import { randomUUID as randomUUID2 } from "node:crypto";
7996
8230
  import { existsSync as existsSync2 } from "node:fs";
7997
8231
  import * as fs62 from "node:fs/promises";
7998
8232
  import * as os22 from "node:os";
@@ -8744,6 +8978,90 @@ class NodeFileSystem2 {
8744
8978
  async mkdir(dirPath) {
8745
8979
  await fs62.mkdir(dirPath, { recursive: true });
8746
8980
  }
8981
+ async acquireLock(lockPath) {
8982
+ const canonicalPath = await this.canonicalizeLockTarget(lockPath);
8983
+ const lockFile = `${canonicalPath}.lock`;
8984
+ const ownerId = randomUUID2();
8985
+ const start = Date.now();
8986
+ while (true) {
8987
+ try {
8988
+ await fs62.writeFile(lockFile, ownerId, { flag: "wx" });
8989
+ return this.createLockRelease(lockFile, ownerId);
8990
+ } catch (error) {
8991
+ if (!this.hasErrnoCode(error, "EEXIST")) {
8992
+ throw error;
8993
+ }
8994
+ const stats = await fs62.stat(lockFile).catch(() => null);
8995
+ if (stats && Date.now() - stats.mtimeMs > LOCK_STALE_MS2) {
8996
+ const reclaimed = await fs62.rm(lockFile, { force: true }).then(() => true).catch(() => false);
8997
+ if (reclaimed)
8998
+ continue;
8999
+ }
9000
+ if (Date.now() - start > LOCK_MAX_WAIT_MS2) {
9001
+ throw new Error(`ELOCKED: timed out waiting for lock on ${canonicalPath}`);
9002
+ }
9003
+ await new Promise((resolve22) => setTimeout(resolve22, LOCK_RETRY_MIN_MS2 + Math.random() * LOCK_RETRY_JITTER_MS2));
9004
+ }
9005
+ }
9006
+ }
9007
+ async canonicalizeLockTarget(lockPath) {
9008
+ const absolute = path22.resolve(lockPath);
9009
+ const fullReal = await fs62.realpath(absolute).catch(() => null);
9010
+ if (fullReal)
9011
+ return fullReal;
9012
+ const parent = path22.dirname(absolute);
9013
+ const base = path22.basename(absolute);
9014
+ const canonicalParent = await fs62.realpath(parent).catch(() => parent);
9015
+ return path22.join(canonicalParent, base);
9016
+ }
9017
+ createLockRelease(lockFile, ownerId) {
9018
+ const heartbeatStart = Date.now();
9019
+ let heartbeatTimer;
9020
+ let stopped = false;
9021
+ const stopHeartbeat = () => {
9022
+ stopped = true;
9023
+ if (heartbeatTimer)
9024
+ clearTimeout(heartbeatTimer);
9025
+ };
9026
+ const scheduleNextHeartbeat = () => {
9027
+ if (stopped)
9028
+ return;
9029
+ if (Date.now() - heartbeatStart >= LOCK_MAX_HOLD_MS2) {
9030
+ stopped = true;
9031
+ return;
9032
+ }
9033
+ heartbeatTimer = setTimeout(() => {
9034
+ runHeartbeat();
9035
+ }, LOCK_HEARTBEAT_MS2);
9036
+ heartbeatTimer.unref?.();
9037
+ };
9038
+ const runHeartbeat = async () => {
9039
+ if (stopped)
9040
+ return;
9041
+ const current = await fs62.readFile(lockFile, "utf-8").catch(() => null);
9042
+ if (stopped)
9043
+ return;
9044
+ if (current !== ownerId) {
9045
+ stopped = true;
9046
+ return;
9047
+ }
9048
+ const now = Date.now() / 1000;
9049
+ await fs62.utimes(lockFile, now, now).catch(() => {});
9050
+ scheduleNextHeartbeat();
9051
+ };
9052
+ scheduleNextHeartbeat();
9053
+ let released = false;
9054
+ return async () => {
9055
+ if (released)
9056
+ return;
9057
+ released = true;
9058
+ stopHeartbeat();
9059
+ const current = await fs62.readFile(lockFile, "utf-8").catch(() => null);
9060
+ if (current === ownerId) {
9061
+ await fs62.rm(lockFile, { force: true });
9062
+ }
9063
+ };
9064
+ }
8747
9065
  async rm(filePath) {
8748
9066
  await fs62.rm(filePath, { recursive: true, force: true });
8749
9067
  }
@@ -8789,9 +9107,18 @@ class NodeFileSystem2 {
8789
9107
  }
8790
9108
  }
8791
9109
  isEnoent(error) {
8792
- return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
9110
+ return this.hasErrnoCode(error, "ENOENT");
9111
+ }
9112
+ hasErrnoCode(error, code) {
9113
+ return typeof error === "object" && error !== null && "code" in error && error.code === code;
8793
9114
  }
8794
9115
  }
9116
+ var LOCK_HEARTBEAT_MS2 = 5000;
9117
+ var LOCK_STALE_MS2 = 15000;
9118
+ var LOCK_MAX_WAIT_MS2 = 20000;
9119
+ var LOCK_MAX_HOLD_MS2 = 60000;
9120
+ var LOCK_RETRY_MIN_MS2 = 100;
9121
+ var LOCK_RETRY_JITTER_MS2 = 200;
8795
9122
  var init_node = __esm(() => {
8796
9123
  init_open();
8797
9124
  });
@@ -8803,7 +9130,7 @@ var init_src = __esm(() => {
8803
9130
  fsInstance2 = new NodeFileSystem2;
8804
9131
  });
8805
9132
  var require_coreipc = __commonJS((exports, module) => {
8806
- var __dirname3 = "/Users/alexandru.oltean/github/cli/node_modules/@uipath/coreipc";
9133
+ var __dirname3 = "/home/runner/work/cli/cli/node_modules/@uipath/coreipc";
8807
9134
  /*! For license information please see index.js.LICENSE.txt */
8808
9135
  (function(e, t) {
8809
9136
  typeof exports == "object" && typeof module == "object" ? module.exports = t() : typeof define == "function" && define.amd ? define([], t) : typeof exports == "object" ? exports.ipc = t() : e.ipc = t();
@@ -26603,6 +26930,9 @@ var require_dist = __commonJS((exports) => {
26603
26930
  exports.RobotProxyConstructor = RobotProxyConstructor;
26604
26931
  __exportStar(require_agent(), exports);
26605
26932
  });
26933
+ var init_server = __esm(() => {
26934
+ init_constants();
26935
+ });
26606
26936
  init_constants();
26607
26937
  var DEFAULT_CLIENT_ID = "36dea5b8-e8bb-423d-8e7b-c808df8f1c00";
26608
26938
  var AUTH_FILE_CONFIG_KEY = Symbol.for("@uipath/auth/AuthFileConfig");
@@ -26626,32 +26956,7 @@ class InvalidBaseUrlError extends Error {
26626
26956
  this.name = "InvalidBaseUrlError";
26627
26957
  }
26628
26958
  }
26629
- var DEFAULT_SCOPES = [
26630
- "offline_access",
26631
- "ProcessMining",
26632
- "OrchestratorApiUserAccess",
26633
- "StudioWebBackend",
26634
- "IdentityServerApi",
26635
- "ConnectionService",
26636
- "DataService",
26637
- "DataServiceApiUserAccess",
26638
- "DocumentUnderstanding",
26639
- "EnterpriseContextService",
26640
- "Directory",
26641
- "JamJamApi",
26642
- "LLMGateway",
26643
- "LLMOps",
26644
- "OMS",
26645
- "RCS.FolderAuthorization",
26646
- "RCS.TagsManagement",
26647
- "TestmanagerApiUserAccess",
26648
- "AutomationSolutions",
26649
- "StudioWebTypeCacheService",
26650
- "Docs.GPT.Search",
26651
- "Insights",
26652
- "ReferenceToken",
26653
- "Audit.Read"
26654
- ];
26959
+ var DEFAULT_SCOPES = ["openid", "profile", "offline_access"];
26655
26960
  var normalizeAndValidateBaseUrl = (rawUrl) => {
26656
26961
  let baseUrl = rawUrl;
26657
26962
  if (baseUrl.endsWith("/identity_/")) {
@@ -26701,7 +27006,8 @@ var resolveConfigAsync = async ({
26701
27006
  if (!clientSecret && fileAuth.clientSecret) {
26702
27007
  clientSecret = fileAuth.clientSecret;
26703
27008
  }
26704
- const scopes = customScopes && customScopes.length > 0 ? customScopes : fileAuth.scopes && fileAuth.scopes.length > 0 ? fileAuth.scopes : DEFAULT_SCOPES;
27009
+ const isExternalAppAuth = clientId !== DEFAULT_CLIENT_ID && Boolean(clientSecret);
27010
+ const scopes = customScopes && customScopes.length > 0 ? customScopes : fileAuth.scopes && fileAuth.scopes.length > 0 ? fileAuth.scopes : isExternalAppAuth ? [] : DEFAULT_SCOPES;
26705
27011
  return {
26706
27012
  clientId,
26707
27013
  clientSecret,
@@ -27188,6 +27494,129 @@ function normalizeTokenRefreshFailure() {
27188
27494
  function normalizeTokenRefreshUnavailableFailure() {
27189
27495
  return "token refresh failed before authentication completed";
27190
27496
  }
27497
+ function errorMessage(error) {
27498
+ return error instanceof Error ? error.message : String(error);
27499
+ }
27500
+ function computeExpirationThreshold(ensureTokenValidityMinutes) {
27501
+ return new Date(Date.now() + (ensureTokenValidityMinutes ?? 0) * 60 * 1000);
27502
+ }
27503
+ async function runRefreshLocked(inputs) {
27504
+ const {
27505
+ absolutePath,
27506
+ refreshToken: callerRefreshToken,
27507
+ customAuthority,
27508
+ ensureTokenValidityMinutes,
27509
+ loadEnvFile,
27510
+ saveEnvFile,
27511
+ refreshFn,
27512
+ resolveConfig
27513
+ } = inputs;
27514
+ const expirationThreshold = computeExpirationThreshold(ensureTokenValidityMinutes);
27515
+ let fresh;
27516
+ try {
27517
+ fresh = await loadEnvFile({ envPath: absolutePath });
27518
+ } catch (error) {
27519
+ return {
27520
+ kind: "fail",
27521
+ status: {
27522
+ loginStatus: "Refresh Failed",
27523
+ hint: "Could not read the auth file while refreshing. Retry, or run 'uip login' to re-authenticate.",
27524
+ tokenRefresh: {
27525
+ attempted: false,
27526
+ success: false,
27527
+ errorMessage: `auth file read failed: ${errorMessage(error)}`
27528
+ }
27529
+ }
27530
+ };
27531
+ }
27532
+ const freshAccess = fresh.UIPATH_ACCESS_TOKEN;
27533
+ const freshExp = freshAccess ? getTokenExpiration(freshAccess) : undefined;
27534
+ if (freshAccess && freshExp && freshExp > expirationThreshold) {
27535
+ return {
27536
+ kind: "ok",
27537
+ accessToken: freshAccess,
27538
+ refreshToken: fresh.UIPATH_REFRESH_TOKEN ?? callerRefreshToken,
27539
+ expiration: freshExp,
27540
+ tokenRefresh: { attempted: false, success: true }
27541
+ };
27542
+ }
27543
+ const tokenForIdP = fresh.UIPATH_REFRESH_TOKEN ?? callerRefreshToken;
27544
+ let refreshedAccess;
27545
+ let refreshedRefresh;
27546
+ try {
27547
+ const config = await resolveConfig({ customAuthority });
27548
+ const refreshed = await refreshFn({
27549
+ refreshToken: tokenForIdP,
27550
+ tokenEndpoint: config.tokenEndpoint,
27551
+ clientId: config.clientId,
27552
+ expectedAuthority: customAuthority
27553
+ });
27554
+ refreshedAccess = refreshed.accessToken;
27555
+ refreshedRefresh = refreshed.refreshToken;
27556
+ } catch (error) {
27557
+ const isOAuthFailure = isTokenRefreshOAuthFailure(error);
27558
+ const hint = isOAuthFailure ? "Run 'uip login' to re-authenticate — the stored refresh token is invalid or expired." : "Token refresh failed. Check your network connection, then retry or run 'uip login' to re-authenticate.";
27559
+ const message = isOAuthFailure ? normalizeTokenRefreshFailure() : normalizeTokenRefreshUnavailableFailure();
27560
+ return {
27561
+ kind: "fail",
27562
+ status: {
27563
+ loginStatus: "Refresh Failed",
27564
+ hint,
27565
+ tokenRefresh: {
27566
+ attempted: true,
27567
+ success: false,
27568
+ errorMessage: message
27569
+ }
27570
+ }
27571
+ };
27572
+ }
27573
+ const refreshedExp = getTokenExpiration(refreshedAccess);
27574
+ if (!refreshedExp || refreshedExp <= new Date) {
27575
+ return {
27576
+ kind: "fail",
27577
+ status: {
27578
+ loginStatus: "Refresh Failed",
27579
+ hint: "The identity server returned an unusable token. Run 'uip login' to re-authenticate.",
27580
+ tokenRefresh: {
27581
+ attempted: true,
27582
+ success: false,
27583
+ errorMessage: "refreshed token has no valid expiration claim"
27584
+ }
27585
+ }
27586
+ };
27587
+ }
27588
+ try {
27589
+ await saveEnvFile({
27590
+ envPath: absolutePath,
27591
+ data: {
27592
+ UIPATH_ACCESS_TOKEN: refreshedAccess,
27593
+ UIPATH_REFRESH_TOKEN: refreshedRefresh
27594
+ },
27595
+ merge: true
27596
+ });
27597
+ return {
27598
+ kind: "ok",
27599
+ accessToken: refreshedAccess,
27600
+ refreshToken: refreshedRefresh,
27601
+ expiration: refreshedExp,
27602
+ tokenRefresh: { attempted: true, success: true }
27603
+ };
27604
+ } catch (error) {
27605
+ const msg = errorMessage(error);
27606
+ return {
27607
+ kind: "ok",
27608
+ accessToken: refreshedAccess,
27609
+ refreshToken: refreshedRefresh,
27610
+ expiration: refreshedExp,
27611
+ persistenceWarning: `Access token refreshed in memory but could not be written to ${absolutePath}: ${msg}. The next CLI invocation will fail until the file can be updated — run 'uip login' to re-authenticate.`,
27612
+ tokenRefresh: {
27613
+ attempted: true,
27614
+ success: true,
27615
+ errorMessage: `persistence failed: ${msg}`
27616
+ }
27617
+ };
27618
+ }
27619
+ }
27191
27620
  var getLoginStatusWithDeps = async (options = {}, deps = {}) => {
27192
27621
  const {
27193
27622
  resolveEnvFilePath = resolveEnvFilePathAsync,
@@ -27262,73 +27691,103 @@ var getLoginStatusWithDeps = async (options = {}, deps = {}) => {
27262
27691
  let refreshToken = credentials.UIPATH_REFRESH_TOKEN;
27263
27692
  let expiration = getTokenExpiration(accessToken);
27264
27693
  let persistenceWarning;
27694
+ let lockReleaseFailed = false;
27265
27695
  let tokenRefresh;
27266
- const expirationThreshold = new Date(Date.now() + (ensureTokenValidityMinutes ?? 0) * 60 * 1000);
27267
- if (expiration && expiration <= expirationThreshold && refreshToken) {
27268
- let refreshedAccess;
27269
- let refreshedRefresh;
27696
+ const outerThreshold = computeExpirationThreshold(ensureTokenValidityMinutes);
27697
+ const tryGlobalCredsHint = async () => {
27698
+ const fs72 = getFs();
27699
+ const globalPath = fs72.path.join(fs72.env.homedir(), envFilePath);
27700
+ if (absolutePath === globalPath)
27701
+ return;
27702
+ if (!await fs72.exists(globalPath))
27703
+ return;
27270
27704
  try {
27271
- const config = await resolveConfig({
27272
- customAuthority: credentials.UIPATH_URL
27273
- });
27274
- const refreshed = await refreshTokenFn({
27275
- refreshToken,
27276
- tokenEndpoint: config.tokenEndpoint,
27277
- clientId: config.clientId,
27278
- expectedAuthority: credentials.UIPATH_URL
27279
- });
27280
- refreshedAccess = refreshed.accessToken;
27281
- refreshedRefresh = refreshed.refreshToken;
27282
- } catch (error) {
27283
- const isOAuthFailure = isTokenRefreshOAuthFailure(error);
27284
- const hint = isOAuthFailure ? "Run 'uip login' to re-authenticate — the stored refresh token is invalid or expired." : "Token refresh failed. Check your network connection, then retry or run 'uip login' to re-authenticate.";
27285
- const errorMessage = isOAuthFailure ? normalizeTokenRefreshFailure() : normalizeTokenRefreshUnavailableFailure();
27286
- return {
27287
- loginStatus: "Refresh Failed",
27288
- hint,
27289
- tokenRefresh: {
27290
- attempted: true,
27291
- success: false,
27292
- errorMessage
27293
- }
27294
- };
27705
+ const globalCreds = await loadEnvFile({ envPath: globalPath });
27706
+ if (!globalCreds.UIPATH_ACCESS_TOKEN)
27707
+ return;
27708
+ const globalExp = getTokenExpiration(globalCreds.UIPATH_ACCESS_TOKEN);
27709
+ if (globalExp && globalExp <= new Date)
27710
+ return;
27711
+ return `Local credentials file at ${absolutePath} has expired credentials. Valid credentials exist in ${globalPath}. Remove the local file or run 'uip login' to re-authenticate.`;
27712
+ } catch {
27713
+ return;
27295
27714
  }
27296
- const refreshedExp = getTokenExpiration(refreshedAccess);
27297
- if (!refreshedExp || refreshedExp <= new Date) {
27715
+ };
27716
+ if (expiration && expiration <= outerThreshold && refreshToken) {
27717
+ let release;
27718
+ try {
27719
+ release = await getFs().acquireLock(absolutePath);
27720
+ } catch (error) {
27721
+ const msg = errorMessage(error);
27722
+ const globalHint = await tryGlobalCredsHint();
27723
+ if (globalHint) {
27724
+ return {
27725
+ loginStatus: "Expired",
27726
+ accessToken,
27727
+ refreshToken,
27728
+ baseUrl: credentials.UIPATH_URL,
27729
+ organizationName: credentials.UIPATH_ORGANIZATION_NAME,
27730
+ organizationId: credentials.UIPATH_ORGANIZATION_ID,
27731
+ tenantName: credentials.UIPATH_TENANT_NAME,
27732
+ tenantId: credentials.UIPATH_TENANT_ID,
27733
+ expiration,
27734
+ source: "file",
27735
+ hint: globalHint,
27736
+ tokenRefresh: {
27737
+ attempted: false,
27738
+ success: false,
27739
+ errorMessage: `lock acquisition failed: ${msg}`
27740
+ }
27741
+ };
27742
+ }
27298
27743
  return {
27299
27744
  loginStatus: "Refresh Failed",
27300
- hint: "The identity server returned an unusable token. Run 'uip login' to re-authenticate.",
27745
+ hint: "Could not acquire the auth-file lock — too many concurrent `uip` processes, or a permission issue on the auth directory. Retry, or run 'uip login' to re-authenticate.",
27301
27746
  tokenRefresh: {
27302
- attempted: true,
27747
+ attempted: false,
27303
27748
  success: false,
27304
- errorMessage: "refreshed token has no valid expiration claim"
27749
+ errorMessage: `lock acquisition failed: ${msg}`
27305
27750
  }
27306
27751
  };
27307
27752
  }
27308
- accessToken = refreshedAccess;
27309
- refreshToken = refreshedRefresh;
27310
- expiration = refreshedExp;
27753
+ let lockedFailure;
27311
27754
  try {
27312
- await saveEnvFile({
27313
- envPath: absolutePath,
27314
- data: {
27315
- UIPATH_ACCESS_TOKEN: accessToken,
27316
- UIPATH_REFRESH_TOKEN: refreshToken
27317
- },
27318
- merge: true
27755
+ const outcome = await runRefreshLocked({
27756
+ absolutePath,
27757
+ refreshToken,
27758
+ customAuthority: credentials.UIPATH_URL,
27759
+ ensureTokenValidityMinutes,
27760
+ loadEnvFile,
27761
+ saveEnvFile,
27762
+ refreshFn: refreshTokenFn,
27763
+ resolveConfig
27319
27764
  });
27320
- tokenRefresh = {
27321
- attempted: true,
27322
- success: true
27323
- };
27324
- } catch (error) {
27325
- const msg = error instanceof Error ? error.message : String(error);
27326
- persistenceWarning = `Access token refreshed in memory but could not be written to ${absolutePath}: ${msg}. The next CLI invocation will fail until the file can be updated — run 'uip login' to re-authenticate.`;
27327
- tokenRefresh = {
27328
- attempted: true,
27329
- success: true,
27330
- errorMessage: `persistence failed: ${msg}`
27331
- };
27765
+ if (outcome.kind === "fail") {
27766
+ lockedFailure = outcome.status;
27767
+ } else {
27768
+ accessToken = outcome.accessToken;
27769
+ refreshToken = outcome.refreshToken;
27770
+ expiration = outcome.expiration;
27771
+ tokenRefresh = outcome.tokenRefresh;
27772
+ if (outcome.persistenceWarning) {
27773
+ persistenceWarning = outcome.persistenceWarning;
27774
+ }
27775
+ }
27776
+ } finally {
27777
+ try {
27778
+ await release();
27779
+ } catch {
27780
+ lockReleaseFailed = true;
27781
+ }
27782
+ }
27783
+ if (lockedFailure) {
27784
+ const globalHint = await tryGlobalCredsHint();
27785
+ const base = globalHint ? {
27786
+ ...lockedFailure,
27787
+ loginStatus: "Expired",
27788
+ hint: globalHint
27789
+ } : lockedFailure;
27790
+ return lockReleaseFailed ? { ...base, lockReleaseFailed: true } : base;
27332
27791
  }
27333
27792
  }
27334
27793
  const result = {
@@ -27343,23 +27802,13 @@ var getLoginStatusWithDeps = async (options = {}, deps = {}) => {
27343
27802
  expiration,
27344
27803
  source: "file",
27345
27804
  ...persistenceWarning ? { hint: persistenceWarning, persistenceFailed: true } : {},
27805
+ ...lockReleaseFailed ? { lockReleaseFailed: true } : {},
27346
27806
  ...tokenRefresh ? { tokenRefresh } : {}
27347
27807
  };
27348
27808
  if (result.loginStatus === "Expired") {
27349
- const fs72 = getFs();
27350
- const globalPath = fs72.path.join(fs72.env.homedir(), envFilePath);
27351
- if (absolutePath !== globalPath && await fs72.exists(globalPath)) {
27352
- try {
27353
- const globalCreds = await loadEnvFile({
27354
- envPath: globalPath
27355
- });
27356
- if (globalCreds.UIPATH_ACCESS_TOKEN) {
27357
- const globalExp = getTokenExpiration(globalCreds.UIPATH_ACCESS_TOKEN);
27358
- if (!globalExp || globalExp > new Date) {
27359
- result.hint = `Local credentials file at ${absolutePath} has expired credentials. Valid credentials exist in ${globalPath}. Remove the local file or run 'uip login' to re-authenticate.`;
27360
- }
27361
- }
27362
- } catch {}
27809
+ const globalHint = await tryGlobalCredsHint();
27810
+ if (globalHint) {
27811
+ result.hint = globalHint;
27363
27812
  }
27364
27813
  }
27365
27814
  return result;
@@ -27374,6 +27823,7 @@ var getLoginStatusAsync = async (options = {}) => {
27374
27823
  };
27375
27824
  init_src();
27376
27825
  init_src();
27826
+ init_server();
27377
27827
  var SERVICE_PATH = {
27378
27828
  "llm-gateway": "llmgateway_"
27379
27829
  };
package/package.json CHANGED
@@ -1,33 +1,27 @@
1
1
  {
2
- "name": "@uipath/llmgw-tool",
3
- "version": "1.1.0",
4
- "description": "CLI plugin for UiPath AI Trust Layer Bring-Your-Own LLM connections.",
5
- "private": false,
6
- "repository": {
7
- "type": "git",
8
- "url": "https://github.com/UiPath/cli.git",
9
- "directory": "packages/admin/llmgw-tool"
10
- },
11
- "publishConfig": {
12
- "registry": "https://registry.npmjs.org/"
13
- },
14
- "keywords": [
15
- "cli-tool"
16
- ],
17
- "type": "module",
18
- "main": "./dist/tool.js",
19
- "exports": {
20
- ".": "./dist/tool.js"
21
- },
22
- "files": [
23
- "dist"
24
- ],
25
- "devDependencies": {
26
- "@types/bun": "^1.3.11",
27
- "@uipath/llmgw-sdk": "1.1.0",
28
- "@uipath/common": "1.1.0",
29
- "commander": "^14.0.3",
30
- "typescript": "^6.0.2"
31
- },
32
- "gitHead": "06e8c8f566df4b87da4a008635483c62f64f33f0"
2
+ "name": "@uipath/llmgw-tool",
3
+ "license": "MIT",
4
+ "version": "1.2.0",
5
+ "description": "CLI plugin for UiPath AI Trust Layer Bring-Your-Own LLM connections.",
6
+ "private": false,
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/UiPath/cli.git",
10
+ "directory": "packages/admin/llmgw-tool"
11
+ },
12
+ "publishConfig": {
13
+ "registry": "https://registry.npmjs.org/"
14
+ },
15
+ "keywords": [
16
+ "cli-tool"
17
+ ],
18
+ "type": "module",
19
+ "main": "./dist/tool.js",
20
+ "exports": {
21
+ ".": "./dist/tool.js"
22
+ },
23
+ "files": [
24
+ "dist"
25
+ ],
26
+ "gitHead": "65fabb84552758b2710d8ca68470e70a9b1d19bc"
33
27
  }